3b1b-manim/from_3b1b/active/monster.py
2020-08-16 09:46:49 -07:00

2702 lines
83 KiB
Python

from manimlib.imports import *
MONSTER_SIZE = 808017424794512875886459904961710757005754368000000000
def get_monster(height=3):
monster = SVGMobject("monster")
monster.set_fill(GREY_BROWN)
monster.set_stroke(GREY_BROWN, 0)
monster.set_height(height)
return monster
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_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):
if perm is None:
targets = list(mobs)
random.shuffle(targets)
else:
targets = [mobs[i] for i in perm]
return LaggedStart(
*[
ApplyMethod(m1.move_to, m2, path_arc=arc)
for m1, m2 in zip(mobs, targets)
],
lag_ratio=lag_factor / len(mobs),
)
# Scenes
class Thumbnail(Scene):
def construct(self):
monster = get_monster()
monster.set_height(7)
monster.to_edge(LEFT)
monster.set_gloss(0.2)
words = TextMobject("The\\\\Monster", alignment="")
words.scale(3)
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.8)
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
title = TextMobject("Group theory")
title.scale(2)
title.to_edge(UP)
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.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()
# 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 Z2
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("Z_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("Z_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.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(2949120)
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 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(4):
self.play(permutation_animation(dots, lag_factor=1))
# 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(self.teacher.bubble),
FadeOut(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)
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.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(YELLOW, 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.1)
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 = TextMobject("{:,}".format(MONSTER_SIZE))[0]
globals()['size_label'] = size_label
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)
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)
# 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))
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 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):
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.set_width(FRAME_WIDTH / 4)
s_rects.arrange(RIGHT, buff=MED_LARGE_BUFF)
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=SMALL_BUFF)
for rect in s_rects
])
self.play(LaggedStartMap(ShowCreation, lines))
self.play(FadeIn(s4, DOWN))
self.wait()
self.embed()