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()