diff --git a/from_3b1b/active/monster.py b/from_3b1b/active/monster.py new file mode 100644 index 00000000..e32353ec --- /dev/null +++ b/from_3b1b/active/monster.py @@ -0,0 +1,2702 @@ +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()