3b1b-videos/_2020/monster.py
2021-01-01 20:10:38 -08:00

4480 lines
139 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from manim_imports_ext import *
MONSTER_SIZE = 808017424794512875886459904961710757005754368000000000
BABY_MONSTER_SIZE = 4154781481226426191177580544000000
def get_monster(height=3):
monster = SVGMobject("monster")
monster.set_fill(GREY_BROWN)
monster.set_stroke(BLACK, 0.1)
monster.set_height(height)
return monster
def get_baby_monster():
return get_monster().stretch(0.7, 1)
def blink_monster(monster):
monster.generate_target()
left_eye_points = monster.target[0].points[498:528]
right_eye_points = monster.target[0].points[582:612]
for points in left_eye_points, right_eye_points:
points[:, 1] = points[0, 1]
return MoveToTarget(monster, rate_func=squish_rate_func(there_and_back, 0.4, 0.6))
def get_size_bars(mob, stroke_width=3, buff=SMALL_BUFF):
bars = VGroup(*[Line(UP, DOWN) for x in range(2)])
bars.match_height(mob)
bars[0].next_to(mob, LEFT, buff=buff)
bars[1].next_to(mob, RIGHT, buff=buff)
bars.set_stroke(WHITE, stroke_width)
return bars
def get_monster_size_label():
size_label = TextMobject("{:,}".format(MONSTER_SIZE))[0]
size_parts = VGroup(*[
size_label[i:i + 12]
for i in range(0, len(size_label), 12)
])
size_parts.arrange(DOWN, buff=SMALL_BUFF, aligned_edge=LEFT)
return size_label
def get_snowflake(height=2):
snowflake = SVGMobject("snowflake")
snowflake.set_fill(GREY_C, 1)
snowflake.set_gloss(1)
snowflake.set_shadow(0.2)
snowflake.set_stroke(WHITE, 1)
snowflake.set_height(height)
return snowflake
def get_cube(color=BLUE_D, opacity=1, height=2, frame=None):
poor_cube = Cube()
cube = Cube(square_resolution=(10, 10))
cube.set_color(color, opacity)
cube.center()
for face, p_face in zip(cube, poor_cube):
face.add(*[
Line3D(p_face.points[i], p_face.points[j], width=0.02, color=GREY_B)
for i, j in [(0, 1), (1, 3), (3, 2), (2, 0)]
])
cube.set_height(height)
return cube
def get_glassy_cube(frame):
verts = np.array(list(it.product(*3 * [[-1, 1]])))
edges = [
(v1, v2)
for v1, v2 in it.combinations(verts, 2)
if sum(v1 == v2) == 2
]
corner_dots = Group(*[
Sphere(resolution=(51, 26),).set_height(0.25).move_to(vert)
for vert in verts
])
corner_dots.set_color(GREY_B)
edge_rods = Group(*[
Line3D(v1, v2)
for v1, v2 in edges
])
faces = Cube(square_resolution=(10, 10))
faces.set_height(2)
faces.set_color(BLUE_E, 0.3)
faces.add_updater(lambda m, f=frame: m.sort(lambda p: np.dot(p, [np.sign(f.euler_angles[0]) * 0.2, -1, 0.2])))
cube = Group(corner_dots, edge_rods, faces)
cube.corner_dots = corner_dots
cube.edge_rods = edge_rods
cube.faces = faces
return cube
def get_rot_icon(degrees, mobject, mini_mob_height=1.25):
mini_mob = mobject.copy()
temp_height = 1.75
mini_mob.set_height(temp_height)
mini_mob.set_stroke(width=0)
mini_mob.center()
angle = min(degrees * DEGREES, 170 * DEGREES)
arc1 = Arrow(
RIGHT, rotate_vector(RIGHT, angle),
path_arc=angle,
buff=0
)
arc2 = arc1.copy().rotate(PI, about_point=ORIGIN)
label = Integer(degrees, unit="^\\circ")
label.set_height(0.25)
half_vect = rotate_vector(RIGHT, angle / 2)
label.next_to(half_vect, half_vect, buff=MED_SMALL_BUFF)
icon = VGroup(mini_mob, arc1, arc2, label)
icon.scale(mini_mob_height / temp_height)
return icon
def get_flip_icon(angle, mobject, opacity=0.75, mini_mob_height=1.25):
mini_mob = mobject.copy()
mini_mob.set_stroke(width=0)
mini_mob.set_fill(opacity=opacity)
mini_mob.set_height(mini_mob_height)
mini_mob.center()
sym_line = DashedLine(LEFT, RIGHT)
sym_line.set_stroke(WHITE, 2)
sym_line.set_width(1.2 * mini_mob_height)
sym_line.rotate(angle)
sym_line.move_to(mini_mob)
back_line = sym_line.copy()
back_line.set_stroke(BLACK, 5)
return VGroup(mini_mob, back_line, sym_line)
def get_permutation_arrows(mobs, permutation, arc=PI / 2):
arrows = VGroup()
for i in range(len(permutation)):
j = permutation[i]
u = -1 if mobs[i].get_x() < mobs[j].get_x() else 1
arrow = Arrow(
mobs[i].get_edge_center(u * UP),
mobs[j].get_edge_center(u * UP),
buff=SMALL_BUFF,
path_arc=arc,
)
arrow.insert_n_curves(10)
arrow.set_stroke(BLACK, 2, background=True)
arrow.set_fill(BLUE, 0.8)
arrows.add(arrow)
return arrows
def permutation_animation(mobs, perm=None, arc=PI / 2, lag_factor=3, run_time=None):
if perm is None:
targets = list(mobs)
random.shuffle(targets)
else:
targets = [mobs[i] for i in perm]
kw = {"lag_ratio": lag_factor / len(mobs)}
if run_time is not None:
kw["run_time"] = run_time
return LaggedStart(
*[
ApplyMethod(m1.move_to, m2, path_arc=arc)
for m1, m2 in zip(mobs, targets)
],
**kw
)
def get_named_image(name, height=3):
image = ImageMobject(name)
image.set_height(height)
name = TextMobject(name)
name.match_width(image)
name.next_to(image, DOWN)
group = Group(image, name)
return group
# Scenes
class Thumbnail(Scene):
def construct(self):
monster = get_monster()
monster.set_height(7)
monster.to_edge(LEFT)
monster.set_gloss(0.2)
words = VGroup(
TextMobject("The"),
TextMobject("Monster"),
TextMobject("Group"),
)
words.scale(3.5)
words.arrange(DOWN, buff=0.5, aligned_edge=LEFT)
words.set_stroke(BLACK, 3, background=True)
words.to_edge(RIGHT)
self.add(monster)
self.add(words)
class AskAboutFavoriteMegaNumber(TeacherStudentsScene):
CONFIG = {
"background_color": BLACK,
}
def construct(self):
self.remove(self.pi_creatures)
# YouTubers
title = TextMobject("What's your favorite number $> 1{,}000{,}000$?")
title.set_width(FRAME_WIDTH - 1)
title.to_edge(UP)
images = Group(
ImageMobject("standupmaths"),
ImageMobject("singingbanana"),
ImageMobject("Ben Sparks"),
ImageMobject("Zoe Griffiths"),
ImageMobject("tomrocksmaths"),
ImageMobject("James Tanton"),
ImageMobject("blackpenredpen"),
ImageMobject("Eddie Woo"),
)
images.arrange_in_grid(2, 4, buff=MED_LARGE_BUFF)
images.set_width(FRAME_WIDTH - 2)
images.next_to(title, DOWN, MED_LARGE_BUFF)
self.play(
FadeIn(title, DOWN),
LaggedStartMap(
FadeIn, images,
lambda m: (m, -0.1 * m.get_center()),
lag_ratio=0.3,
run_time=5,
)
)
self.wait()
# Pi Creatures
self.teacher_says(
"And we want\\\\you to join!",
target_mode="surprised",
bubble_kwargs={
"height": 3,
"width": 4,
},
added_anims=[
VFadeIn(self.pi_creatures),
images.scale, 0.2,
images.space_out_submobjects, 10,
images.set_opacity, 0,
],
)
self.remove(images)
self.change_student_modes("guilty", "hooray", "wave_2")
self.wait(5)
class IntroduceMonsterSize(Scene):
def construct(self):
# Show number
max_width = FRAME_WIDTH - 1
size_label = TextMobject("{:,}".format(MONSTER_SIZE))[0]
size_label.set_width(max_width)
n_syms = len(size_label)
partials = VGroup()
for n in range(len(size_label) + 1):
partial = size_label[:n].copy()
partial.set_height(1.5)
if partial.get_width() > max_width:
partial.set_width(max_width)
partial.center()
partial.set_color(interpolate_color(BLUE, YELLOW, n / n_syms))
partials.add(partial)
self.play(
UpdateFromAlphaFunc(
size_label,
lambda m, a: m.set_submobjects(
partials[int(a * n_syms)].submobjects
),
run_time=6,
rate_func=bezier([0, 0, 1, 1])
)
)
self.wait()
# Show factorization
factors = TexMobject(
r"= 2^{46} \cdot 3^{20} \cdot 5^{9} \cdot 7^{6} \cdot 11^{2} \cdot 13^{3} \cdot 17 \cdot 19 \cdot 23 \cdot 29 \cdot 31 \cdot 41 \cdot 47 \cdot 59 \cdot 71"
)
factors.set_width(max_width)
approx = TexMobject("\\approx 8\\times 10^{53}")
approx.set_height(0.8)
approx.move_to(DOWN)
factors.next_to(approx, UP, buff=MED_LARGE_BUFF)
self.play(
size_label.next_to, factors, UP, MED_LARGE_BUFF,
FadeIn(factors, 0.5 * DOWN),
Write(approx),
)
self.wait()
value_group = VGroup(size_label, factors, approx)
# Jupiter
jupiter = TexturedSurface(Sphere(), "JupiterTexture")
jupiter.rotate(90 * DEGREES, RIGHT)
jupiter.set_height(3.5)
jupiter.to_edge(DOWN)
jupiter.add_updater(lambda m, dt: m.rotate(-0.1 * dt, UP))
jupiter.set_shadow(0.9)
self.play(
UpdateFromAlphaFunc(jupiter, lambda m, a: m.set_opacity(a)),
ApplyMethod(value_group.to_edge, UP, run_time=2)
)
self.wait(4)
# Alternate intelligences
alien = SVGMobject("alien")
alien.set_height(3)
alien.to_corner(DL)
alien.set_stroke(GREEN, width=0)
alien.set_fill(GREEN_E, 1)
server = SVGMobject("server_stack")
server.set_height(3)
server.to_corner(DR)
server.set_stroke(BLACK, 2)
server.set_fill(GREY, 1)
server.set_gloss(0.5)
alien_words = TextMobject("Interesting!")
alien_words.set_color(GREEN)
alien_words.next_to(alien, UR, buff=-MED_SMALL_BUFF)
server_words = TextMobject("Very interesting\\\\indeed!")
server_words.next_to(server, LEFT)
self.play(
FadeOut(jupiter, DOWN),
DrawBorderThenFill(alien),
)
self.play(Write(server))
self.wait()
for words in alien_words, server_words:
self.play(Write(words, run_time=1))
self.wait()
# What is it?
question = TextMobject("What is it?")
question.scale(2)
question.move_to(UP)
self.play(
LaggedStartMap(
FadeOut,
VGroup(factors, approx, alien_words, alien, server_words, server),
lambda m: (m, DOWN),
),
ApplyMethod(size_label.move_to, 0.5 * DOWN, run_time=2),
FadeIn(question, UP, run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1)),
)
self.wait()
monster = get_monster()
monster.next_to(size_label, UP)
monster.to_edge(RIGHT, buff=2)
m_size_bars = get_size_bars(monster, buff=MED_SMALL_BUFF, stroke_width=5)
self.play(
question.shift, 2 * LEFT,
DrawBorderThenFill(monster),
run_time=2,
)
self.play(ShowCreation(m_size_bars, lag_ratio=0.4))
self.wait(2)
self.play(LaggedStart(*[
FadeOut(mob, DOWN)
for mob in self.mobjects
]))
class IntroduceGroupTheory(Scene):
def construct(self):
# Title
over_title = TextMobject("An introduction\\\\to")
over_title.scale(2.5)
over_title.move_to(ORIGIN, DOWN)
title = TextMobject("Group theory")
title.scale(2.5)
title.next_to(over_title, DOWN, buff=0.5)
arrows = VGroup(Vector(DOWN), Vector(UP))
arrows.arrange(RIGHT, buff=SMALL_BUFF)
arrows.scale(2)
arrows.next_to(title, DOWN, buff=MED_LARGE_BUFF)
sym_amb = SVGMobject("symmetry_ambigram")
sym_amb.set_stroke(width=0)
sym_amb.set_fill(BLUE, 1)
sym_amb.match_width(title)
sym_amb.next_to(arrows, DOWN)
sym_amb_ghost = sym_amb.copy()
sym_amb_ghost.set_fill(WHITE, 0.2)
self.add(over_title, title)
self.wait()
self.play(
title.scale, 2.0 / 2.5,
title.to_edge, UP,
FadeOut(over_title, UP)
)
self.play(FadeIn(title, DOWN))
self.play(
LaggedStartMap(GrowArrow, arrows, run_time=1),
Write(sym_amb, run_time=2)
)
self.add(sym_amb_ghost, sym_amb)
self.play(Rotate(sym_amb, PI, run_time=2))
self.remove(sym_amb_ghost)
self.wait()
# Symmetries of a face
face = ImageMobject("average_face")
face.set_height(5)
sym_word = TextMobject("Symmetric")
sym_word.scale(2)
sym_word.to_edge(UP)
face.next_to(sym_word, DOWN, buff=MED_LARGE_BUFF)
sym_word.save_state()
sym_word.replace(sym_amb)
sym_word.set_opacity(0)
face_citation = TextMobject("``Average face'' from the Face Research Lab\\\\DeBruine, Lisa \\& Jones, Benedict (2017)")
face_citation.set_height(0.4)
face_citation.next_to(face, DOWN)
face_citation.to_corner(DL)
sym_line = DashedLine(face.get_top(), face.get_bottom())
sym_line.scale(1.05)
sym_line.set_stroke(WHITE, 2)
self.play(
FadeIn(face, DOWN),
FadeIn(face_citation),
LaggedStartMap(FadeOut, VGroup(*arrows, title), lambda m: (m, UP)),
sym_amb.replace, sym_word.saved_state, {"stretch": True},
sym_amb.set_opacity, 0,
Restore(sym_word),
)
self.remove(sym_amb)
self.wait()
self.play(ShowCreation(sym_line), FadeOut(face_citation))
sym_line.rotate(PI)
self.play(ApplyMethod(face.stretch, -1, 0, run_time=2))
self.wait()
sym_to_action = TextMobject("Symmetry", " $\\Rightarrow$ ", "\\emph{Action}")
sym_to_action.set_color_by_tex("Action", YELLOW)
sym_to_action.replace(sym_word, dim_to_match=1)
sym_word.unlock_triangulation()
self.play(
ReplacementTransform(sym_word[0], sym_to_action[0]),
Write(sym_to_action[1]),
FadeIn(sym_to_action[2], LEFT),
)
self.play(ApplyMethod(face.stretch, -1, 0, run_time=2))
self.wait()
# Symmetries of a snowflake
snowflake = get_snowflake()
snowflake.set_height(5)
snowflake.next_to(sym_to_action, DOWN, MED_LARGE_BUFF)
self.play(
FadeOut(face),
Uncreate(sym_line),
ShowCreationThenFadeOut(snowflake.copy().set_stroke(WHITE, 2).set_fill(opacity=0)),
FadeIn(snowflake, run_time=2),
)
def get_flake_rot_icon(degrees, snowflake=snowflake):
return get_rot_icon(degrees, snowflake)
def get_flake_flip_icon(angle, snowflake=snowflake):
return get_flip_icon(angle, snowflake)
rot_icons = VGroup(
get_flake_rot_icon(60),
get_flake_rot_icon(120),
)
flip_icons = VGroup(
get_flake_flip_icon(60 * DEGREES),
get_flake_flip_icon(30 * DEGREES),
)
for icons, vect in [(rot_icons, LEFT), (flip_icons, RIGHT)]:
icons.arrange(DOWN, buff=MED_LARGE_BUFF)
icons.to_edge(vect)
self.play(
FadeIn(rot_icons[0]),
Rotate(snowflake, 60 * DEGREES)
)
self.wait()
self.play(
FadeIn(rot_icons[1]),
Rotate(snowflake, 120 * DEGREES)
)
self.wait()
sym_line = DashedLine(snowflake.get_bottom(), snowflake.get_top())
sym_line.scale(1.1)
sym_line.set_stroke(WHITE, 2)
sym_line.set_angle(60 * DEGREES)
sym_line.move_to(snowflake)
self.play(
ShowCreation(sym_line),
FadeIn(flip_icons[0]),
)
self.play(
Rotate(snowflake, PI, axis=rotate_vector(RIGHT, 60 * DEGREES)),
)
self.play(
sym_line.set_angle, 30 * DEGREES,
sym_line.move_to, snowflake,
FadeIn(flip_icons[1]),
)
self.play(
Rotate(snowflake, PI, axis=rotate_vector(RIGHT, 30 * DEGREES)),
)
# Collection of all snowflake symmetries
rot_icons.generate_target()
flip_icons.generate_target()
d6_group = VGroup(
get_flake_rot_icon(0),
rot_icons.target[0],
rot_icons.target[1],
get_flake_rot_icon(180),
get_flake_rot_icon(-120),
get_flake_rot_icon(-60),
get_flake_flip_icon(0),
flip_icons.target[1],
flip_icons.target[0],
get_flake_flip_icon(90 * DEGREES),
get_flake_flip_icon(120 * DEGREES),
get_flake_flip_icon(150 * DEGREES),
)
d6_group.arrange_in_grid(2, 6)
d6_group.set_width(FRAME_WIDTH - 2)
d6_group.set_gloss(0)
d6_group.set_shadow(0)
for mob in d6_group.get_family():
if isinstance(mob, SVGMobject):
mob.set_fill(GREY_C, 1)
mob.set_stroke(WHITE, 0.25)
self.play(
MoveToTarget(rot_icons),
MoveToTarget(flip_icons),
ApplyMethod(snowflake.scale, 0, remover=True),
ApplyMethod(sym_line.scale, 0, remover=True),
)
self.play(LaggedStartMap(FadeIn, d6_group))
self.remove(rot_icons, flip_icons)
self.wait()
# Name groups
group_name = TextMobject("Group", "$^*$")
group_name.scale(2)
group_name.to_edge(UP)
footnote = TextMobject("$^*$er...kind of. Keep watching")
footnote.set_height(0.3)
footnote.to_corner(UR)
footnote.add(group_name[1])
footnote.set_color(YELLOW)
group_name.remove(group_name[1])
d6_rect = SurroundingRectangle(d6_group)
d6_rect.set_stroke(BLUE, 2)
self.play(
FadeOut(sym_to_action, UP),
FadeIn(group_name, DOWN),
ShowCreation(d6_rect),
)
self.play(
FadeIn(
footnote,
rate_func=there_and_back_with_pause,
run_time=3,
remover=True
)
)
self.wait()
anims = []
for icon, deg in zip(d6_group[1:], [60, 120, 180, -120, -60]):
anims.append(Rotate(icon[0], deg * DEGREES))
for icon, deg in zip(d6_group[6:], range(0, 180, 30)):
anims.append(Rotate(icon[0], PI, axis=rotate_vector(RIGHT, deg * DEGREES)))
random.shuffle(anims)
self.play(LaggedStart(*anims, lag_ratio=0.3, run_time=5))
self.play(LaggedStart(*reversed(anims), lag_ratio=0.3, run_time=5))
# Identity
id_rect = SurroundingRectangle(d6_group[0])
id_words = TextMobject("The do-nothing", "\\\\action", alignment="")
id_words.to_corner(UL)
id_arrow = Arrow(id_words[1].get_bottom(), id_rect.get_top(), buff=0.2)
id_arrow.match_color(id_rect)
self.play(
ShowCreation(id_rect)
)
self.play(
FadeIn(id_words, lag_ratio=0.1),
GrowArrow(id_arrow),
)
self.wait()
# Count d6
rects = VGroup(id_rect, *map(SurroundingRectangle, d6_group[1:]))
counter = Integer(0)
counter.set_height(0.8)
counter.next_to(d6_rect, DOWN, MED_LARGE_BUFF)
counter.set_color(YELLOW)
counter.add_updater(lambda m, rects=rects: m.set_value(len(rects)))
self.add(counter)
self.play(
FadeOut(id_words),
FadeOut(id_arrow),
ShowIncreasingSubsets(rects, int_func=np.ceil, run_time=2),
UpdateFromAlphaFunc(counter, lambda m, a: m.set_opacity(a))
)
self.wait()
d6_name = TexMobject("D_6")
d6_name.scale(2)
d6_name.move_to(counter)
d6_name.set_color(BLUE)
self.play(
FadeOut(rects, lag_ratio=0.1),
FadeOut(counter, 0.2 * UP),
FadeIn(d6_name, 0.2 * DOWN),
)
self.wait()
# Name C2
face_group = Group(face, face.deepcopy())
face_group.set_height(4)
face_group.arrange(RIGHT, buff=LARGE_BUFF)
face_group.next_to(group_name, DOWN, MED_LARGE_BUFF)
sym_line = DashedLine(2 * UP, 2 * DOWN)
sym_line.set_stroke(WHITE, 2)
sym_line.move_to(face_group[1])
sym_line.set_height(face_group[1].get_height() * 1.1)
face_group[1].add(sym_line)
self.play(
d6_rect.replace, face_group, {"stretch": True},
d6_rect.scale, 1.1,
FadeOut(d6_group),
FadeOut(d6_name, DOWN),
*[
FadeIn(f, -0.5 * f.get_center())
for f in face_group
],
)
self.play(face_group[1].stretch, -1, 0)
self.wait()
z2_name = TexMobject("C_2")
z2_name.match_color(d6_name)
z2_name.match_height(d6_name)
z2_name.next_to(d6_rect, DOWN, MED_LARGE_BUFF)
self.play(Write(z2_name))
self.wait()
class ZooOfGroups(ThreeDScene):
def construct(self):
self.camera.light_source.move_to([-10, 5, 20])
dot_pair = VGroup(Dot(), Dot())
dot_pair.set_height(0.5)
dot_pair.arrange(RIGHT, buff=LARGE_BUFF)
dot_pair.set_color(GREY_B)
snowflake = get_snowflake(height=2)
k4_axes = Group(
Line3D(LEFT, RIGHT, color=RED),
Line3D(DOWN, UP, color=GREEN),
Line3D(IN, OUT, color=BLUE),
)
k4_axes.set_height(3)
quat_group = TexMobject("\\{1, -1, i , -i\\\\j, -j, k, -k\\}")
cube = get_cube(color=BLUE_D, opacity=1)
cube.rotate(15 * DEGREES, OUT)
cube.rotate(80 * DEGREES, LEFT)
sphere = Sphere()
sphere = Group(
sphere,
SurfaceMesh(sphere, resolution=(21, 11))
)
sphere[1].set_stroke(WHITE, 0.5, 0.5)
sphere.rotate(90 * DEGREES, LEFT)
sphere.rotate(0.2 * DEGREES, RIGHT)
sphere[0].sort_faces_back_to_front()
sphere.rotate(90 * DEGREES, UP)
sphere.set_height(3)
circle = Circle()
circle.set_height(3)
monster_object = TexMobject("196{,}", "883")
monster_object.arrange(DOWN, buff=0, aligned_edge=LEFT)
monster_object.set_height(1.5)
monster_object.add(Eyes(monster_object))
monster_object[-1].scale(0.8, about_edge=DR)
qubit = TexMobject(
"\\alpha|0\\rangle + \\beta|1\\rangle",
tex_to_color_map={"\\alpha": BLUE, "\\beta": YELLOW}
)
qubit.set_height(1)
qubit.set_height(1)
groups = Group(
Group(TexMobject("C_2"), dot_pair),
Group(TexMobject("D_6"), snowflake),
Group(TexMobject("K_4"), k4_axes),
Group(TexMobject("Q_8"), quat_group),
Group(TexMobject("S_4"), cube),
Group(TexMobject("SO(3)"), sphere),
Group(TexMobject("\\mathds{R}^+ / \\mathds{Z}"), circle),
Group(TexMobject("SU(2)"), qubit),
Group(get_monster(), monster_object),
)
for group in groups:
group[0].set_height(1)
group.arrange(RIGHT, buff=LARGE_BUFF)
groups[-1][0].scale(2)
groups.arrange_in_grid(3, 3)
groups.set_width(FRAME_WIDTH - 1)
groups[:3].shift(0.5 * UP)
groups[-3:].shift(0.5 * DOWN)
self.play(LaggedStart(*[
FadeIn(group[0], -0.5 * group.get_center())
for group in groups
]))
self.play(LaggedStart(*[
FadeInFromLarge(group[1])
for group in groups
]))
self.play(LaggedStart(
Rotate(dot_pair, PI),
Blink(monster_object[-1]),
Rotate(cube, PI / 2, axis=cube[0].get_center() - cube[-1].get_center()),
Rotate(snowflake, 120 * DEGREES),
Rotate(k4_axes, PI, axis=RIGHT),
Rotate(sphere, 170 * DEGREES, axis=UP),
run_time=3,
lag_ratio=0.1,
))
self.wait()
self.play(
groups[-1].scale, 3,
groups[-1].center,
groups[-1].space_out_submobjects, 1.5,
*[
FadeOut(mob, mob.get_center() - groups[-1].get_center())
for mob in groups[:-1]
]
)
self.play(monster_object[-1].look_at, groups[-1][0])
self.play(Blink(monster_object[-1]))
self.play(blink_monster(groups[-1][0]))
self.wait()
class SymmetriesOfACube(ThreeDScene):
def construct(self):
# Setup
frame = self.camera.frame
light = self.camera.light_source
light.move_to(5 * LEFT + 20 * DOWN + 10 * OUT)
plane = NumberPlane(x_range=(-10, 10), y_range=(-10, 10))
plane.shift(IN)
cube = get_cube(color=BLUE_D, opacity=1)
cube.set_gloss(0.5)
cube.set_shadow(0.2)
frame.set_rotation(
phi=70 * DEGREES,
theta=-30 * DEGREES,
)
frame.add_updater(lambda m, dt, sc=self: m.set_theta(-30 * DEGREES * np.cos(sc.time * 0.05)))
self.add(frame)
self.add(plane)
self.add(cube)
# Ask about structure
question = TextMobject("What structure is being preserved?")
question.set_height(0.7)
question.to_edge(UP)
question.fix_in_frame()
def get_rotation(deg, axis, cube=cube):
return Rotate(cube, deg * DEGREES, axis=axis, run_time=1.5)
pairs = [
(90, UP),
(90, RIGHT),
(90, OUT),
(120, [1, 1, 1]),
(120, [1, -1, 1]),
(180, UP),
]
for deg, axis in pairs:
self.play(get_rotation(deg, axis))
if axis is pairs[1][1]:
self.play(FadeIn(question, DOWN))
self.wait()
# Count cube symmetries
count_label = TextMobject("24 ", "symmetries")
count_label.set_color_by_tex("24", YELLOW)
count_label.set_height(0.7)
count_label.fix_in_frame()
count_label.to_edge(UP)
self.play(
FadeIn(count_label, DOWN),
FadeOut(question, UP),
)
self.play(get_rotation(120, [1, -1, -1]))
self.wait()
self.play(get_rotation(90, LEFT))
self.wait()
self.play(get_rotation(120, [1, -1, -1]))
self.wait()
self.play(get_rotation(180, OUT))
self.wait()
# Bigger group
reflection_plane = Square3D(resolution=(10, 10))
reflection_plane.set_width(4)
reflection_plane.move_to(cube)
reflection_plane.set_color(GREY, opacity=0.75)
reflection_plane.rotate(PI / 2, DOWN)
cross24 = Cross(count_label[0])
cross24.fix_in_frame()
label48 = TexMobject("48")
label48.set_color(GREEN)
label48.match_height(count_label[0])
label48.move_to(count_label[0], DOWN)
label48.fix_in_frame()
self.play(FadeInFromLarge(reflection_plane))
self.play(
ShowCreation(cross24),
ApplyMethod(cube.stretch, -1, 0),
)
self.wait()
self.play(
Rotate(reflection_plane, PI / 2, axis=UP)
)
self.play(
ApplyMethod(cube.stretch, -1, 2),
)
self.wait()
self.play(Rotate(reflection_plane, PI / 4, UP))
self.play(
cube.stretch, -1, 2,
cube.rotate, PI / 2, UP,
)
self.wait()
self.add(count_label[0], cross24)
self.play(
count_label[0].shift, 2 * LEFT,
cross24.shift, 2 * LEFT,
FadeIn(label48, UP),
)
self.play(
reflection_plane.rotate, PI / 4, UP,
reflection_plane.rotate, PI / 2, OUT,
)
self.play(
cube.stretch, -1, 1,
)
self.wait()
self.play(FadeOut(reflection_plane))
self.wait()
# Permute faces
cross48 = Cross(label48)
cross48.fix_in_frame()
self.play(ShowCreation(cross48))
label48.add(cross48)
label24 = count_label[0]
label24.add(cross24)
count_label.remove(label24)
def explostion_transform(self=self, cube=cube):
cube_copy = cube.copy()
self.play(
cube.space_out_submobjects, 1.5,
cube.shift, 0.5 * OUT,
)
exploded_cube_copy = cube.copy()
self.play(LaggedStart(*[
Rotate(
face,
axis=face.get_center() - cube.get_center(),
angle=random.choice([0, PI / 2, -PI / 2, PI])
)
for face in cube
]))
perm = list(range(6))
random.shuffle(perm)
globals()['perm'] = perm # TODO
self.play(LaggedStart(*[
Transform(face, cube[perm[i]])
for i, face in enumerate(cube)
], lag_ratio=0.1))
cube.become(exploded_cube_copy)
self.play(Transform(cube, cube_copy))
self.wait()
for x in range(3):
explostion_transform()
# Largest size
count = Integer(188743680)
count.fix_in_frame()
old_counts = VGroup(label24, label48)
old_counts.generate_target()
old_counts.target.to_edge(LEFT)
count.match_height(old_counts)
count.next_to(old_counts.target, RIGHT, buff=LARGE_BUFF)
count.set_color(BLUE_B)
self.play(
MoveToTarget(old_counts),
FadeIn(count),
count_label.next_to, count, RIGHT,
)
for x in range(3):
explostion_transform()
self.wait(2)
class WeirdCubeSymmetryUnderbrace(Scene):
def construct(self):
brace = Brace(Line(LEFT, RIGHT).set_width(3), DOWN)
tex = brace.get_tex("(8^6)(6!)")
VGroup(brace, tex).set_color(WHITE)
VGroup(brace, tex).set_stroke(BLACK, 8, background=True)
self.play(
GrowFromCenter(brace),
FadeIn(tex, 0.25 * UP)
)
self.wait()
class PermutationGroups(Scene):
def construct(self):
# Setup
question = TextMobject("What about no structure?")
question.scale(1.5)
question.to_edge(UP)
perm_words = TextMobject("All ", "permutations")
perm_words.scale(1.5)
perm_words.next_to(question, DOWN, buff=0.7)
perm_words.set_color(BLUE)
dots = VGroup(*[Dot() for x in range(6)])
dots.set_fill(GREY_B)
dots.set_height(0.5)
dots.arrange(RIGHT, buff=LARGE_BUFF)
dots.shift(DOWN)
alt_dots = dots.copy()
self.add(question)
self.play(ShowIncreasingSubsets(dots))
# Permutations
def get_permutation(self=self, dots=dots, arc=PI / 2):
perm = list(range(len(dots)))
random.shuffle(perm)
arrows = get_permutation_arrows(dots, perm, arc)
for i, dot in enumerate(dots):
dot.target = dots[perm[i]]
arrows.set_opacity(0)
return Succession(
UpdateFromAlphaFunc(arrows, lambda m, a: m.set_opacity(a)),
LaggedStartMap(MoveToTarget, dots, path_arc=arc, lag_ratio=0.15, run_time=2),
UpdateFromAlphaFunc(arrows, lambda m, a: m.set_opacity(1 - a)),
)
permutations = Succession(*[
get_permutation()
for x in range(20)
])
animated_perm_mob = cycle_animation(permutations)
self.add(animated_perm_mob)
self.wait(5)
self.play(FadeIn(perm_words, UP))
self.wait(10)
# Count perms
perm_count = TexMobject("6!")
perm_count.match_height(perm_words[0])
perm_count.match_color(perm_words[0])
perm_count.move_to(perm_words[0], RIGHT)
full_count = Integer(720, edge_to_fix=RIGHT)
full_count.match_height(perm_count)
full_count.move_to(perm_count, DR)
full_count.shift(0.7 * RIGHT)
full_count.match_color(perm_count)
equals = TexMobject("=")
equals.scale(1.5)
equals.next_to(full_count, LEFT)
equals.match_color(perm_count)
perm_count.next_to(equals, LEFT)
full_count.set_value(0)
self.remove(animated_perm_mob)
dots = alt_dots
self.add(alt_dots)
self.play(
FadeIn(full_count, LEFT),
FadeOut(perm_words[0], RIGHT),
perm_words[1].shift, 0.7 * RIGHT,
)
all_perms = list(it.permutations(range(6)))
arrows = VGroup()
self.add(arrows)
self.play(
ChangeDecimalToValue(full_count, 720),
UpdateFromAlphaFunc(
arrows,
lambda m, a, dots=dots, all_perms=all_perms: m.set_submobjects(
get_permutation_arrows(dots, all_perms[int(np.round(719 * a))])
)
),
run_time=20,
)
self.play(
FadeIn(perm_count, RIGHT),
Write(equals),
)
self.wait(2)
perm_label = VGroup(perm_count, equals, full_count, perm_words[1])
# Revisit snowflake symmetries
dots.generate_target()
for dot, point in zip(dots.target, compass_directions(6, UP)):
dot.move_to(2 * point)
self.play(
FadeOut(arrows),
FadeOut(perm_label, UP),
FadeOut(question, 0.5 * UP),
MoveToTarget(dots),
)
lines = VGroup()
for d1, d2 in it.combinations(dots, 2):
lines.add(Line(
d1.get_center(),
d2.get_center(),
buff=d1.get_width() / 4,
))
lines.set_stroke(WHITE, 2)
hexy = VGroup(dots, lines)
hexy.unlock_unit_normal()
hexy.add_updater(lambda m: m.refresh_unit_normal())
self.play(LaggedStartMap(ShowCreation, lines))
self.wait()
self.play(Rotate(hexy, 60 * DEGREES))
self.wait()
self.play(Rotate(hexy, -120 * DEGREES))
self.wait()
self.play(Rotate(hexy, PI, axis=UP))
self.wait()
self.play(Rotate(hexy, PI, axis=rotate_vector(RIGHT, 60 * DEGREES)))
self.wait()
# Back to a row
dots.generate_target()
dots.target.arrange(RIGHT, buff=LARGE_BUFF)
dots.target.move_to(0.5 * DOWN)
for line in lines:
line.generate_target()
line.target.set_angle(0)
line.target.set_stroke(WHITE, 0, 0)
perm_label.to_edge(UP, buff=LARGE_BUFF)
self.play(
MoveToTarget(dots),
FadeIn(perm_label),
LaggedStartMap(MoveToTarget, lines, run_time=1.5)
)
# Bump it up to 12
new_dots = dots.copy()
new_dots.shift(1.5 * DOWN)
new_perm_label = VGroup(
TexMobject("12!"),
TexMobject("="),
Integer(math.factorial(12)),
TextMobject("permutations")[0],
)
new_perm_label.arrange(RIGHT)
new_perm_label.match_height(perm_label)
new_perm_label.set_color(YELLOW)
new_perm_label.move_to(perm_label)
new_perm_label[0].align_to(perm_label[2][0], DOWN)
perm_label.unlock_triangulation()
old_full_count_center = full_count.get_center()
self.play(
ChangeDecimalToValue(
perm_label[2], new_perm_label[2].get_value(),
run_time=3
),
UpdateFromAlphaFunc(
perm_label[2], lambda m, a: m.move_to(interpolate(
old_full_count_center,
new_perm_label[2].get_center(),
a
)).set_color(interpolate_color(BLUE, YELLOW, a))
),
ShowIncreasingSubsets(new_dots),
*[
ReplacementTransform(perm_label[i], new_perm_label[i])
for i in [0, 1, 3]
]
)
self.remove(perm_label)
perm_label = new_perm_label
self.add(perm_label)
dots.add(*new_dots)
self.wait()
for x in range(5):
perm = list(range(12))
random.shuffle(perm)
self.play(LaggedStart(*[
Transform(dots[i], dots[perm[i]], path_arc=PI / 2)
for i in range(12)
]))
self.wait()
# Show 101 dots
new_perm_label = VGroup(
TexMobject("101!"),
TexMobject("\\approx"),
TexMobject("9.43 \\times 10^{159}"),
TextMobject("permutations")[0]
)
new_perm_label.arrange(RIGHT)
new_perm_label.match_height(perm_label)
new_perm_label[2].align_to(new_perm_label[0], DOWN)
new_perm_label[3].shift(SMALL_BUFF * DOWN)
new_perm_label.move_to(perm_label, RIGHT)
new_dots = VGroup(*[dots[0].copy() for x in range(101)])
new_dots.arrange_in_grid(7, 13)
new_dots.set_height(5)
new_dots.to_edge(DOWN)
self.play(
FadeOut(perm_label),
FadeIn(new_perm_label),
ReplacementTransform(dots, new_dots[-len(dots):]),
ShowIncreasingSubsets(new_dots[:-len(dots)], run_time=2)
)
self.add(new_dots)
perm_label = new_perm_label
dots = new_dots
labels = VGroup()
for i, dot in enumerate(new_dots):
label = Integer(i + 1, fill_color=BLACK)
label.replace(dot, dim_to_match=1)
label.scale(0.3)
labels.add(label)
labels.set_stroke(width=0)
self.play(FadeIn(labels))
self.remove(labels)
for dot, label in zip(dots, labels):
dot.add(label)
for x in range(6):
self.play(permutation_animation(dots, lag_factor=1))
self.wait(0.5)
# Name S_n
perm_label[3].generate_target()
new_perm_label = VGroup(
TexMobject("n!").match_height(perm_label[0]),
perm_label[3].target,
)
new_perm_label.arrange(RIGHT, buff=MED_LARGE_BUFF)
new_perm_label[0].align_to(new_perm_label[1][-1], DOWN)
new_perm_label.next_to(perm_label[0], RIGHT, MED_LARGE_BUFF)
new_perm_label[0].save_state()
new_perm_label[0].replace(perm_label[0], stretch=True)
new_perm_label[0].set_opacity(0)
Sn_name = TexMobject("S_n")
Sn_name.match_height(new_perm_label)
Sn_name.next_to(new_perm_label, RIGHT, buff=LARGE_BUFF)
Sn_name.set_color(YELLOW)
self.play(
perm_label[0].replace, new_perm_label[0], {"stretch": True},
perm_label[0].set_opacity, 0,
FadeOut(perm_label[1:3], RIGHT),
MoveToTarget(perm_label[3]),
Restore(new_perm_label[0]),
)
self.play(FadeIn(Sn_name, LEFT))
self.remove(perm_label)
perm_label = new_perm_label
self.add(perm_label)
self.play(permutation_animation(dots))
# Down to a square
new_dots = dots[:4]
faders = dots[4:]
new_dots.generate_target()
for dot in new_dots.target:
dot.set_height(0.8)
new_dots.target.arrange_in_grid(2, 2, buff=LARGE_BUFF)
new_dots.target.center()
self.play(
MoveToTarget(new_dots),
FadeOut(faders),
)
dots = new_dots
for x in range(2):
self.play(permutation_animation(dots, [2, 0, 3, 1], lag_factor=0))
self.wait()
class IsItUseful(TeacherStudentsScene):
def construct(self):
self.student_says(
"Is any of\\\\this useful?",
student_index=2,
target_mode="sassy",
added_anims=[self.teacher.change, "guilty"]
)
self.change_student_modes("angry", "confused")
self.wait(3)
self.teacher_says("Extremely!")
self.change_student_modes("pondering", "thinking", "pondering", look_at_arg=self.screen)
self.wait(4)
class SolutionsToPolynomials(Scene):
def construct(self):
# Show quintic shuffling
colors = list(Color(BLUE).range_to(YELLOW, 5))
quintic = TexMobject(
"x^5 - x - 1",
"=",
"(x - r_0)",
"(x - r_1)",
"(x - r_2)",
"(x - r_3)",
"(x - r_4)",
tex_to_color_map={
f"r_{i}": colors[i]
for i in range(5)
}
)
root_syms = VGroup(*[quintic.get_part_by_tex(f"r_{i}") for i in range(5)])
quintic.set_width(FRAME_WIDTH - 1)
quintic.to_edge(UP)
plane = ComplexPlane(x_range=(-2, 2), y_range=(-2, 2))
plane.set_height(6)
plane.to_edge(DOWN, buff=MED_SMALL_BUFF)
plane.add_coordinate_labels()
for label in plane.coordinate_labels:
label.scale(0.7, about_edge=UR)
def get_root_dots(roots, plane=plane, colors=colors):
return VGroup(*[
Dot(
plane.n2p(root),
radius=0.1,
color=color,
).set_stroke(BLACK, 2, background=True)
for root, color in zip(roots, colors)
])
root_dots = get_root_dots([
1.1673,
0.181232 + 1.08395j,
0.181232 - 1.08395j,
-0.764884 + 0.352472j,
-0.764884 - 0.352472j,
])
self.add(quintic)
self.add(plane)
self.play(LaggedStart(*[
ReplacementTransform(rs.copy(), rd)
for rs, rd in zip(root_syms, root_dots)
], run_time=3, lag_ratio=0.3))
self.wait()
root_syms.save_state()
root_dots.save_state()
for x in range(5):
perm = list(range(5))
random.shuffle(perm)
self.play(*[
permutation_animation(mob, perm, arc=30 * DEGREES, lag_factor=0.5)
for mob in [root_syms, root_dots]
])
self.wait(0.5)
self.play(
Restore(root_syms, path_arc=60 * DEGREES),
Restore(root_dots, path_arc=60 * DEGREES),
)
# Down to quadratic
quadratic_lhs = TexMobject("x^2 - x - 1")
quadratic_lhs.match_height(quintic[0])
quadratic_lhs.move_to(quintic[0], RIGHT)
self.play(
FadeOut(quintic[0], UP),
FadeIn(quadratic_lhs, DOWN),
FadeOut(quintic[8:], UP),
MaintainPositionRelativeTo(root_dots, plane),
UpdateFromAlphaFunc(root_dots, lambda m, a: m.set_opacity(1 - a)),
plane.to_edge, LEFT,
)
self.remove(root_dots)
root_dots.set_opacity(1)
root_dots.save_state()
quad_root_dots = get_root_dots([
(1 + u * np.sqrt(5)) / 2 for u in [-1, 1]
])
self.play(LaggedStart(*[
ReplacementTransform(root_sym.copy(), root_dot)
for root_dot, root_sym in zip(quad_root_dots, root_syms)
]))
self.wait()
# Quadratic formula
quadratic_formula = TexMobject(
"{-b \\pm \\sqrt{\\,b^2 - 4ac} \\over 2a}",
)
quadratic_formula.set_height(1.5)
quadratic_formula.to_edge(RIGHT, buff=LARGE_BUFF)
quad_form_name = TextMobject("Quadratic formula")
quad_form_name.set_height(0.5)
quad_form_name.next_to(quadratic_formula, DOWN, LARGE_BUFF)
quad_form_name.set_color(GREY_B)
self.play(
Write(quadratic_formula),
FadeIn(quad_form_name, DOWN)
)
self.wait()
# Cubic
cubic_lhs = TexMobject("x^3 - x - 1")
cubic_lhs.replace(quadratic_lhs)
cubic_root_dots = get_root_dots([
1.3247,
-0.66236 + 0.56228j,
-0.66236 - 0.56228j,
])
cubic_formula = TexMobject(
r"\sqrt[3]{-\frac{q}{2}+\sqrt{\frac{q^{2}}{4}+\frac{p^{3}}{27}}}+\sqrt[3]{-\frac{q}{2}-\sqrt{\frac{q^{2}}{4}+\frac{p^{3}}{27}}}",
)
cubic_formula.replace(quadratic_formula, dim_to_match=1)
cubic_formula.to_edge(RIGHT, buff=MED_SMALL_BUFF)
cubic_formula.scale(0.8, about_edge=RIGHT)
cubic_form_name = TextMobject("Cubic formula (reduced)")
cubic_form_name.replace(quad_form_name, dim_to_match=1)
cubic_form_name.match_style(quad_form_name)
self.play(
ReplacementTransform(quad_root_dots, cubic_root_dots),
FadeIn(quintic[8:11], DOWN),
FadeIn(cubic_lhs, DOWN),
FadeOut(quadratic_lhs, UP),
)
self.play(
LaggedStart(
FadeOut(quadratic_formula, 2 * RIGHT),
FadeOut(quad_form_name, 2 * RIGHT),
),
LaggedStart(
FadeIn(cubic_formula, 2 * LEFT),
FadeIn(cubic_form_name, 2 * LEFT),
),
)
self.wait()
# Quartic (largely copied from above)
quartic_lhs = TexMobject("x^4 - x - 1")
quartic_lhs.replace(quadratic_lhs)
quartic_root_dots = get_root_dots([
1.2207,
-0.72449,
-0.24813 + 1.0340j,
-0.24813 - 1.0340j,
])
quartic_formula = TexMobject(r"""
r_{i}&=-\frac{b}{4 a}-S \pm \frac{1}{2} \sqrt{-4 S^{2}-2 p \pm \frac{q}{S}}\\\\
&\text{Where}\\\\
p&=\frac{8 a c-3 b^{2}}{8 a^{2}} \qquad \qquad
q=\frac{b^{3}-4 a b c+8 a^{2} d}{8 a^{3}}\\\\
S&=\frac{1}{2} \sqrt{-\frac{2}{3} p+\frac{1}{3 a}\left(Q+\frac{\Delta_{0}}{Q}\right)}\\\\
Q&=\sqrt[3]{\frac{\Delta_{1}+\sqrt{\Delta_{1}^{2}-4 \Delta_{0}^{3}}}{2}}\\\\
\Delta_{0}&=c^{2}-3 b d+12 a e\\\\
\Delta_{1}&=2 c^{3}-9 b c d+27 b^{2} e+27 a d^{2}-72 a c e\\\\
""")
quartic_formula.set_height(6)
quartic_formula.next_to(plane, RIGHT, LARGE_BUFF)
self.play(
FadeOut(cubic_formula, 2 * RIGHT),
FadeOut(cubic_form_name, 2 * RIGHT),
ReplacementTransform(cubic_root_dots, quartic_root_dots),
FadeIn(quintic[11:14], DOWN),
FadeIn(quartic_lhs, DOWN),
FadeOut(cubic_lhs, UP),
)
self.play(
Write(quartic_formula, run_time=3),
)
self.wait(2)
# Back to quintic
self.play(
ReplacementTransform(quartic_root_dots, root_dots),
FadeIn(quintic[0], DOWN),
FadeOut(quartic_lhs, UP),
FadeIn(quintic[14:], DOWN),
FadeOut(quartic_formula, 0.1 * DOWN, lag_ratio=0.01),
)
# Wonder about the quintic
mathy = PiCreature(color=GREY)
mathy.flip()
mathy.next_to(quintic, DOWN, buff=1.5)
mathy.to_edge(RIGHT)
self.play(
VFadeIn(mathy),
mathy.change, "confused", root_syms,
)
self.play(Blink(mathy))
self.wait()
self.play(
mathy.change, "pondering", root_syms[3]
)
self.play(Blink(mathy))
self.wait()
mathy.add_updater(lambda m, sym=root_syms[3]: m.look_at(sym))
# Show a few permutations
s5_name = TexMobject("S_5")
s5_name.scale(1.5)
s5_name.next_to(plane, RIGHT, MED_LARGE_BUFF, aligned_edge=UP)
s5_name.shift(DOWN)
s5_lines = VGroup()
for dot in root_dots:
line = Line(s5_name.get_left(), dot.get_center())
line.match_color(dot)
line.set_stroke(width=1)
line.dot = dot
line.start = line.get_start()
s5_lines.add(line)
s5_lines.set_stroke(opacity=0.5)
self.play(
FadeIn(s5_name),
ShowCreation(s5_lines, lag_ratio=0.5),
)
for line in s5_lines:
line.add_updater(lambda m: m.put_start_and_end_on(m.start, m.dot.get_center()))
self.add(*s5_lines)
for x in range(5):
perm = list(range(5))
random.shuffle(perm)
self.play(*[
permutation_animation(mob, perm, arc=30 * DEGREES, lag_factor=0.5)
for mob in [root_syms, root_dots]
])
self.wait(0.5)
self.play(
VFadeOut(s5_lines),
Restore(root_syms),
Restore(root_dots),
FadeOut(mathy),
)
# No formula
r0_value = TexMobject(
"r_0", "=", "1.1673\\dots",
)
r0_value.set_color_by_tex("r_0", BLUE)
r0_value.scale(1.5)
r0_value.next_to(plane, RIGHT, MED_LARGE_BUFF)
r0_value.shift(DOWN)
self.play(
LaggedStart(*[
AnimationGroup(
ShowCreationThenFadeAround(dot),
ShowCreationThenFadeAround(sym),
)
for sym, dot in zip(root_syms, root_dots)
], lag_ratio=0.3, run_time=3),
)
self.play(TransformFromCopy(root_syms[0], r0_value[0]))
self.play(Write(r0_value[1:]))
self.add(r0_value)
self.wait()
# Arithmetic symbols
symbols = VGroup(*[
TexMobject(s)
for s in ["+", "-", "\\times", "/", "\\sqrt[n]{\\qquad}"]
])
symbols[:4].arrange_in_grid(2, 2)
symbols[4].next_to(symbols[:4], RIGHT, MED_LARGE_BUFF)
symbols.move_to(s5_name)
symbols.to_edge(RIGHT)
symbols_rect = SurroundingRectangle(symbols, buff=MED_SMALL_BUFF)
symbols_rect.set_stroke(BLUE, 2)
arrow = Arrow(symbols_rect.get_corner(DL), r0_value[2][3].get_top())
cross = Cross(arrow)
cross.stretch(0.5, 1)
self.play(
FadeIn(symbols, lag_ratio=0.2, run_time=1.5),
ShowCreation(symbols_rect),
)
self.wait()
self.play(GrowArrow(arrow))
self.play(ShowCreation(cross))
self.wait()
class MentionGroupsInPhysics(TeacherStudentsScene):
def construct(self):
# Intro
self.teacher_says("Groups are ubiquitous\\\\in physics.")
self.change_student_modes("thinking", "happy", "hesitant")
self.wait(4)
noether = ImageMobject("EmmyNoether")
noether.set_height(3)
noether.to_corner(UL)
nt_label = TextMobject("Noether's theorem")
nt_label.set_height(0.5)
nt_label.move_to(self.hold_up_spot, DOWN)
self.play(
FadeIn(nt_label, DOWN),
FadeIn(noether, DOWN),
FadeOut(VGroup(self.teacher.bubble, self.teacher.bubble.content)),
self.teacher.change, "raise_right_hand",
)
self.change_student_modes("pondering", "pondering", "thinking", look_at_arg=nt_label)
# Theorem
nt_label.generate_target()
nt_label.target.center().to_edge(UP)
rule = VGroup(
TextMobject("Conservation law", color=BLUE),
TexMobject("\\Leftrightarrow"),
TextMobject("Symmetry", color=YELLOW),
)
rule.set_height(0.5)
rule.arrange(RIGHT)
rule.next_to(nt_label.target, DOWN, MED_LARGE_BUFF)
self.look_at(
nt_label.target,
added_anims=[MoveToTarget(nt_label)]
)
self.play(
self.teacher.change, "happy", rule,
*[
FadeIn(part, rule.get_center() - part.get_center())
for part in rule
],
)
self.wait(2)
# Examples
examples = VGroup(
TextMobject("Momentum", " $\\Leftrightarrow$ ", "Translation in space"),
TextMobject("Energy", " $\\Leftrightarrow$ ", "Translation in time"),
)
examples.arrange(DOWN, buff=MED_LARGE_BUFF)
examples.next_to(rule, DOWN, buff=MED_LARGE_BUFF)
for example in examples:
example[0].set_color(BLUE)
example[2].set_color(YELLOW)
example.shift((rule[1].get_x() - example[1].get_x()) * RIGHT)
self.play(
self.teacher.change, "raise_right_hand",
FadeIn(examples[0], UP),
self.get_student_changes("confused", "erm", "pondering")
)
self.look_at(rule)
self.wait()
self.play(FadeIn(examples[1], UP))
self.wait(4)
self.change_student_modes("thinking", "maybe", "thinking")
self.wait(4)
class AmbientDodecSymmetries(ThreeDScene):
def construct(self):
pass
class NotGroupsGroupAction(Scene):
def construct(self):
words = VGroup(
TextMobject("Group"),
TextMobject("Group", " action"),
)
words.scale(2)
words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
words.to_corner(UL)
group, group_action = words
cross = Cross(group)
self.add(group)
self.wait()
self.play(ShowCreation(cross))
self.play(
TransformFromCopy(group[0], group_action[0]),
Animation(cross.copy(), remover=True)
)
self.play(Write(group_action[1]))
self.wait()
class ElementsAsAbstractions(TeacherStudentsScene):
def construct(self):
# Three
self.teacher_says("Three")
self.wait()
s_copies = self.students.copy()
s_copies.scale(0.3)
bubble = self.students[0].get_bubble(
s_copies,
width=5,
height=4,
)
self.play(
self.students[0].change, "pondering",
Write(bubble),
FadeIn(bubble.content, lag_ratio=0.3),
)
self.wait(2)
numeral = Integer(3)
numeral.replace(bubble.content, dim_to_match=1)
bubble.content.generate_target()
for pi in bubble.content.target:
pi.change("horrified")
pi.shift(UP)
pi.set_opacity(0)
self.play(MoveToTarget(bubble.content))
self.remove(bubble.content)
self.play(
Write(numeral),
self.students[0].change, "happy", numeral,
)
self.look_at(numeral)
self.wait(2)
# Element of D6
self.camera.light_source.set_x(0)
snowflake = get_snowflake()
rot_icon = get_rot_icon(60, snowflake)
inclusion = VGroup(
rot_icon,
TexMobject("\\in").scale(2),
TexMobject("D_6").scale(2),
)
inclusion.arrange(RIGHT)
inclusion.next_to(self.hold_up_spot, UL, MED_LARGE_BUFF)
self.play(
LaggedStart(
FadeOut(self.teacher.bubble),
FadeOut(self.teacher.bubble.content),
FadeOut(bubble),
FadeOut(numeral),
FadeIn(inclusion, DOWN),
),
self.teacher.change, "raise_right_hand",
)
self.look_at(inclusion)
self.wait()
self.play(Rotate(rot_icon[0], 60 * DEGREES))
self.wait()
rot_icon.generate_target()
rot_icon.target.to_corner(UL)
r_sym = TexMobject("r").scale(2)
r_sym.move_to(rot_icon, RIGHT)
self.look_at(
rot_icon.target,
added_anims=[MoveToTarget(rot_icon)],
)
self.look_at(
r_sym,
added_anims=[Write(r_sym)]
)
self.change_all_student_modes(
"confused",
look_at_arg=r_sym,
)
self.wait(2)
inclusion.remove(rot_icon)
inclusion.add(r_sym)
# Back to 3
numeral.move_to(self.hold_up_spot, DOWN)
self.play(
inclusion.to_edge, LEFT,
inclusion.set_color, GREY_B,
Write(numeral),
self.get_student_changes(*3 * ["pondering"], look_at_arg=numeral),
self.teacher.change, "tease", numeral,
)
self.wait(2)
# Operations
add = TexMobject("3", "+", "5", "=", "8")
mult = TexMobject("3", "\\cdot", "5", "=", "15")
ops = VGroup(add, mult)
ops.match_height(numeral)
ops.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT)
ops.to_corner(UR, buff=LARGE_BUFF)
self.remove(numeral)
self.play(
LaggedStart(*[
TransformFromCopy(numeral[0], form[0])
for form in ops
]),
*[
ApplyMethod(pi.look_at, ops)
for pi in self.pi_creatures
]
)
self.play(
LaggedStart(*[
Write(form[1:])
for form in ops
])
)
self.wait(3)
# Literal forms
quincunx = VGroup(*[Dot() for x in range(5)])
quincunx[:4].arrange_in_grid()
quincunx.space_out_submobjects(0.7)
triplet = VGroup(quincunx[0], quincunx[3], quincunx[4]).copy()
triplet.set_color(BLUE)
octet = VGroup(*[Dot() for x in range(9)])
octet.arrange_in_grid(3, 3)
octet.remove(octet[4])
octet.space_out_submobjects(0.5)
sum_dots = VGroup(triplet, quincunx, octet)
for sd, sym in zip(sum_dots, ops[0][0::2]):
sd.move_to(sym)
octet.shift(SMALL_BUFF * RIGHT)
quincunx_trip = VGroup(*[quincunx.copy() for x in range(3)])
quincunx_trip.arrange(RIGHT, buff=MED_LARGE_BUFF)
for quin in quincunx_trip:
rect = SurroundingRectangle(quin)
rect.set_stroke(BLUE, 3)
quin.add(rect)
quincunx_trip.move_to(ops[1][2], RIGHT)
fifteen = VGroup(*[Dot() for x in range(15)])
fifteen.arrange_in_grid(3, 5)
fifteen.space_out_submobjects(0.5)
fifteen.move_to(ops[1][4], LEFT)
mult_dots = VGroup(quincunx_trip, fifteen)
self.play(
FadeOut(ops[0][0::2], UP),
FadeIn(sum_dots, DOWN),
)
self.play(
FadeOut(VGroup(*ops[1][:3], ops[1][4]), UP),
FadeIn(mult_dots, DOWN),
self.get_student_changes(*3 * ["erm"], look_at_arg=mult_dots),
)
self.wait(4)
self.play(
LaggedStart(*[
ApplyMethod(mob.scale, 0, remover=True)
for mob in [*sum_dots, *mult_dots]
]),
LaggedStart(*[
FadeIn(mob)
for mob in [*ops[0][0::2], *ops[1][:3], ops[1][4]]
]),
self.get_student_changes(*3 * ["happy"]),
)
self.wait(2)
# Show group sum
flip_icon = get_flip_icon(0, snowflake)
rhs_icon = get_flip_icon(30 * DEGREES, snowflake)
rot_icon.generate_target()
group_prod = VGroup(
rot_icon.target,
TexMobject("\\times").scale(2),
flip_icon,
TexMobject("=").scale(2),
rhs_icon
)
group_prod.set_gloss(0)
for icon in group_prod[::2]:
icon[0].set_stroke(width=0)
icon[0].set_fill(GREY_A, 1)
group_prod.arrange(RIGHT)
group_prod.to_edge(UP)
self.play(
FadeOut(ops, RIGHT),
FadeOut(inclusion, LEFT),
MoveToTarget(rot_icon),
LaggedStartMap(FadeIn, group_prod[1:], lag_ratio=0.5, run_time=2),
self.get_student_changes(
"sassy", "erm", "confused",
look_at_arg=group_prod,
),
self.teacher.change, "raise_right_hand",
)
group_prod.replace_submobject(0, rot_icon)
self.add(group_prod)
self.wait(2)
# Show successive actions
snowflake = get_snowflake()
snowflake.move_to(0.5 * UP)
snowflake.match_x(group_prod[1])
alt_flake = snowflake.copy()
alt_flake.match_x(rhs_icon)
self.play(
TransformFromCopy(rot_icon[0], snowflake)
)
def get_numbers(flake):
vect = 1.2 * (flake.get_top() - flake.get_center())
points = VGroup(*[
VectorizedPoint(rotate_vector(vect, angle))
for angle in np.arange(0, TAU, TAU / 6)
])
points.move_to(flake)
numbers = VGroup(*[Integer(i + 1) for i in range(6)])
numbers.scale(0.5)
for num, point in zip(numbers, points):
num.point = point
num.add_updater(lambda m: m.move_to(m.point))
flake.add(points)
return numbers
sn_nums = get_numbers(snowflake)
as_nums = get_numbers(alt_flake)
self.play(
FadeIn(sn_nums, lag_ratio=0.1),
self.get_student_changes(*3 * ["pondering"], look_at_arg=snowflake)
)
self.add(*sn_nums)
self.play(
Rotate(snowflake, PI, RIGHT),
ShowCreationThenFadeAround(flip_icon),
)
self.wait()
self.play(
Rotate(snowflake, 60 * DEGREES),
ShowCreationThenFadeAround(rot_icon),
)
self.wait()
self.look_at(
alt_flake,
added_anims=[TransformFromCopy(rhs_icon[0], alt_flake)]
)
self.play(FadeIn(as_nums, lag_ratio=0.1))
self.add(*as_nums)
line = rhs_icon.submobjects[-1].copy()
line.scale(2)
line.set_stroke(YELLOW, 3)
line.move_to(alt_flake)
self.play(ShowCreation(line))
self.play(
Rotate(alt_flake, PI, axis=line.get_vector())
)
self.play(
FadeOut(line),
self.get_student_changes(*3 * ["thinking"])
)
self.wait(3)
class MultiplicationTable(Scene):
def construct(self):
# Grid
grid = VGroup(*[Square() for x in range(64)])
grid.arrange_in_grid(8, 8, buff=0)
grid.set_stroke(WHITE, 2)
grid.set_height(6.5)
grid.to_edge(DOWN, buff=0.5)
self.add(grid)
# Action icons
square = Square()
square.rotate(45 * DEGREES)
square.set_height(1)
square.set_fill(BLUE_D, 1)
icons = VGroup(
*[
get_rot_icon(deg, square)
for deg in [0, 90, 180, -90]
] + [
get_flip_icon(angle, square, opacity=1)
for angle in np.arange(0, PI, PI / 4)
]
)
icons[0].remove(icons[0][-1])
icons.match_height(grid[0])
icons.scale(0.8)
for icon in icons:
icon[0].rotate(-45 * DEGREES)
left_icons = icons.copy()
top_icons = icons.copy()
for icon_group, grid_group, vect in [(left_icons, grid[0::8], LEFT), (top_icons, grid[:8], UP)]:
for gs, icon in zip(grid_group, icon_group):
icon.shift(gs.get_edge_center(vect) - icon[0].get_center())
icon.shift(0.6 * gs.get_width() * vect)
for icon in top_icons:
icon[0].set_fill(GREY_BROWN)
self.add(left_icons, top_icons)
# Figure out full table
def pmult(perm1, perm2):
return [perm1[i] for i in perm2]
r = [1, 2, 3, 0]
s = [3, 2, 1, 0]
r2 = pmult(r, r)
r3 = pmult(r2, r)
perms = [
list(range(4)), r, r2, r3,
s,
pmult(r, s),
pmult(r2, s),
pmult(r3, s),
]
table = np.zeros((8, 8), dtype=int)
table_icons = VGroup()
for n, square in enumerate(grid):
i = n // 8
j = n % 8
perm = pmult(perms[i], perms[j])
index = perms.index(perm)
table[i, j] = index
icon = icons[index].copy()
icon[0].set_color(BLUE_E)
icon.set_opacity(1)
icon.shift(square.get_center() - icon[0].get_center())
pre_icon = VGroup(icon.copy(), icon.copy())
pre_icon.save_state()
pre_icon.set_opacity(0)
pre_icon[0].move_to(left_icons[i])
pre_icon[1].move_to(top_icons[j])
icon.pre_icon = pre_icon
table_icons.add(icon)
# Show all product
sorted_icons = list(table_icons)
frame = self.camera.frame
frame.save_state()
frame.scale(0.6)
frame.move_to(top_icons.get_top() + MED_SMALL_BUFF * UL, UP)
turn_animation_into_updater(Restore(frame, run_time=20, rate_func=bezier([0, 0, 1, 1])))
self.add(frame)
for sorted_index, icon in enumerate(sorted_icons):
n = table_icons.submobjects.index(icon)
i = n // 8
j = n % 8
rects = VGroup(
SurroundingRectangle(left_icons[i]),
SurroundingRectangle(top_icons[j]),
grid[n].copy().set_fill(GREEN_E, 0.5)
)
rects.set_stroke(YELLOW, 2)
self.add(rects, *self.mobjects)
self.add(icon)
if sorted_index < 8:
pass # Don't wait
elif sorted_index < 24:
self.wait(1)
else:
self.wait(0.15)
self.remove(rects)
self.add(table_icons)
self.wait(2)
# Symbolically
symbols = VGroup(
TexMobject("1"),
TexMobject("r"),
TexMobject("r^2"),
TexMobject("r^3"),
TexMobject("s"),
TexMobject("rs"),
TexMobject("r^2 s"),
TexMobject("r^3 s"),
)
symbols.set_height(0.4 * grid[0].get_height())
left_symbols = symbols.copy()
top_symbols = symbols.copy()
for symbol_group, icon_group in [(left_symbols, left_icons), (top_symbols, top_icons)]:
for symbol, icon in zip(symbol_group, icon_group):
symbol.move_to(icon[0], DOWN)
table_symbols = VGroup()
for n, icon in enumerate(table_icons):
i = n // 8
j = n % 8
symbol = symbols[table[i, j]].copy()
symbol.move_to(icon[0], DOWN)
table_symbols.add(symbol)
self.play(
LaggedStart(*[
ApplyMethod(mob.scale, 0, remover=True)
for mob in [*left_icons, *top_icons, *table_icons]
]),
LaggedStart(*[
GrowFromCenter(mob)
for mob in [*left_symbols, *top_symbols, *table_symbols]
]),
)
self.wait()
# Show some products
last_rects = VGroup()
for x in range(10):
n = random.randint(0, 63)
i = n // 8
j = n % 8
rects = VGroup(
SurroundingRectangle(left_symbols[i]),
SurroundingRectangle(top_symbols[j]),
grid[n].copy().set_stroke(YELLOW, 4).set_fill(YELLOW, 0.5)
)
self.add(rects, *self.mobjects)
self.play(
FadeOut(last_rects),
FadeIn(rects),
)
self.wait(2)
last_rects = rects
self.play(FadeOut(last_rects))
self.wait()
class UsualMultiplicationTable(Scene):
def construct(self):
# Setup grid
grid = VGroup(*[
VGroup(*[Square() for x in range(4)]).arrange(RIGHT, buff=0)
for y in range(4)
]).arrange(DOWN, buff=0)
grid.set_height(6)
grid.to_edge(DOWN, buff=0.5)
grid.set_fill(GREY_E, 1)
dots = VGroup(
*[TexMobject("\\dots").scale(2).next_to(row, RIGHT) for row in grid[:-1]],
*[TexMobject("\\vdots").scale(2).next_to(square, DOWN) for square in grid[-1][:-1]],
TexMobject("\\ddots").scale(2).next_to(grid[-1][-1], DR),
)
self.add(grid)
# Setup abstract dots
table_dots = VGroup()
for i, row in zip(it.count(1), grid):
for j, square in zip(it.count(1), row):
dots = VGroup(*[Dot() for x in range(i * j)])
dots.arrange_in_grid(i, j, buff=SMALL_BUFF)
dots.scale(0.9)
dots.move_to(square)
table_dots.add(dots)
left_dots = table_dots[0::4].copy()
left_dots.shift(grid[0][0].get_width() * LEFT)
left_dots.set_color(BLUE)
top_dots = table_dots[0:4].copy()
top_dots.shift(grid[0][0].get_height() * UP)
top_dots.set_color(RED)
dot_groups = VGroup(left_dots, top_dots, table_dots)
# Numerals
sym_groups = VGroup()
for dot_group in dot_groups:
sym_group = VGroup()
for dots in dot_group:
numeral = Integer(len(dots))
numeral.set_height(0.6)
numeral.move_to(dots)
numeral.match_color(dots)
sym_group.add(numeral)
sym_groups.add(sym_group)
left_syms, top_syms, table_syms = sym_groups
# Add symbols
ls_copies = left_syms.copy().unlock_triangulation()
ts_copies = top_syms.copy().unlock_triangulation()
self.add(left_syms, top_syms)
self.play(LaggedStart(*[
AnimationGroup(
Transform(ls_copies[i].copy(), table_syms[4 * i + j].copy(), remover=True),
Transform(ts_copies[j].copy(), table_syms[4 * i + j].copy(), remover=True),
)
for i, j in it.product(range(4), range(4))
], lag_ratio=0.3))
self.add(table_syms)
self.wait()
# To dots
self.play(
FadeOut(sym_groups),
FadeIn(dot_groups),
)
self.wait()
# Show a few products
last_rects = VGroup()
ns = random.sample(range(16), 5)
for n in ns:
i = n // 4
j = n % 4
rects = VGroup(
SurroundingRectangle(left_dots[i]),
SurroundingRectangle(top_dots[j]),
grid[i][j].copy().set_fill(YELLOW, 0.5),
)
rects.set_stroke(YELLOW, 4)
self.play(FadeIn(rects), FadeOut(last_rects), run_time=0.5)
self.wait()
last_rects = rects
self.play(FadeOut(last_rects))
# Back to syms
self.play(
dot_groups.fade, 0.8,
FadeIn(sym_groups),
)
self.wait()
# Benefits
frame = self.camera.frame
frame.generate_target()
frame.target.set_x(grid.get_right()[0])
frame.target.scale(1.1)
benefit = VGroup(
TextMobject("Abstraction").scale(1.5),
Vector(DOWN),
TextMobject("Less cumbersome").scale(1.5),
)
benefit.arrange(DOWN)
benefit.next_to(grid, RIGHT, buff=LARGE_BUFF)
turn_animation_into_updater(MoveToTarget(frame, run_time=3))
self.add(frame)
self.play(Write(benefit[0]))
self.play(GrowArrow(benefit[1]))
self.play(FadeIn(benefit[2], UP))
self.wait()
class MentionTheMonster(Scene):
def construct(self):
monster = get_monster()
monster.set_height(6)
self.add(monster)
self.wait()
self.play(blink_monster(monster))
self.wait()
size_label = get_monster_size_label()
size_label.match_height(monster)
size_label.to_edge(RIGHT, buff=LARGE_BUFF)
self.play(
ApplyMethod(monster.next_to, size_label, LEFT, LARGE_BUFF, run_time=2),
ShowIncreasingSubsets(size_label, run_time=6)
)
self.play(blink_monster(monster))
self.wait()
class FrustratedAtGroups(TeacherStudentsScene):
def construct(self):
formula = TexMobject(r"|G|=|Z(G)|+\sum i\left[G: C_{G}\left(x_{i}\right)\right]")
formula.move_to(self.hold_up_spot, DOWN)
formula.shift(0.5 * UL)
self.play(
self.teacher.change, "raise_right_hand",
FadeIn(formula, DOWN),
)
self.change_student_modes("confused", "horrified", "pleading")
self.look_at(formula.get_left())
self.wait(2)
self.look_at(formula.get_right())
self.wait(2)
class WikiPageOnGroups(ExternallyAnimatedScene):
pass
class AnalogyWithCounts(Scene):
def construct(self):
# Setup
line = Line(LEFT, RIGHT)
words = TextMobject("Abstraction of")
words.match_width(line)
words.scale(0.9)
words.next_to(line, UP, SMALL_BUFF)
line.add(words)
line.rotate(-90 * DEGREES)
line.scale(0.5)
diagrams = VGroup(*[
VGroup(mob1, line.copy(), mob2)
for mob1, mob2 in [
(TextMobject("Groups"), TextMobject("Symmetry actions")),
(TexMobject("D_6"), get_snowflake(height=1)),
(TextMobject("Numbers"), TextMobject("Counts")),
(TexMobject("9").scale(1.5), VGroup(*[Dot() for x in range(9)]).arrange_in_grid(buff=SMALL_BUFF)),
]
])
for diagram, vect in zip(diagrams, [LEFT, LEFT, RIGHT, RIGHT]):
diagram[0].set_color(YELLOW)
diagram[2].set_fill(BLUE)
diagram.arrange(DOWN)
diagram.scale(1.5)
diagram.shift(3.5 * vect - diagram[1].get_center())
# Show diagrams
self.add(diagrams[0][0])
self.play(
Write(diagrams[0][1]),
FadeIn(diagrams[0][2], 2 * UP),
)
self.wait()
self.play(*[
AnimationGroup(
ReplacementTransform(
m2.copy().replace(m1, stretch=True).set_opacity(0),
m2,
),
Transform(
m1.copy(),
m1.copy().replace(m2, stretch=True).set_opacity(0),
remover=True
)
)
for m1, m2 in zip(diagrams[0], diagrams[2])
])
self.wait()
self.play(
FadeOut(diagrams[0]),
FadeIn(diagrams[1]),
)
flake = diagrams[1][2]
self.add(flake)
self.play(
FadeOut(diagrams[2]),
FadeIn(diagrams[3]),
Rotate(flake, 60 * DEGREES),
)
self.play(Rotate(flake, PI, UP))
self.play(Rotate(flake, -120 * DEGREES))
self.play(Rotate(flake, PI, RIGHT))
self.play(Rotate(flake, 120 * DEGREES))
self.play(Rotate(flake, PI, UP))
self.play(
VFadeOut(diagrams[1]),
Rotate(flake, 180 * DEGREES),
FadeIn(diagrams[0]),
FadeOut(diagrams[3]),
FadeIn(diagrams[2]),
)
self.wait(2)
class ButWhy(TeacherStudentsScene):
def construct(self):
self.student_says(
"But, why?",
target_mode="maybe",
added_anims=[LaggedStart(
ApplyMethod(self.teacher.change, "guilty"),
ApplyMethod(self.students[0].change, "confused"),
ApplyMethod(self.students[1].change, "sassy"),
lag_ratio=0.5,
)]
)
self.wait(3)
class CubeRotations(ThreeDScene):
def construct(self):
# Set frame motion
frame = self.camera.frame
frame.set_rotation(phi=80 * DEGREES)
frame.add_updater(lambda m, sc=self: m.set_rotation(theta=-20 * DEGREES * np.cos(0.1 * sc.time)))
self.add(frame)
# Setup cube
cube = get_glassy_cube(frame)
cube.set_height(3)
axes = ThreeDAxes(axis_config={"include_tip": False})
axes.apply_depth_test()
self.add(axes)
self.add(cube)
# Apply rotations
quats = self.get_quaternions()
self.wait()
for quat in quats:
angle, axis = angle_axis_from_quaternion(quat)
line = Line3D(-5 * axis, 5 * axis, prefered_creation_axis=0)
line.set_color(YELLOW)
if angle < 1e-6:
line.scale(0)
# line.apply_depth_test()
deg_label = Integer(int(np.round(angle / DEGREES)), unit="^\\circ")
deg_label.scale(2)
deg_label.to_edge(UP)
deg_label.shift(2 * LEFT)
deg_label.fix_in_frame()
self.add(line, *self.mobjects)
self.play(ShowCreation(line), FadeIn(deg_label))
self.play(Rotate(cube, angle, axis=axis))
line.scale(-1)
self.play(Uncreate(line), FadeOut(deg_label))
def get_quaternions(self, n_rotations=30):
ijk = [
quaternion_from_angle_axis(90 * DEGREES, axis)
for axis in [RIGHT, UP, OUT]
]
result = []
for x in range(n_rotations):
n = random.randint(1, 10)
curr = quaternion_from_angle_axis(0, RIGHT)
for y in range(n):
curr = quaternion_mult(curr, random.choice(ijk))
result.append(curr)
# Add on those rotations around diagonals for the end
for oi in [OUT, IN]:
for vect in [UL, UR, DR, DL]:
result.append(quaternion_from_angle_axis(120 * DEGREES, vect + oi))
return result
class QuadrupletShufflings(CubeRotations):
def construct(self):
# Background
bg_rect = FullScreenFadeRectangle()
bg_rect.set_fill(GREY_E, 1)
bg_rect.set_stroke(width=0)
self.add(bg_rect)
# Setup dots
dots = VGroup(*[Dot() for x in range(4)])
dots.set_height(0.5)
dots.arrange(RIGHT, buff=MED_LARGE_BUFF)
dots.set_color(GREY_B)
for n, dot in enumerate(dots):
label = Integer(n + 1)
label.set_height(0.25)
label.set_color(BLACK)
label.move_to(dot)
dot.add(label)
self.add(dots)
self.wait()
# Permutations
for quat in self.get_quaternions():
perm = self.quaternion_to_perm(quat)
arrows = get_permutation_arrows(dots, perm)
self.play(FadeIn(arrows))
self.play(permutation_animation(dots, perm, lag_factor=0.2, run_time=1))
self.play(FadeOut(arrows))
def quaternion_to_perm(self, quat):
angle, axis = angle_axis_from_quaternion(quat)
base_vects = [UL, UR, DR, DL]
rot_vects = [
rotate_vector(v + OUT, angle, axis)
for v in base_vects
]
perm = []
for vect in rot_vects:
if vect[2] < 0:
vect *= -1
vect[2] = 0
i = np.argmin([get_norm(vect - bv) for bv in base_vects])
perm.append(i)
return perm
class EightShufflingsOfOrderThree(Scene):
def construct(self):
bg_rect = FullScreenFadeRectangle()
bg_rect.set_fill(GREY_E, 1)
bg_rect.set_stroke(width=0)
self.add(bg_rect)
# Setup dots
dots_template = VGroup(*[Dot() for x in range(4)])
dots_template.set_height(0.5)
dots_template.arrange(RIGHT, buff=MED_SMALL_BUFF)
dots_template.set_color(GREY_B)
for n, dot in enumerate(dots_template):
label = Integer(n + 1)
label.set_height(0.25)
label.set_color(BLACK)
label.move_to(dot)
dot.add(label)
all_dots = VGroup(*[dots_template.copy() for x in range(8)])
all_dots.arrange_in_grid(4, 2, buff=1.25)
VGroup(all_dots[0::2], all_dots[1::2]).arrange(RIGHT, buff=2)
d_gen = iter(all_dots)
for trip in it.combinations(range(4), 3):
trip = np.array(trip)
perm1 = np.array(list(range(4)))
perm2 = np.array(list(range(4)))
perm1[trip] = trip[[1, 2, 0]]
perm2[trip] = trip[[2, 0, 1]]
for perm in [perm1, perm2]:
dots = next(d_gen)
arrows = get_permutation_arrows(dots, perm)
dots.add(arrows)
dots.perm = perm
for i in trip:
dots[i].set_stroke(YELLOW, 1)
dots[i][1].set_stroke(width=0)
self.play(ShowIncreasingSubsets(all_dots, run_time=4, rate_func=linear))
self.wait()
for x in range(3):
self.play(*[
permutation_animation(dots, dots.perm, lag_factor=0)
for dots in all_dots
])
self.wait()
class Isomorphism(Scene):
def construct(self):
# Frame
frame = self.camera.frame
frame.focal_distance = 20
# Rotation equation
def get_rot_icon(angle=0, axis=OUT, frame=frame):
cube = get_glassy_cube(frame)
cube.set_height(1)
arc_arrows = VGroup(*[
Arrow(
u * RIGHT,
u * rotate_vector(RIGHT, 160 * DEGREES),
buff=0,
path_arc=160 * DEGREES,
width=0.05,
)
for u in [1, -1]
])
arc_arrows.set_color(GREY_B)
axis_line = DashedLine(IN, OUT)
axis_line.set_stroke(YELLOW, 2)
rot_icon = Group(arc_arrows, axis_line, cube)
rot_icon.set_gloss(0.5)
rot_icon.apply_depth_test()
rot_icon.rotate(angle, axis)
rot_icon.rotate(-15 * DEGREES, OUT)
rot_icon.rotate(75 * DEGREES, LEFT)
return rot_icon
rot_icon_equation = Group(
get_rot_icon(90 * DEGREES, UP),
TexMobject("\\times").scale(2),
get_rot_icon(90 * DEGREES, RIGHT),
TexMobject("=").scale(2),
get_rot_icon(0, OUT),
)
rot_icons = rot_icon_equation[0::2]
rot_icon_equation.arrange(RIGHT, buff=LARGE_BUFF)
rot_icon_equation.shift(1.5 * UP)
icon_labels = VGroup(*[
TextMobject(f"$180^\\circ$ about\\\\{axis} axis")
for axis in "xyz"
])
for icon, label in zip(rot_icon_equation[0::2], icon_labels):
icon[-1][-1].set_opacity(0.5)
label.scale(0.8)
label.move_to(icon)
label.to_edge(UP)
icon.add(label)
# Permutation equation
dots = VGroup(*[Dot() for x in range(4)])
dots.set_height(0.4)
dots.set_color(GREY_B)
dots.arrange(RIGHT)
# for n, dot in enumerate(dots):
# label = Integer(n + 1)
# label.set_color(BLACK)
# label.set_height(0.6 * dot.get_height())
# label.move_to(dot)
# dot.add(label)
perms = [
[1, 0, 3, 2],
[3, 2, 1, 0],
[2, 3, 0, 1],
]
perm_terms = VGroup()
for perm in perms:
perm_term = VGroup(dots.copy(), get_permutation_arrows(dots, perm))
perm_term.perm = perm
perm_terms.add(perm_term)
perm_equation = VGroup(
perm_terms[0],
TexMobject("\\times").scale(2),
perm_terms[1],
TexMobject("=").scale(2),
perm_terms[2],
)
perm_equation.arrange(RIGHT, buff=LARGE_BUFF)
perm_equation.move_to(2 * DOWN)
# Bijection lines
bij_lines = VGroup()
for m1, m2 in zip(rot_icons, perm_terms):
line = Line(m1.get_bottom(), m2.get_top(), buff=0.2)
line.set_angle(-PI / 2, about_point=line.get_center())
bij_lines.add(line)
bij_lines.set_color(GREEN)
rot_icons[-1].match_x(bij_lines[2])
# Add terms
self.add(rot_icons)
for rot_icon, line, perm_term in zip(rot_icons, bij_lines, perm_terms):
self.play(
# FadeIn(rot_icon),
GrowFromPoint(line, line.get_top()),
FadeIn(perm_term, 2 * UP),
)
self.wait()
self.play(Write(VGroup(
*rot_icon_equation[1::2],
*perm_equation[1::2],
)))
self.wait(2)
# Composition
rot_anims = [
AnimationGroup(
Rotate(rot_icon[2], PI, axis=rot_icon[1].get_vector()),
ShowCreationThenFadeAround(rot_icon[-1]),
)
for rot_icon in rot_icons
]
perm_anims = [
permutation_animation(perm_term[0], perm_term.perm, lag_factor=0.1)
for perm_term in perm_terms
]
self.play(rot_anims[1])
self.play(rot_anims[0])
self.wait()
self.play(rot_anims[2])
self.wait()
self.play(LaggedStartMap(ShowCreation, bij_lines, lag_ratio=0.5))
self.wait()
self.play(perm_anims[1])
self.play(perm_anims[0])
self.wait()
self.play(perm_anims[2])
self.wait()
class IsomorphismWord(Scene):
def construct(self):
word = TextMobject("``Isomorphism''")
word.scale(2)
word.to_edge(UP)
self.play(FadeIn(word, DOWN))
self.wait()
class AskAboutCubeDiagonals(QuadrupletShufflings):
def construct(self):
# Setup
frame = self.camera.frame
frame.set_rotation(phi=80 * DEGREES)
frame.add_updater(lambda m, sc=self: m.set_rotation(theta=-20 * DEGREES * np.cos(0.1 * sc.time)))
cube = get_glassy_cube(frame)
cube.set_height(3)
axes = ThreeDAxes(axis_config={"include_tip": False})
colors = [RED, GREEN, BLUE, YELLOW]
diagonals = Group(*[
Line3D(vect + OUT, -vect - OUT, color=color)
for color, vect in zip(colors, [UL, UR, DR, DL])
])
diagonals.match_height(cube.edge_rods)
diag_markers = Group(*[
Line3D(ORIGIN, UP, color=color)
for color in colors
])
diag_markers.arrange(RIGHT, buff=MED_LARGE_BUFF)
diag_markers.to_corner(UL, buff=LARGE_BUFF)
diag_markers.fix_in_frame()
# Color corners
cds = cube.corner_dots
for diag in diagonals:
globals()['diag'] = diag
Group(*[
cds[np.argmin([get_norm(cd.get_center() - diag.points[i]) for cd in cds])]
for i in [0, -1]
]).match_color(diag)
cube.add_to_back(diagonals)
# Rotations
self.add(axes, cube)
self.add(diag_markers)
self.add(frame)
for quat in self.get_quaternions():
angle, axis = angle_axis_from_quaternion(quat)
perm = self.quaternion_to_perm(quat)
perm_arrows = get_permutation_arrows(diag_markers, perm)
perm_arrows.fix_in_frame()
self.play(FadeIn(perm_arrows))
self.play(
Rotate(cube, angle, axis=axis),
permutation_animation(diag_markers, perm, lag_factor=0.1),
run_time=2,
)
self.play(FadeOut(perm_arrows))
self.wait()
inv_perm = self.quaternion_to_perm(quaternion_conjugate(quat))
diag_markers.set_submobjects([diag_markers[i] for i in inv_perm])
class S4WithMultipleChildren(Scene):
def construct(self):
# Setup
bg_rect = FullScreenFadeRectangle()
bg_rect.set_fill(GREY_E, 1)
self.add(bg_rect)
s_rects = VGroup(*[ScreenRectangle() for x in range(3)])
s_rects.arrange(RIGHT, buff=MED_LARGE_BUFF)
s_rects.set_width(FRAME_WIDTH - 1)
s_rects.set_stroke(WHITE, 2)
s_rects.set_fill(BLACK, 1)
s_rects.move_to(DOWN)
self.add(s_rects)
s4_label = TexMobject("S_4")
s4_label.scale(2)
s4_label.to_edge(UP)
lines = VGroup(*[
Line(rect.get_top(), s4_label.get_bottom(), buff=0.2)
for rect in s_rects
])
# Arising
self.play(LaggedStartMap(ShowCreation, lines, lag_ratio=0.5))
self.play(FadeIn(s4_label, DOWN))
self.wait(2)
# Triplets
three = Integer(3)
three.scale(2)
three.move_to(s4_label)
pis = VGroup(*[Randolph(color=c) for c in [BLUE_C, BLUE_E, BLUE_D]])
pis.arrange(RIGHT)
vects = VGroup(*[Vector(RIGHT) for x in range(3)])
vects.arrange(RIGHT, buff=SMALL_BUFF)
vects.rotate(30 * DEGREES)
vects.set_color(TEAL)
triangle = RegularPolygon(3)
triangle.set_stroke(BLUE_B, 4)
triangle.add(*[Dot(vert, color=BLUE_D) for vert in triangle.get_vertices()])
triangle.set_stroke(background=True)
triplets = VGroup(pis, vects, triangle)
for trip, rect in zip(triplets, s_rects):
trip.set_width(0.8 * rect.get_width())
if trip.get_height() > 0.8 * rect.get_height():
trip.set_height(0.8 * rect.get_height())
trip.move_to(rect)
self.play(
FadeOut(s4_label, UP),
FadeIn(three, DOWN),
LaggedStartMap(FadeIn, pis, lag_ratio=0.5, run_time=1),
)
for trip in triplets[1:]:
self.play(FadeIn(trip, lag_ratio=0.5))
self.play(Blink(pis[0]))
self.play(Blink(pis[2]))
# Back to s4
self.play(
FadeOut(three, DOWN),
FadeIn(s4_label, UP),
FadeOut(triplets, lag_ratio=0.2, run_time=2),
)
self.wait()
class AutQ8(Scene):
def construct(self):
tex = TexMobject("\\text{Aut}(Q_8)", tex_to_color_map={"Q_8": BLUE})
tex.scale(2)
self.play(Write(tex))
self.wait()
class GroupsBeyondActions(Scene):
def construct(self):
groups = TextMobject("Groups")
sym_acts = TextMobject("Symmetric\\\\Actions")
others = TextMobject("Other things\\\\which ``multiply''")
VGroup(groups, sym_acts, others).scale(1.5)
line = Line(UP, DOWN)
sym_acts.set_color(BLUE)
others.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5))
VGroup(
groups, line, sym_acts
).arrange(DOWN, buff=MED_LARGE_BUFF)
line.add_updater(lambda m: m.put_start_and_end_on(
groups.get_bottom() + 0.3 * DOWN,
sym_acts.get_top() + 0.3 * UP,
))
others.move_to(sym_acts)
others.to_edge(RIGHT)
others_line = Line(groups.get_bottom(), others.get_top(), buff=0.3)
self.add(groups, line, sym_acts)
self.wait()
self.play(
sym_acts.to_edge, LEFT, LARGE_BUFF,
FadeIn(others, 2 * UL),
ShowCreation(others_line),
run_time=2,
)
self.wait()
class RAddToRMult(ExternallyAnimatedScene):
pass
class AskAboutAllTheGroups(TeacherStudentsScene):
def construct(self):
# Ask
question = TextMobject("What are all the groups", "?")
self.teacher_holds_up(question)
self.change_student_modes("pondering", "thinking", "erm")
self.wait(2)
question.generate_target()
question.target.to_corner(UL)
self.teacher_says(
"Now you can\\\\ask something\\\\more sophisticated.",
target_mode="hooray",
added_anims=[
MoveToTarget(question, run_time=2),
self.get_student_changes(
"erm", "pondering", "pondering",
look_at_arg=question.target
)
]
)
self.wait(2)
self.play(
RemovePiCreatureBubble(
self.teacher, target_mode="tease",
look_at_arg=question.get_right(),
),
question.set_x, 0,
)
# Add up to isomorphism
caveat = TextMobject("up to \\emph{isomorphism}")
caveat.next_to(question, DOWN)
caveat.set_color(YELLOW)
self.play(
Write(caveat, run_time=1),
question[1].next_to, caveat[0][-1], RIGHT, SMALL_BUFF, DOWN,
question[1].match_color, caveat,
self.get_student_changes(*3 * ["thinking"], look_at_arg=question)
)
question.add(caveat)
self.wait(2)
self.look_at(self.screen)
self.wait(2)
self.look_at(question)
# Alt question
sym_question = TextMobject("What are all the\\\\", "symmetric", " things", "?")
self.teacher_holds_up(sym_question)
cross = Cross(sym_question)
self.look_at(
cross,
added_anims=[
ShowCreation(cross),
self.get_student_changes("erm", "sassy", "hesitant")
]
)
self.wait()
abs_question = TextMobject("What are all the", " \\emph{ways}\\\\", "things ", "can be ", "symmetric", "?")
new_words = VGroup(abs_question[1], abs_question[3])
new_words.match_color(caveat)
abs_question.move_to(sym_question)
abs_question.shift_onto_screen()
self.play(
*[
ReplacementTransform(sym_question[i], abs_question[j], path_arc=10 * DEGREES)
for i, j in [(0, 0), (1, 4), (2, 2), (3, 5)]
],
FadeOut(cross),
self.get_student_changes("happy", "thinking", "tease"),
)
self.play(
self.teacher.change, "speaking", abs_question,
Write(new_words),
)
self.look_at(abs_question)
self.wait(2)
# Formula
suggestions = VGroup(*[
TextMobject("Some ", word, "?", tex_to_color_map={word: color})
for word, color in [
("formula", RED),
("procedure", MAROON_B),
("algorithm", PINK),
]
])
for words in suggestions:
words.move_to(abs_question, UP)
words.to_edge(LEFT, buff=LARGE_BUFF)
self.play(
FadeIn(suggestions[0], DOWN),
self.get_student_changes(*3 * ["pondering"], look_at_arg=suggestions),
self.teacher.change, "happy",
)
self.wait()
for words1, words2 in zip(suggestions, suggestions[1:]):
self.play(
FadeOut(words1[1], UP),
FadeIn(words2[1], DOWN),
ReplacementTransform(words1[2], words2[2])
)
self.remove(words1)
self.add(words2)
self.wait()
self.wait(3)
self.play(
FadeOut(
VGroup(question, abs_question, suggestions[-1]),
0.5 * DOWN,
lag_ratio=0.02,
run_time=2,
)
)
class ThisQuestionIsHard(Scene):
def construct(self):
# Setup line
line = NumberLine(x_range=(0, 1, 0.1), width=12)
line.shift(UP)
arrows = VGroup(
Arrow(line.n2p(0.5), line.n2p(0), fill_color=GREEN),
Arrow(line.n2p(0.5), line.n2p(1), fill_color=RED),
)
arrows.shift(2.5 * DOWN)
words = VGroup(TextMobject("Easier"), TextMobject("Harder"))
for word, arrow in zip(words, arrows):
word.match_color(arrow)
word.next_to(arrow, DOWN, SMALL_BUFF)
self.add(line)
self.add(arrows)
self.add(words)
# Add problems
problems = VGroup(
TexMobject("1 + 1"),
VGroup(
TexMobject("\\frac{2^{289}+1}{2^{17}+1}=2^{a_{1}}+\\ldots+2^{a_{k}}"),
TexMobject("a_1 < \\ldots < a_k"),
TexMobject("a_1, \\dots, a_k \\in \\mathds{Z}^+"),
),
VGroup(
TexMobject("{a \\over b + c} + {b \\over c + a} + {c \\over b + c} = 4"),
TexMobject("a, b, c \\in \\mathds{Z}^+"),
),
VGroup(
TexMobject("x^n + y^n = z^n"),
TexMobject("x, y, z \\in \\mathds{Z}"),
),
)
colors = Color(GREEN).range_to(RED, 4)
for prob, x, color in zip(problems, [0, 0.3, 0.7, 1], colors):
triangle = Triangle()
triangle.set_height(0.2)
triangle.set_stroke(width=0)
triangle.set_fill(color, 1)
triangle.move_to(line.n2p(x), UP)
prob.arrange(DOWN)
prob.scale(0.5)
prob.next_to(triangle, DOWN)
prob.add(triangle)
prob.set_color(color)
self.add(problems)
# Group question
tri = Triangle(start_angle=-90 * DEGREES)
tri.set_height(0.3)
tri.set_stroke(width=0)
tri.set_fill(GREY_B, 1)
tri.move_to(line.n2p(0.5), DOWN)
question = TextMobject("What are all\\\\the groups?")
question.next_to(tri, UP)
ext_line = line.copy()
ext_line.move_to(line.get_right(), LEFT)
frame = self.camera.frame
self.play(
DrawBorderThenFill(tri),
FadeIn(question, DOWN)
)
question.add(tri)
self.play(question.move_to, line.n2p(0.9), DOWN)
self.wait()
self.play(
ShowCreation(ext_line),
question.move_to, line.n2p(1.3), DOWN,
ApplyMethod(frame.scale, 1.3, {"about_edge": LEFT}, run_time=2)
)
self.wait()
class AmbientSnowflakeSymmetries(Scene):
def construct(self):
title = TexMobject("D_6")
title.scale(3)
title.to_edge(LEFT, buff=1)
title.set_color(BLUE)
self.add(title)
snowflake = get_snowflake()
snowflake.set_height(5)
snowflake.set_stroke(width=0)
snowflake.move_to(2 * RIGHT)
self.add(snowflake)
for n in range(10):
if random.choice([True, False]):
deg = random.choice([-120, -60, 60, 120])
icon = get_rot_icon(deg, snowflake, snowflake.get_height())
anim = Rotate(snowflake, deg * DEGREES)
else:
deg = random.choice(range(30, 180, 30))
angle = deg * DEGREES
icon = get_flip_icon(angle, snowflake, mini_mob_height=snowflake.get_height())
anim = Rotate(snowflake, PI, axis=rotate_vector(RIGHT, angle))
icon.shift(snowflake.get_center() - icon[0].get_center())
self.play(anim, FadeIn(icon[1:]))
self.play(FadeOut(icon[1:]))
class IntroduceSimpleGroups(Scene):
def construct(self):
# Setup
bg_rect = FullScreenFadeRectangle(fill_color=GREY_E, fill_opacity=1)
self.add(bg_rect)
groups = TextMobject("Groups")
groups.scale(2)
groups.to_edge(UP)
inf_groups = TextMobject("Infinite groups")
fin_groups = TextMobject("Finite groups")
children = VGroup(inf_groups, fin_groups)
children.scale(1.5)
children.arrange(RIGHT, buff=2)
children.next_to(groups, DOWN, buff=2)
child_lines = VGroup(*[
Line(groups.get_bottom(), child.get_top(), buff=0)
for child in children
])
child_lines.set_stroke(WHITE, 2)
s_rects = VGroup(*[
ScreenRectangle(height=3).move_to(child)
for child in children
])
s_rects.next_to(children, DOWN)
s_rects.set_fill(BLACK, 1)
# Introductions
self.add(groups)
self.wait()
self.play(
ShowCreation(child_lines[0]),
FadeIn(inf_groups, 2 * UR),
FadeIn(s_rects[0])
)
self.wait(2)
self.play(
ShowCreation(child_lines[1]),
FadeIn(fin_groups, 2 * UL),
FadeIn(s_rects[1]),
)
self.wait()
self.add(s_rects, fin_groups)
self.play(
Uncreate(child_lines),
FadeOut(inf_groups, 3 * LEFT),
FadeOut(s_rects[0], 3 * LEFT),
FadeOut(groups, 2 * UP),
fin_groups.move_to, groups,
s_rects[1].replace, bg_rect,
s_rects[1].set_stroke, {"width": 0},
run_time=2,
)
self.remove(s_rects, bg_rect)
# Comparison titles
bg_rects = VGroup(*[
Rectangle(
height=FRAME_HEIGHT,
width=FRAME_WIDTH / 3,
fill_color=color,
fill_opacity=1,
stroke_width=0
)
for color in [GREY_D, GREY_E, BLACK]
])
bg_rects.arrange(RIGHT, buff=0)
bg_rects.center()
fin_groups.generate_target()
titles = VGroup(
TextMobject("Integers").scale(1.5),
TextMobject("Molecules").scale(1.5),
fin_groups.target,
)
sub_titles = VGroup(*[
TextMobject("break down into\\\\", word)
for word in ("primes", "atoms", "simple groups")
])
for rect, title, sub_title in zip(bg_rects, titles, sub_titles):
title.move_to(rect, UP)
title.shift(0.5 * DOWN)
sub_title.next_to(title, DOWN)
sub_title[1].set_color(BLUE)
sub_title.align_to(sub_titles[0], UP)
# Comparison diagrams
H_sphere = Sphere()
H_sphere.set_height(0.5)
H_sphere.set_color(RED)
H_atom = Group(
H_sphere,
TextMobject("H").scale(0.5).move_to(H_sphere)
)
O_sphere = Sphere()
O_sphere.set_height(1)
O_sphere.set_color(BLUE)
O_atom = Group(
O_sphere,
TextMobject("O").scale(0.75).move_to(O_sphere)
)
H2O = Group(
O_atom.copy(),
H_atom.copy().move_to([-0.45, 0.35, 0]),
H_atom.copy().move_to([0.45, 0.35, 0]),
)
trees = Group(
VGroup(
Integer(60),
VGroup(*map(Integer, [2, 2, 3, 5])),
),
Group(
H2O,
Group(H_atom.copy(), H_atom.copy(), O_atom.copy())
),
VGroup(
TexMobject("S_4"),
VGroup(*[
TexMobject(
"C_" + str(n),
fill_color=(RED_B if n == 2 else BLUE)
)
for n in [2, 2, 2, 3]
]),
)
)
for tree, rect in zip(trees, bg_rects):
root, children = tree
children.arrange(RIGHT, buff=0.35)
children.next_to(root, DOWN, buff=1.2)
lines = VGroup()
for child in children:
lines.add(Line(root.get_bottom(), child.get_top(), buff=0.1))
tree.add(lines)
tree.move_to(rect)
tree.shift(DOWN)
# Slice screen
self.add(bg_rects, fin_groups)
for rect in bg_rects:
rect.save_state()
rect.shift(LEFT)
rect.set_fill(BLACK, 1)
self.play(
MoveToTarget(fin_groups),
LaggedStartMap(Restore, bg_rects, lag_ratio=0.3)
)
# Breakdowns
for title, sub_title, tree in zip(titles, sub_titles, trees):
root, children, lines = tree
self.play(
FadeIn(title),
FadeIn(root),
)
globals()['root'] = root
self.play(
ShowCreation(lines),
LaggedStart(*[
GrowFromPoint(child, root)
for child in children
]),
FadeIn(sub_title)
)
self.wait()
# Theorem
theorem_name = TextMobject("(JordanHölder Theorem)")
theorem_name.scale(0.7)
theorem_name.next_to(sub_titles[2], DOWN)
self.play(Write(theorem_name, run_time=1))
self.wait()
class SymmetriesOfCirleAndLine(Scene):
def construct(self):
line = NumberLine((0, 100, 1))
line.move_to(2 * UP)
circle = Circle(radius=1)
circle_ticks = VGroup(*[
Line(0.95 * vect, 1.05 * vect)
for vect in compass_directions(12)
])
circle.add(circle_ticks)
circle.set_stroke(BLUE, 3)
circle.scale(1.5)
circle.next_to(line, DOWN, buff=2)
circle.shift(RIGHT)
R_label = TexMobject("\\mathds{R}")
RmodZ = TexMobject("\\mathds{R} / \\mathds{Z}")
R_label.set_height(0.9)
R_label.next_to(line, UP, MED_LARGE_BUFF)
RmodZ.set_height(0.9)
RmodZ.next_to(circle, LEFT, LARGE_BUFF)
R_label.match_x(RmodZ)
self.add(line)
self.add(circle)
self.add(R_label)
self.add(RmodZ)
# Rotations and shifts
for n in range(10):
x = interpolate(-20, 20, random.random())
self.play(
line.shift, x * RIGHT,
Rotate(circle, -x / PI),
run_time=2,
)
self.wait()
self.embed()
class QuinticImpliesCyclicDecomposition(Scene):
def construct(self):
# Title
title = TextMobject("Quintic formula")
title.scale(1.5)
title.to_edge(UP)
details = TextMobject(
"Solve ",
"$a_5 x^5 + a_4 x^4 + a_3 x^3 + a_2 x^2 + a_1 x + a_0$\\\\",
" using only ",
"+, -, $\\times$, $/$, and $\\sqrt[n]{\\quad}$"
)
details[1].set_color(BLUE)
details[3].set_color(TEAL)
details.match_width(title)
details.scale(1.2)
details.next_to(title, DOWN)
self.clear()
self.add(title)
self.wait()
self.play(FadeIn(details, 0.5 * UP))
self.wait()
full_title = VGroup(title, details)
# Show Implication
implies = TexMobject("\\Downarrow").scale(2)
implies.next_to(details, DOWN, LARGE_BUFF)
s5 = TexMobject("S_5")
prime_children = VGroup(
TexMobject("C_{p_1}"),
TexMobject("C_{p_2}"),
TexMobject("\\vdots"),
TexMobject("C_{p_n}"),
)
prime_children.set_color(RED_B)
real_children = VGroup(
TexMobject("A_5"),
TexMobject("C_2"),
)
real_children.set_color(GREEN)
for children, buff in (prime_children, MED_LARGE_BUFF), (real_children, LARGE_BUFF):
children.arrange(DOWN, buff=buff, aligned_edge=LEFT)
children.next_to(s5, RIGHT, buff=0.5 + 0.25 * len(children))
children.lines = VGroup()
for child in children:
children.lines.add(
Line(s5.get_right(), child.get_left(), buff=0.1)
)
prime_children[2].shift(SMALL_BUFF * RIGHT)
VGroup(
s5, prime_children.lines, prime_children,
real_children.lines, real_children,
).next_to(implies, DOWN)
# Show decomps
implies.save_state()
implies.stretch(0, 1, about_edge=UP)
self.play(
Restore(implies),
GrowFromPoint(s5, implies.get_top()),
)
self.play(
LaggedStartMap(ShowCreation, prime_children.lines, lag_ratio=0.3),
LaggedStartMap(
FadeIn, prime_children,
lambda m: (m, s5.get_right() - m.get_center()),
lag_ratio=0.3,
)
)
self.wait()
self.play(
FadeOut(prime_children, RIGHT),
*[
ApplyMethod(line.scale, 0, {"about_point": line.get_end()}, remover=True)
for line in prime_children.lines
],
LaggedStartMap(ShowCreation, real_children.lines, lag_ratio=0.3),
LaggedStartMap(
FadeIn, real_children,
lambda m: (m, s5.get_right() - m.get_center()),
lag_ratio=0.3,
)
)
self.play(ShowCreationThenFadeAround(real_children[0]))
# Reverse implication
title_rect = SurroundingRectangle(full_title)
title_rect.set_stroke(RED, 2)
not_exists = TextMobject("No\\\\such\\\\thing")
not_exists.match_height(title_rect)
not_exists.set_color(RED)
not_exists.next_to(title_rect, LEFT)
full_title.add(title_rect, not_exists)
self.play(
Rotate(implies, PI),
VFadeIn(title_rect),
VFadeIn(not_exists),
full_title.shift, 0.5 * RIGHT,
)
class CommentOnNontrivialFactFromGroupDecomposition(TeacherStudentsScene):
def construct(self):
self.student_says(
"I...don't\\\\get it.",
target_mode="confused",
student_index=2,
look_at_arg=self.screen,
added_anims=[self.teacher.change, "guilty"],
)
self.change_student_modes("maybe", "tired", look_at_arg=self.screen)
self.wait(4)
fp_words = TextMobject("Fact about\\\\polynomials")
fp_words.scale(1.25)
as_words = TextMobject("``Atomic structure''\\\\of a group")
as_words.scale(1.25)
implies = TexMobject("\\Rightarrow").scale(2)
self.teacher_holds_up(
fp_words,
added_anims=[
RemovePiCreatureBubble(self.students[2]),
]
)
self.change_student_modes("pondering", "hesitant", "plain", look_at_arg=fp_words)
self.wait()
as_words.next_to(fp_words, LEFT, buff=1.5)
implies.move_to(midpoint(as_words.get_right(), fp_words.get_left()))
self.play(
FadeIn(as_words, RIGHT),
Write(implies),
self.get_student_changes(*3 * ["pondering"], look_at_arg=as_words)
)
self.wait(5)
class TwoStepsToAllFiniteGroups(Scene):
def construct(self):
# List
title = TextMobject("How to categorize all finite groups")
title.scale(1.5)
title.add(Underline(title))
title.to_edge(UP)
steps = VGroup(
TextMobject("1. ", "Find all the ", "simple groups", "."),
TextMobject("2. ", "Find all the ", "ways to\\\\", "combine ", "simple groups", "."),
)
steps[1][3:].next_to(steps[1][1], DOWN, SMALL_BUFF, aligned_edge=LEFT)
steps.arrange(DOWN, aligned_edge=LEFT, buff=2)
steps.set_y(-0.5)
steps.to_edge(LEFT)
for step in steps:
step.set_color_by_tex("simple groups", TEAL)
self.add(title)
self.wait()
self.play(LaggedStartMap(
FadeIn, VGroup(steps[0][0], steps[1][0]),
lambda m: (m, RIGHT),
lag_ratio=0.4,
))
self.wait()
self.play(FadeIn(steps[0][1:], lag_ratio=0.1))
self.wait()
self.play(
TransformFromCopy(steps[0][1], steps[1][1]),
TransformFromCopy(steps[0][2], steps[1][4]),
FadeIn(VGroup(*[steps[1][i] for i in (2, 3, 5)])),
)
self.wait()
# Periodic table
table = VGroup(*[
VGroup(*[
Square() for x in range(n)
]).arrange(DOWN, buff=0)
for n in [7, 6, *[4] * 10, *[6] * 5, 7]
])
table.arrange(RIGHT, buff=0, aligned_edge=DOWN)
table.set_width(4)
table.to_edge(RIGHT)
table.match_y(steps[0])
table.set_stroke(GREY_A, 2)
table_arrow = Arrow(
steps[0].get_right(), table.get_left(),
buff=0.5,
)
self.play(
GrowArrow(table_arrow),
FadeIn(table, lag_ratio=0.1, run_time=3),
)
self.wait()
# Chemistry
chem_words = TextMobject("All of chemistry")
chem_words.match_y(steps[1])
chem_words.match_x(table)
chem_words.set_color(RED)
chem_arrow = Arrow(
steps[1].get_right(), chem_words.get_left(),
buff=0.5
)
self.play(
GrowArrow(chem_arrow),
Write(chem_words),
)
self.wait()
# Found all simples
top_group = VGroup(steps[0], table_arrow, table)
bottom_group = VGroup(steps[1], chem_arrow, chem_words)
frame = self.camera.frame
top_rect = SurroundingRectangle(top_group, buff=MED_LARGE_BUFF)
top_rect.set_stroke(GREEN, 4)
check = Checkmark()
check.set_height(0.7)
check.next_to(top_rect, UP, aligned_edge=LEFT)
check.shift(RIGHT)
self.play(
frame.scale, 1.1,
bottom_group.shift, 0.5 * DOWN,
bottom_group.set_opacity, 0.5,
ShowCreation(top_rect),
FadeOut(title, UP),
)
self.play(Write(check))
self.wait()
proof_words = TextMobject("(prove you have them all.)")
proof_words.next_to(steps[0], DOWN)
proof_words.set_color(GREY_A)
self.play(Write(proof_words, run_time=2))
self.wait()
# What was involved
stats = VGroup(
TextMobject("1955-2004"),
TextMobject("$10{,}000+$ pages"),
TextMobject("100's of mathematicians"),
TextMobject("Plenty of computation"),
)
stats.arrange_in_grid(buff=1.5, aligned_edge=LEFT)
stats.next_to(top_rect, DOWN, buff=LARGE_BUFF)
for stat in stats:
dot = Dot()
dot.next_to(stat, LEFT)
stat.add(dot)
turn_animation_into_updater(ApplyMethod(frame.shift, DOWN, run_time=3))
self.add(frame)
for stat in stats:
self.play(FadeIn(stat), bottom_group.set_opacity, 0)
self.wait(0.5)
self.remove(bottom_group)
# 2004 paper mention
stats.generate_target()
stats.target.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
stats.target.move_to(stats, UL)
last_paper = TextMobject(
"The Classification of Quasithin Groups\\\\",
"Aschbacher and Smith (2004)\\\\",
"(12{,}000 pages!)",
)
last_paper.scale(0.7)
last_paper[:-1].set_color(YELLOW)
last_paper.move_to(stats, RIGHT)
last_paper.shift(RIGHT)
self.play(
FadeIn(last_paper, DOWN),
MoveToTarget(stats),
)
self.wait()
# Quote
quote = TextMobject(
"""
``...undoubtedly one of the most\\\\
extraordinary theorems that pure\\\\
mathematics has ever seen.''\\\\
""",
"-Richard Elwes",
alignment="",
)
quote.scale(0.9)
quote[0].set_color(YELLOW)
quote[-1].shift(MED_SMALL_BUFF * DR)
quote.move_to(last_paper, LEFT)
self.play(
FadeIn(quote),
FadeOut(last_paper),
)
self.wait()
class ClassificationOfSimpleGroups(Scene):
def construct(self):
# Title
class_title = TextMobject("Classification of finite simple groups")
class_title.set_width(FRAME_WIDTH - 4)
class_title.add(Underline(class_title).set_color(GREY_B))
class_title.to_edge(UP)
self.add(class_title)
# 18 families
square_template = Square(side_length=0.3)
square_template.set_stroke(GREY_B, 2)
square_template.set_fill(BLUE_E, 0.5)
families_grid = VGroup(*[
VGroup(*[square_template.copy() for x in range(8)])
for y in range(18)
])
for family in families_grid:
family.arrange(DOWN, buff=0)
dots = TexMobject("\\vdots")
dots.next_to(family, DOWN)
family.add(dots)
families_grid.arrange(RIGHT, buff=MED_SMALL_BUFF)
families_grid.to_edge(LEFT)
families_grid.set_y(-1)
families_title = TextMobject("18 infinite families")
families_title.set_color(BLUE)
families_title.next_to(families_grid, UP, MED_LARGE_BUFF)
self.play(
FadeIn(families_title),
FadeIn(families_grid, lag_ratio=0.1, run_time=4),
)
self.wait()
# Analogize to periodic table
families_grid.generate_target()
families_grid.save_state()
families_grid.target.arrange(RIGHT, buff=0)
families_grid.target.move_to(families_grid)
faders = VGroup(*[
column[:n]
for column, n in zip(families_grid.target, [
0, 1, *[3] * 10, *[1] * 5, 0,
])
])
faders.set_opacity(0)
self.play(MoveToTarget(families_grid, lag_ratio=0.001))
self.wait(2)
self.play(Restore(families_grid))
# Sporadic
sporadics = VGroup(*[square_template.copy() for x in range(26)])
buff = 0.1
sporadics[:20].arrange_in_grid(10, 2, buff=buff)
sporadics[20:].arrange(DOWN, buff=buff)
sporadics[20:].next_to(sporadics[:20], RIGHT, buff)
sporadics.next_to(families_grid, RIGHT, buff=1.5, aligned_edge=UP)
sporadics.set_fill(YELLOW_E)
pre_sporadics_title = TextMobject("26", " leftovers")
sporadics_title = TextMobject("26", " ``sporadic''\\\\groups")
for title in pre_sporadics_title, sporadics_title:
title.set_color(YELLOW)
title.next_to(sporadics, UP, MED_LARGE_BUFF)
sporadics_title.shift(MED_SMALL_BUFF * DOWN)
self.play(
FadeIn(pre_sporadics_title, DOWN),
ShowIncreasingSubsets(sporadics, run_time=3),
)
self.wait()
self.play(
ReplacementTransform(pre_sporadics_title[0], sporadics_title[0]),
FadeOut(pre_sporadics_title[1], UP),
FadeIn(sporadics_title[1], DOWN),
)
self.wait()
# Show prime cyclic groups
families_grid.save_state()
column = families_grid[0]
def prepare_column(column):
column.generate_target()
column.target.set_height(FRAME_HEIGHT)
column.target.move_to(5 * LEFT)
column.target.to_edge(UP)
column.target[-1].scale(0.5, about_point=column.target[-2].get_bottom())
prepare_column(column)
self.play(
MoveToTarget(column),
FadeOut(families_grid[1:], lag_ratio=0.1),
FadeOut(families_title),
FadeOut(class_title, UP),
FadeOut(sporadics, 2 * RIGHT),
FadeOut(sporadics_title, 2 * RIGHT),
)
# C5
c_names = VGroup(*[
TexMobject(f"C_{{{p}}}")
for p in [2, 3, 5, 7, 11, 13, 17, 19]
])
def put_in_square(mob, square, factor=0.4):
mob.set_height(factor * square.get_height())
mob.move_to(square)
def put_names_in_column(names, column):
for name, square in zip(names, column):
put_in_square(name, square)
put_names_in_column(c_names, column)
self.play(FadeIn(c_names, lag_ratio=0.1))
self.wait()
pentagon = RegularPolygon(5)
pentagon.set_height(3)
pentagon.set_fill(TEAL_E, 0.5)
pentagon.set_stroke(WHITE, 1)
pentagon.move_to(2 * RIGHT)
c_names.save_state()
c5 = c_names[2]
c5.generate_target()
c5.target.scale(2)
c5.target.next_to(pentagon, UP, LARGE_BUFF)
self.play(
MoveToTarget(c5),
DrawBorderThenFill(pentagon, run_time=1),
)
pcenter = center_of_mass(pentagon.get_vertices())
for n in [1, -2, 2]:
self.play(Rotate(pentagon, n * TAU / 5, about_point=pcenter))
self.wait(0.5)
self.play(
Restore(c_names),
FadeOut(pentagon)
)
c_names.generate_target()
c_names.target.replace(families_grid.saved_state[0])
c_names.target.scale(0.9)
c_names.target.set_opacity(0)
families_grid[1:].fade(1)
self.play(
Restore(families_grid),
MoveToTarget(c_names, remover=True)
)
# Alternating
column = families_grid[1]
prepare_column(column)
self.play(
MoveToTarget(column),
FadeOut(families_grid[0]),
FadeOut(families_grid[2:], lag_ratio=0.1),
)
a_names = VGroup(*[
TexMobject(f"A_{{{n}}}")
for n in range(5, 5 + len(column) - 1)
])
put_names_in_column(a_names, column)
self.play(FadeIn(a_names, lag_ratio=0.1))
dots = VGroup(*[Dot() for x in range(5)])
dots.set_height(0.5)
dots.arrange(RIGHT, buff=MED_LARGE_BUFF)
dots.set_submobject_colors_by_gradient(RED, YELLOW)
dots.move_to(RIGHT)
a5 = a_names[0]
a5.save_state()
a5.generate_target()
a5.target.scale(2)
a5.target.next_to(dots, UP, buff=1.5)
self.play(
FadeIn(dots, lag_ratio=0.1),
MoveToTarget(a5),
)
for x in range(5):
perm = list(range(5))
swaps = 1 # Lie
while swaps % 2 == 1:
random.shuffle(perm)
swaps = 0
for i, j in it.combinations(perm, 2):
if j < i:
swaps += 1
arrows = get_permutation_arrows(dots, perm)
self.play(
FadeIn(arrows),
permutation_animation(dots, perm, lag_factor=0.1),
)
self.play(FadeOut(arrows))
self.wait()
self.play(
FadeOut(dots, lag_ratio=0.1),
Restore(a5),
)
self.wait()
a_names.generate_target()
a_names.target.replace(families_grid.saved_state[1])
a_names.target.scale(0.9)
a_names.target.fade(1)
self.play(
Restore(families_grid),
MoveToTarget(a_names, remover=True),
FadeIn(families_title),
FadeIn(class_title),
)
# Others
others_rect = SurroundingRectangle(families_grid[2:])
others_rect.set_stroke(YELLOW, 2)
others_name = TextMobject("Groups of\\\\Lie type")
others_name.set_color(YELLOW)
others_name.next_to(others_rect, RIGHT)
self.play(ShowCreation(others_rect))
self.play(FadeIn(others_name))
self.wait()
self.play(FadeOut(others_name), FadeOut(others_rect))
# Back to sporadics
self.play(
ShowIncreasingSubsets(sporadics),
FadeIn(sporadics_title),
)
self.wait()
# Look closer at sporadics
sporadics.generate_target()
sporadics.target.rotate(PI, axis=UL)
sporadics.target.set_width(FRAME_WIDTH - 1)
sporadics.target.center().to_edge(DOWN)
sporadics_title.save_state()
sporadics.save_state()
self.play(
sporadics_title.scale, 1.25,
sporadics_title.center,
sporadics_title.to_edge, UP,
MoveToTarget(sporadics),
FadeOut(families_grid, LEFT),
FadeOut(families_title, LEFT),
FadeOut(class_title, UP),
)
# Monster
monster = get_monster()
put_in_square(monster, sporadics[0], 0.9)
monster_name = TextMobject("Monster", " group")
monster_name.next_to(sporadics[0], UP, LARGE_BUFF)
monster_name.shift_onto_screen()
monster_arrow = Arrow(monster_name.get_bottom(), monster.get_top())
size_label = TextMobject("{:,}".format(MONSTER_SIZE))[0]
size_label.scale(0.8)
size_label.move_to(monster_name, LEFT)
self.play(
sporadics[0].set_fill, {"opacity": 0},
FadeIn(monster)
)
self.play(
Write(monster_name),
GrowArrow(monster_arrow),
)
self.wait()
# sporadics_title.generate_target()
# sporadics_title.target.scale(1 / 1.5)
# sporadics_title.target.to_corner(UR)
self.play(
monster_name.shift, 0.6 * UP,
ShowIncreasingSubsets(size_label, run_time=2),
# MoveToTarget(sporadics_title, run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1))
)
self.wait()
# Baby monster
full_monster_label = VGroup(monster_name, size_label)
full_monster_label.save_state()
full_monster_label.generate_target()
full_monster_label.target.to_edge(UP, buff=MED_SMALL_BUFF)
full_monster_label.target.set_opacity(0.7)
baby_name = TextMobject("Baby monster group")
baby_name.move_to(size_label, LEFT)
baby_arrow = Arrow(baby_name.get_bottom(), sporadics[1].get_corner(UR) + SMALL_BUFF * DL)
baby_arrow.set_stroke(BLACK, 6, background=True)
baby_size_label = TextMobject("{:,}".format(BABY_MONSTER_SIZE))[0]
baby_size_label.scale(0.8)
baby_size_label.move_to(baby_name, LEFT)
baby_monster = get_baby_monster()
baby_monster.set_width(0.9 * sporadics[1].get_width())
baby_monster.move_to(sporadics[1])
baby_monster.shift(SMALL_BUFF * DOWN)
self.remove(monster_arrow)
self.play(
MoveToTarget(full_monster_label),
TransformFromCopy(monster_arrow, baby_arrow),
FadeIn(baby_name, DOWN),
sporadics[1].set_fill, {"opacity": 0},
FadeIn(baby_monster),
FadeOut(sporadics_title, UP),
)
self.wait()
self.play(
baby_name.shift, 0.6 * UP,
ShowIncreasingSubsets(baby_size_label, run_time=2)
)
self.wait()
# 20 vs. 6
top_20 = sporadics[:20]
top_20.generate_target()
top_20.target.shift(2 * UP)
top_20.target[1:].set_fill(GREEN, 0.8)
self.play(
MoveToTarget(top_20),
MaintainPositionRelativeTo(monster, sporadics[0]),
MaintainPositionRelativeTo(baby_monster, sporadics[1]),
UpdateFromAlphaFunc(baby_monster, lambda m, a: m.set_opacity(1 - a), remover=True),
ApplyMethod(baby_arrow.scale, 0, {"about_point": baby_arrow.get_start()}, remover=True),
FadeOut(baby_size_label, UP),
FadeOut(baby_name, UP),
full_monster_label.set_fill, WHITE, 1,
)
monster_name.generate_target()
monster_name.target.arrange(DOWN, buff=MED_SMALL_BUFF, aligned_edge=LEFT)
monster_name.target.next_to(sporadics[0], UP, SMALL_BUFF, aligned_edge=LEFT)
happy_family_name = TextMobject("``Happy family''")
happy_family_name.set_height(0.7)
happy_family_name.to_edge(UP)
happy_family_name.set_color(GREEN)
self.play(
MoveToTarget(monster_name),
FadeOut(size_label, DOWN),
FadeIn(happy_family_name, UP),
)
self.wait()
pariahs = sporadics[20:]
pariahs_name = TextMobject("``Pariahs''")
pariahs_name.set_height(0.7)
pariahs_name.next_to(pariahs, UP, MED_LARGE_BUFF)
pariahs_name.set_color(YELLOW)
self.play(
FadeIn(pariahs_name, UP),
pariahs.set_fill, YELLOW, 0.7,
)
self.wait()
class ImSorryWhat(TeacherStudentsScene):
def construct(self):
self.student_says(
"I'm sorry, what?!",
target_mode="sassy",
look_at_arg=self.screen,
)
self.change_student_modes("angry", "maybe", "sassy", look_at_arg=self.screen)
self.wait(3)
self.change_student_modes("pleading", "confused", "erm", look_at_arg=self.screen)
self.wait(5)
class TellMeTheresAChildrensBook(TeacherStudentsScene):
def construct(self):
self.student_says(
"Please tell me\\\\there's a children's\\\\book about this!",
target_mode="surprised",
added_anims=[self.teacher.change, "tease"]
)
self.change_student_modes("happy", "coin_flip_1", look_at_arg=self.screen)
self.look_at(self.screen)
self.wait(5)
class AskWhatTheMonsterActsOn(Scene):
def construct(self):
# Setup
question = TextMobject(
"The monster group describes the symmetries of ",
"$\\underline{\\qquad\\qquad\\qquad}$",
)
question[1].set_color(YELLOW)
question.to_edge(UP)
monster = get_monster()
monster.set_height(5)
monster.to_corner(DL)
self.add(monster)
self.play(FadeIn(question, DOWN))
self.wait()
# Dimension counts
dim_words = VGroup(*[
TextMobject(
"Something in\\\\",
"{:,}".format(n),
" dimensions",
"?"
)
for n in [2, 3, 4, 5, 196883]
])
dim_words.scale(1.25)
dim_words.to_edge(RIGHT, buff=LARGE_BUFF)
dim_words.set_y(1.5)
final_words = dim_words[-1]
dim = Integer(196883, edge_to_fix=ORIGIN)
dim.replace(final_words[1])
dim.match_style(final_words[1])
final_words.replace_submobject(1, dim)
final_words[-1].set_opacity(0)
cross = Cross(dim_words)
cross.set_stroke(RED, 8)
for dim_word in dim_words[:-1]:
cross.replace(dim_word[1], stretch=True)
self.add(dim_word)
self.wait()
self.play(ShowCreation(cross, run_time=0.5))
self.wait(0.5)
self.remove(dim_word, cross)
penult = dim_words[-2]
dim.set_value(0)
for i in 0, 2:
final_words[i].save_state()
final_words[i].replace(penult[i])
self.play(
Restore(final_words[0]),
Restore(final_words[2]),
FadeOut(penult[3]),
ChangingDecimal(
dim,
lambda a: interpolate(5, 196883, a),
run_time=8,
rate_func=rush_from,
),
UpdateFromAlphaFunc(
Mobject(),
lambda m, a, fw=final_words: fw.set_color(interpolate_color(WHITE, YELLOW, a)),
remover=True
)
)
final_words.add(dim)
self.add(final_words)
self.wait()
self.play(blink_monster(monster))
self.wait()
# Elements of the monster
in_sym = TexMobject("\\in")
in_sym.scale(2.5)
monster.generate_target()
monster.target.center().to_edge(RIGHT)
in_sym.next_to(monster.target, LEFT, MED_LARGE_BUFF)
self.play(
MoveToTarget(monster),
FadeOut(question, 3 * RIGHT),
FadeOut(final_words, 2 * RIGHT),
FadeIn(in_sym, 3 * LEFT),
)
matrix = IntegerMatrix(
np.random.randint(0, 2, size=(6, 6)),
v_buff=0.8,
h_buff=0.8,
)
matrix.set_height(4)
matrix.next_to(in_sym, LEFT, MED_LARGE_BUFF)
mob_matrix = matrix.get_mob_matrix()
groups_to_dots = [
(mob_matrix[4, :4], TexMobject("\\vdots")),
(mob_matrix[:4, 4], TexMobject("\\ldots")),
(VGroup(mob_matrix[4, 4]), TexMobject("\\ddots")),
]
for group, dots in groups_to_dots:
for elem in group:
dots_copy = dots.copy()
dots_copy.move_to(elem)
elem.set_submobjects(dots_copy)
braces = VGroup(
Brace(matrix.get_entries(), DOWN),
Brace(matrix.get_entries(), LEFT),
)
braces[1].shift(MED_SMALL_BUFF * LEFT)
for brace in braces:
brace.add(brace.get_text("196{,}882"))
braces.set_color(BLUE)
gigs_label = TextMobject("Each element $\\approx$ 4.5 Gigabytes of data!")
gigs_label.next_to(matrix, UP, MED_LARGE_BUFF)
self.play(Write(matrix, run_time=1))
self.play(
LaggedStartMap(GrowFromCenter, braces, lag_ratio=0.5, run_time=1),
FadeIn(gigs_label, DOWN)
)
self.play(blink_monster(monster))
self.wait()
class MonsterQuotes(Scene):
def construct(self):
images = [
get_named_image("John Conway"),
get_named_image("Richard Borcherds"),
]
quotes = [
TextMobject(
"""
``Nothing has given me the feeling\\\\
that I understand why the monster\\\\
is there.''\\\\
""",
alignment="",
),
TextMobject(
"""
``The monster simple group . . . appears\\\\
to rely on numerous bizarre coincidences\\\\
to exist.''\\\\
""",
alignment="",
),
]
faders = []
self.clear()
for image, quote, color in zip(images, quotes, [YELLOW, WHITE]):
quote[0].set_color(color)
image.set_height(5)
image.to_edge(LEFT)
image[1].set_color(GREY_B)
quote.next_to(image, RIGHT, buff=1, aligned_edge=UP)
quote.shift(DOWN)
quote.shift_onto_screen()
self.play(
FadeIn(image, DOWN),
FadeIn(quote, lag_ratio=0.1, run_time=3),
*faders,
)
self.wait(4)
faders = [FadeOut(image, UP), FadeOut(quote)]
class MonstrousMoonshine(Scene):
def construct(self):
# Time line
decades = list(range(1970, 2030, 10))
timeline = NumberLine(
(decades[0], decades[-1], 1),
numbers_with_elongated_ticks=decades,
tick_size=0.075,
width=13,
)
timeline.add_numbers(decades, number_config={"group_with_commas": False})
timeline.move_to(DOWN)
self.add(timeline)
triangle = Triangle()
triangle.rotate(PI)
triangle.set_height(0.25)
triangle.set_fill(BLUE_C, 1)
triangle.set_stroke(WHITE, 0)
triangle.move_to(timeline.n2p(2020), DOWN)
self.add(triangle)
self.play(
triangle.move_to, timeline.n2p(1978), DOWN,
run_time=3,
)
# McKay
mckay = get_named_image("John McKay")
mckay[0].flip()
mckay.next_to(triangle, UP)
self.play(FadeIn(mckay, DOWN))
self.wait()
theories = VGroup(
TextMobject("Finite group theory"),
TextMobject("Galois theory"),
)
theories.arrange(RIGHT, buff=1.5)
theories.next_to(mckay, RIGHT, LARGE_BUFF)
theories_line, theories_arrow = [
func(
*[theory.get_top() for theory in theories],
path_arc=-90 * DEGREES,
buff=0.2,
).shift(0.25 * UP)
for func in [Line, Arrow]
]
theories_line.set_stroke(BLUE_C, 6)
theories_arrow.set_fill(BLUE_C)
self.play(FadeIn(theories[0], LEFT))
self.play(
GrowFromPoint(theories[1], theories_line.get_start(), path_arc=-90 * DEGREES),
ShowCreationThenFadeOut(theories_line, run_time=2),
FadeIn(theories_arrow, rate_func=squish_rate_func(smooth, 0.3, 1), run_time=2)
)
self.wait()
theories_group = VGroup(theories, theories_arrow)
# j function
j_func = TexMobject(
"j(\\tau) =q^{-1}+744+196{,}884 q+21{,}493{,}760 q^{2}+864{,}299{,}970 q^{3}+\\cdots\\\\",
"\\big(q = e^{2\\pi i \\tau}\\big)",
tex_to_color_map={
"\\tau": TEAL,
"q": BLUE,
"196{,}884": WHITE,
}
)
j_func.scale(0.7)
j_func[-5:].shift([-0.5, -0.25, 0])
j_func.next_to(mckay, RIGHT, aligned_edge=DOWN)
j_func.shift(0.5 * UP)
special_num = j_func.get_part_by_tex("196{,}884")
special_num_underline = Underline(special_num)
special_num_underline.set_stroke(YELLOW, 3)
self.play(
theories_group.scale, 0.5, {"about_edge": UL},
theories_group.to_edge, UP,
Write(j_func)
)
self.wait()
self.play(
ShowCreation(special_num_underline),
special_num.set_color, YELLOW,
)
self.wait()
j_coloring = ImageMobject("J_Invariant_Coloring")
j_coloring.set_width(FRAME_WIDTH)
j_coloring.to_edge(DOWN, buff=0)
j_coloring.set_opacity(0.25)
self.add(j_coloring, *self.mobjects)
self.play(FadeIn(j_coloring))
self.wait(0.5)
self.play(FadeOut(j_coloring), FadeOut(special_num_underline))
self.wait()
# Conway
conway = get_named_image("John Conway")
conway[0].flip()
conway.move_to(mckay)
conway.to_edge(RIGHT)
moonshine = TextMobject("Moonshine!")
moonshine.set_height(0.6)
moonshine.next_to(conway, LEFT, MED_LARGE_BUFF, aligned_edge=UP)
moonshine.set_color(YELLOW)
self.play(
FadeOut(timeline, DOWN),
FadeOut(triangle, DOWN),
j_func.to_edge, DOWN,
FadeOut(theories_group),
FadeIn(conway, DOWN),
)
self.play(Write(moonshine))
self.wait()
# Conjecture
conjecture = TextMobject("Monstrous ", "moonshine\\\\", " conjecture")
conjecture_icon = VGroup(
get_monster().set_height(1),
TexMobject("\\leftrightarrow"),
TexMobject("j(\\tau)", tex_to_color_map={"\\tau": TEAL}),
)
conjecture_icon.arrange(RIGHT, buff=SMALL_BUFF)
conjecture_icon.set_height(1.5)
conjecture_icon.next_to(timeline.n2p(1979), UP, MED_LARGE_BUFF)
conjecture.next_to(conjecture_icon, UP, MED_LARGE_BUFF)
self.play(
FadeIn(timeline, DOWN),
FadeIn(triangle, DOWN),
FadeOut(mckay, UP),
Transform(
moonshine,
moonshine.copy().replace(conjecture[1], stretch=True).fade(1),
remover=True,
),
ReplacementTransform(
conjecture[1].copy().replace(moonshine, stretch=True).fade(1),
conjecture[1],
),
Write(conjecture[0::2]),
)
self.play(
FadeIn(conjecture_icon, lag_ratio=0.1),
triangle.move_to, timeline.n2p(1979), DOWN,
)
self.play(blink_monster(conjecture_icon[0]))
self.wait()
conjecture_group = VGroup(conjecture, conjecture_icon)
# Borcherds
borcherds = get_named_image("Richard Borcherds")
borcherds.next_to(timeline.n2p(1992), UP, MED_LARGE_BUFF)
self.play(
conjecture_group.scale, 0.5, {"about_edge": DOWN},
triangle.move_to, timeline.n2p(1992), DOWN,
FadeOut(conway, RIGHT),
FadeIn(borcherds, 2 * LEFT)
)
self.wait(2)
self.play(
triangle.move_to, timeline.n2p(1998), DOWN,
MaintainPositionRelativeTo(borcherds, triangle),
)
medal = ImageMobject("Fields Medal")
medal.set_height(1)
medal.move_to(borcherds.get_corner(UR), LEFT)
self.play(FadeIn(medal, DOWN))
self.wait()
class StringTheory(Scene):
def construct(self):
monster = get_monster()
monster.set_height(4)
arrow = TexMobject("\\leftrightarrow").scale(2)
n = 10
points = compass_directions(n)
freqs = np.random.random(n) + 0.3
string = VMobject()
string.set_stroke([BLUE, TEAL, BLUE, TEAL, BLUE, TEAL, BLUE], 4)
def update_string(string, points=points, freqs=freqs, sc=self):
for point, freq in zip(points, freqs):
point[:] = normalize(point) * (1 + 0.2 * np.sin(TAU * freq * sc.time))
string.set_points_smoothly([*points, points[0]])
string.add_updater(update_string)
arrow.next_to(string, LEFT, LARGE_BUFF)
monster.next_to(arrow, LEFT, LARGE_BUFF)
# self.camera.frame.move_to(arrow)
self.camera.frame.scale(0.5)
st_name = TextMobject("String theory")
st_name.scale(1.5)
st_name.next_to(string, UP, LARGE_BUFF)
sg_name = TextMobject("Sporadic groups")
sg_name.scale(1.5)
sg_name.next_to(monster, DOWN)
self.add(monster)
self.add(arrow)
self.add(string)
self.wait(2)
self.play(Write(sg_name))
self.play(Write(st_name))
for x in range(4):
self.play(blink_monster(monster))
self.wait(5)
class MonsterThanks(PatreonEndScreen):
pass