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