3b1b-manim/topics/combinatorics.py

178 lines
6.2 KiB
Python

from constants import *
from mobject.tex_mobject import TexMobject
from mobject.vectorized_mobject import VMobject
from scene.scene import Scene
from utils.simple_functions import choose
DEFAULT_COUNT_NUM_OFFSET = (FRAME_X_RADIUS - 1, FRAME_Y_RADIUS - 1, 0)
DEFAULT_COUNT_RUN_TIME = 5.0
class CountingScene(Scene):
def count(self, items, item_type = "mobject", *args, **kwargs):
if item_type == "mobject":
self.count_mobjects(items, *args, **kwargs)
elif item_type == "region":
self.count_regions(items, *args, **kwargs)
else:
raise Exception("Unknown item_type, should be mobject or region")
return self
def count_mobjects(
self, mobjects, mode = "highlight",
color = "red",
display_numbers = True,
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME):
"""
Note, leaves final number mobject as "number" attribute
mode can be "highlight", "show_creation" or "show", otherwise
a warning is given and nothing is animating during the count
"""
if len(mobjects) > 50: #TODO
raise Exception("I don't know if you should be counting \
too many mobjects...")
if len(mobjects) == 0:
raise Exception("Counting mobject list of length 0")
if mode not in ["highlight", "show_creation", "show"]:
raise Warning("Unknown mode")
frame_time = run_time / len(mobjects)
if mode == "highlight":
self.add(*mobjects)
for mob, num in zip(mobjects, it.count(1)):
if display_numbers:
num_mob = TexMobject(str(num))
num_mob.center().shift(num_offset)
self.add(num_mob)
if mode == "highlight":
original_color = mob.color
mob.set_color(color)
self.wait(frame_time)
mob.set_color(original_color)
if mode == "show_creation":
self.play(ShowCreation(mob, run_time = frame_time))
if mode == "show":
self.add(mob)
self.wait(frame_time)
if display_numbers:
self.remove(num_mob)
if display_numbers:
self.add(num_mob)
self.number = num_mob
return self
def count_regions(self, regions,
mode = "one_at_a_time",
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME,
**unused_kwargsn):
if mode not in ["one_at_a_time", "show_all"]:
raise Warning("Unknown mode")
frame_time = run_time / (len(regions))
for region, count in zip(regions, it.count(1)):
num_mob = TexMobject(str(count))
num_mob.center().shift(num_offset)
self.add(num_mob)
self.set_color_region(region)
self.wait(frame_time)
if mode == "one_at_a_time":
self.reset_background()
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
return self
class PascalsTriangle(VMobject):
CONFIG = {
"nrows" : 7,
"height" : FRAME_HEIGHT - 1,
"width" : 1.5*FRAME_X_RADIUS,
"portion_to_fill" : 0.7
}
def generate_points(self):
self.cell_height = float(self.height) / self.nrows
self.cell_width = float(self.width) / self.nrows
self.bottom_left = (self.cell_width * self.nrows / 2.0)*LEFT + \
(self.cell_height * self.nrows / 2.0)*DOWN
num_to_num_mob = {}
self.coords_to_mobs = {}
self.coords = [
(n, k)
for n in range(self.nrows)
for k in range(n+1)
]
for n, k in self.coords:
num = choose(n, k)
center = self.coords_to_center(n, k)
num_mob = TexMobject(str(num))
scale_factor = min(
1,
self.portion_to_fill * self.cell_height / num_mob.get_height(),
self.portion_to_fill * self.cell_width / num_mob.get_width(),
)
num_mob.center().scale(scale_factor).shift(center)
if n not in self.coords_to_mobs:
self.coords_to_mobs[n] = {}
self.coords_to_mobs[n][k] = num_mob
self.add(*[
self.coords_to_mobs[n][k]
for n, k in self.coords
])
return self
def coords_to_center(self, n, k):
x_offset = self.cell_width * (k+self.nrows/2.0 - n/2.0)
y_offset = self.cell_height * (self.nrows - n)
return self.bottom_left + x_offset*RIGHT + y_offset*UP
def generate_n_choose_k_mobs(self):
self.coords_to_n_choose_k = {}
for n, k in self.coords:
nck_mob = TexMobject(r"{%d \choose %d}"%(n, k))
scale_factor = min(
1,
self.portion_to_fill * self.cell_height / nck_mob.get_height(),
self.portion_to_fill * self.cell_width / nck_mob.get_width(),
)
center = self.coords_to_mobs[n][k].get_center()
nck_mob.center().scale(scale_factor).shift(center)
if n not in self.coords_to_n_choose_k:
self.coords_to_n_choose_k[n] = {}
self.coords_to_n_choose_k[n][k] = nck_mob
return self
def fill_with_n_choose_k(self):
if not hasattr(self, "coords_to_n_choose_k"):
self.generate_n_choose_k_mobs()
self.submobjects = []
self.add(*[
self.coords_to_n_choose_k[n][k]
for n, k in self.coords
])
return self
def generate_sea_of_zeros(self):
zero = TexMobject("0")
self.sea_of_zeros = []
for n in range(self.nrows):
for a in range((self.nrows - n)/2 + 1):
for k in (n + a + 1, -a -1):
self.coords.append((n, k))
mob = zero.copy()
mob.shift(self.coords_to_center(n, k))
self.coords_to_mobs[n][k] = mob
self.add(mob)
return self