mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
1706 lines
60 KiB
Python
1706 lines
60 KiB
Python
#!/usr/bin/env python
|
|
|
|
import numpy as np
|
|
import itertools as it
|
|
import operator as op
|
|
from copy import deepcopy
|
|
from random import random, randint
|
|
import sys
|
|
import inspect
|
|
|
|
|
|
from animation import *
|
|
from mobject import *
|
|
from constants import *
|
|
from region import *
|
|
from scene import Scene, GraphScene, PascalsTriangleScene
|
|
from script_wrapper import command_line_create_scene
|
|
|
|
RADIUS = SPACE_HEIGHT - 0.1
|
|
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
|
MOVIE_PREFIX = "moser/"
|
|
RADIANS = np.arange(0, 6, 6.0/7)
|
|
MORE_RADIANS = np.append(RADIANS, RADIANS + 0.5)
|
|
N_PASCAL_ROWS = 7
|
|
BIG_N_PASCAL_ROWS = 11
|
|
|
|
def int_list_to_string(int_list):
|
|
return "-".join(map(str, int_list))
|
|
|
|
def moser_function(n):
|
|
return choose(n, 4) + choose(n, 2) + 1
|
|
|
|
###########################################
|
|
|
|
class CircleScene(Scene):
|
|
args_list = [
|
|
(RADIANS[:x],)
|
|
for x in range(1, len(RADIANS)+1)
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return str(len(args[0])) #Length of radians
|
|
|
|
def __init__(self, radians, radius = RADIUS, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
self.radius = radius
|
|
self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius)
|
|
self.points = [
|
|
(self.radius * np.cos(angle), self.radius * np.sin(angle), 0)
|
|
for angle in radians
|
|
]
|
|
self.dots = [Dot(point) for point in self.points]
|
|
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
|
|
self.n_equals = tex_mobject(
|
|
"n=%d"%len(radians),
|
|
).shift((-SPACE_WIDTH+1, SPACE_HEIGHT-1.5, 0))
|
|
self.add(self.circle, self.n_equals, *self.dots + self.lines)
|
|
|
|
|
|
def generate_intersection_dots(self):
|
|
"""
|
|
Generates and adds attributes intersection_points and
|
|
intersection_dots, but does not yet add them to the scene
|
|
"""
|
|
self.intersection_points = [
|
|
intersection((p[0], p[2]), (p[1], p[3]))
|
|
for p in it.combinations(self.points, 4)
|
|
]
|
|
self.intersection_dots = [
|
|
Dot(point) for point in self.intersection_points
|
|
]
|
|
|
|
def chop_lines_at_intersection_points(self):
|
|
if not hasattr(self, "intersection_dots"):
|
|
self.generate_intersection_dots()
|
|
self.remove(*self.lines)
|
|
self.lines = []
|
|
for point_pair in it.combinations(self.points, 2):
|
|
int_points = filter(
|
|
lambda p : is_on_line(p, *point_pair),
|
|
self.intersection_points
|
|
)
|
|
points = list(point_pair) + int_points
|
|
points = map(lambda p : (p[0], p[1], 0), points)
|
|
points.sort(cmp = lambda x,y: cmp(x[0], y[0]))
|
|
self.lines += [
|
|
Line(points[i], points[i+1])
|
|
for i in range(len(points)-1)
|
|
]
|
|
self.add(*self.lines)
|
|
|
|
def chop_circle_at_points(self):
|
|
self.remove(self.circle)
|
|
self.circle_pieces = []
|
|
self.smaller_circle_pieces = []
|
|
for i in range(len(self.points)):
|
|
pp = self.points[i], self.points[(i+1)%len(self.points)]
|
|
transform = np.array([
|
|
[pp[0][0], pp[1][0], 0],
|
|
[pp[0][1], pp[1][1], 0],
|
|
[0, 0, 1]
|
|
])
|
|
circle = deepcopy(self.circle)
|
|
smaller_circle = deepcopy(self.circle)
|
|
for c in circle, smaller_circle:
|
|
c.points = np.dot(
|
|
c.points,
|
|
np.transpose(np.linalg.inv(transform))
|
|
)
|
|
c.filter_out(
|
|
lambda p : p[0] < 0 or p[1] < 0
|
|
)
|
|
if c == smaller_circle:
|
|
c.filter_out(
|
|
lambda p : p[0] > 4*p[1] or p[1] > 4*p[0]
|
|
)
|
|
c.points = np.dot(
|
|
c.points,
|
|
np.transpose(transform)
|
|
)
|
|
self.circle_pieces.append(circle)
|
|
self.smaller_circle_pieces.append(smaller_circle)
|
|
self.add(*self.circle_pieces)
|
|
|
|
def generate_regions(self):
|
|
self.regions = plane_partition_from_points(*self.points)
|
|
interior = Region(lambda x, y : x**2 + y**2 < self.radius**2)
|
|
map(lambda r : r.intersect(interior), self.regions)
|
|
self.exterior = interior.complement()
|
|
|
|
|
|
class CountSections(CircleScene):
|
|
def __init__(self, *args, **kwargs):
|
|
CircleScene.__init__(self, *args, **kwargs)
|
|
self.remove(*self.lines)
|
|
self.animate(*[
|
|
Transform(Dot(points[i]),Line(points[i], points[1-i]))
|
|
for points in it.combinations(self.points, 2)
|
|
for i in (0, 1)
|
|
], run_time = 2.0)
|
|
regions = plane_partition_from_points(*self.points)
|
|
interior = Region(lambda x, y : x**2 + y**2 < self.radius**2)
|
|
map(lambda r : r.intersect(interior), regions)
|
|
regions = filter(lambda r : r.bool_grid.any(), regions)
|
|
self.count_regions(regions, num_offset = ORIGIN)
|
|
|
|
class MoserPattern(CircleScene):
|
|
args_list = [(MORE_RADIANS,)]
|
|
def __init__(self, radians, *args, **kwargs):
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
self.remove(*self.dots + self.lines + [self.n_equals])
|
|
n_equals, num = tex_mobject(["n=", "10"]).split()
|
|
for mob in n_equals, num:
|
|
mob.shift((-SPACE_WIDTH + 1.5, SPACE_HEIGHT - 1.5, 0))
|
|
self.add(n_equals)
|
|
for n in range(1, len(radians)+1):
|
|
self.add(*self.dots[:n])
|
|
self.add(*[Line(p[0], p[1]) for p in it.combinations(self.points[:n], 2)])
|
|
tex_stuffs = [
|
|
tex_mobject(str(moser_function(n))),
|
|
tex_mobject(str(n)).shift(num.get_center())
|
|
]
|
|
self.add(*tex_stuffs)
|
|
self.dither(0.5)
|
|
self.remove(*tex_stuffs)
|
|
|
|
def hpsq_taylored_alpha(t):
|
|
return 0.3*np.sin(5*t-5)*np.exp(-20*(t-0.6)**2) + high_inflection_0_to_1(t)
|
|
|
|
class HardProblemsSimplerQuestions(Scene):
|
|
def __init__(self, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
right_center = np.array((4, 1, 0))
|
|
left_center = np.array((-5, 1, 0))
|
|
scale_factor = 0.7
|
|
fermat = dict([
|
|
(
|
|
sym,
|
|
CompoundMobject(*tex_mobjects(
|
|
["x","^"+sym,"+","y","^"+sym,"=","z","^"+sym]
|
|
))
|
|
)
|
|
for sym in ["n", "2", "3"]
|
|
])
|
|
# not_that_hard = text_mobject("(maybe not that hard...)").scale(0.5)
|
|
fermat2, fermat2_jargon = tex_mobject([
|
|
r"&x^2 + y^2 = z^2 \\",
|
|
r"""
|
|
&(3, 4, 5) \\
|
|
&(5, 12, 13) \\
|
|
&(15, 8, 17) \\
|
|
&\quad \vdots \\
|
|
(m^2 - &n^2, 2mn, m^2 + n^2) \\
|
|
&\quad \vdots
|
|
"""
|
|
]).split()
|
|
fermat3, fermat3_jargon = tex_mobject([
|
|
r"&x^3 + y^3 = z^3\\",
|
|
r"""
|
|
&y^3 = (z - x)(z - \omega x)(z - \omega^2 x) \\
|
|
&\mathds{Z}[\omega] \text{ is a UFD...}
|
|
"""
|
|
]).split()
|
|
for mob in [fermat2, fermat3, fermat["2"], fermat["3"],
|
|
fermat2_jargon, fermat3_jargon]:
|
|
mob.scale(scale_factor)
|
|
fermat["2"].shift(right_center)
|
|
fermat["3"].shift(left_center)
|
|
fermat["n"].shift((0, SPACE_HEIGHT - 1, 0))
|
|
shift_val = right_center - fermat2.get_center()
|
|
fermat2.shift(shift_val)
|
|
fermat2_jargon.shift(shift_val)
|
|
shift_val = left_center - fermat3.get_center()
|
|
fermat3.shift(shift_val)
|
|
fermat3_jargon.shift(shift_val)
|
|
|
|
copies = [
|
|
deepcopy(fermat["n"]).center().scale(scale_factor).shift(c)
|
|
for c in [left_center, right_center]
|
|
]
|
|
self.add(fermat["n"])
|
|
self.animate(*[
|
|
Transform(deepcopy(fermat["n"]), f_copy)
|
|
for f_copy in copies
|
|
])
|
|
self.remove(*self.mobjects)
|
|
self.add(fermat["n"])
|
|
self.animate(*[
|
|
CounterclockwiseTransform(mobs[0], mobs[1])
|
|
for f_copy, sym in zip(copies, ["3", "2"])
|
|
for mobs in zip(f_copy.split(), fermat[sym].split())
|
|
])
|
|
self.remove(*self.mobjects)
|
|
self.add(fermat["n"], fermat2, fermat3)
|
|
self.dither()
|
|
|
|
circle_grid = CompoundMobject(
|
|
Circle(),
|
|
Grid(radius = 2),
|
|
tex_mobject(r"\mathds{R}^2").shift((2, -2, 0))
|
|
)
|
|
start_line = Line((-1, 0, 0), (-1, 2, 0))
|
|
end_line = Line((-1, 0, 0), (-1, -2, 0))
|
|
for mob in circle_grid, start_line, end_line:
|
|
mob.scale(0.5).shift(right_center + (0, 2, 0))
|
|
|
|
other_grid = CompoundMobject(
|
|
Grid(radius = 2),
|
|
tex_mobject(r"\mathds{C}").shift((2, -2, 0))
|
|
)
|
|
omega = np.array((0.5, 0.5*np.sqrt(3), 0))
|
|
dots = CompoundMobject(*[
|
|
Dot(t*np.array((1, 0, 0)) + s * omega)
|
|
for t, s in it.product(range(-2, 3), range(-2, 3))
|
|
])
|
|
for mob in dots, other_grid:
|
|
mob.scale(0.5).shift(left_center + (0, 2, 0))
|
|
|
|
self.add(circle_grid, other_grid)
|
|
self.animate(
|
|
FadeIn(fermat2_jargon),
|
|
FadeIn(fermat3_jargon),
|
|
CounterclockwiseTransform(start_line, end_line),
|
|
ShowCreation(dots)
|
|
)
|
|
self.dither()
|
|
all_mobjects = CompoundMobject(*self.mobjects)
|
|
self.remove(*self.mobjects)
|
|
self.animate(
|
|
Transform(
|
|
all_mobjects,
|
|
Point((SPACE_WIDTH, 0, 0))
|
|
),
|
|
Transform(
|
|
Point((-SPACE_WIDTH, 0, 0)),
|
|
CompoundMobject(*CircleScene(RADIANS).mobjects)
|
|
)
|
|
)
|
|
|
|
class CountLines(CircleScene):
|
|
def __init__(self, radians, *args, **kwargs):
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
#TODO, Count things explicitly?
|
|
text_center = (self.radius + 1, self.radius -1, 0)
|
|
scale_factor = 0.4
|
|
text = tex_mobject(r"\text{How Many Lines?}", size = r"\large")
|
|
n = len(radians)
|
|
formula, answer = tex_mobject([
|
|
r"{%d \choose 2} = \frac{%d(%d - 1)}{2} = "%(n, n, n),
|
|
str(choose(n, 2))
|
|
])
|
|
text.scale(scale_factor).shift(text_center)
|
|
x = text_center[0]
|
|
new_lines = [
|
|
Line((x-1, y, 0), (x+1, y, 0))
|
|
for y in np.arange(
|
|
-(self.radius - 1),
|
|
self.radius - 1,
|
|
(2*self.radius - 2)/len(self.lines)
|
|
)
|
|
]
|
|
self.add(text)
|
|
self.dither()
|
|
self.animate(*[
|
|
Transform(line1, line2, run_time = 2)
|
|
for line1, line2 in zip(self.lines, new_lines)
|
|
])
|
|
self.dither()
|
|
self.remove(text)
|
|
self.count(new_lines)
|
|
anims = [FadeIn(formula)]
|
|
for mob in self.mobjects:
|
|
if mob == self.number:
|
|
anims.append(Transform(mob, answer))
|
|
else:
|
|
anims.append(FadeOut(mob))
|
|
self.animate(*anims, run_time = 1)
|
|
|
|
class CountIntersectionPoints(CircleScene):
|
|
def __init__(self, radians, *args, **kwargs):
|
|
radians = [r % (2*np.pi) for r in radians]
|
|
radians.sort()
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
|
|
intersection_points = [
|
|
intersection((p[0], p[2]), (p[1], p[3]))
|
|
for p in it.combinations(self.points, 4)
|
|
]
|
|
intersection_dots = [Dot(point) for point in intersection_points]
|
|
text_center = (self.radius + 0.5, self.radius -0.5, 0)
|
|
size = r"\large"
|
|
scale_factor = 0.4
|
|
text = tex_mobject(r"\text{How Many Intersection Points?}", size = size)
|
|
n = len(radians)
|
|
formula, answer = tex_mobject([
|
|
r"{%d \choose 4} = \frac{%d(%d - 1)(%d - 2)(%d-3)}{1\cdot 2\cdot 3 \cdot 4}="%(n, n, n, n, n),
|
|
str(choose(n, 4))
|
|
]).split()
|
|
text.scale(scale_factor).shift(text_center)
|
|
self.add(text)
|
|
self.count(intersection_dots, mode="show", num_offset = ORIGIN)
|
|
self.dither()
|
|
anims = []
|
|
for mob in self.mobjects:
|
|
if mob == self.number: #put in during count
|
|
anims.append(Transform(mob, answer))
|
|
else:
|
|
anims.append(FadeOut(mob))
|
|
anims.append(Animation(formula))
|
|
self.animate(*anims, run_time = 1)
|
|
|
|
class NonGeneralPosition(CircleScene):
|
|
args_list = []
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return ""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
radians = np.arange(1, 7)
|
|
new_radians = (np.pi/3)*radians
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
|
|
new_cs = CircleScene(new_radians)
|
|
center_region = reduce(
|
|
Region.intersect,
|
|
[
|
|
HalfPlane((self.points[x], self.points[(x+3)%6]))
|
|
for x in [0, 4, 2]#Ya know, trust it
|
|
]
|
|
)
|
|
center_region
|
|
text = tex_mobject(r"\text{This region disappears}", size = r"\large")
|
|
text.center().scale(0.5).shift((-self.radius, self.radius-0.3, 0))
|
|
arrow = Arrow(
|
|
point = (-0.35, -0.1, 0),
|
|
direction = (1, -1, 0),
|
|
length = self.radius + 1,
|
|
color = "white",
|
|
)
|
|
|
|
self.highlight_region(center_region, "green")
|
|
self.add(text, arrow)
|
|
self.dither(2)
|
|
self.remove(text, arrow)
|
|
self.reset_background()
|
|
self.animate(*[
|
|
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
|
|
for mob1, mob2 in zip(self.mobjects, new_self.mobjects)
|
|
])
|
|
|
|
class GeneralPositionRule(Scene):
|
|
def __init__(self, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
tuples = [
|
|
(
|
|
np.arange(0, 2*np.pi, np.pi/3),
|
|
"Not okay",
|
|
zip(range(3), range(3, 6))
|
|
),
|
|
(
|
|
RADIANS,
|
|
"Okay",
|
|
[],
|
|
),
|
|
(
|
|
np.arange(0, 2*np.pi, np.pi/4),
|
|
"Not okay",
|
|
zip(range(4), range(4, 8))
|
|
),
|
|
(
|
|
[2*np.pi*random() for x in range(5)],
|
|
"Okay",
|
|
[],
|
|
),
|
|
]
|
|
first_time = True
|
|
for radians, words, pairs in tuples:
|
|
cs = CircleScene(radians)
|
|
self.add(*cs.mobjects)
|
|
words_mob = text_mobject(words).scale(2).shift((5, 3, 0))
|
|
if not first_time:
|
|
self.add(words_mob)
|
|
if words == "Okay":
|
|
words_mob.highlight("green")
|
|
self.dither(2)
|
|
else:
|
|
words_mob.highlight()
|
|
intersecting_lines = [
|
|
line.scale_in_place(0.3).highlight()
|
|
for i, j in pairs
|
|
for line in [Line(cs.points[i], cs.points[j])]
|
|
]
|
|
self.animate(*[
|
|
ShowCreation(line, run_time = 1.0)
|
|
for line in intersecting_lines
|
|
])
|
|
if first_time:
|
|
self.animate(Transform(
|
|
CompoundMobject(*intersecting_lines),
|
|
words_mob
|
|
))
|
|
first_time = False
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
|
|
|
|
class LineCorrespondsWithPair(CircleScene):
|
|
args_list = [
|
|
(RADIANS, 2, 5),
|
|
(RADIANS, 0, 4)
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return int_list_to_string(args[1:])
|
|
|
|
def __init__(self, radians, dot0_index, dot1_index,
|
|
*args, **kwargs):
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
#Remove from self.lines list, so they won't be faded out
|
|
radians = list(radians)
|
|
r1, r2 = radians[dot0_index], radians[dot1_index]
|
|
line_index = list(it.combinations(radians, 2)).index((r1, r2))
|
|
line, dot0, dot1 = self.lines[line_index], self.dots[dot0_index], self.dots[dot1_index]
|
|
self.lines.remove(line)
|
|
self.dots.remove(dot0)
|
|
self.dots.remove(dot1)
|
|
self.dither()
|
|
self.animate(*[
|
|
ApplyMethod(mob.fade, 0.2)
|
|
for mob in self.lines + self.dots
|
|
])
|
|
self.animate(*[
|
|
Transform(
|
|
dot, Dot(dot.get_center(), 3*dot.radius),
|
|
alpha_func = there_and_back
|
|
)
|
|
for dot in (dot0, dot1)
|
|
])
|
|
self.animate(Transform(line, dot0))
|
|
|
|
class IllustrateNChooseK(Scene):
|
|
args_list = [
|
|
(n, k)
|
|
for n in range(1, 10)
|
|
for k in range(n+1)
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return int_list_to_string(args)
|
|
|
|
def __init__(self, n, k, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
nrange = range(1, n+1)
|
|
tuples = list(it.combinations(nrange, k))
|
|
nrange_mobs = tex_mobject([str(n) + r'\;' for n in nrange]).split()
|
|
tuple_mobs = tex_mobjects(
|
|
[
|
|
(r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p)
|
|
for p, c in zip(tuples, it.count())
|
|
],
|
|
size = r"\small",
|
|
)#TODO, scale these up!
|
|
tuple_terms = {
|
|
2 : "pairs",
|
|
3 : "triplets",
|
|
4 : "quadruplets",
|
|
}
|
|
tuple_term = tuple_terms[k] if k in tuple_terms else "tuples"
|
|
if k == 2:
|
|
str1 = r"{%d \choose %d} = \frac{%d(%d - 1)}{2}="%(n, k, n, n)
|
|
elif k == 4:
|
|
str1 = r"""
|
|
{%d \choose %d} =
|
|
\frac{%d(%d - 1)(%d-2)(%d-3)}{1\cdot 2\cdot 3 \cdot 4}=
|
|
"""%(n, k, n, n, n, n)
|
|
else:
|
|
str1 = r"{%d \choose %d} ="%(n, k)
|
|
form1, count, form2 = tex_mobject([
|
|
str1,
|
|
"%d"%choose(n, k),
|
|
r" \text{ total %s}"%tuple_term
|
|
])
|
|
pronunciation = text_mobject(
|
|
"(pronounced ``%d choose %d\'\')"%(n, k)
|
|
)
|
|
for mob in nrange_mobs:
|
|
mob.shift((0, 2, 0))
|
|
for mob in form1, count, form2:
|
|
mob.scale(0.75).shift((0, -SPACE_HEIGHT + 1, 0))
|
|
count_center = count.get_center()
|
|
for mob in tuple_mobs:
|
|
mob.scale(0.6)
|
|
pronunciation.shift(
|
|
form1.get_center() + (0, 1, 0)
|
|
)
|
|
|
|
self.add(*nrange_mobs)
|
|
self.dither()
|
|
run_time = 6.0
|
|
frame_time = run_time / len(tuples)
|
|
for tup, count in zip(tuples, it.count()):
|
|
count_mob = tex_mobject(str(count+1))
|
|
count_mob.center().shift(count_center)
|
|
self.add(count_mob)
|
|
tuple_copy = CompoundMobject(*[nrange_mobs[index-1] for index in tup])
|
|
tuple_copy.highlight()
|
|
self.add(tuple_copy)
|
|
self.add(tuple_mobs[count])
|
|
self.dither(frame_time)
|
|
self.remove(count_mob)
|
|
self.remove(tuple_copy)
|
|
self.add(count_mob)
|
|
self.animate(FadeIn(CompoundMobject(form1, form2, pronunciation)))
|
|
|
|
class IntersectionPointCorrespondances(CircleScene):
|
|
args_list = [
|
|
(RADIANS, range(0, 7, 2)),
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return int_list_to_string(args[1])
|
|
|
|
def __init__(self, radians, indices, *args, **kwargs):
|
|
assert(len(indices) == 4)
|
|
indices.sort()
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
intersection_point = intersection(
|
|
(self.points[indices[0]], self.points[indices[2]]),
|
|
(self.points[indices[1]], self.points[indices[3]])
|
|
)
|
|
if len(intersection_point) == 2:
|
|
intersection_point = list(intersection_point) + [0]
|
|
intersection_dot = Dot(intersection_point)
|
|
intersection_dot_arrow = Arrow(intersection_point).nudge()
|
|
self.add(intersection_dot)
|
|
pairs = list(it.combinations(range(len(radians)), 2))
|
|
lines_to_save = [
|
|
self.lines[pairs.index((indices[p0], indices[p1]))]
|
|
for p0, p1 in [(0, 2), (1, 3)]
|
|
]
|
|
dots_to_save = [
|
|
self.dots[p]
|
|
for p in indices
|
|
]
|
|
line_statement = tex_mobject(r"\text{Pair of Lines}")
|
|
dots_statement = tex_mobject(r"&\text{Quadruplet of} \\ &\text{outer dots}")
|
|
for mob in line_statement, dots_statement:
|
|
mob.center()
|
|
mob.scale(0.7)
|
|
mob.shift((SPACE_WIDTH-2, SPACE_HEIGHT - 1, 0))
|
|
fade_outs = []
|
|
line_highlights = []
|
|
dot_highlights = []
|
|
dot_pointers = []
|
|
for mob in self.mobjects:
|
|
if mob in lines_to_save:
|
|
line_highlights.append(Highlight(mob))
|
|
elif mob in dots_to_save:
|
|
dot_highlights.append(Highlight(mob))
|
|
dot_pointers.append(Arrow(mob.get_center()).nudge())
|
|
elif mob != intersection_dot:
|
|
fade_outs.append(FadeOut(mob, alpha_func = not_quite_there))
|
|
|
|
self.add(intersection_dot_arrow)
|
|
self.animate(Highlight(intersection_dot))
|
|
self.remove(intersection_dot_arrow)
|
|
self.animate(*fade_outs)
|
|
self.dither()
|
|
self.add(line_statement)
|
|
self.animate(*line_highlights)
|
|
self.remove(line_statement)
|
|
self.dither()
|
|
self.add(dots_statement, *dot_pointers)
|
|
self.animate(*dot_highlights)
|
|
self.remove(dots_statement, *dot_pointers)
|
|
|
|
class LinesIntersectOutside(CircleScene):
|
|
args_list = [
|
|
(RADIANS, [2, 4, 5, 6]),
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return int_list_to_string(args[1])
|
|
|
|
def __init__(self, radians, indices, *args, **kwargs):
|
|
assert(len(indices) == 4)
|
|
indices.sort()
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
intersection_point = intersection(
|
|
(self.points[indices[0]], self.points[indices[1]]),
|
|
(self.points[indices[2]], self.points[indices[3]])
|
|
)
|
|
intersection_point = tuple(list(intersection_point) + [0])
|
|
intersection_dot = Dot(intersection_point)
|
|
pairs = list(it.combinations(range(len(radians)), 2))
|
|
lines_to_save = [
|
|
self.lines[pairs.index((indices[p0], indices[p1]))]
|
|
for p0, p1 in [(0, 1), (2, 3)]
|
|
]
|
|
self.animate(*[
|
|
FadeOut(mob, alpha_func = not_quite_there)
|
|
for mob in self.mobjects if mob not in lines_to_save
|
|
])
|
|
self.animate(*[
|
|
Transform(
|
|
Line(self.points[indices[p0]], self.points[indices[p1]]),
|
|
Line(self.points[indices[p0]], intersection_point))
|
|
for p0, p1 in [(0, 1), (3, 2)]
|
|
] + [ShowCreation(intersection_dot)])
|
|
|
|
class QuadrupletsToIntersections(CircleScene):
|
|
def __init__(self, radians, *args, **kwargs):
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
quadruplets = it.combinations(range(len(radians)), 4)
|
|
frame_time = 1.0
|
|
for quad in quadruplets:
|
|
intersection_dot = Dot(intersection(
|
|
(self.points[quad[0]], self.points[quad[2]]),
|
|
(self.points[quad[1]], self.points[quad[3]])
|
|
)).repeat(3)
|
|
dot_quad = [deepcopy(self.dots[i]) for i in quad]
|
|
for dot in dot_quad:
|
|
dot.scale_in_place(2)
|
|
dot_quad = CompoundMobject(*dot_quad)
|
|
dot_quad.highlight()
|
|
self.add(dot_quad)
|
|
self.dither(frame_time / 3)
|
|
self.animate(Transform(
|
|
dot_quad,
|
|
intersection_dot,
|
|
run_time = 3*frame_time/2
|
|
))
|
|
|
|
class GraphsAndEulersFormulaJoke(Scene):
|
|
def __init__(self, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
axes = CompoundMobject(
|
|
NumberLine(),
|
|
NumberLine().rotate(np.pi / 2)
|
|
)
|
|
graph = ParametricFunction(
|
|
lambda t : (10*t, ((10*t)**3 - 10*t), 0),
|
|
expected_measure = 40.0
|
|
)
|
|
graph.filter_out(lambda (x, y, z) : abs(y) > SPACE_HEIGHT)
|
|
self.add(axes)
|
|
self.animate(ShowCreation(graph), run_time = 1.0)
|
|
eulers = tex_mobject("e^{\pi i} = -1").shift((0, 3, 0))
|
|
self.animate(CounterclockwiseTransform(
|
|
deepcopy(graph), eulers
|
|
))
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.add(eulers)
|
|
self.animate(CounterclockwiseTransform(
|
|
CompoundMobject(axes, graph),
|
|
Point((-SPACE_WIDTH, SPACE_HEIGHT, 0))
|
|
))
|
|
self.animate(CounterclockwiseTransform(
|
|
eulers,
|
|
Point((SPACE_WIDTH, SPACE_HEIGHT, 0))
|
|
))
|
|
|
|
class DefiningGraph(GraphScene):
|
|
def __init__(self, *args, **kwargs):
|
|
GraphScene.__init__(self, *args, **kwargs)
|
|
word_center = (0, 3, 0)
|
|
vertices_word = text_mobject("``Vertices\"").shift(word_center)
|
|
edges_word = text_mobject("``Edges\"").shift(word_center)
|
|
dots, lines = self.vertices, self.edges
|
|
self.remove(*dots + lines)
|
|
all_dots = CompoundMobject(*dots)
|
|
self.animate(ShowCreation(all_dots))
|
|
self.remove(all_dots)
|
|
self.add(*dots)
|
|
self.animate(FadeIn(vertices_word))
|
|
self.dither()
|
|
self.remove(vertices_word)
|
|
self.animate(*[
|
|
ShowCreation(line) for line in lines
|
|
])
|
|
self.animate(FadeIn(edges_word))
|
|
|
|
#Move to new graph
|
|
# new_graph = deepcopy(self.graph)
|
|
# new_graph.vertices = [
|
|
# (v[0] + 3*random(), v[1] + 3*random(), 0)
|
|
# for v in new_graph.vertices
|
|
# ]
|
|
# new_graph_scene = GraphScene(new_graph)
|
|
# self.animate(*[
|
|
# Transform(m[0], m[1])
|
|
# for m in zip(self.mobjects, new_graph_scene.mobjects)
|
|
# ], run_time = 7.0)
|
|
|
|
class IntersectCubeGraphEdges(GraphScene):
|
|
args_list = []
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return ""
|
|
def __init__(self, *args, **kwargs):
|
|
GraphScene.__init__(self, CubeGraph(), *args, **kwargs)
|
|
self.remove(self.edges[0], self.edges[4])
|
|
self.animate(*[
|
|
Transform(
|
|
Line(self.points[i], self.points[j]),
|
|
CurvedLine(self.points[i], self.points[j]),
|
|
)
|
|
for i, j in [(0, 1), (5, 4)]
|
|
])
|
|
|
|
|
|
class DoubledEdges(GraphScene):
|
|
def __init__(self, *args, **kwargs):
|
|
GraphScene.__init__(self, *args, **kwargs)
|
|
lines_to_double = self.edges[:9:3]
|
|
crazy_lines = [
|
|
(
|
|
line,
|
|
Line(line.end, line.start),
|
|
CurvedLine(line.start, line.end) ,
|
|
CurvedLine(line.end, line.start)
|
|
)
|
|
for line in lines_to_double
|
|
]
|
|
anims = []
|
|
outward_curved_lines = []
|
|
kwargs = {"run_time" : 3.0}
|
|
for straight, backwards, inward, outward in crazy_lines:
|
|
anims += [
|
|
Transform(straight, inward, **kwargs),
|
|
Transform(backwards, outward, **kwargs),
|
|
]
|
|
outward_curved_lines.append(outward)
|
|
self.animate(*anims)
|
|
self.dither()
|
|
self.remove(*outward_curved_lines)
|
|
|
|
class EulersFormula(GraphScene):
|
|
def __init__(self, *args, **kwargs):
|
|
GraphScene.__init__(self, *args, **kwargs)
|
|
terms = "V - E + F =2".split(" ")
|
|
form = dict([
|
|
(key, mob)
|
|
for key, mob in zip(terms, tex_mobjects(terms))
|
|
])
|
|
for mob in form.values():
|
|
mob.shift((0, SPACE_HEIGHT-0.7, 0))
|
|
formula = CompoundMobject(*[form[k] for k in form.keys() if k != "=2"])
|
|
new_form = dict([
|
|
(key, deepcopy(mob).shift((0, -0.7, 0)))
|
|
for key, mob in zip(form.keys(), form.values())
|
|
])
|
|
self.add(formula)
|
|
colored_dots = [
|
|
deepcopy(d).scale_in_place(1.5).highlight("red")
|
|
for d in self.dots
|
|
]
|
|
colored_edges = [
|
|
CompoundMobject(
|
|
Line(midpoint, start),
|
|
Line(midpoint, end),
|
|
).highlight("red")
|
|
for e in self.edges
|
|
for start, end, midpoint in [
|
|
(e.start, e.end, (e.start + e.end)/2.0)
|
|
]
|
|
]
|
|
frame_time = 0.3
|
|
|
|
self.generate_regions()
|
|
parameters = [
|
|
(colored_dots, "V", "mobject", "-", "show"),
|
|
(colored_edges, "E", "mobject", "+", "show"),
|
|
(self.regions, "F", "region", "=2", "show_all")
|
|
]
|
|
for items, letter, item_type, symbol, mode in parameters:
|
|
self.count(
|
|
items,
|
|
item_type = item_type,
|
|
mode = mode,
|
|
num_offset = new_form[letter].get_center(),
|
|
run_time = frame_time*len(items)
|
|
)
|
|
self.dither()
|
|
if item_type == "mobject":
|
|
self.remove(*items)
|
|
self.add(new_form[symbol])
|
|
|
|
class CannotDirectlyApplyEulerToMoser(CircleScene):
|
|
def __init__(self, radians, *args, **kwargs):
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
self.remove(self.n_equals)
|
|
n_equals, intersection_count = tex_mobject([
|
|
r"&n = %d\\"%len(radians),
|
|
r"&{%d \choose 4} = %d"%(len(radians), choose(len(radians), 4))
|
|
]).split()
|
|
shift_val = self.n_equals.get_center() - n_equals.get_center()
|
|
for mob in n_equals, intersection_count:
|
|
mob.shift(shift_val)
|
|
self.add(n_equals)
|
|
yellow_dots = [
|
|
d.highlight("yellow").scale_in_place(2)
|
|
for d in deepcopy(self.dots)
|
|
]
|
|
yellow_lines = CompoundMobject(*[
|
|
l.highlight("yellow") for l in deepcopy(self.lines)
|
|
])
|
|
self.animate(*[
|
|
ShowCreation(dot) for dot in yellow_dots
|
|
], run_time = 1.0)
|
|
self.dither()
|
|
self.remove(*yellow_dots)
|
|
self.animate(ShowCreation(yellow_lines))
|
|
self.dither()
|
|
self.remove(yellow_lines)
|
|
cannot_intersect = text_mobject(r"""
|
|
Euler's formula does not apply to \\
|
|
graphs whose edges intersect!
|
|
"""
|
|
)
|
|
cannot_intersect.center()
|
|
for mob in self.mobjects:
|
|
mob.fade(0.3)
|
|
self.add(cannot_intersect)
|
|
self.dither()
|
|
self.remove(cannot_intersect)
|
|
for mob in self.mobjects:
|
|
mob.fade(1/0.3)
|
|
self.generate_intersection_dots()
|
|
self.animate(FadeIn(intersection_count), *[
|
|
ShowCreation(dot) for dot in self.intersection_dots
|
|
])
|
|
|
|
class ShowMoserGraphLines(CircleScene):
|
|
def __init__(self, radians, *args, **kwargs):
|
|
radians = list(set(map(lambda x : x%(2*np.pi), radians)))
|
|
radians.sort()
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
n, plus_n_choose_4 = tex_mobject(["n", "+{n \\choose 4}"]).split()
|
|
n_choose_2, plus_2_n_choose_4, plus_n = tex_mobject([
|
|
r"{n \choose 2}",r"&+2{n \choose 4}\\",r"&+n"
|
|
]).split()
|
|
for mob in n, plus_n_choose_4, n_choose_2, plus_2_n_choose_4, plus_n:
|
|
mob.shift((SPACE_WIDTH - 2, SPACE_HEIGHT-1, 0))
|
|
self.chop_lines_at_intersection_points()
|
|
self.add(*self.intersection_dots)
|
|
small_lines = [
|
|
deepcopy(line).scale_in_place(0.5)
|
|
for line in self.lines
|
|
]
|
|
|
|
for mobs, symbol in [
|
|
(self.dots, n),
|
|
(self.intersection_dots, plus_n_choose_4),
|
|
(self.lines, n_choose_2)
|
|
]:
|
|
self.add(symbol)
|
|
compound = CompoundMobject(*mobs)
|
|
if mobs in (self.dots, self.intersection_dots):
|
|
self.remove(*mobs)
|
|
self.animate(CounterclockwiseTransform(
|
|
compound,
|
|
deepcopy(compound).scale(1.05),
|
|
alpha_func = there_and_back,
|
|
run_time = 2.0,
|
|
))
|
|
else:
|
|
compound.highlight("yellow")
|
|
self.animate(ShowCreation(compound))
|
|
self.remove(compound)
|
|
if mobs == self.intersection_dots:
|
|
self.remove(n, plus_n_choose_4)
|
|
|
|
self.animate(*[
|
|
Transform(line, small_line, run_time = 3.0)
|
|
for line, small_line in zip(self.lines, small_lines)
|
|
])
|
|
yellow_lines = CompoundMobject(*[
|
|
line.highlight("yellow") for line in small_lines
|
|
])
|
|
self.add(plus_2_n_choose_4)
|
|
self.animate(ShowCreation(yellow_lines))
|
|
self.dither()
|
|
self.remove(yellow_lines)
|
|
self.chop_circle_at_points()
|
|
self.animate(*[
|
|
Transform(p, sp, run_time = 3.0)
|
|
for p, sp in zip(self.circle_pieces, self.smaller_circle_pieces)
|
|
])
|
|
self.add(plus_n)
|
|
self.animate(ShowCreation(CompoundMobject(*[
|
|
mob.highlight("yellow") for mob in self.circle_pieces
|
|
])))
|
|
|
|
class HowIntersectionChopsLine(CircleScene):
|
|
args_list = [
|
|
(RADIANS, range(0, 7, 2)),
|
|
]
|
|
@staticmethod
|
|
def args_to_string(*args):
|
|
return int_list_to_string(args[1])
|
|
|
|
def __init__(self, radians, indices, *args, **kwargs):
|
|
assert(len(indices) == 4)
|
|
indices.sort()
|
|
CircleScene.__init__(self, radians, *args, **kwargs)
|
|
intersection_point = intersection(
|
|
(self.points[indices[0]], self.points[indices[2]]),
|
|
(self.points[indices[1]], self.points[indices[3]])
|
|
)
|
|
if len(intersection_point) == 2:
|
|
intersection_point = list(intersection_point) + [0]
|
|
pairs = list(it.combinations(range(len(radians)), 2))
|
|
lines = [
|
|
Line(self.points[indices[p0]], self.points[indices[p1]])
|
|
for p0, p1 in [(2, 0), (1, 3)]
|
|
]
|
|
self.remove(*[
|
|
self.lines[pairs.index((indices[p0], indices[p1]))]
|
|
for p0, p1 in [(0, 2), (1, 3)]
|
|
])
|
|
self.add(*lines)
|
|
self.animate(*[
|
|
FadeOut(mob)
|
|
for mob in self.mobjects
|
|
if mob not in lines
|
|
])
|
|
new_lines = [
|
|
Line(line.start, intersection_point)
|
|
for line in lines
|
|
] + [
|
|
Line(intersection_point, line.end)
|
|
for line in lines
|
|
]
|
|
self.animate(*[
|
|
Transform(
|
|
line,
|
|
Line(
|
|
(-3, h, 0),
|
|
(3, h, 0)
|
|
),
|
|
alpha_func = there_and_back,
|
|
run_time = 3.0
|
|
)
|
|
for line, h in zip(lines, (-1, 1))
|
|
])
|
|
self.remove(*lines)
|
|
self.animate(*[
|
|
Transform(
|
|
line,
|
|
deepcopy(line).scale(1.1).scale_in_place(1/1.1),
|
|
run_time = 1.5
|
|
)
|
|
for line in new_lines
|
|
])
|
|
|
|
|
|
class ApplyEulerToMoser(CircleScene):
|
|
def __init__(self, *args, **kwargs):
|
|
radius = 2
|
|
CircleScene.__init__(self, *args, radius = radius, **kwargs)
|
|
self.generate_intersection_dots()
|
|
self.chop_lines_at_intersection_points()
|
|
self.chop_circle_at_points()
|
|
self.generate_regions()
|
|
for dot in self.dots + self.intersection_dots:
|
|
dot.scale_in_place(radius / RADIUS)
|
|
self.remove(*self.mobjects)
|
|
|
|
V = {}
|
|
minus = {}
|
|
minus1 = {}
|
|
E = {}
|
|
plus = {}
|
|
plus1 = {}
|
|
plus2 = {}
|
|
F = {}
|
|
equals = {}
|
|
two = {}
|
|
two1 = {}
|
|
n = {}
|
|
n1 = {}
|
|
nc2 = {}
|
|
nc4 = {}
|
|
nc41 = {}
|
|
dicts = [V, minus, minus1, E, plus, plus1, plus2, F,
|
|
equals, two, two1, n, n1, nc2, nc4, nc41]
|
|
|
|
V[1], minus[1], E[1], plus[1], F[1], equals[1], two[1] = \
|
|
tex_mobject(["V", "-", "E", "+", "F", "=", "2"]).split()
|
|
F[2], equals[2], E[2], minus[2], V[2], plus[2], two[2] = \
|
|
tex_mobject(["F", "=", "E", "-", "V", "+", "2"]).split()
|
|
F[3], equals[3], E[3], minus[3], n[3], minus1[3], nc4[3], plus[3], two[3] = \
|
|
tex_mobject(["F", "=", "E", "-", "n", "-", r"{n \choose 4}", "+", "2"]).split()
|
|
F[4], equals[4], nc2[4], plus1[4], two1[4], nc41[4], plus2[4], n1[4], minus[4], n[4], minus1[4], nc4[4], plus[4], two[4] = \
|
|
tex_mobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "+", "n","-", "n", "-", r"{n \choose 4}", "+", "2"]).split()
|
|
F[5], equals[5], nc2[5], plus1[5], two1[5], nc41[5], minus1[5], nc4[5], plus[5], two[5] = \
|
|
tex_mobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "-", r"{n \choose 4}", "+", "2"]).split()
|
|
F[6], equals[6], nc2[6], plus1[6], nc4[6], plus[6], two[6] = \
|
|
tex_mobject(["F", "=", r"{n \choose 2}", "+", r"{n \choose 4}", "+", "2"]).split()
|
|
F[7], equals[7], two[7], plus[7], nc2[7], plus1[7], nc4[7] = \
|
|
tex_mobject(["F", "=", "2", "+", r"{n \choose 2}", "+", r"{n \choose 4}"]).split()
|
|
shift_val = (0, 3, 0)
|
|
for d in dicts:
|
|
if not d:
|
|
continue
|
|
main_key = d.keys()[0]
|
|
for key in d.keys():
|
|
d[key].shift(shift_val)
|
|
main_center = d[main_key].get_center()
|
|
for key in d.keys():
|
|
d[key] = deepcopy(d[main_key]).shift(
|
|
d[key].get_center() - main_center
|
|
)
|
|
|
|
self.animate(*[
|
|
CounterclockwiseTransform(d[1], d[2], run_time = 2.0)
|
|
for d in [V, minus, E, plus, F, equals, two]
|
|
])
|
|
self.dither()
|
|
F[1].highlight()
|
|
self.add(*self.lines + self.circle_pieces)
|
|
for region in self.regions:
|
|
self.highlight_region(region)
|
|
self.highlight_region(self.exterior, "blue")
|
|
self.dither()
|
|
self.reset_background()
|
|
F[1].highlight("white")
|
|
E[1].highlight()
|
|
self.remove(*self.lines + self.circle_pieces)
|
|
self.animate(*[
|
|
Transform(
|
|
deepcopy(line),
|
|
deepcopy(line).scale_in_place(0.5),
|
|
run_time = 2.0,
|
|
)
|
|
for line in self.lines
|
|
] + [
|
|
Transform(
|
|
deepcopy(cp), scp,
|
|
run_time = 2.0
|
|
)
|
|
for cp, scp in zip(self.circle_pieces, self.smaller_circle_pieces)
|
|
])
|
|
self.dither()
|
|
E[1].highlight("white")
|
|
V[1].highlight()
|
|
self.add(*self.dots + self.intersection_dots)
|
|
self.remove(*self.lines + self.circle_pieces)
|
|
self.animate(*[
|
|
Transform(
|
|
deepcopy(dot),
|
|
deepcopy(dot).scale_in_place(1.4).highlight("yellow")
|
|
)
|
|
for dot in self.dots + self.intersection_dots
|
|
] + [
|
|
Transform(
|
|
deepcopy(line),
|
|
deepcopy(line).fade(0.4)
|
|
)
|
|
for line in self.lines + self.circle_pieces
|
|
])
|
|
self.dither()
|
|
all_mobs = [mob for mob in self.mobjects]
|
|
self.remove(*all_mobs)
|
|
self.add(*[d[1] for d in [V, minus, E, plus, F, equals, two]])
|
|
V[1].highlight("white")
|
|
two[1].highlight()
|
|
self.dither()
|
|
self.add(*all_mobs)
|
|
self.remove(*[d[1] for d in [V, minus, E, plus, F, equals, two]])
|
|
self.animate(
|
|
Transform(V[2].repeat(2), CompoundMobject(n[3], minus1[3], nc4[3])),
|
|
*[
|
|
Transform(d[2], d[3])
|
|
for d in [F, equals, E, minus, plus, two]
|
|
]
|
|
)
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.animate(
|
|
Transform(E[3], CompoundMobject(
|
|
nc2[4], plus1[4], two1[4], nc41[4], plus2[4], n1[4]
|
|
)),
|
|
*[
|
|
Transform(d[3], d[4])
|
|
for d in [F, equals, minus, n, minus1, nc4, plus, two]
|
|
] + [
|
|
Transform(
|
|
deepcopy(line),
|
|
deepcopy(line).scale_in_place(0.5),
|
|
)
|
|
for line in self.lines
|
|
] + [
|
|
Transform(deepcopy(cp), scp)
|
|
for cp, scp in zip(self.circle_pieces, self.smaller_circle_pieces)
|
|
],
|
|
run_time = 2.0
|
|
)
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.animate(
|
|
Transform(
|
|
CompoundMobject(plus2[4], n1[4], minus[4], n[4]),
|
|
Point((SPACE_WIDTH, SPACE_HEIGHT, 0))
|
|
),
|
|
*[
|
|
Transform(d[4], d[5])
|
|
for d in [F, equals, nc2, plus1, two1,
|
|
nc41, minus1, nc4, plus, two]
|
|
]
|
|
)
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.animate(
|
|
Transform(nc41[5], nc4[6]),
|
|
Transform(two1[5], Point(nc4[6].get_center())),
|
|
*[
|
|
Transform(d[5], d[6])
|
|
for d in [F, equals, nc2, plus1, nc4, plus, two]
|
|
]
|
|
)
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.animate(
|
|
CounterclockwiseTransform(two[6], two[7]),
|
|
CounterclockwiseTransform(plus[6], plus[7]),
|
|
*[
|
|
Transform(d[6], d[7])
|
|
for d in [F, equals, nc2, plus1, nc4]
|
|
]
|
|
)
|
|
self.dither()
|
|
self.add(*self.lines + self.circle_pieces)
|
|
for region in self.regions:
|
|
self.highlight_region(region)
|
|
self.dither()
|
|
self.highlight_region(self.exterior, "blue")
|
|
self.dither()
|
|
self.highlight_region(self.exterior, "black")
|
|
self.remove(two[6])
|
|
two = two[7]
|
|
one = tex_mobject("1").shift(two.get_center())
|
|
two.highlight("red")
|
|
self.add(two)
|
|
self.animate(CounterclockwiseTransform(two, one))
|
|
|
|
class FormulaRelatesToPowersOfTwo(Scene):
|
|
def __init__(self, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
pof2_range = [1, 2, 3, 4, 5, 10]
|
|
strings = [
|
|
[
|
|
r"&1 + {%d \choose 2} + {%d \choose 4} ="%(n, n),
|
|
r"1 + %d + %d ="%(choose(n, 2), choose(n, 4)),
|
|
r"%d \\"%moser_function(n)
|
|
]
|
|
for n in [1, 2, 3, 4, 5, 10]
|
|
]
|
|
everything = tex_mobjects(sum(strings, []), size = r"\large")
|
|
scale_factor = 1
|
|
for mob in everything:
|
|
mob.scale(scale_factor)
|
|
CompoundMobject(*everything).show()
|
|
forms = everything[0::3]
|
|
sums = everything[1::3]
|
|
results = everything[2::3]
|
|
self.add(*forms)
|
|
self.animate(*[
|
|
FadeIn(s) for s in sums
|
|
])
|
|
self.dither()
|
|
self.animate(*[
|
|
Transform(deepcopy(s), result)
|
|
for s, result in zip(sums, results)
|
|
])
|
|
powers_of_two = [
|
|
tex_mobject("2^{%d}"%(i-1)
|
|
).scale(scale_factor
|
|
).shift(result.get_center()
|
|
).highlight()
|
|
for i, result in zip(pof2_range, results)
|
|
]
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.add(*forms + sums + results)
|
|
self.animate(*[
|
|
CounterclockwiseTransform(result, pof2)
|
|
for result, pof2 in zip(results, powers_of_two)
|
|
])
|
|
|
|
class DrawPascalsTriangle(PascalsTriangleScene):
|
|
def __init__(self, *args, **kwargs):
|
|
PascalsTriangleScene.__init__(self, *args, **kwargs)
|
|
self.remove(*self.mobjects)
|
|
self.add(self.coords_to_mobs[0][0])
|
|
for n in range(1, nrows):
|
|
starts = [deepcopy(self.coords_to_mobs[n-1][0])]
|
|
starts += [
|
|
CompoundMobject(
|
|
self.coords_to_mobs[n-1][k-1],
|
|
self.coords_to_mobs[n-1][k]
|
|
)
|
|
for k in range(1, n)
|
|
]
|
|
starts.append(deepcopy(self.coords_to_mobs[n-1][n-1]))
|
|
self.animate(*[
|
|
Transform(starts[i], self.coords_to_mobs[n][i],
|
|
run_time = 1.5, black_out_extra_points = False)
|
|
for i in range(n+1)
|
|
])
|
|
|
|
class PascalRuleExample(PascalsTriangleScene):
|
|
def __init__(self, nrows, *args, **kwargs):
|
|
assert(nrows > 1)
|
|
PascalsTriangleScene.__init__(self, nrows, *args, **kwargs)
|
|
self.dither()
|
|
n = randint(2, nrows-1)
|
|
k = randint(1, n-1)
|
|
self.coords_to_mobs[n][k].highlight("green")
|
|
self.dither()
|
|
plus = tex_mobject("+").scale(0.5)
|
|
nums_above = [self.coords_to_mobs[n-1][k-1], self.coords_to_mobs[n-1][k]]
|
|
plus.center().shift(sum(map(Mobject.get_center, nums_above)) / 2)
|
|
self.add(plus)
|
|
for mob in nums_above + [plus]:
|
|
mob.highlight("yellow")
|
|
self.dither()
|
|
|
|
class PascalsTriangleWithNChooseK(PascalsTriangleScene):
|
|
def __init__(self, *args, **kwargs):
|
|
PascalsTriangleScene.__init__(self, *args, **kwargs)
|
|
self.generate_n_choose_k_mobs()
|
|
mob_dicts = (self.coords_to_mobs, self.coords_to_n_choose_k)
|
|
for i in [0, 1]:
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
self.animate(*[
|
|
CounterclockwiseTransform(
|
|
deepcopy(mob_dicts[i][n][k]),
|
|
mob_dicts[1-i][n][k]
|
|
)
|
|
for n, k in self.coords
|
|
])
|
|
self.remove(*self.mobjects)
|
|
self.add(*[mob_dicts[1-i][n][k] for n, k in self.coords])
|
|
|
|
class PascalsTriangleNChooseKExample(PascalsTriangleScene):
|
|
args_list = [
|
|
(N_PASCAL_ROWS, 5, 3),
|
|
]
|
|
@staticmethod
|
|
def args_to_string(nrows, n, k):
|
|
return "%d_n=%d_k=%d"%(nrows, n, k)
|
|
|
|
def __init__(self, nrows, n, k, *args, **kwargs):
|
|
PascalsTriangleScene.__init__(self, nrows, *args, **kwargs)
|
|
dither_time = 0.5
|
|
triangle_terms = [self.coords_to_mobs[a][b] for a, b in self.coords]
|
|
formula_terms = left, n_mob, k_mob, right = tex_mobject([
|
|
r"\left(", str(n), r"\atop %d"%k, r"\right)"
|
|
])
|
|
formula_center = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
|
|
self.remove(*triangle_terms)
|
|
self.add(*formula_terms)
|
|
self.dither()
|
|
self.animate(*
|
|
[
|
|
ShowCreation(mob) for mob in triangle_terms
|
|
]+[
|
|
ApplyMethod(mob.shift, formula_center)
|
|
for mob in formula_terms
|
|
],
|
|
run_time = 1.0
|
|
)
|
|
self.remove(n_mob, k_mob)
|
|
for a in range(n+1):
|
|
row = [self.coords_to_mobs[a][b] for b in range(a+1)]
|
|
a_mob = tex_mobject(str(a))
|
|
a_mob.shift(n_mob.get_center())
|
|
a_mob.highlight("green")
|
|
self.add(a_mob)
|
|
for mob in row:
|
|
mob.highlight("green")
|
|
self.dither(dither_time)
|
|
if a < n:
|
|
for mob in row:
|
|
mob.highlight("white")
|
|
self.remove(a_mob)
|
|
self.dither()
|
|
for b in range(k+1):
|
|
b_mob = tex_mobject(str(b))
|
|
b_mob.shift(k_mob.get_center())
|
|
b_mob.highlight("yellow")
|
|
self.add(b_mob)
|
|
self.coords_to_mobs[n][b].highlight("yellow")
|
|
self.dither(dither_time)
|
|
if b < k:
|
|
self.coords_to_mobs[n][b].highlight("green")
|
|
self.remove(b_mob)
|
|
self.animate(*[
|
|
ApplyMethod(mob.fade, 0.2)
|
|
for mob in triangle_terms
|
|
if mob != self.coords_to_mobs[n][k]
|
|
])
|
|
self.dither()
|
|
|
|
class PascalsTriangleSumRows(PascalsTriangleScene):
|
|
def __init__(self, *args, **kwargs):
|
|
PascalsTriangleScene.__init__(self, *args, **kwargs)
|
|
pluses = []
|
|
powers_of_two = []
|
|
equalses = []
|
|
powers_of_two_symbols = []
|
|
plus = tex_mobject("+")
|
|
desired_plus_width = self.coords_to_mobs[0][0].get_width()
|
|
if plus.get_width() > desired_plus_width:
|
|
plus.scale(desired_plus_width / plus.get_width())
|
|
for n, k in self.coords:
|
|
if k == 0:
|
|
continue
|
|
new_plus = deepcopy(plus)
|
|
new_plus.center().shift(self.coords_to_mobs[n][k].get_center())
|
|
new_plus.shift((-self.cell_width / 2.0, 0, 0))
|
|
pluses.append(new_plus)
|
|
equals = tex_mobject("=")
|
|
equals.scale(min(1, 0.7 * self.cell_height / equals.get_width()))
|
|
for n in range(self.nrows):
|
|
new_equals = deepcopy(equals)
|
|
pof2 = tex_mobjects(str(2**n))
|
|
symbol = tex_mobject("2^{%d}"%n)
|
|
desired_center = np.array((
|
|
self.diagram_width / 2.0,
|
|
self.coords_to_mobs[n][0].get_center()[1],
|
|
0
|
|
))
|
|
new_equals.shift(desired_center - new_equals.get_center())
|
|
desired_center += (1.5*equals.get_width(), 0, 0)
|
|
scale_factor = self.coords_to_mobs[0][0].get_height() / pof2.get_height()
|
|
for mob in pof2, symbol:
|
|
mob.center().scale(scale_factor).shift(desired_center)
|
|
symbol.shift((0, 0.5*equals.get_height(), 0)) #FUAH! Stupid
|
|
powers_of_two.append(pof2)
|
|
equalses.append(new_equals)
|
|
powers_of_two_symbols.append(symbol)
|
|
self.animate(FadeIn(CompoundMobject(*pluses)))
|
|
run_time = 0.5
|
|
to_remove = []
|
|
for n in range(self.nrows):
|
|
start = CompoundMobject(*[self.coords_to_mobs[n][k] for k in range(n+1)])
|
|
to_remove.append(start)
|
|
self.animate(
|
|
Transform(start, powers_of_two[n]),
|
|
FadeIn(equalses[n]),
|
|
run_time = run_time
|
|
)
|
|
self.dither()
|
|
self.remove(*to_remove)
|
|
self.add(*powers_of_two)
|
|
for n in range(self.nrows):
|
|
self.animate(CounterclockwiseTransform(
|
|
powers_of_two[n], powers_of_two_symbols[n],
|
|
run_time = run_time
|
|
))
|
|
self.remove(powers_of_two[n])
|
|
self.add(powers_of_two_symbols[n])
|
|
|
|
|
|
class MoserSolutionInPascal(PascalsTriangleScene):
|
|
args_list = [
|
|
(N_PASCAL_ROWS, n)
|
|
for n in range(3, 8)
|
|
] + [
|
|
(BIG_N_PASCAL_ROWS, 10)
|
|
]
|
|
@staticmethod
|
|
def args_to_string(nrows, n):
|
|
return "%d_n=%d"%(nrows,n)
|
|
|
|
def __init__(self, nrows, n, *args, **kwargs):
|
|
PascalsTriangleScene.__init__(self, nrows, *args, **kwargs)
|
|
term_color = "green"
|
|
self.generate_n_choose_k_mobs()
|
|
self.remove(*[self.coords_to_mobs[n0][k0] for n0, k0 in self.coords])
|
|
terms = one, plus0, n_choose_2, plus1, n_choose_4 = tex_mobject([
|
|
"1", "+", r"{%d \choose 2}"%n, "+", r"{%d \choose 4}"%n
|
|
]).split()
|
|
target_terms = []
|
|
for k in range(len(terms)):
|
|
if k%2 == 0 and k <= n:
|
|
new_term = deepcopy(self.coords_to_n_choose_k[n][k])
|
|
new_term.highlight(term_color)
|
|
else:
|
|
new_term = Point(
|
|
self.coords_to_center(n, k)
|
|
)
|
|
target_terms.append(new_term)
|
|
self.add(*terms)
|
|
self.dither()
|
|
self.animate(*
|
|
[
|
|
FadeIn(self.coords_to_n_choose_k[n0][k0])
|
|
for n0, k0 in self.coords
|
|
if (n0, k0) not in [(n, 0), (n, 2), (n, 4)]
|
|
]+[
|
|
Transform(term, target_term)
|
|
for term, target_term in zip(terms, target_terms)
|
|
]
|
|
)
|
|
self.dither()
|
|
term_range = range(0, min(4, n)+1, 2)
|
|
target_terms = dict([
|
|
(k, deepcopy(self.coords_to_mobs[n][k]).highlight(term_color))
|
|
for k in term_range
|
|
])
|
|
self.animate(*
|
|
[
|
|
CounterclockwiseTransform(
|
|
self.coords_to_n_choose_k[n0][k0],
|
|
self.coords_to_mobs[n0][k0]
|
|
)
|
|
for n0, k0 in self.coords
|
|
if (n0, k0) not in [(n, k) for k in term_range]
|
|
]+[
|
|
CounterclockwiseTransform(terms[k], target_terms[k])
|
|
for k in term_range
|
|
]
|
|
)
|
|
self.dither()
|
|
for k in term_range:
|
|
if k == 0:
|
|
above_terms = [self.coords_to_n_choose_k[n-1][k]]
|
|
elif k == n:
|
|
above_terms = [self.coords_to_n_choose_k[n-1][k-1]]
|
|
else:
|
|
above_terms = [
|
|
self.coords_to_n_choose_k[n-1][k-1],
|
|
self.coords_to_n_choose_k[n-1][k],
|
|
]
|
|
self.add(self.coords_to_mobs[n][k])
|
|
self.animate(Transform(
|
|
terms[k],
|
|
CompoundMobject(*above_terms).highlight(term_color)
|
|
))
|
|
self.remove(*above_terms)
|
|
self.dither()
|
|
terms_sum = tex_mobject(str(moser_function(n)))
|
|
terms_sum.shift((SPACE_WIDTH-1, terms[0].get_center()[1], 0))
|
|
terms_sum.highlight(term_color)
|
|
self.animate(Transform(CompoundMobject(*terms), terms_sum))
|
|
|
|
class RotatingPolyhedra(Scene):
|
|
args_list = [
|
|
([Cube, Dodecahedron],)
|
|
]
|
|
@staticmethod
|
|
def args_to_string(class_list):
|
|
return "".join([c.__name__ for c in class_list])
|
|
|
|
def __init__(self, polyhedra_classes, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
rot_kwargs = {
|
|
"radians" : np.pi / 2,
|
|
"run_time" : 5.0,
|
|
"axis" : [0, 1, 0]
|
|
}
|
|
polyhedra = [
|
|
Class().scale(1.5).shift((1, 0, 0))
|
|
for Class in polyhedra_classes
|
|
]
|
|
curr_mob = polyhedra.pop()
|
|
for mob in polyhedra:
|
|
self.animate(TransformAnimations(
|
|
Rotating(curr_mob, **rot_kwargs),
|
|
Rotating(mob, **rot_kwargs)
|
|
))
|
|
for m in polyhedra:
|
|
m.rotate(rot_kwargs["radians"], rot_kwargs["axis"])
|
|
self.animate(Rotating(curr_mob, **rot_kwargs))
|
|
|
|
class ExplainNChoose2Formula(Scene):
|
|
args_list = [(7,2,6)]
|
|
@staticmethod
|
|
def args_to_string(n, a, b):
|
|
return "n=%d_a=%d_b=%d"%(n, a, b)
|
|
|
|
def __init__(self, n, a, b, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
r_paren, a_mob, comma, b_mob, l_paren = tex_mobjects(
|
|
("( %d , %d )"%(a, b)).split(" ")
|
|
)
|
|
parens = CompoundMobject(r_paren, comma, l_paren)
|
|
nums = [tex_mobject(str(k)) for k in range(1, n+1)]
|
|
height = 1.5*nums[0].get_height()
|
|
for x in range(n):
|
|
nums[x].shift((0, x*height, 0))
|
|
nums_compound = CompoundMobject(*nums)
|
|
nums_compound.shift(a_mob.get_center() - nums[0].get_center())
|
|
n_mob, n_minus_1, over_2 = tex_mobject([
|
|
str(n), "(%d-1)"%n, r"\over{2}"
|
|
]).split()
|
|
for part in n_mob, n_minus_1, over_2:
|
|
part.shift((SPACE_WIDTH-1.5, SPACE_HEIGHT-1, 0))
|
|
|
|
self.add(parens, n_mob)
|
|
up_unit = np.array((0, height, 0))
|
|
self.animate(ApplyMethod(nums_compound.shift, -(n-1)*up_unit))
|
|
self.animate(ApplyMethod(nums_compound.shift, (n-a)*up_unit))
|
|
self.remove(nums_compound)
|
|
nums = nums_compound.split()
|
|
a_mob = nums.pop(a-1)
|
|
nums_compound = CompoundMobject(*nums)
|
|
self.add(a_mob, nums_compound)
|
|
self.dither()
|
|
right_shift = b_mob.get_center() - a_mob.get_center()
|
|
right_shift[1] = 0
|
|
self.animate(
|
|
ApplyMethod(nums_compound.shift, right_shift),
|
|
FadeIn(n_minus_1)
|
|
)
|
|
self.animate(ApplyMethod(nums_compound.shift, (a-b)*up_unit))
|
|
self.remove(nums_compound)
|
|
nums = nums_compound.split()
|
|
b_mob = nums.pop(b-2 if a < b else b-1)
|
|
self.add(b_mob)
|
|
self.animate(*[
|
|
CounterclockwiseTransform(
|
|
mob,
|
|
Point(mob.get_center()).highlight("black")
|
|
)
|
|
for mob in nums
|
|
])
|
|
self.animate(*[
|
|
ApplyMethod(mob.shift, (0, 1, 0))
|
|
for mob in parens, a_mob, b_mob
|
|
])
|
|
parens_copy = deepcopy(parens).shift((0, -2, 0))
|
|
a_center = a_mob.get_center()
|
|
b_center = b_mob.get_center()
|
|
a_copy = deepcopy(a_mob).center().shift(b_center - (0, 2, 0))
|
|
b_copy = deepcopy(b_mob).center().shift(a_center - (0, 2, 0))
|
|
self.add(over_2, deepcopy(a_mob), deepcopy(b_mob))
|
|
self.animate(
|
|
CounterclockwiseTransform(a_mob, a_copy),
|
|
CounterclockwiseTransform(b_mob, b_copy),
|
|
FadeIn(parens_copy),
|
|
FadeIn(text_mobject("is considered the same as"))
|
|
)
|
|
|
|
class ExplainNChoose4Formula(Scene):
|
|
args_list = [(7,)]
|
|
@staticmethod
|
|
def args_to_string(n):
|
|
return "n=%d"%n
|
|
|
|
def __init__(self, n, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
# quad = list(it.combinations(range(1,n+1), 4))[randint(0, choose(n, 4)-1)]
|
|
quad = (4, 2, 5, 1)
|
|
tuple_mobs = tex_mobjects(
|
|
("( %d , %d , %d , %d )"%quad).split(" ")
|
|
)
|
|
quad_mobs = tuple_mobs[1::2]
|
|
parens = CompoundMobject(*tuple_mobs[0::2])
|
|
form_mobs = tex_mobject([
|
|
str(n), "(%d-1)"%n, "(%d-2)"%n,"(%d-3)"%n,
|
|
r"\over {4 \cdot 3 \cdot 2 \cdot 1}"
|
|
]).split()
|
|
form_mobs = CompoundMobject(*form_mobs).scale(0.7).shift((4, 3, 0)).split()
|
|
nums = [tex_mobject(str(k)) for k in range(1, n+1)]
|
|
height = 1.5*nums[0].get_height()
|
|
for x in range(n):
|
|
nums[x].shift((0, x*height, 0))
|
|
nums_compound = CompoundMobject(*nums)
|
|
nums_compound.shift(quad_mobs[0].get_center() - nums[0].get_center())
|
|
curr_num = 1
|
|
self.add(parens)
|
|
up_unit = np.array((0, height, 0))
|
|
for i in range(4):
|
|
self.add(form_mobs[i])
|
|
self.animate(ApplyMethod(
|
|
nums_compound.shift, (curr_num-quad[i])*up_unit))
|
|
self.remove(nums_compound)
|
|
nums = nums_compound.split()
|
|
chosen = nums[quad[i]-1]
|
|
nums[quad[i]-1] = Point(chosen.get_center()).highlight("black")
|
|
nums_compound = CompoundMobject(*nums)
|
|
self.add(chosen)
|
|
if i < 3:
|
|
right_shift = quad_mobs[i+1].get_center() - chosen.get_center()
|
|
right_shift[1] = 0
|
|
self.animate(
|
|
ApplyMethod(nums_compound.shift, right_shift)
|
|
)
|
|
else:
|
|
self.animate(*[
|
|
CounterclockwiseTransform(
|
|
mob,
|
|
Point(mob.get_center()).highlight("black")
|
|
)
|
|
for mob in nums
|
|
])
|
|
curr_num = quad[i]
|
|
self.remove(*self.mobjects)
|
|
num_perms_explain = text_mobject(
|
|
r"There are $(4 \cdot 3 \cdot 2 \cdot 1)$ total permutations"
|
|
).shift((0, -2, 0))
|
|
self.add(parens, num_perms_explain, *form_mobs)
|
|
perms = list(it.permutations(range(4)))
|
|
for count in range(6):
|
|
perm = perms[randint(0, 23)]
|
|
new_quad_mobs = [
|
|
deepcopy(quad_mobs[i]).shift(
|
|
quad_mobs[perm[i]].get_center() - \
|
|
quad_mobs[i].get_center()
|
|
)
|
|
for i in range(4)
|
|
]
|
|
compound_quad = CompoundMobject(*quad_mobs)
|
|
self.animate(CounterclockwiseTransform(
|
|
compound_quad,
|
|
CompoundMobject(*new_quad_mobs)
|
|
))
|
|
self.remove(compound_quad)
|
|
quad_mobs = new_quad_mobs
|
|
|
|
class IntersectionChoppingExamples(Scene):
|
|
def __init__(self, *args, **kwargs):
|
|
Scene.__init__(self, *args, **kwargs)
|
|
pairs1 = [
|
|
((-1,-1, 0), (-1, 0, 0)),
|
|
((-1, 0, 0), (-1, 1, 0)),
|
|
((-2, 0, 0), (-1, 0, 0)),
|
|
((-1, 0, 0), ( 1, 0, 0)),
|
|
(( 1, 0, 0), ( 2, 0, 0)),
|
|
(( 1,-1, 0), ( 1, 0, 0)),
|
|
(( 1, 0, 0), ( 1, 1, 0)),
|
|
]
|
|
pairs2 = pairs1 + [
|
|
(( 1, 1, 0), ( 1, 2, 0)),
|
|
(( 0, 1, 0), ( 1, 1, 0)),
|
|
(( 1, 1, 0), ( 2, 1, 0)),
|
|
]
|
|
for pairs, exp in [(pairs1, "3 + 2(2) = 7"),
|
|
(pairs2, "4 + 2(3) = 10")]:
|
|
lines = [Line(*pair).scale(2) for pair in pairs]
|
|
self.add(tex_mobject(exp).shift((0, SPACE_HEIGHT-1, 0)))
|
|
self.add(*lines)
|
|
self.dither()
|
|
self.animate(*[
|
|
Transform(line, deepcopy(line).scale(1.2).scale_in_place(1/1.2))
|
|
for line in lines
|
|
])
|
|
self.count(lines, run_time = 3.0, num_offset = ORIGIN)
|
|
self.dither()
|
|
self.remove(*self.mobjects)
|
|
|
|
|
|
##################################################
|
|
|
|
if __name__ == "__main__":
|
|
command_line_create_scene(MOVIE_PREFIX)
|
|
|
|
|
|
|