mirror of
https://github.com/3b1b/videos.git
synced 2025-08-05 16:48:47 +00:00
4480 lines
139 KiB
Python
4480 lines
139 KiB
Python
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("(Jordan–Hö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
|