forked from 3b1b/videos
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcombinatorics.py
184 lines (165 loc) · 6.54 KB
/
combinatorics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from manimlib.constants import *
from manimlib.mobject.numbers import Integer
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VMobject, VGroup
from manimlib.scene.scene import Scene
from manimlib.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 = OldTex(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 = OldTex(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
def combinationMobject(n, k):
return Integer(choose(n, k))
class GeneralizedPascalsTriangle(VMobject):
nrows = 7
height = FRAME_HEIGHT - 1
width = 1.5 * FRAME_X_RADIUS
portion_to_fill = 0.7
submob_class = combinationMobject
def init_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
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:
center = self.coords_to_center(n, k)
num_mob = self.submob_class(n, k) # OldTex(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 = OldTex(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.set_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 = OldTex("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
def get_lowest_row(self):
n = self.nrows - 1
lowest_row = VGroup(*[
self.coords_to_mobs[n][k]
for k in range(n + 1)
])
return lowest_row
class PascalsTriangle(GeneralizedPascalsTriangle):
submob_class = combinationMobject