from manim_imports_ext import * from _2022.newton_fractal import * MANDELBROT_COLORS = [ "#00065c", "#061e7e", "#0c37a0", "#205abc", "#4287d3", "#D9EDE4", "#F0F9E4", "#BA9F6A", "#573706", ] def get_c_dot_label(dot, get_c, font_size=24, direction=UP): c_label = VGroup( OldTex("c = ", font_size=font_size), DecimalNumber(get_c(), font_size=font_size, include_sign=True) ).arrange(RIGHT, buff=0.075) c_label[0].shift(0.02 * DOWN) c_label.set_color(YELLOW) c_label.set_stroke(BLACK, 5, background=True) c_label.add_updater(lambda m: m.next_to(dot, direction, SMALL_BUFF)) c_label.add_updater(lambda m: m[1].set_value(get_c())) return c_label def get_iteration_label(font_size=36): kw = { "tex_to_color_map": { "z_0": BLUE_C, "z_1": BLUE_D, "z_2": GREEN_D, "z_3": GREEN_E, "z_{n + 1}": GREEN_D, "z_n": BLUE, "\\longrightarrow": WHITE, }, "font_size": font_size, } iterations = OldTex( """ z_0 \\longrightarrow z_1 \\longrightarrow z_2 \\longrightarrow z_3 \\longrightarrow \\cdots """, **kw ) for part in iterations.get_parts_by_tex("\\longrightarrow"): f = OldTex("f", **kw) f.scale(0.5) f.next_to(part, UP, buff=0) part.add(f) rule = OldTex("z_{n + 1} &= f(z_n)", **kw) result = VGroup(rule, iterations) result.arrange(DOWN, buff=MED_LARGE_BUFF) return result class MandelbrotFractal(NewtonFractal): CONFIG = { "shader_folder": "mandelbrot_fractal", "data_dtype": [ ('point', np.float32, (3,)), ], "scale_factor": 1.0, "offset": ORIGIN, "colors": MANDELBROT_COLORS, "n_colors": 9, "parameter": complex(0, 0), "n_steps": 300, "mandelbrot": True, } def init_uniforms(self): Mobject.init_uniforms(self) self.uniforms["mandelbrot"] = float(self.mandelbrot) self.set_parameter(self.parameter) self.set_opacity(self.opacity) self.set_scale(self.scale_factor) self.set_colors(self.colors) self.set_offset(self.offset) self.set_n_steps(self.n_steps) def set_parameter(self, c): self.uniforms["parameter"] = np.array([c.real, c.imag]) return self def set_opacity(self, opacity): self.uniforms["opacity"] = opacity return self def set_colors(self, colors): for n in range(len(colors)): self.uniforms[f"color{n}"] = color_to_rgb(colors[n]) return self class JuliaFractal(MandelbrotFractal): CONFIG = { "n_steps": 100, "mandelbrot": False, } def set_c(self, c): self.set_parameter(c) # Scenes class MandelbrotSetPreview(Scene): def construct(self): plane = ComplexPlane( (-2, 1), (-2, 2), background_line_style={ "stroke_color": GREY_B, "stroke_opacity": 0.5, } ) plane.set_width(0.7 * FRAME_WIDTH) plane.axes.set_stroke(opacity=0.5) plane.add_coordinate_labels(font_size=18) mandelbrot = MandelbrotFractal(plane) mandelbrot.set_n_steps(0) self.add(mandelbrot, plane) self.play( mandelbrot.animate.set_n_steps(300), rate_func=lambda a: a**3, run_time=10, ) self.wait() class HolomorphicDynamics(Scene): def construct(self): self.show_goals() self.complex_functions() self.repeated_functions() self.example_fractals() def show_goals(self): background = FullScreenRectangle() self.add(background) title = self.title = Text("Holomorphic Dynamics", font_size=60) title.to_edge(UP) title.set_stroke(BLACK, 3, background=True) underline = Underline(title, buff=-0.05) underline.scale(1.2) underline.insert_n_curves(20) underline.set_stroke(YELLOW, [1, *5 * [3], 1]) self.add(title) self.add(underline, title) self.play(ShowCreation(underline)) self.wait() frames = Square().replicate(2) frames.set_height(5) frames.set_width(6, stretch=True) frames.set_stroke(WHITE, 2) frames.set_fill(BLACK, 1) frames.arrange(RIGHT, buff=1) frames.to_edge(DOWN) goals = VGroup( # OldTexText("Newton's fractal $\\leftrightarrow$ Mandelbrot"), # OldTexText("Tie up loose ends"), OldTexText("Goal 1: Other Mandelbrot occurrences"), OldTexText("Goal 2: Tie up loose ends"), ) goals.set_width(frames[0].get_width()) goals.set_fill(GREY_A) for goal, frame in zip(goals, frames): goal.next_to(frame, UP) goal.align_to(goals[0], UP) self.play( FadeIn(frames[0]), FadeIn(goals[0], 0.5 * UP), ) self.wait() self.play( FadeIn(frames[1]), FadeIn(goals[1], 0.5 * UP), ) self.wait() # Transition rect = SurroundingRectangle(title.get_part_by_text("Holomorphic")) rect.set_stroke(YELLOW, 2) self.play( ReplacementTransform(underline, rect), FadeOut(background), LaggedStartMap(FadeOut, VGroup( *goals, *frames, )) ) self.title_rect = rect def complex_functions(self): kw = { "tex_to_color_map": { "\\mathds{C}": BLUE, "z": YELLOW, } } f_def = VGroup( OldTex("f : \\mathds{C} \\rightarrow \\mathds{C}", **kw), OldTex("f'(z) \\text{ exists}", **kw) ) f_def.arrange(RIGHT, aligned_edge=DOWN, buff=LARGE_BUFF) f_def.next_to(self.title, DOWN, buff=MED_LARGE_BUFF) for part in f_def: self.play(Write(part, stroke_width=1)) self.wait() # Examples examples = VGroup( OldTex("f(z) = z^2 + 1", **kw), OldTex("f(z) = e^z", **kw), OldTex("f(z) = \\sin\\left(z\\right)", **kw), OldTex("\\vdots") ) examples.arrange(DOWN, buff=0.35, aligned_edge=LEFT) examples[-1].shift(0.25 * RIGHT) examples.set_width(2.5) examples.to_corner(UL) self.play(LaggedStartMap( FadeIn, examples, shift=0.25 * DOWN, run_time=3, lag_ratio=0.5 )) # Transition rect = self.title_rect new_rect = SurroundingRectangle(self.title.get_part_by_text("Dynamics")) new_rect.match_style(rect) self.play( FadeOut(examples, lag_ratio=0.1), FadeOut(f_def, lag_ratio=0.1), Transform(rect, new_rect) ) self.wait() def repeated_functions(self): words = OldTexText("For some function $f(z)$,") rule, iterations = get_iteration_label() group = VGroup(words, iterations, rule) group.arrange(DOWN, buff=MED_LARGE_BUFF) group.next_to(self.title, DOWN, LARGE_BUFF) group.to_edge(LEFT) self.play( FadeIn(words), FadeIn(iterations, lag_ratio=0.2, run_time=2) ) self.play(FadeIn(rule, 0.5 * DOWN)) self.wait() self.play(LaggedStartMap(FadeOut, VGroup( words, iterations, rule, self.title_rect ))) def example_fractals(self): newton = OldTex("z - {P(z) \\over P'(z)}") mandelbrot = OldTex("z^2 + c") exponential = OldTex("a^z") rhss = VGroup(newton, mandelbrot, exponential) f_eqs = VGroup() lhss = VGroup() for rhs in rhss: rhs.generate_target() lhs = OldTex("f(z) = ") lhs.next_to(rhs, LEFT) f_eqs.add(VGroup(lhs, rhs)) lhss.add(lhs) VGroup(exponential, mandelbrot).shift(0.05 * UP) f_eqs.arrange(RIGHT, buff=1.5) f_eqs.next_to(self.title, DOWN, MED_LARGE_BUFF) rects = Square().replicate(3) rects.arrange(RIGHT, buff=0.2 * rects.get_width()) rects.set_width(FRAME_WIDTH - 1) rects.center().to_edge(DOWN, buff=LARGE_BUFF) rects.set_stroke(WHITE, 1) arrows = VGroup() for rect, f_eq in zip(rects, f_eqs): arrow = Vector(0.5 * DOWN) arrow.next_to(rect, UP) arrows.add(arrow) f_eq.next_to(arrow, UP) f_eqs[0].match_y(f_eqs[1]) self.play( FadeOut(self.title, UP), LaggedStartMap(Write, rhss), LaggedStartMap(FadeIn, lhss), LaggedStartMap(FadeIn, rects), LaggedStartMap(ShowCreation, arrows), ) self.wait() class HolomorphicPreview(Scene): def construct(self): in_plane = ComplexPlane( (-2, 2), (-2, 2), height=5, width=5, ) in_plane.add_coordinate_labels(font_size=18) in_plane.to_corner(DL) out_plane = in_plane.deepcopy() out_plane.to_corner(DR) input_word = Text("Input") output_word = Text("Output") input_word.next_to(in_plane, UP) output_word.next_to(out_plane, UP) self.add(in_plane, out_plane, input_word, output_word) # Show tiny neighborhood tiny_plane = ComplexPlane( (-2, 2), (-2, 2), height=0.5, width=0.5, axis_config={ "stroke_width": 1.0, }, background_line_style={ "stroke_width": 1.0, }, faded_line_ratio=1, ) tiny_plane.move_to(in_plane.c2p(1, 1)) for plane in in_plane, out_plane: plane.generate_target() for mob in plane.target.family_members_with_points(): mob.set_opacity(mob.get_opacity() * 0.25) self.play( ShowCreation(tiny_plane), MoveToTarget(in_plane), ) self.wait() def f(z): w = z - complex(1, 1) return complex(-1, 0.5) + complex(-1, 1) * w + 0.2 * w**2 tiny_plane.prepare_for_nonlinear_transform() tiny_plane_image = tiny_plane.copy() tiny_plane_image.apply_function( lambda p: out_plane.n2p(f(in_plane.p2n(p))) ) arrow = Arrow( tiny_plane, tiny_plane_image, path_arc=-PI / 4, stroke_width=5, ) f_label = OldTex("f(z)") f_label.next_to(arrow, UP, SMALL_BUFF) words = Text("Looks roughly like\nscaling + rotating") words.set_width(2.5) words.move_to(VGroup(in_plane, out_plane)) self.play( ShowCreation(arrow), FadeIn(f_label, 0.1 * UP), ) self.play( TransformFromCopy(tiny_plane, tiny_plane_image), MoveToTarget(out_plane), FadeIn(words), run_time=2, ) self.wait(2) class AmbientRepetition(Scene): n_steps = 30 # c = -0.6436875 + -0.441j c = -0.5436875 + -0.641j show_labels = True def construct(self): plane = ComplexPlane((-2, 2), (-2, 2)) plane.set_height(6) plane.add_coordinate_labels(font_size=18) plane.to_corner(DR, buff=SMALL_BUFF) self.add(plane) font_size = 30 z0 = complex(0, 0) dot = Dot(color=BLUE) dot.move_to(plane.n2p(z0)) z_label = OldTex("z", font_size=font_size) z_label.set_stroke(BLACK, 5, background=True) z_label.next_to(dot, UP, SMALL_BUFF) if not self.show_labels: z_label.set_opacity(0) self.add(dot, z_label) self.add(TracedPath(dot.get_center, stroke_width=1)) func = self.func def get_new_point(): z = plane.p2n(dot.get_center()) return plane.n2p(func(z)) for n in range(self.n_steps): new_point = get_new_point() arrow = Arrow(dot.get_center(), new_point, buff=dot.get_height() / 2) dot_copy = dot.copy() dot_copy.move_to(new_point) dot_copy.set_color(YELLOW) fz_label = OldTex("f(z)", font_size=font_size) fz_label.set_stroke(BLACK, 8, background=True) fz_label.next_to(dot_copy, normalize(new_point - dot.get_center()), buff=0) if not self.show_labels: fz_label.set_opacity(0) self.add(dot, dot_copy, arrow, z_label) self.play( ShowCreation(arrow), TransformFromCopy(dot, dot_copy), FadeInFromPoint(fz_label, z_label.get_center()), ) self.wait(0.5) to_fade = VGroup( dot.copy(), z_label.copy(), dot_copy, arrow, fz_label, ) dot.move_to(dot_copy) z_label.next_to(dot, UP, SMALL_BUFF) self.remove(z_label) self.play( *map(FadeOut, to_fade), FadeIn(z_label), ) def func(self, z): return 2 * ((z / 2)**2 + self.c) class AmbientRepetitionLimitPoint(AmbientRepetition): n_steps = 30 c = complex() c = 0.234 + 0.222j show_labels = False class AmbientRepetitionInfty(AmbientRepetition): c = -0.7995 + 0.3503j n_steps = 12 class AmbientRepetitionChaos(AmbientRepetition): def func(self, c): return complex( random.random() * 4 - 2, random.random() * 4 - 2, ) class Recap(VideoWrapper): title = "Newton's fractal quick recap" animate_boundary = False screen_height = 6.3 class RepeatedNewtonPlain(RepeatedNewton): n_steps = 20 colors = ROOT_COLORS_DEEP class RationalFunctions(Scene): def construct(self): # Show function equation = OldTex( "f(z)", "=", "z - {P(z) \\over P'(z)}", "=", "z - {z^3 - 1 \\over 3z^2}", "=", "{2z^3 + 1 \\over 3z^2}" ) iter_brace = Brace(equation[2], UP) iter_text = iter_brace.get_text("What's being iterated") VGroup(iter_brace, iter_text).set_color(BLUE_D) example_brace = Brace(equation[4], DOWN) example_text = example_brace.get_text("For example") VGroup(example_brace, example_text).set_color(TEAL_D) self.play( FadeIn(equation[:3]), GrowFromCenter(iter_brace), FadeIn(iter_text, 0.5 * UP) ) self.wait() self.play( FadeIn(equation[3:5]), GrowFromCenter(example_brace), FadeIn(example_text, 0.5 * DOWN), ) self.wait() self.play( Write(equation[5]), LaggedStart(*( FadeTransform(equation[4][i].copy(), equation[6][j]) for i, j in zip( [1, 0, *range(2, 10)], [0, 0, *range(1, 9)], ) ), lag_ratio=0.02) ) self.add(equation[6]) self.play( LaggedStart(*( FadeTransform(equation[4][i].copy().set_opacity(0), equation[6][j].copy().set_opacity(0)) for i, j in zip( [0, 0, 0, 0], [0, 0, 0, 0], ) ), lag_ratio=0.02) ) self.add(equation) self.wait() # Name rational function box = SurroundingRectangle(equation[6], buff=SMALL_BUFF) box.set_stroke(YELLOW, 2) rational_name = OldTexText("``Rational function''") rational_name.next_to(box, UP, buff=1.5) arrow = Arrow(rational_name, box) self.play( Write(rational_name), ShowCreation(arrow), ShowCreation(box), ) self.wait() # Forget about the Newton's method origins self.play( equation[:2].animate.next_to(ORIGIN, LEFT, SMALL_BUFF), equation[2:6].animate.set_opacity(0.5).scale(0.5).to_corner(DR), VGroup(equation[6], box).animate.next_to(ORIGIN, RIGHT, SMALL_BUFF), MaintainPositionRelativeTo( VGroup(rational_name, arrow), box, ), FadeOut( VGroup(example_brace, example_text, iter_brace, iter_text), shift=2 * RIGHT + DOWN ) ) self.wait() # Other rational functions frame = self.camera.frame rhs = equation[6] functions = VGroup( OldTex("3z^4 + z^3 + 4 \\over z^5 + 5z + 9"), OldTex("2z^6 + 7z^4 + 1z \\over 8z^3 + 2z^2 + 8"), OldTex("(z^2 + 1)^2 \\over 4z(z^2 - 1)"), OldTex("az + b \\over cz + d"), OldTex("z^2 + az + b \\over z^2 + cz + d"), OldTex( "a_n z^n + \\cdots + a_0 \\over " "b_m z^m + \\cdots + b_0" ), OldTex("z^2 + c \\over 1"), ) for function in functions: function.replace(rhs, dim_to_match=1) function.move_to(rhs, LEFT) function.set_max_width(rhs.get_width() + 2) for n, function in enumerate(functions): self.play( FadeOut(rhs, lag_ratio=0.1), FadeIn(function, lag_ratio=0.1), box.animate.set_opacity(0), ) self.remove(box) self.wait() if n == 0: self.play( rational_name.animate.next_to(box, UP), arrow.animate.scale(0, about_edge=DOWN), ApplyMethod(frame.shift, 2 * DOWN, run_time=2), FadeOut(equation[2:6]), ) else: self.wait() rhs = function[0] self.play( FadeOut(box), FadeOut(rational_name), FadeOut(rhs[4:]), rhs[:4].animate.next_to(ORIGIN, RIGHT, SMALL_BUFF).shift(0.07 * UP), frame.animate.shift(DOWN), ) self.wait() class ShowFatouAndJulia(Scene): def construct(self): time_range = (1900, 2020) timeline = NumberLine( (*time_range, 1), tick_size=0.025, longer_tick_multiple=4, big_tick_numbers=range(*time_range, 10), ) timeline.stretch(0.25, 0) timeline.add_numbers( range(*time_range, 10), group_with_commas=False, ) timeline.set_y(-3) timeline.to_edge(LEFT, buff=0) line = Line(timeline.n2p(1917), timeline.n2p(1920)) line.scale(2) brace = Brace(line, UP) brace.stretch(1 / 2, 0) line.stretch(0.6, 0) line.insert_n_curves(20) line.set_stroke(BLUE, [1, *3 * [5], 1]) kw = {"label_direction": UP, "height": 3} figures = Group( get_figure("Pierre_Fatou", "Pierre Fatou", "1878-1929", **kw), get_figure("Gaston_Julia", "Gaston Julia", "1893-1978", **kw), ) figures.set_height(3) figures.arrange(RIGHT, buff=LARGE_BUFF) figures.next_to(brace, UP) self.add(timeline) self.add(figures) for figure in figures: self.remove(*figure[2:]) frame = self.camera.frame frame.save_state() frame.align_to(timeline, RIGHT) self.play(Restore(frame, run_time=3)) self.play(LaggedStart(*( Write(VGroup(*figure[2:])) for figure in figures ), lag_ratio=0.7)) self.play( GrowFromCenter(brace), ShowCreation(line), ) self.wait() # Names names = VGroup(*(figure[2][-5:] for figure in figures)) rects = VGroup(*(SurroundingRectangle(name, buff=0.05) for name in names)) rects.set_stroke(BLUE, 2) self.play(LaggedStartMap(ShowCreation, rects)) class IveSeenThis(TeacherStudentsScene): def construct(self): equation = OldTex("f(z) = z^2 + c") equation.to_edge(UP) self.add(equation) mandelbrot_outline = ImageMobject("Mandelbrot_boundary") mandelbrot_outline.set_height(3) mandelbrot_outline.move_to(self.students[2].get_corner(UR)) mandelbrot_outline.shift(1.2 * UP) self.student_says( "I've seen this one", target_mode="surprised", look_at=equation, added_anims=[ self.students[0].change("tease", equation), self.students[1].change("happy", equation), self.teacher.change("happy", equation), ] ) self.play(self.teacher.change("tease")) self.wait(2) self.add(mandelbrot_outline, *self.mobjects) self.play( FadeOut(self.background), RemovePiCreatureBubble( self.students[2], target_mode="raise_right_hand", look_at=mandelbrot_outline, ), FadeIn(mandelbrot_outline, 0.5 * UP, scale=2), self.students[0].change("pondering", mandelbrot_outline), self.students[1].change("thinking", mandelbrot_outline), self.teacher.change("happy", self.students[2].eyes), ) self.wait(4) self.embed() class MandelbrotIntro(Scene): n_iterations = 30 def construct(self): self.add_process_description() self.add_plane() self.show_iterations() self.add_mandelbrot_image() def add_process_description(self): kw = { "tex_to_color_map": { "{c}": YELLOW, } } terms = self.terms = VGroup( OldTex("z_{n + 1} = z_n^2 + {c}", **kw), OldTex("{c} \\text{ can be changed}", **kw), OldTex("z_0 = 0", **kw), ) terms.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) terms.to_corner(UL) equation = OldTex("f(z) = z^2 + c") equation.to_edge(UP) self.process_terms = terms self.add(equation) self.wait() self.play(FadeTransform(equation, terms[0])) def add_plane(self): plane = self.plane = ComplexPlane((-2, 1), (-2, 2)) plane.set_height(4) plane.set_height(1.5 * FRAME_HEIGHT) plane.next_to(2 * LEFT, RIGHT, buff=0) plane.add_coordinate_labels(font_size=24) self.add(plane) def show_iterations(self): plane = self.plane # c0 = complex(-0.2, 0.95) c0 = complex(-0.6, 0.4) c_dot = self.c_dot = Dot() c_dot.set_fill(YELLOW) c_dot.set_stroke(BLACK, 5, background=True) c_dot.move_to(plane.n2p(c0)) c_dot.add_updater(lambda m: m) # Null n_iter_tracker = ValueTracker(1) def get_n_iters(): return int(n_iter_tracker.get_value()) def get_c(): return plane.p2n(c_dot.get_center()) def update_lines(lines): z1 = 0 c = get_c() new_lines = [] for n in range(get_n_iters()): try: z2 = z1**2 + c new_lines.append(Line( plane.n2p(z1), plane.n2p(z2), stroke_color=GREY, stroke_width=2, )) new_lines.append(Dot( plane.n2p(z2), fill_color=YELLOW, fill_opacity=0.5, radius=0.05, )) z1 = z2 except Exception: pass lines.set_submobjects(new_lines) c_label = get_c_dot_label(c_dot, get_c) lines = VGroup() lines.set_stroke(background=True) lines.add_updater(update_lines) self.add(lines, c_dot, c_label) def increase_step(run_time=1.0): n_iter_tracker.increment_value(1) lines.update() lines.suspend_updating() self.add(*lines, c_dot, c_label) self.play( ShowCreation(lines[-2]), TransformFromCopy(lines[-3], lines[-1]), run_time=run_time ) self.add(lines, c_dot, c_label) lines.resume_updating() kw = { "tex_to_color_map": { "c": YELLOW, } } new_lines = VGroup( OldTex("z_1 = 0^2 + c = c", **kw), OldTex("z_2 = c^2 + c", **kw), OldTex("z_3 = (c^2 + c)^2 + c", **kw), OldTex("z_4 = ((c^2 + c)^2 + c)^2 + c", **kw), OldTex("\\vdots", **kw), ) new_lines.arrange(DOWN, aligned_edge=LEFT) new_lines[-2].scale(0.8, about_edge=LEFT) new_lines.next_to(self.process_terms[2], DOWN, aligned_edge=LEFT) new_lines[-1].match_x(new_lines[-2][0][2]) # Show c self.wait() self.play(Write(self.process_terms[1])) self.wait(10) # Show first step dot = Dot(plane.n2p(0)) self.play(FadeIn(self.process_terms[2], 0.5 * DOWN)) self.play(FadeIn(dot, scale=0.2, run_time=2)) self.play(FadeOut(dot)) self.play(FadeIn(new_lines[0], 0.5 * DOWN)) self.play(ShowCreationThenFadeOut( lines[0].copy().set_stroke(BLUE, 5) )) self.wait(3) # Show second step self.play(FadeIn(new_lines[1], 0.5 * DOWN)) increase_step() self.wait(10) # Show 3rd to nth steps self.play(FadeIn(new_lines[2], 0.5 * DOWN)) increase_step() self.play(FadeIn(new_lines[3], 0.5 * DOWN)) increase_step() self.wait(5) self.play(FadeIn(new_lines[4])) for n in range(self.n_iterations): increase_step(run_time=0.25) # Play around self.wait(15) def add_mandelbrot_image(self): mandelbrot_set = MandelbrotFractal(self.plane) self.add(mandelbrot_set, *self.mobjects) self.play( FadeIn(mandelbrot_set, run_time=2), # self.plane.animate.set_opacity(0.5) self.plane.animate.set_stroke(WHITE, opacity=0.25) ) # Listeners def on_mouse_motion(self, point, d_point): super().on_mouse_motion(point, d_point) if self.window.is_key_pressed(ord(" ")): self.c_dot.move_to(point) class BenSparksVideoWrapper(VideoWrapper): title = "Heavily inspired from Ben Sparks" class AckoNet(VideoWrapper): title = "Acko.net, How to Fold a Julia Fractal" class ParameterSpaceVsSeedSpace(Scene): def construct(self): boxes = Square().get_grid(2, 2, buff=0) boxes.set_stroke(WHITE, 2) boxes.set_height(6) boxes.set_width(10, stretch=True) boxes.to_corner(DR) self.add(boxes) f_labels = VGroup( OldTex("f(z) = z^2 + c"), OldTex("f(z) = z - {P(z) \\over P'(z)}"), ) kw = { "tex_to_color_map": { "function": YELLOW, "seed": BLUE_D, } } top_labels = VGroup( OldTexText("One seed\\\\Pixel $\\leftrightarrow$ function", **kw), OldTexText("One function\\\\Pixel $\\leftrightarrow$ seed", **kw), ) for f_label, box in zip(f_labels, boxes[::2]): f_label.set_max_width(3.25) f_label.next_to(box, LEFT) for top_label, box in zip(top_labels, boxes): top_label.next_to(box, UP, buff=0.2) for f_label in f_labels: self.play(FadeIn(f_label, 0.25 * LEFT)) self.wait() self.play(FadeIn(top_labels[0])) self.wait() self.play(TransformMatchingTex( top_labels[0].copy(), top_labels[1] )) self.wait() class MandelbrotStill(Scene): def construct(self): plane = ComplexPlane((-3, 2), (-1.3, 1.3)) plane.set_height(FRAME_HEIGHT) fractal = MandelbrotFractal(plane) self.add(fractal) class JuliaStill(Scene): def construct(self): plane = ComplexPlane((-4, 4), (-1.5, 1.5)) plane.set_height(FRAME_HEIGHT) fractal = JuliaFractal(plane) fractal.set_c(-0.03 + 0.74j) fractal.set_n_steps(100) self.add(fractal) class ClassicJuliaSetDemo(MandelbrotIntro): def construct(self): # Init planes kw = { "background_line_style": { "stroke_width": 0.5, } } planes = VGroup( ComplexPlane((-2, 1), (-1.6, 1.6), **kw), ComplexPlane((-2, 2), (-2, 2), **kw), ) for plane, corner in zip(planes, [DL, DR]): plane.set_stroke(WHITE, opacity=0.5) plane.set_height(6) plane.to_corner(corner, buff=MED_SMALL_BUFF) plane.to_edge(DOWN, SMALL_BUFF) planes[1].add_coordinate_labels(font_size=18) planes[0].add_coordinate_labels( (-1, 0, 1, 1j, -1j), font_size=18 ) # Init fractals mandelbrot = MandelbrotFractal(planes[0]) julia = JuliaFractal(planes[1]) fractals = Group(mandelbrot, julia) self.add(*fractals, *planes) # Add c_dot c_dot = self.c_dot = Dot(radius=0.05) c_dot.set_fill(YELLOW, 1) c_dot.move_to(planes[0].c2p(-0.5, 0.5)) c_dot.add_updater(lambda m: m) def get_c(): return planes[0].p2n(c_dot.get_center()) c_label = get_c_dot_label(c_dot, get_c, direction=UR) julia.add_updater(lambda m: m.set_c(get_c())) self.add(c_dot, c_label) # Add labels kw = { "tex_to_color_map": { "{z_0}": GREY_A, "{c}": YELLOW, "\\text{Pixel}": BLUE_D, }, } title = OldTexText("Iterate\\\\$z^2 + c$") title.move_to(Line(*planes)) title[0][-1].set_color(YELLOW) self.add(title) labels = VGroup( VGroup( OldTex("{c} \\leftrightarrow \\text{Pixel}", **kw), OldTex("z_0 = 0", **kw), ), VGroup( OldTex("{c} = \\text{const.}", **kw), OldTex("z_0 \\leftrightarrow \\text{Pixel}", **kw), ) ) for label, plane in zip(labels, planes): label.arrange(DOWN, aligned_edge=LEFT) label.next_to(plane, UP) space_labels = VGroup( OldTex("{c}\\text{-space}", **kw), OldTex("{z_0}\\text{-space}", **kw), ) for label, plane in zip(space_labels, planes): label.scale(0.5) label.move_to(plane, UL).shift(SMALL_BUFF * DR) self.add(space_labels) # Animations self.add(labels[0]) self.wait(2) self.play( TransformFromCopy( labels[0][0][0], labels[1][0][0], ), FadeIn(labels[1][0][1]), ) self.wait(2) self.play( TransformFromCopy( labels[0][1].get_part_by_tex("z_0"), labels[1][1].get_part_by_tex("z_0"), ), FadeTransform( labels[0][0][1:].copy(), labels[1][1][1:], ), ) class AskAboutGeneralTheory(TeacherStudentsScene): def construct(self): self.teacher_says( OldTexText("Think about constructing\\\\a general theory"), added_anims=[self.change_students( "pondering", "thinking", "pondering", look_at=UP, )] ) self.wait(3) self.teacher_says( OldTexText("What questions would\\\\you ask?"), target_mode="tease", ) self.play_student_changes("thinking", "pondering") self.wait(3) self.embed() class NewtonRuleLabel(Scene): def construct(self): rule = get_newton_rule() rule.scale(1.5) rule.set_stroke(BLACK, 5, background=True) rule.to_corner(UL) box = SurroundingRectangle(rule, buff=0.2) box.set_fill(BLACK, 0.9) box.set_stroke(WHITE, 1) VGroup(box, rule).to_corner(UL, buff=0) self.add(box, rule) class FixedPoints(Scene): def construct(self): # Set the stage iter_label = self.add_labels() rule, iterations = iter_label plane = self.add_plane() z_dot, z_label = self.add_z_dot() # Ask question question = OldTexText("When does $z$ stay fixed in place?") question.next_to(plane, RIGHT, MED_LARGE_BUFF, aligned_edge=UP) arrow = self.get_arrow_loop(z_dot) # f(z) = z t2c = {"z": BLUE} kw = { "tex_to_color_map": t2c, "isolate": ["=", "\\Rightarrow", "A(", "B(", ")"], } equation = OldTex("f(z) = z", **kw) equation.next_to(question, DOWN, MED_LARGE_BUFF) newton_example = OldTex( "z - {P(z) \\over P'(z)} = z", "\\quad \\Leftrightarrow \\quad ", "P(z) = 0", **kw, ) newton_example.next_to(equation, DOWN, buff=LARGE_BUFF) mandelbrot_example = OldTex( "\\text{Exercise 1a: Find the fixed points}\\\\", "\\text{of }", "f(z) = z^2 + c", alignment="\\centering", **kw ) mandelbrot_example[1:].match_x(mandelbrot_example[0]) mandelbrot_example.move_to(newton_example) fixed_point = Text("Fixed point") fixed_point.next_to(equation, DOWN, LARGE_BUFF) fixed_point.to_edge(RIGHT) fp_arrow = Arrow( fixed_point.get_left(), equation[1].get_bottom(), path_arc=-PI / 4, ) fp_group = VGroup(fixed_point, fp_arrow) fp_group.set_color(YELLOW) self.play(FadeIn(equation, DOWN)) self.wait(2) self.play( FadeIn(fixed_point), ShowCreation(fp_arrow), ) self.wait() self.play( FadeIn(newton_example, shift=0.5 * DOWN), FadeOut(fp_group), ) self.wait(2) self.play( FadeOut(newton_example, 0.5 * DOWN), FadeIn(mandelbrot_example, 0.5 * DOWN), ) self.wait(2) # Rational function question_group = VGroup(question, equation) question_group.generate_target() iterations.generate_target() VGroup(question_group.target, iterations.target).to_edge(UP) rational_parts = VGroup( OldTex("{A(z) \\over B(z)} = z", **kw), OldTex("A(z) = z \\cdot B(z)", **kw), OldTex("A(z) - z \\cdot B(z) = 0", **kw), ) rational_parts.arrange(DOWN, buff=MED_LARGE_BUFF) for part, tex in zip(rational_parts[1:], ("=", "-")): curr_x = part.get_part_by_tex(tex).get_x() target_x = rational_parts[0].get_part_by_tex("=").get_x() part.shift((target_x - curr_x) * RIGHT) rational_parts.next_to(question_group.target, DOWN, LARGE_BUFF) self.play( FadeOut(rule, UP), MoveToTarget(question_group), MoveToTarget(iterations), FadeOut(mandelbrot_example), FadeIn(rational_parts[0]) ) self.wait() for p1, p2 in zip(rational_parts, rational_parts[1:]): self.play( TransformMatchingTex( p1.copy(), p2, path_arc=PI / 2, run_time=2, fade_transform_mismatches=True, ) ) self.wait(2) rect = SurroundingRectangle(rational_parts[-1]) solution_words = Text("Must have\nsolutions!", font_size=36) solution_words.set_color(YELLOW) solution_words.next_to(rect, RIGHT) solution_words.shift_onto_screen() self.play( ShowCreation(rect), Write(solution_words, run_time=1), ) self.wait() example_roots = [ -1.5 + 0.5j, -1.5 - 0.5j, -1.0 + 1.2j, -1.0 - 1.2j, 1.0 + 1.0j, 1.0 - 1.0j, 0.5, 1.7, ] glow_dots = VGroup(*( glow_dot(plane.n2p(root)) for root in example_roots )) self.play(LaggedStartMap( FadeIn, glow_dots, scale=0.5, lag_ratio=0.2, )) self.wait() # Ask about stability def get_arrows(point, inward=False): arrows = VGroup(*( Arrow(ORIGIN, vect, buff=0.3) for vect in compass_directions(8) )) arrows.set_height(1) arrows.move_to(point) if inward: for arrow in arrows: arrow.rotate(PI) return arrows outward_arrows = get_arrows(glow_dots[2]) inward_arrows = get_arrows(glow_dots[4], inward=True) arrow_groups = VGroup(inward_arrows, outward_arrows) stability_words = VGroup( Text("Attracting"), Text("Repelling"), ) for words, arrows in zip(stability_words, arrow_groups): words.scale(0.7) words.next_to(arrows, UP, SMALL_BUFF) words.set_color(GREY_A) words.set_stroke(BLACK, 5, background=True) stability_question = Text( "When are fixed points stable?" ) stability_question.move_to(question) stable_underline = Underline( stability_question.get_part_by_text("stable") ) stable_underline.insert_n_curves(20) stable_underline.scale(1.2) stable_underline.set_stroke(MAROON_B, [1, *4 * [4], 1]) self.play( FadeOut(question, RIGHT), FadeIn(stability_question, RIGHT), FadeOut(arrow), FadeOut(z_dot), FadeOut(z_label), ) self.play(ShowCreation(stable_underline)) self.wait() for words, arrows in zip(stability_words, arrow_groups): self.play( FadeIn(words), ShowCreation(arrows, lag_ratio=0.2, run_time=3) ) self.wait() # Show derivative condition morty = Mortimer(height=2) morty.to_corner(DR) deriv_ineq = OldTex("|f'(z)| < 1", **kw) deriv_ineq.next_to(equation, DOWN, MED_LARGE_BUFF) equation.generate_target() group = VGroup(equation.target, deriv_ineq) group.arrange(RIGHT, buff=LARGE_BUFF) group.move_to(equation) attracting_condition = deriv_ineq.copy() repelling_condition = OldTex("|f'(z)| > 1", **kw) conditions = VGroup(attracting_condition, repelling_condition) for condition, words in zip(conditions, stability_words): condition.scale(0.7) condition.set_stroke(BLACK, 5, background=True) condition.move_to(words, DOWN) if conditions is conditions[0]: condition.shift(SMALL_BUFF * UP) words.generate_target() words.target.next_to(condition, UP, buff=MED_SMALL_BUFF) self.play( LaggedStartMap(FadeOut, VGroup( *rational_parts, rect, solution_words, )), VFadeIn(morty), morty.change("tease"), ) self.play(PiCreatureSays( morty, "Use derivatives!", target_mode="hooray", bubble_config={ "height": 2, "width": 4, } )) self.play(Blink(morty)) self.wait() self.play(RemovePiCreatureBubble(morty, target_mode="happy")) for condition, words in zip(conditions, stability_words): self.play( Write(condition), MoveToTarget(words), ) self.wait() self.play( FadeInFromPoint(deriv_ineq, morty.get_corner(UL)), MoveToTarget(equation), morty.change("raise_right_hand") ) self.play(Blink(morty)) # Newton derivative examples newton_case = VGroup( OldTex("f(z) = z - {P(z) \\over P'(z)}", **kw), OldTex("f'(z) = {P(z)P''(z) \\over P'(z)^2}", **kw), OldTex("P(z) = 0 \\quad \\Rightarrow \\quad f'(z) = 0", **kw), ) newton_case.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF) newton_case.scale(0.8) newton_case.next_to(equation, DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) alt_line1 = OldTex("f'(z) = 1 - {P'(z)P'(z) - P(z)P''(z) \\over P'(z)^2}", **kw) alt_line1.match_height(newton_case[1]) alt_line1.move_to(newton_case[1], LEFT) self.play( TransformMatchingTex(equation.copy()[:3], newton_case[0]), morty.change("pondering", newton_case[0]), ) self.wait() self.play( TransformMatchingTex( deriv_ineq[:-1].copy(), alt_line1[:4], ) ) self.wait() self.play( FadeIn(alt_line1[4:], lag_ratio=0.1, run_time=1.5), morty.animate.scale(0.8, about_edge=DR).change("sassy", alt_line1), ) self.play(Blink(morty)) self.wait() self.play( TransformMatchingTex(alt_line1[4:], newton_case[1][4:]), morty.change("tease", alt_line1), ) self.remove(alt_line1) self.add(newton_case[1]) self.wait() self.play(Blink(morty)) self.wait() self.play( morty.change("pondering", newton_case[2]), FadeIn(newton_case[2]) ) self.play(Blink(morty)) self.wait() # Show super-attraction super_arrows = VGroup(*( get_arrows(dot, inward=True) for dot in glow_dots )) attraction_anims = [] for cluster in super_arrows: for arrow in cluster: new_arrow = arrow.copy() new_arrow.scale(1.5) new_arrow.set_stroke(YELLOW, 8) attraction_anims.append( ShowCreationThenFadeOut(new_arrow) ) rect = SurroundingRectangle(newton_case[2][6:]) super_words = OldTexText("``Superattracting''", font_size=36) super_words.set_color(YELLOW) super_words.next_to(rect, DOWN, SMALL_BUFF) self.play( morty.change("thinking"), ShowCreation(rect), FadeIn(super_words) ) self.play( LaggedStartMap(FadeOut, VGroup( *conditions, *stability_words, outward_arrows, inward_arrows, )), LaggedStartMap(FadeIn, super_arrows, scale=0.5) ) self.play( LaggedStart(*attraction_anims, lag_ratio=0.02), run_time=3 ) self.wait() self.play( LaggedStartMap(FadeOut, VGroup( *super_arrows, *newton_case, rect, super_words, morty, glow_dots, )) ) # Mandelbrot exercise part1 = mandelbrot_example part1.set_height(0.9) part2 = OldTexText( "Exercise 1b: Determine when at least\\\\" "one fixed point is attracting.", ) part3 = OldTexText( "Exercise 1c$^{**}$: Show that the set of values\\\\", "$c$ satisfying this form a cardioid.", tex_to_color_map={"$c$": YELLOW} ) parts = VGroup(part1, part2, part3) for part in part2, part3: part.scale(part1[0][0].get_height() / part[0][0].get_height()) parts.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) parts.next_to( VGroup(equation, deriv_ineq), DOWN, buff=LARGE_BUFF, ) mandelbrot = MandelbrotFractal(plane) R = 0.25 cardioid = ParametricCurve( lambda t: plane.c2p( 2 * R * math.cos(t) - R * math.cos(2 * t), 2 * R * math.sin(t) - R * math.sin(2 * t), ), t_range=(0, TAU) ) cardioid.set_stroke(YELLOW, 4) self.add(mandelbrot, plane) plane.generate_target(use_deepcopy=True) plane.target.set_stroke(WHITE, opacity=0.5) self.play( MoveToTarget(plane), FadeIn(mandelbrot), FadeIn(part1), ) self.wait() self.play(Write(part2)) self.wait() print(self.num_plays) self.play(Write(part3)) self.play(ShowCreation(cardioid, run_time=4, rate_func=linear)) self.play( cardioid.animate.set_fill(YELLOW, 0.25), run_time=2 ) self.wait() def add_labels(self): iter_label = get_iteration_label(48) iter_label.to_edge(UP) self.add(iter_label) return iter_label def add_plane(self): plane = self.plane = ComplexPlane( (-2, 2), (-2, 2), background_line_style={ "stroke_width": 1, } ) plane.set_height(5) plane.to_corner(DL) plane.add_coordinate_labels(font_size=16) self.add(plane) return plane def add_z_dot(self, z=1 + 1j, z_tex="z"): z_dot = Dot(radius=0.05, color=BLUE) z_dot.move_to(self.plane.n2p(z)) z_label = OldTex(z_tex, font_size=30) z_label.next_to(z_dot, UL, buff=0) self.add(z_dot, z_label) return z_dot, z_label def get_arrow_loop(self, dot): arrow = Line( dot.get_bottom(), dot.get_right(), path_arc=330 * DEGREES, buff=0.05, ) arrow.add_tip(width=0.15, length=0.15) arrow.set_color(GREY_A) return arrow class UseNewton(TeacherStudentsScene): def construct(self): self.teacher_says( OldTexText( "You could solve\\\\ $A(z) - z\\cdot B(z) = 0$ \\\\", "using Newton's method" ), bubble_config={ "width": 4, "height": 3, }, target_mode="hooray", added_anims=[ self.change_students("confused", "erm", "maybe") ] ) self.wait(2) self.student_says( Text("Too meta..."), target_mode="sassy", index=2, ) self.wait(3) class DescribeDerivative(Scene): zoom_in_frame = False z = 1 z_str = "{1}" fz_str = "1" fpz_str = "2" def construct(self): # Add plane plane = ComplexPlane((-4, 4), (-2, 2)) plane.set_height(FRAME_HEIGHT) plane.add_coordinate_labels(font_size=18) self.add(plane) # Add function labels z_str = self.z_str fz_str = self.fz_str fpz_str = self.fpz_str kw = { "tex_to_color_map": { "z": GREY_A, z_str: YELLOW, }, "isolate": ["f", "(", ")", "=", z_str, fz_str, fpz_str], } f_label = OldTex("f(z) = z^2", **kw) df_label = OldTex("f'(z) = 2z", **kw) labels = VGroup(f_label, df_label) labels.arrange(DOWN) labels.to_corner(UL) labels.set_stroke(BLACK, 5, background=True) corner_rect = SurroundingRectangle(labels, buff=0.25) corner_rect.set_stroke(WHITE, 2) corner_rect.set_fill(BLACK, 0.9) corner_group = VGroup(corner_rect, *labels) corner_group.to_corner(UL, buff=0) df_question = Text("Derivative?") df_question.set_stroke(BLACK, 5, background=True) df_arrow = Vector(LEFT) df_arrow.next_to(f_label, RIGHT, SMALL_BUFF) df_question.next_to(df_arrow, RIGHT, SMALL_BUFF) self.add(corner_rect, f_label) self.play( FadeIn(df_question, 0.2 * RIGHT), ShowCreation(df_arrow) ) self.wait() self.play( FadeIn(df_label, shift=0.5 * DOWN), FadeOut(df_question), Uncreate(df_arrow), ) # Add dots density = 10 dot_radius = 0.025 dots = DotCloud([ plane.c2p(x, y) for x in np.arange(-3.7, 3.7, 1.0 / density) for y in np.arange(-2.0, 2.1, 1.0 / density) ]) dots.set_radius(dot_radius) dots.set_color(GREY_B) dots.set_gloss(0.2) dots.set_opacity(0.5) dots.add_updater(lambda m: m) epsilon = 5e-3 tiny_dots = DotCloud([ plane.n2p(self.z + epsilon * complex(x, y)) for x in np.arange(-20, 20) for y in np.arange(-10, 10) ]) tiny_dots.set_radius(dot_radius * 15 * epsilon) tiny_dots.set_opacity(0.75) tiny_dots.set_color_by_gradient(YELLOW, BLUE) tiny_dots.set_gloss(0.2) self.add(dots, corner_group) self.play(ShowCreation(dots), run_time=2) self.wait() # Show function evaluation ex_labels = VGroup( OldTex(f"f({z_str}) = {fz_str}", **kw), OldTex(f"f'({z_str}) = {fpz_str}", **kw), ) for ex_label, gen_label in zip(ex_labels, labels): ex_label.next_to(gen_label, RIGHT, MED_LARGE_BUFF) ex_labels[0].align_to(ex_labels[1], LEFT) corner_rect.generate_target() corner_rect.target.set_width( VGroup(ex_labels, labels).get_width() + 0.5, about_edge=LEFT, stretch=True, ) self.add(*corner_group) self.play( MoveToTarget(corner_rect), FadeIn(ex_labels), ) self.wait() # Apply function fade_anims = [] if self.zoom_in_frame: rect = self.camera.frame plane.generate_target() fade_anims = [ ApplyMethod(dots.set_opacity, 0, rate_func=squish_rate_func(smooth, 0.5, 1.0)), plane.animate.set_stroke(width=0.25), ] else: rect = ScreenRectangle() rect.set_height(FRAME_HEIGHT) rect.set_stroke(WHITE, 2) self.play( rect.animate.replace(tiny_dots, 1).move_to(plane.n2p(self.z)), *fade_anims, FadeIn(tiny_dots), run_time=3, ) self.wait() # def func(p): # z = plane.p2n(p) # return plane.n2p(z**2) def homotopy(x, y, z, t): z = plane.p2n([x, y, z]) return plane.n2p(z**(1 + t)) rc = rect.get_center() path = ParametricCurve(lambda t: homotopy(*rc, t)) self.play( Homotopy(homotopy, dots), Homotopy(homotopy, tiny_dots), MoveAlongPath(rect, path), # dots.animate.apply_function(func), # tiny_dots.animate.apply_function(func), # rect.animate.move_to(func(rect.get_center())), run_time=5, ) self.wait() class DescribeDerivativeInnerFrame(DescribeDerivative): zoom_in_frame = True class DescribeDerivativeIExample(DescribeDerivative): z = 1j z_str = "{i}" fz_str = "-1" fpz_str = "2i" class DescribeDerivativeIExampleInnerFrame(DescribeDerivativeIExample): zoom_in_frame = True class LooksLikeTwoMult(Scene): const = "2" def construct(self): tex = OldTexText(f"Looks like $z \\rightarrow {self.const}\\cdot z$") tex.set_stroke(BLACK, 5, background=True) self.play(FadeIn(tex, lag_ratio=0.1)) self.wait() class LooksLikeTwoiMult(LooksLikeTwoMult): const = "2i" class Cycles(FixedPoints): def construct(self): # Set the stage iter_label = self.add_labels() rule, iterations = iter_label self.remove(rule) iterations.to_edge(UP) plane = self.add_plane() z0_dot, z0_label = self.add_z_dot(complex(-1.1, 0.6), "z_0") z1_dot, z1_label = self.add_z_dot(complex(0.2, -0.5), "z_1") z1_label.next_to(z1_dot, UR, SMALL_BUFF) z_dots = VGroup(z0_dot, z1_dot) z_labels = VGroup(z0_label, z1_label) # Ask question question = OldTexText("When does $z$ cycle?") question.next_to(plane, UP, MED_SMALL_BUFF) kw = {"path_arc": PI / 3, "buff": 0.1} arrows = VGroup( Arrow(z0_dot, z1_dot, **kw), Arrow(z1_dot, z0_dot, **kw), ) arrows.set_stroke(opacity=0.75) z_dot = z0_dot.copy() z_dot.set_color(YELLOW) self.add(question) self.play( ShowCreation(arrows[0]), TransformFromCopy(z0_dot, z1_dot, path_arc=-PI / 3), TransformFromCopy(z0_label, z1_label, path_arc=-PI / 3), ) self.wait() self.play( TransformFromCopy(z1_dot, z_dot, path_arc=-PI / 3), ShowCreation(arrows[1]), ) self.wait() for n in range(1, 5): self.play(z_dot.move_to, z_dots[n % 2], path_arc=PI / 3) self.wait() # Show formula kw = { "tex_to_color_map": {"z": BLUE}, "isolate": ["f"], } f2_equation = OldTex("f(f(z)) = z", **kw) f2_equation.next_to(plane, RIGHT, MED_LARGE_BUFF, aligned_edge=UP) julia_fractal = JuliaFractal(plane) julia_fractal.set_c(-0.18 + 0.77j) z2c = OldTex("f(z) = z^2 + c", **kw) z2c.next_to(f2_equation, RIGHT, LARGE_BUFF) self.play(FadeIn(f2_equation, 0.25 * DOWN)) self.wait() self.add(julia_fractal, plane, z_dots, z_dot, z_labels, arrows, z_dot) julia_fractal.set_opacity(0) self.play( julia_fractal.animate.set_opacity(0.75), Write(z2c), ) self.wait() # Example with z^2 + c julia_f2_eqs = VGroup( OldTex("(z^2 + c)^2 + c = z", **kw), OldTex("z^4 + 2cz^2 -z + c^2 + c = 0", **kw), ) julia_f2_eqs.arrange(DOWN, buff=0.7, aligned_edge=LEFT) julia_f2_eqs.next_to(f2_equation, DOWN, buff=1.0, aligned_edge=LEFT) eq_arrows = VGroup( Arrow(f2_equation.get_bottom(), julia_f2_eqs.get_top()), Arrow(z2c.get_bottom(), julia_f2_eqs.get_top()), ) self.play( *map(ShowCreation, eq_arrows), FadeIn(julia_f2_eqs[0], 0.5 * DOWN) ) self.wait() self.play( TransformMatchingShapes( julia_f2_eqs[0].copy(), julia_f2_eqs[1] ) ) self.wait() # Add fixed_points fixed_dots = VGroup( Dot(plane.c2p(0.8, -0.5), color=GREY_A), Dot(plane.c2p(-0.9, -0.6), color=GREY_A), ) arrow_loops = VGroup(*( self.get_arrow_loop(dot) for dot in fixed_dots )) for dot, loop in zip(fixed_dots, arrow_loops): loop.set_color(GREY_B) self.play( FadeIn(dot, scale=0.3), ShowCreation(loop), ) self.wait() self.play( LaggedStartMap(FadeOut, VGroup( eq_arrows, *julia_f2_eqs, *fixed_dots, *arrow_loops, )) ) # N cycles fn_eq = OldTex( "f(f(\\cdots f(z) \\cdots)) = z", **kw ) fn_eq.move_to(f2_equation, LEFT) fn_eq.shift(SMALL_BUFF * DOWN) brace = Brace( fn_eq[:fn_eq.index_of_part_by_tex("z")], DOWN ) brace_tex = brace.get_tex("n \\text{ times}", buff=SMALL_BUFF) brace_tex.scale(0.7, about_edge=UP) for z in [-0.2 + 0.6j, 1.1 - 0.6j, 0.4 + 0.2j]: dot = z0_dot.copy() dot.set_fill(BLUE_D) dot.move_to(plane.n2p(z)) z_dots.add(dot) n_arrows = VGroup() for d1, d2 in adjacent_pairs(z_dots): arrow = Arrow(d1, d2, buff=0.1, path_arc=PI / 8) arrow.set_stroke(WHITE, opacity=0.7) n_arrows.add(arrow) dot_anims = [] n = len(z_dots) for k in range(1, n + 1): dot_anims.append( ApplyMethod(z_dot.move_to, z_dots[k % n], path_arc=PI / 8) ) self.play( TransformMatchingShapes(f2_equation, fn_eq), FadeOut(z2c), ReplacementTransform(arrows, n_arrows), *map(GrowFromCenter, z_dots[2:]) ) self.play( GrowFromCenter(brace), FadeIn(brace_tex, SMALL_BUFF * DOWN), Succession(*dot_anims, run_time=3), ) self.wait() # Ask about how many solutions morty = Mortimer(height=2) morty.to_corner(DR) z2c.next_to(fn_eq, UP, buff=MED_LARGE_BUFF) self.play( PiCreatureSays( morty, "How many solutions?", bubble_config={"height": 2, "width": 4} ), ) self.play(Blink(morty)) self.wait() self.play( FadeIn(z2c), RemovePiCreatureBubble( morty, target_mode="pondering", look_at=z2c, ) ) self.wait() # 1,000,000-cycles million = Integer(1e6, font_size=36) million.next_to(brace, DOWN) million.set_value(0) mega_poly = OldTex( "z^{2^{1{,}000{,}000}} +", "\\cdots \\text{(nightmare)} \\cdots", "= 0", **kw ) mega_poly.next_to(million, DOWN, buff=0.75) mega_poly.align_to(fn_eq, LEFT) expr = self.show_composition(million, morty, **kw) self.play( ChangeDecimalToValue(million, 1e6, run_time=2), VFadeIn(million), FadeOut(brace_tex), morty.change("raise_right_hand", fn_eq) ) self.play(Blink(morty)) self.play( FadeOut(expr), Write(mega_poly), morty.animate.set_height(1.8, about_edge=DR).change("horrified", mega_poly), ) self.play(morty.animate.look_at(mega_poly.get_right())) self.wait() # Show "million" dots N = 5000 points = np.random.random((N, 3)) points[:, 2] = 0 dots = DotCloud(points, radius=0) dots.replace(plane) dots.set_radius(0.01) dots.set_color(GREY_B) dots.set_opacity(1) dots.add_updater(lambda m: m) self.play( FadeOut(z_labels), FadeOut(z_dots), FadeOut(n_arrows), morty.change("erm", dots), ShowCreation(dots, run_time=5), ) self.wait() self.play( FadeOut(dots), FadeOut(mega_poly), FadeOut(morty), FadeOut(z2c), ) # Rational map rational = OldTex("f(z) = {A(z) \\over B(z)}", **kw) rational.next_to(million, DOWN, LARGE_BUFF) rational.align_to(fn_eq, LEFT) self.play(FadeIn(rational)) for arrow, dot in zip(n_arrows, [*z_dots[1:], z_dots[0]]): self.play( ShowCreation(arrow), z_dot.animate.move_to(dot), path_arc=PI / 6, ) self.add(dot, z_dot) # Ask about attracting cycle new_question = Text("When is a cycle attracting?") new_question.get_part_by_text("attracting").set_color(YELLOW) new_question.next_to(question, RIGHT, LARGE_BUFF) self.play( Write(new_question, run_time=1), fn_eq.animate.shift(0.5 * DOWN), FadeOut(brace), FadeOut(million), ) circle = Circle(radius=0.5) circle.set_stroke(YELLOW, 1, 1) circle.set_fill(YELLOW, 0.25) h_tracker = ValueTracker(1.0) circle.add_updater(lambda m: m.set_height(h_tracker.get_value())) circle.add_updater(lambda m: m.move_to(z_dot)) multipliers = [0.9, 0.9, 1.2, 0.5, 1.1] self.add(circle, z_dot) self.play(GrowFromCenter(circle)) for n in range(3): for mult, dot in zip(multipliers, [*z_dots[1:], z_dots[0]]): self.play( ApplyMethod(z_dot.move_to, dot, path_arc=PI / 6), h_tracker.animate.set_value(h_tracker.get_value() * mult), ) # Possibly add on a bit for Fatou's theorem? theorem = OldTexText( "Theorem (Fatou 1919): If $f(z)$ has an\\\\", "attracting cycle, then at least one solution\\\\", "to $f'(z) = 0$ will fall into it.", font_size=36, ) theorem.arrange(DOWN, buff=0.15, aligned_edge=LEFT) theorem.next_to(fn_eq, DOWN, LARGE_BUFF, aligned_edge=LEFT) self.play( rational.animate.set_height(0.8).next_to(fn_eq, RIGHT, LARGE_BUFF), FadeIn(theorem), ) def show_composition(self, ref_mob, morty, **kwargs): tex = "z^2 + c" polys = VGroup(OldTex(tex, **kwargs)) for n in range(20): new_tex_parts = ["\\left(", tex, "\\right)^2 + c"] polys.add(OldTex(*new_tex_parts)) if n < 3: tex = "".join(new_tex_parts) for poly in polys: poly.set_max_width(5) poly.next_to(ref_mob, DOWN, LARGE_BUFF, aligned_edge=LEFT) degree = VGroup(Text("Degree: "), Integer(2)) degree.arrange(RIGHT) degree.next_to(polys, DOWN, aligned_edge=LEFT) curr_poly = polys[0] self.play( FadeIn(curr_poly), FadeIn(degree), morty.change("tease", curr_poly), ) for n, poly in enumerate(polys[1:]): anims = [] if n == 4: anims.append(morty.change("erm")) self.play( curr_poly.animate.replace(poly[1]), FadeIn(poly[::2]), UpdateFromAlphaFunc( degree, lambda m, a: m[1].set_value( (2**(n + 1) if a < 0.5 else 2**(n + 2)) ) ), *anims, run_time=(1 if n < 5 else 0.25) ) poly.replace_submobject(1, curr_poly) self.add(poly) curr_poly = poly self.wait() self.play(FadeOut(degree)) return poly class TwoToMillionPoints(Scene): c = -0.18 + 0.77j plane_height = 7 def construct(self): plane, julia_fractal = self.get_plane_and_fractal() words = OldTexText("$\\approx 2^{1{,}000{,}000}$ solutions!") words.set_stroke(BLACK, 8, background=True) words.move_to(plane, UL) words.shift(MED_SMALL_BUFF * DR) points = self.get_julia_set_points(plane, 100000, 1000) dots = DotCloud(points) dots.set_color(YELLOW) dots.set_opacity(1) dots.set_radius(0.025) dots.add_updater(lambda m: m) dots.make_3d() self.add(julia_fractal, plane, words) self.play(ShowCreation(dots, run_time=10)) def get_plane_and_fractal(self): plane = ComplexPlane((-2, 2), (-2, 2)) plane.set_height(self.plane_height) fractal = JuliaFractal(plane) fractal.set_c(self.c) return plane, fractal def get_julia_set_points(self, plane, n_points, n_steps): values = np.array([ complex(math.cos(x), math.sin(x)) for x in np.linspace(0, TAU, n_points) ]) c = self.c for n in range(n_steps): units = -1 + 2 * np.random.randint(0, 2, len(values)) values[:] = (units * np.sqrt(values[:])) - c values += c return np.array(list(map(plane.n2p, values))) class CyclesHaveSolutions(Scene): def construct(self): text = VGroup( OldTex( "f^n(z) = z \\text{ has solutions}", tex_to_color_map={"z": BLUE}, ), OldTex("\\sim D^n \\text{ of them...}"), ) text.arrange(DOWN) for part in text: self.play(FadeIn(part)) self.wait() class MandelbrotFunctions(Scene): def construct(self): kw = {"tex_to_color_map": {"c": YELLOW}} group = VGroup( OldTex("f(z) = z^2 + c", **kw), OldTex("f'(z) = 2z"), ) group.arrange(DOWN, aligned_edge=LEFT) self.add(group) class AmbientNewtonRepetition(RepeatedNewton): coefs = [-4, 0, -3, 0, 1] show_fractal_background = True show_coloring = False n_steps = 20 dot_density = 10.0 points_scalar = 2.0 dots_config = { "radius": 0.025, "color": GREY_A, "gloss": 0.4, "shadow": 0.1, "opacity": 0.5, } class AmbientNewtonBoundary(AmbientNewtonRepetition): def construct(self): self.add_plane() fractal = self.get_fractal() self.remove(self.plane) fractal.set_julia_highlight(1e-4) fractal.set_colors(5 * [WHITE]) self.play(GrowFromPoint( fractal, fractal.get_corner(UL), run_time=5 )) self.wait() class CyclicAttractor(RepeatedNewton): coefs = [2, -2, 0, 1] n_steps = 20 show_coloring = False cluster_radius = 0.5 def add_plane(self): super().add_plane() self.plane.axes.set_stroke(GREY_B, 1) self.plane.scale(1.7) def add_labels(self): super().add_labels() eq = self.corner_group[1] self.play(FlashAround(eq, run_time=3)) def get_original_points(self): return [ (r * np.cos(theta), r * np.sin(theta), 0) for r in np.linspace(0, self.cluster_radius, 10) for theta in np.linspace(0, TAU, int(50 * r)) + TAU * np.random.random() ] class CyclicAttractorSmallRadius(CyclicAttractor): cluster_radius = 0.25 colors = ROOT_COLORS_DEEP[0::2] def construct(self): super().construct() fractal = NewtonFractal( self.plane, coefs=self.coefs, colors=self.colors, black_for_cycles=True, ) dots = VGroup(*( Dot(rd.get_center()) for rd in self.root_dots )) dots.set_stroke(BLACK, 3) dots.set_fill(opacity=0) self.add(fractal, *self.mobjects, dots) self.play( FadeIn(fractal), self.plane.animate.fade(0.5) ) self.wait() class CyclicExercise(Scene): def construct(self): words = OldTexText( "Exercise 2: If $f(z) = z - {z^3 - 2z + 2 \\over 3z^2 - 2}$,\\\\", "and $g(z) = f(f(z))$, confirm that $|g'(0)| < 1$." ) words[1].shift(SMALL_BUFF * DOWN) box = SurroundingRectangle(words, buff=0.45) box.set_stroke(WHITE, 2) box.set_fill(BLACK, 1) group = VGroup(box, words) group.to_edge(UP, buff=0) hint = OldTexText( "Hint: Don't expand out $g(z)$. Use\\\\", "the chain rule: $g'(0) = f'(f(0))f'(0)$" ) hint.scale(0.8) hint.set_color(GREY_A) hint_box = SurroundingRectangle(hint, buff=0.25) hint_box.match_style(box) hint_group = VGroup(hint_box, hint) hint_group.next_to(group, DOWN, buff=0) self.add(group) self.wait(2) self.play(FadeIn(hint_group)) self.wait() class AskHowOftenThisHappensAlt(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("How often does\\\\this happen?"), bubble_config={ "height": 3, "width": 4, }, index=0, ) self.play( self.teacher.change("raise_right_hand", 3 * UR), self.change_students( "raise_left_hand", "pondering", "pondering", look_at=3 * UR, ) ) self.play_student_changes( "raise_left_hand", "erm", "erm", look_at=3 * UR, ) self.wait() self.play(self.teacher.change("tease", 3 * UR)) self.play_student_changes( "confused", "pondering", "thinking", look_at=3 * UR, ) self.wait(8) return self.wait(2) self.play( PiCreatureSays( self.teacher, "You'll like this", target_mode="tease", run_time=1, ), self.students[1].change("thinking", self.teacher.eyes), self.students[2].change("thinking", self.teacher.eyes), ) self.wait(2) class WhatDoesBlackMean(Scene): def construct(self): lhs = OldTexText("$z_n$ never gets\\\\near a root") rhs = OldTexText("$\\Rightarrow$ ", "Black ", ) rhs.next_to(lhs, RIGHT) words = VGroup(lhs, rhs) words.next_to(ORIGIN, UR, MED_LARGE_BUFF) words.to_edge(RIGHT) words.set_stroke(BLACK, 5, background=True) # lhs[0].set_color(BLACK) # lhs[0].set_stroke(width=0) arrow = Arrow(ORIGIN, words.get_left(), buff=0.1) self.play( ShowCreation(arrow), Write(lhs), ) self.wait() self.play(FadeIn(rhs)) self.wait() class PlayWithRootsSeekingCycles(ThreeRootFractal): coefs = [2, -2, 0, 1] def construct(self): super().construct() self.fractal.uniforms["black_for_cycles"] = 1.0 class ShowCenterOfMassPoint(PlayWithRootsSeekingCycles): display_root_values = True only_center = False def construct(self): super().construct() mean_dot = glow_dot(ORIGIN) mean_dot.add_updater(lambda m: m.move_to( sum([rd.get_center() for rd in self.root_dots]) / 3 )) self.add(mean_dot) if self.only_center: mean_dot.set_opacity(0) self.fractal.replace(mean_dot, stretch=True) self.fractal.add_updater(lambda m: m.move_to(mean_dot)) window = Square() window.set_stroke(WHITE, 1) window.set_fill(BLACK, 0) window.replace(self.fractal, stretch=True) window.add_updater(lambda m: m.move_to(mean_dot)) self.add(window) mean_label = OldTex("(r_1 + r_2 + r_3) / 3", font_size=24) mean_label.set_stroke(BLACK, 2, background=True) mean_label.add_updater(lambda m: m.next_to(mean_dot, UP, buff=0.1)) self.add(mean_label) circle = Circle(radius=2) circle.rotate(PI) circle.stretch(0.9) circle.move_to(self.root_dots, LEFT) self.play( MoveAlongPath( self.root_dots[2], circle, rate_func=linear, run_time=20 ) ) self.play( self.root_dots[2].animate.move_to(self.plane.n2p(-3)), rate_func=there_and_back, run_time=10, ) class ShowCenterOfMassPointFocusIn(ShowCenterOfMassPoint): only_center = True class CenterOfMassStatement(Scene): def construct(self): words = OldTexText( "If there's an attracting cycle, the seed\\\\", "$z_0 = (r_1 + r_2 + r_3) / 3$ will fall into it." ) words.set_stroke(BLACK, 8, background=True) words.to_corner(UL) self.play(Write(words)) self.wait() class GenerateCubicParameterPlot(Scene): colors = ROOT_COLORS_DEEP[0::2] def construct(self): # Title colors = self.colors kw = { "tex_to_color_map": { "\\lambda": colors[2], } } title = OldTex( "z_{n+1} = z_n - {P(z_n) \\over P'(z_n)} \\qquad\\qquad ", "P(z) = (z - 1)(z + 1)(z - \\lambda)", font_size=30, **kw ) title.to_edge(UP) self.add(title) # Planes planes = VGroup(*( ComplexPlane( (-2, 2), (-2, 2), background_line_style={ "stroke_color": GREY_B, "stroke_opacity": 0.5, }, ) for x in range(2) )) for plane, vect in zip(planes, [DL, DR]): plane.add_coordinate_labels(font_size=18) plane.set_height(5) plane.to_corner(vect, buff=MED_SMALL_BUFF) root_dots = VGroup(*( Dot(planes[0].n2p(z), color=color) for color, z in zip(colors, [-1, 1, 1j]) )) root_dots.set_stroke(BLACK, 2) lambda_label = OldTex("\\lambda", font_size=36) lambda_label.set_color(interpolate_color(colors[2], WHITE, 0.75)) lambda_label.set_stroke(BLACK, 3, background=True) lambda_label.add_updater(lambda m: m.next_to( root_dots[2], UR, buff=SMALL_BUFF, )) # Fractals left_fractal = NewtonFractal( planes[0], coefs=[-1, 0, 0, 1], colors=colors, black_for_cycles=True, ) left_fractal.add_updater(lambda m: m.set_roots([ planes[0].p2n(rd.get_center()) for rd in root_dots ])) right_fractal = MetaNewtonFractal( planes[1], colors=colors, fixed_roots=[-1, 1], ) row_meta_fractal = right_fractal.deepcopy() col_meta_fractal = right_fractal.deepcopy() self.add(left_fractal, planes[0], planes, root_dots, lambda_label) # Plane titles plane_titles = VGroup( OldTexText("Pixel $\\leftrightarrow z_0$"), OldTexText("Pixel $\\leftrightarrow$ ", "$\\lambda$"), ) plane_titles[1][-1].set_color(colors[2]) for plane_title, plane in zip(plane_titles, planes): plane_title.next_to(plane, UP, MED_SMALL_BUFF) plane_titles[0].align_to(plane_titles[1], UP) self.add(plane_titles[0]) # Show left plane pins = VGroup() for rd in root_dots[:2]: pin = SVGMobject("push_pin") pin.set_fill(GREY_C) pin.set_stroke(width=0) pin.set_gloss(0.5) pin.set_height(0.3) pin.rotate(10 * DEGREES) pin.move_to(rd.get_center(), DR) pins.add(pin) self.play( FadeIn(pin, 0.25 * DR), FlashAround(rd), ) self.wait() circle = Circle() circle.scale(0.5) circle.rotate(PI / 2) circle.move_to(root_dots[2].get_center(), UP) self.play(MoveAlongPath(root_dots[2], circle, run_time=5)) self.play( root_dots[2].animate.move_to(planes[0].get_corner(UL)), run_time=2 ) self.wait() # Center of mass dot com_dot = Dot() com_dot.set_fill(opacity=0) com_dot.set_stroke(YELLOW, 3) com_dot.add_updater(lambda m: m.move_to( np.array([rd.get_center() for rd in root_dots]).mean(0) )) self.play(FadeIn(com_dot, scale=0.5), FadeOut(pins)) self.wait() # Right plane self.play(Write(plane_titles[1])) self.wait() # Show filling process step = 0.1 square = Square() square.set_stroke(WHITE, 2) arrow = Arrow(LEFT, RIGHT, stroke_width=3) self.add(row_meta_fractal, col_meta_fractal, planes[1]) self.add(square, arrow) x_range = np.arange(-2, 2 + step, step) y_range = np.arange(2, -2, -step) thin_height = plane.get_y_unit_size() * step col_meta_fractal.set_height(thin_height) square.set_height(thin_height) epsilon = 1e-6 for y, back in zip(y_range, it.cycle([False, True])): height = max(epsilon, plane.get_y_unit_size() * abs(2 - y)) row_meta_fractal.set_height(height, stretch=True) row_meta_fractal.move_to(planes[1], UP) x0 = (2 if back else -2) for x in (x_range[::-1] if back else x_range): width = max(epsilon, planes[1].get_x_unit_size() * abs(x - x0)) col_meta_fractal.set_width(width, stretch=True) col_meta_fractal.next_to(row_meta_fractal, DOWN, buff=0) col_meta_fractal.align_to(row_meta_fractal, RIGHT if back else LEFT) root_dots[2].move_to(planes[0].c2p(x, y)) square.move_to( col_meta_fractal, DOWN + (LEFT if back else RIGHT) ) self.update_mobjects(0) arrow.put_start_and_end_on( com_dot.get_center(), square.get_center(), ) self.wait(1 / 15) self.play(FadeOut(arrow), FadeOut(square)) self.wait() # Zoom in to meta fractal frame = self.camera.frame self.play( frame.animate.replace(planes[1], 1), run_time=3, ) self.wait() class Z0RuleLabel(Scene): def construct(self): label = OldTex("z_0 = (r_1 + r_2 + r_3) / 3") self.add(label) class WhyFractals(Scene): def construct(self): words = Text("Why fractals?") words.set_stroke(BLACK, 5, background=True) self.play(Write(words)) self.wait() class SmallCircleProperty(Scene): def construct(self): # Titles rule = OldTexText( "For any rational map, color points\\\\based on their limiting behavior...\\\\", "(which limit point, which limit cycle, etc.)", font_size=36, ) rule.to_corner(UL) rule[-1].shift(SMALL_BUFF * DOWN) rule[-1].set_color(GREY_B) r_map = OldTex("z_{n + 1} = A(z_n) / B(z_n)") r_map.to_corner(UR) # Fractal colors = [ MANDELBROT_COLORS[0], BLUE_C, BLUE_E, ROOT_COLORS_DEEP[1], ] plane = ComplexPlane((-2, 2), (-2, 2)) plane.set_height(5) plane.to_corner(DR) fractal = NewtonFractal(plane, coefs=[5, 4j, 3, 2, 1], colors=colors) # Circles circles = Circle(radius=0.6).get_grid(3, 1, buff=0.5) circles.replace(plane, 1) circles.set_x(-1) circles.set_stroke(WHITE, 2) circles[0].set_fill(colors[2], 1) semis = circles[1].copy().pointwise_become_partial(circles[1], 0, 0.5).replicate(2) semis[1].rotate(PI, about_point=circles[1].get_center()) semis[0].set_fill(colors[0], 1) semis[1].set_fill(colors[1], 1) circles[1].add(semis) multi_color_image = ImageMobject("MulticoloredNewtonsMapCircle") multi_color_image.add_updater(lambda m: m.replace(circles[2])) # Circle label circle_labels = VGroup( Text("One color"), Text("Some colors"), Text("All colors"), ) marks = VGroup(Checkmark(), Exmark(), Checkmark()) for label, mark, circle in zip(circle_labels, marks, circles): mark.set_height(0.5 * circle.get_height()) mark.next_to(circle, LEFT, MED_LARGE_BUFF) label.next_to(mark, LEFT, MED_LARGE_BUFF) # Little circles lil_circles = circles[0].replicate(2) lil_circles.set_height(0.2) lil_circles[0].move_to(plane.get_corner(UL) + MED_SMALL_BUFF * DR) lil_circles[1].move_to(plane).shift([0.22, -0.12, 0]) lil_circles[1].set_fill(opacity=0) lines = VGroup() for big, lil in zip(circles[::2], lil_circles): vect = normalize(lil.get_center() - big.get_center()) v1 = rotate_vector(vect, 75 * DEGREES) v2 = rotate_vector(vect, -75 * DEGREES) big.insert_n_curves(20) lines.add(VGroup( Line(lil.get_top(), big.get_boundary_point(v1)), Line(lil.get_bottom(), big.get_boundary_point(v2)), )) lines.set_stroke(WHITE, 1) # Intro anims self.add(rule[0]) self.play(FadeIn(r_map)) self.wait() fractal.set_opacity(0) self.add(fractal) for i in range(1, 5): opacities = np.zeros(4) opacities[:i] = 1 self.play(fractal.animate.set_opacities(*opacities)) self.wait() self.play(FadeIn(rule[-1], lag_ratio=0.1, run_time=2)) self.wait() # Show circles self.add(lil_circles[0]) self.play( TransformFromCopy(lil_circles[0], circles[0]), *map(ShowCreation, lines[0]), FadeIn(circle_labels[0]), ) self.play(Write(marks[0])) self.wait() self.add(multi_color_image) self.add(lil_circles[1]) self.play( TransformFromCopy(lil_circles[1], circles[2]), *map(ShowCreation, lines[1]), FadeIn(circle_labels[2]), ) self.play(Write(marks[2])) self.wait() self.play( FadeIn(circle_labels[1]), FadeIn(circles[1]), ) self.play(Write(marks[1])) self.wait() class MentionFatouSetsAndJuliaSets(Scene): colors = [RED_E, BLUE_E, TEAL_E, MAROON_E] def construct(self): # Introduce terms f_group, j_group = self.get_fractals() f_name, j_name = VGroup( Text("Fatou set"), Text("Julia set"), ) f_name.next_to(f_group, UP, MED_LARGE_BUFF) j_name.next_to(j_group, UP, MED_LARGE_BUFF) self.play( Write(j_name), GrowFromCenter(j_group) ) self.wait() self.play( Write(f_name), *map(GrowFromCenter, f_group) ) self.wait() # Define Fatou set fatou_condition = self.get_fatou_condition() fatou_condition.set_width(FRAME_WIDTH - 1) fatou_condition.center().to_edge(UP, buff=1.0) lhs, arrow, rhs = fatou_condition f_line = Line(LEFT, RIGHT) f_line.match_width(fatou_condition) f_line.next_to(fatou_condition, DOWN) f_line.set_stroke(WHITE, 1) self.play( FadeOut(j_name, RIGHT), FadeOut(j_group, RIGHT), Write(lhs) ) self.wait() for words in lhs[-1]: self.play(FlashUnder( words, buff=0, time_width=1.5 )) self.play(Write(arrow)) self.play(LaggedStart( FadeTransform(f_name.copy(), rhs[1][:8]), FadeIn(rhs), lag_ratio=0.5 )) self.wait() # Show Julia set otherwise = Text("Otherwise...") otherwise.next_to(rhs, DOWN, LARGE_BUFF) j_condition = OldTexText("$z_0 \\in$", " Julia set", " of $f$") j_condition.match_height(rhs) j_condition.next_to(otherwise, DOWN, LARGE_BUFF) j_group.set_height(4.0) j_group.to_edge(DOWN) j_group.set_x(-1.0) j_name = j_condition.get_part_by_tex("Julia set") j_underline = Underline(j_name, buff=0.05) j_underline.set_color(YELLOW) arrow = Arrow( j_name.get_bottom(), j_group.get_right(), path_arc=-45 * DEGREES, ) arrow.set_stroke(YELLOW, 5) julia_set = j_group[0] julia_set.update() julia_set.suspend_updating() julia_copy = julia_set.copy() julia_copy.clear_updaters() julia_copy.set_colors(self.colors) julia_copy.set_julia_highlight(0) mover = f_group[:-4] mover.generate_target() mover.target.match_width(rhs) mover.target.next_to(rhs, UP, MED_LARGE_BUFF) mover.target.shift_onto_screen(buff=SMALL_BUFF) self.play( ShowCreation(f_line), FadeOut(f_name), MoveToTarget(mover), ) self.play( Write(otherwise), FadeIn(j_condition, 0.5 * DOWN) ) self.wait() self.play( ShowCreation(j_underline), ShowCreation(arrow), FadeIn(j_group[1]), FadeIn(julia_copy) ) self.play( GrowFromPoint(julia_set, julia_set.get_corner(UL), run_time=2), julia_copy.animate.set_opacity(0.2) ) self.wait() def get_fractals(self, jy=1.5, fy=-2.5): coefs = roots_to_coefficients([-1.5, 1.5, 1j, -1j]) n = len(coefs) - 1 colors = self.colors f_planes = VGroup(*(self.get_plane() for x in range(n))) f_planes.arrange(RIGHT, buff=LARGE_BUFF) plusses = OldTex("+").replicate(n - 1) f_group = Group(*it.chain(*zip(f_planes, plusses))) f_group.add(f_planes[-1]) f_group.arrange(RIGHT) fatou = Group(*( NewtonFractal(f_plane, coefs=coefs, colors=colors) for f_plane in f_planes )) for i, fractal in enumerate(fatou): opacities = n * [0.2] opacities[i] = 1 fractal.set_opacities(*opacities) f_group.add(*fatou) f_group.set_y(fy) j_plane = self.get_plane() j_plane.set_y(jy) julia = NewtonFractal(j_plane, coefs=coefs, colors=5 * [GREY_A]) julia.set_julia_highlight(1e-3) j_group = Group(julia, j_plane) for fractal, plane in zip((*fatou, julia), (*f_planes, j_plane)): fractal.plane = plane fractal.add_updater( lambda m: m.set_offset( m.plane.get_center() ).set_scale( m.plane.get_x_unit_size() ).replace(m.plane) ) fractals = Group(f_group, j_group) return fractals def get_plane(self): plane = ComplexPlane( (-2, 2), (-2, 2), background_line_style={"stroke_width": 1, "stroke_color": GREY} ) plane.set_height(2) plane.set_opacity(0) box = SurroundingRectangle(plane, buff=0) box.set_stroke(WHITE, 1) plane.add(box) return plane def get_fatou_condition(self): zn = OldTex( "z_0", "\\overset{f}{\\longrightarrow}", "z_1", "\\overset{f}{\\longrightarrow}", "z_2", "\\overset{f}{\\longrightarrow}", "\\dots", "\\longrightarrow" ) words = VGroup( OldTexText("Stable fixed point"), OldTexText("Stable cycle"), OldTexText("$\\infty$"), ) words.arrange(DOWN, aligned_edge=LEFT) brace = Brace(words, LEFT) zn.next_to(brace, LEFT) lhs = VGroup(zn, brace, words) arrow = OldTex("\\Rightarrow") arrow.scale(2) arrow.next_to(lhs, RIGHT, MED_LARGE_BUFF) rhs = OldTex("z_0 \\in", " \\text{Fatou set of $f$}") rhs.next_to(arrow, RIGHT, buff=MED_LARGE_BUFF) result = VGroup(lhs, arrow, rhs) return result class ShowJuliaSetPoint(TwoToMillionPoints): plane_height = 14 show_disk = False n_steps = 60 disk_radius = 0.02 def construct(self): # Background plane, fractal = self.get_plane_and_fractal() plane.add_coordinate_labels(font_size=24) for mob in plane.family_members_with_points(): if isinstance(mob, Line): mob.set_stroke(opacity=0.5 * mob.get_stroke_opacity()) self.add(fractal, plane) # Points points = list(self.get_julia_set_points(plane, n_points=1, n_steps=1000)) def func(p): z = plane.p2n(p) return plane.n2p(z**2 + self.c) for n in range(100): points.append(func(points[-1])) dot = Dot(points[0]) dot.set_color(YELLOW) self.add(dot) if self.show_disk: dot.scale(0.5) disk = dot.copy() disk.insert_n_curves(10000) disk.set_height(plane.get_x_unit_size() * self.disk_radius) disk.set_fill(YELLOW, 0.25) disk.set_stroke(YELLOW, 2, 1) self.add(disk, dot) frame = self.camera.frame path_arc = 30 * DEGREES point = dot.get_center().copy() for n in range(self.n_steps): new_point = func(point) arrow = Arrow(point, new_point, path_arc=path_arc, buff=0) arrow.set_stroke(WHITE, opacity=0.9) self.add(dot.copy().set_opacity(0.5)) anims = [] if self.show_disk: disk.generate_target() disk.target.apply_function(func) disk.target.make_smooth(approx=True) anims.append(MoveToTarget(disk, path_arc=path_arc)) if disk.target.get_height() > frame.get_height(): anims.extend([ mob.animate.scale(2.0) for mob in [frame, fractal] ]) self.play( ApplyMethod(dot.move_to, new_point, path_arc=path_arc), ShowCreation(arrow), *anims, ) self.play(FadeOut(arrow)) point = new_point class ShowJuliaSetPointWithDisk(ShowJuliaSetPoint): show_disk = True n_steps = 11 class AboutFatouDisks(Scene): disk_style = { "fill_color": YELLOW, "fill_opacity": 0.5, "stroke_color": YELLOW, "stroke_width": 1, } def construct(self): words = Text("(Small enough) disks around points in the Fatou set...") words.to_edge(UP) disks, arrows = self.get_disks_and_arrow() arrows[-1].add(OldTex("\\dots").next_to(arrows[-1], RIGHT)) group = VGroup(disks, arrows) group.next_to(words, DOWN) shrink_words = Text("...eventually shrink to 0") shrink_words.next_to(group, DOWN, aligned_edge=RIGHT) self.add(words) self.play_disk_progression(disks, arrows) self.play( FadeIn(arrows[-1]), Write(shrink_words, run_time=2) ) self.wait() def play_disk_progression(self, disks, arrows): self.add(disks[0]) for d1, d2, arrow in zip(disks, disks[1:], arrows): self.play( TransformFromCopy(d1.copy().fade(1), d2), FadeIn(arrow), ) def get_disks(self): radii = [ *np.linspace(0.5, 1, 3), *np.linspace(1, 0, 7)**2 + 0.05, ] disks = VGroup(*(Circle(radius=r, **self.disk_style) for r in radii)) for disk in disks: disk.add(Dot(disk.get_center(), radius=0.01)) return disks def get_disks_and_arrow(self): disks = self.get_disks() arrows = OldTex("\\rightarrow").replicate(len(disks)) group = VGroup(*it.chain(*zip(disks, arrows))) group.arrange(RIGHT) group.set_width(FRAME_WIDTH - 2) return disks, arrows class AboutFatouDisksJustWords(Scene): def construct(self): words = OldTexText( "(Small enough) disks around points in the Fatou set...\\\\", "...eventually shrink to 0" ) words.arrange(DOWN, aligned_edge=LEFT) words.to_corner(UL) words.set_stroke(BLACK, 6, background=True) self.play(FadeIn(words[0], lag_ratio=0.1)) self.wait() self.play(FadeIn(words[1], lag_ratio=0.1)) self.wait() class ShowFatouDiskExample(Scene): disk_radius = 0.1 n_steps = 14 def construct(self): c = -1.06 + 0.11j plane = ComplexPlane((-3, 3), (-2, 2)) for line in plane.family_members_with_points(): line.set_stroke(opacity=0.5 * line.get_stroke_opacity()) plane.set_height(1.8 * FRAME_HEIGHT) plane.add_coordinate_labels(font_size=18) fractal = JuliaFractal(plane, parameter=c) # z0 = -1.1 + 0.1j z0 = -0.3 + 0.2j dot = Dot(plane.n2p(z0), radius=0.025) dot.set_fill(YELLOW) disk = dot.copy() disk.set_height(2 * self.disk_radius * plane.get_x_unit_size()) disk.set_fill(YELLOW, 0.5) disk.set_stroke(YELLOW, 1.0) disk.insert_n_curves(1000) def func(point): return plane.n2p(plane.p2n(point)**2 + c) self.add(fractal, plane) self.add(disk, dot) self.play(DrawBorderThenFill(disk)) path_arc = 10 * DEGREES for n in range(self.n_steps): point = dot.get_center() new_point = func(point) arrow = Arrow(point, new_point, path_arc=path_arc, buff=0.1) self.play( dot.animate.move_to(new_point), disk.animate.apply_function(func), ShowCreation(arrow), path_arc=path_arc, ) self.play(FadeOut(arrow)) self.embed() class AboutJuliaDisks(AboutFatouDisks): def construct(self): words1 = Text("Any tiny disk around a Julia set point...") words1.to_edge(UP) disks, arrows = self.get_disks_and_arrow() group = VGroup(disks, arrows) group.next_to(words1, DOWN) arrows[-1].add(disks[-1].copy().scale(5).next_to(arrows[-1], RIGHT)) words2 = Text("...eventually hits every point in the plane,") words2.next_to(disks, DOWN, aligned_edge=RIGHT) words2.get_part_by_text("every point in the plane").set_color(BLUE) words3 = Text("with at most two exceptions.") words3.next_to(words2, DOWN, MED_LARGE_BUFF, aligned_edge=RIGHT) words4 = OldTexText( "``Stuff goes everywhere'' principle of Julia sets", font_size=60 ) words4.to_edge(DOWN, LARGE_BUFF) VGroup(words1, words2, words3, words4).set_stroke(BLACK, 5, background=True) plane = ComplexPlane((-100, 100), (-50, 50)) plane.scale(2) plane.add_coordinate_labels() plane.add(BackgroundRectangle(plane, opacity=0.25)) plane.set_stroke(background=True) plane.add_updater(lambda m, dt: m.scale(1 - 0.15 * dt)) self.add(words1) self.play_disk_progression(disks, arrows) self.add(plane, *self.mobjects) self.play( FadeIn(arrows[-1]), FadeIn(words2, run_time=2, lag_ratio=0.1), VFadeIn(plane, suspend_updating=False), ) self.wait() self.play( FadeIn(words3, run_time=2, lag_ratio=0.1), ) self.wait(2) self.play(Write(words4)) self.wait(6) self.play(VFadeOut(plane, suspend_updating=False)) def get_disks(self): c = -0.18 + 0.77j z = -0.491 - 0.106j plane = ComplexPlane() disk = Circle(radius=0.1, **self.disk_style) disk.move_to(plane.n2p(z)) disk.insert_n_curves(200) disks = VGroup(disk) for n in range(5): new_disk = disks[-1].copy() new_disk.apply_complex_function(lambda z: z**2 + c) new_disk.make_smooth(approx=True) disks.add(new_disk) for disk in disks: disk.center() disks.set_height(1) return disks class MontelCorrolaryScreenGrab(ExternallyAnimatedScene): pass class DescribeChaos(Scene): def construct(self): j_point = 3 * LEFT j_value = -0.36554 - 0.29968j plane = ComplexPlane((-3, 3), (-2, 2)) plane.scale(50) plane.shift(j_point - plane.n2p(j_value)) fractal = JuliaFractal(plane) fractal.set_c(-0.5 + 0.5j) self.add(fractal, plane) j_dot = Dot(color=YELLOW, radius=0.05) j_dot.move_to(j_point) j_label = Text("Julia set point", color=YELLOW) j_label.next_to(j_dot, UP, buff=1.0).shift(LEFT) j_arrow = Line(j_label.get_bottom(), j_dot.get_center(), buff=0.1) j_arrow.set_stroke(width=3) j_arrow.set_color(YELLOW) surrounding_dots = VGroup(*( Dot(radius=0.05).move_to(j_dot.get_center() + buff * vect) for n, buff in [(6, 0.2), (12, 0.4)] for vect in compass_directions(n) )) surrounding_dots.set_color(GREY_B) # for dot in surrounding_dots: # dot.shift(0.1 * (random.random() - 0.5)) dots_label = Text("Immediate neighbors") dots_label.next_to(surrounding_dots, DOWN) dots_label.set_color(GREY_A) sublabel = Text("drift far away") sublabel.set_color(GREY_A) sublabel.next_to(dots_label, DOWN) fa = sublabel.get_part_by_text("far away") strike = Line(LEFT, RIGHT) strike.set_stroke(RED, 10) strike.replace(fa, 0) new_words = Text("everywhere!") new_words.next_to(fa, DOWN) new_words.set_color(RED) all_words = VGroup(j_label, dots_label, sublabel, new_words) all_words.set_stroke(BLACK, 5, background=True) arrows = VGroup() for dot in surrounding_dots[-12:]: point = dot.get_center() vect = 0.6 * normalize(point - j_dot.get_center()) arrows.add(Arrow(point, point + vect, buff=0.1)) arrows.set_stroke(RED) self.add(j_dot) self.add(surrounding_dots) self.add(j_label, j_arrow) self.add(dots_label) frame = self.camera.frame frame.save_state() frame.replace(plane) self.play(Restore(frame, run_time=5)) self.wait() self.play(FadeIn(sublabel, lag_ratio=0.1)) self.wait() self.play( ShowCreation(strike), FadeIn(new_words, shift=0.25 * DOWN) ) self.add(arrows, all_words, strike) self.play(LaggedStartMap(ShowCreation, arrows)) self.wait() class SimulationOfTinyDisk(RepeatedNewton): coefs = [1, 1, 0, 1] plane_config = { "x_range": (-4, 4), "y_range": (-2, 2), "height": 12, "width": 24, } colors = ROOT_COLORS_DEEP[::2] n_steps = 20 def construct(self): frame = self.camera.frame frame.save_state() def get_height_ratio(): return frame.get_height() / FRAME_HEIGHT self.add_plane() self.add_true_roots() self.add_labels() self.add_fractal_background() fractal = self.fractal julia_set = self.fractal_boundary fractal.set_n_steps(40) julia_set.set_n_steps(40) fractal.set_opacity(0.3) julia_set.set_opacity(1) julia_set.add_updater(lambda m: m.set_julia_highlight( get_height_ratio() * 1e-3 )) julia_set.set_opacity(0.5) # Generate dots point = [1.10049904, 1.38962415, 0.] target_height = 0.0006 cluster_radius = target_height / 50 dots = self.dots = DotCloud() n_radii = 200 dots.set_points([ [cluster_radius * r * math.cos(theta), cluster_radius * r * math.sin(theta), 0] for r in np.linspace(1, 0, n_radii) for theta in np.linspace(0, TAU, int(r * 20)) + random.random() * TAU ]) dots.set_height(0.3) dots.set_gloss(0.5) dots.set_shadow(0.5) dots.set_color(GREY_A) dots.move_to(point) dots.add_updater(lambda m: m.set_radius(0.05 * get_height_ratio())) self.play( frame.animate.set_height(target_height).move_to(point), run_time=6, rate_func=lambda a: smooth(a**0.5), ) self.play(ShowCreation(dots)) self.wait() self.play(Restore(frame, run_time=6, rate_func=lambda a: smooth(a**3))) fractal.set_n_steps(12) julia_set.set_n_steps(12) self.run_iterations() class SimulationAnnotations(Scene): def construct(self): dots = self.dots = DotCloud() n_radii = 200 cluster_radius = 0.5 dots.set_points([ [cluster_radius * r * math.cos(theta), cluster_radius * r * math.sin(theta), 0] for r in np.linspace(1, 0, n_radii) for theta in np.linspace(0, TAU, int(r * 20)) + random.random() * TAU ]) n_points_label = OldTexText("$\\sim 2{,}000$ points") n_points_label.next_to(dots, UP) brace = Brace( # Line(dots.get_bottom(), dots.get_corner(DR)), Line(dots.get_center(), dots.get_right()), DOWN, buff=0 ) brace_tex = OldTex("\\text{Radius } \\approx 1 / 1{,}000{,}000") brace_tex.next_to(dots, DOWN) group = VGroup(n_points_label, brace, brace_tex) group.set_stroke(BLACK, 5, background=True) self.play(Write(n_points_label)) self.play( GrowFromCenter(brace), FadeIn(brace_tex, DOWN) ) self.wait() self.play(FadeOut(group)) class LattesExample(TeacherStudentsScene): def construct(self): example = VGroup( OldTexText("Lattè's example: "), OldTex(r"L(z)=\frac{\left(z^{2}+1\right)^{2}}{4 z\left(z^{2}-1\right)}"), ) example.arrange(RIGHT) example[0].shift(SMALL_BUFF * DOWN) example.move_to(self.hold_up_spot, DOWN) example.set_x(0) j_fact = OldTexText("Julia set of $L(z)$ is all of $\\mathds{C}$") j_fact.move_to(example) subwords = OldTexText("(and the point at $\\infty$)", font_size=36) subwords.set_fill(GREY_A) subwords.next_to(j_fact, DOWN) self.play( self.teacher.change("raise_right_hand", 3 * UR), self.change_students( "pondering", "happy", "tease", look_at=3 * UR ) ) self.wait(3) self.play( self.teacher.change("sassy", example), Write(example) ) self.play_student_changes( "pondering", "pondering", "pondering", look_at=example, ) self.play( example.animate.to_edge(UP), FadeIn(j_fact), self.change_students( "erm", "erm", "erm", look_at=j_fact, ), self.teacher.change("raise_right_hand", j_fact) ) self.play(FadeIn(subwords)) self.wait(3) class JFunctionMention(Scene): def construct(self): image = ImageMobject("j_invariant") image.set_height(5) name = OldTexText("Klein's $j$ function") name.next_to(image, UP) words = Text("A whole story...") words.next_to(image, RIGHT) self.play( FadeIn(image), Write(name) ) self.wait() self.play(Write(words)) self.wait() class LinksBelow(TeacherStudentsScene): def construct(self): self.pi_creatures.flip().flip() self.teacher_says("Links below") self.play_student_changes( "pondering", "thinking", "pondering", look_at=FRAME_HEIGHT * DOWN, ) self.wait(2) self.play(self.teacher.change("happy")) self.wait(4) class MoreAmbientChaos(TwoToMillionPoints): def construct(self): plane = ComplexPlane((-2, 2), (-2, 2)) plane.set_height(7) self.add(plane) point = self.get_julia_set_points(plane, n_points=1, n_steps=1000)[0] epsilon = 1e-6 dots = DotCloud([ point + epsilon * rotate_vector(RIGHT, random.random() * TAU) for x in range(10) ]) dots.set_gloss(0.5) dots.set_shadow(0.5) dots.set_radius(0.075) dots.set_color(YELLOW) def func(p): z = plane.p2n(p) return plane.n2p(z**2 + self.c) n_steps = 100 for n in range(n_steps): points = dots.get_points() values = list(map(plane.p2n, points)) new_values = np.array(list(map(lambda z: z**2 + self.c, values))) new_points = list(map(plane.n2p, new_values)) nn_points = [] for p in new_points: if p[0] < plane.get_left()[0]: p[0] += 2 * (plane.get_left()[0] - p[0]) if p[0] > plane.get_right()[0]: p[0] -= 2 * (p[0] - plane.get_right()[0]) if p[1] < plane.get_bottom()[1]: p[1] += 2 * (plane.get_bottom()[1] - p[1]) if p[1] > plane.get_top()[1]: p[1] -= 2 * (p[1] - plane.get_top()[1]) nn_points.append(p) new_points = nn_points lines = VGroup(*( Line(p1, p2) for p1, p2 in zip(points, new_points) )) lines.set_stroke(WHITE, 1) self.play( dots.animate.set_points(new_points), ShowCreation(lines, lag_ratio=0), ) self.add(lines[0].copy().set_opacity(0.25)) for line in lines: line.rotate(PI) self.play(FadeOut(lines)) class HighlightedJulia(IntroNewtonFractal): coefs = [-1.0, 0.0, 0.0, 1.0, 0.0, 1.0] def construct(self): # self.init_fractal(root_colors=ROOT_COLORS_DEEP[0::2]) self.init_fractal(root_colors=ROOT_COLORS_DEEP) fractal = self.fractal def get_height_ratio(): return self.camera.frame.get_height() / FRAME_HEIGHT fractal.set_colors(5 * [WHITE]) fractal.add_updater(lambda m: m.set_julia_highlight(get_height_ratio() * 1e-3)) fractal.set_n_steps(50) # self.play( # fractal.animate.set_julia_highlight(1e-3), # run_time=5 # ) # self.embed() class MetaFractal(IntroNewtonFractal): fixed_roots = [-1, 1] z0 = complex(0.5, 0) n_steps = 200 def construct(self): colors = ROOT_COLORS_DEEP[0::2] self.plane_config["faded_line_ratio"] = 3 plane = self.get_plane() root_dots = self.root_dots = VGroup(*( Dot(plane.n2p(root), color=color) for root, color in zip(self.fixed_roots, colors) )) root_dots.set_stroke(BLACK, 3) fractal = MetaNewtonFractal( plane, fixed_roots=self.fixed_roots, colors=colors, n_steps=self.n_steps, # z0=self.z0, ) fractal.add_updater(lambda f: f.set_fixed_roots([ plane.p2n(dot.get_center()) for dot in root_dots ])) self.add(fractal, plane) self.add(root_dots) point1 = np.array([1.62070862, 1.68700851, 0.]) point2 = np.array([0.81263967, 2.84042313, 0.]) height1 = 0.083 height2 = 0.035 frame = self.camera.frame frame.save_state() frame.generate_target() frame.target.move_to(point1) frame.target.set_height(height1) fractal.set_saturation_factor(2) plane.remove(plane.coordinate_labels) self.play( MoveToTarget(frame), run_time=8, rate_func=bezier([0, 0, 1, 1]) ) self.play( fractal.animate.set_saturation_factor(4), run_time=3 ) self.play( UpdateFromAlphaFunc( frame, lambda m, a: m.set_height( interpolate( interpolate(height1, 2, a), interpolate(2, height2, a), a, ), ).move_to( interpolate(point1, point2, a) ) ), run_time=10 ) self.wait(2) self.play( Restore(frame), fractal.animate.set_saturation_factor(0), run_time=7 ) self.wait() class Part1EndScroll(PatreonEndScreen): CONFIG = { # "title_text": "", "scroll_time": 30, # "show_pis": False, } class AmbientJulia(Scene): def construct(self): plane = ComplexPlane( (-4, 4), (-2, 2), background_line_style={ "stroke_color": GREY_A, "stroke_width": 1, } ) plane.axes.set_stroke(width=1, opacity=0.5) plane.set_height(14) fractal = JuliaFractal(plane) fractal.set_n_steps(100) R = 0.25 cardioid = ParametricCurve( lambda t: plane.c2p( 2 * R * math.cos(t) - R * math.cos(2 * t), 2 * R * math.sin(t) - R * math.sin(2 * t), ), t_range=(0, TAU) ) t_tracker = ValueTracker(0) get_t = t_tracker.get_value fractal.add_updater(lambda m: m.set_c( plane.p2n(cardioid.pfp(get_t())) )) self.add(fractal, plane) self.play( t_tracker.animate.set_value(1), rate_func=linear, run_time=300 )