from manim_imports_ext import * from _2022.quintic import coefficients_to_roots from _2022.quintic import roots_to_coefficients from _2022.quintic import dpoly from _2022.quintic import poly ROOT_COLORS_BRIGHT = [RED, GREEN, BLUE, YELLOW, MAROON_B] ROOT_COLORS_DEEP = ["#440154", "#3b528b", "#21908c", "#5dc963", "#29abca"] CUBIC_COLORS = [RED_E, TEAL_E, BLUE_E] def glow_dot(point, r_min=0.05, r_max=0.15, color=YELLOW, n=20, opacity_mult=1.0): result = VGroup(*( Dot(point, radius=interpolate(r_min, r_max, a)) for a in np.linspace(0, 1, n) )) result.set_fill(color, opacity=opacity_mult / n) return result def get_newton_rule(font_size=36, var="z", **kwargs): terms = [f"{var}_n", f"{var}_{{n + 1}}"] t0, t1 = terms return OldTex( t1, "=", t0, "-", "{P(", t0, ")", "\\over ", "P'(", t0, ")}", font_size=36, **kwargs ) def coefs_to_poly_string(coefs): n = len(coefs) - 1 tex_str = "" if coefs[-1] == 1 else str(int(coefs[-1])) tex_str += f"z^{{{n}}}" for c, k in zip(coefs[-2::-1], it.count(n - 1, -1)): if c == 0: continue if isinstance(c, complex): num_str = "({:+}".format(int(c.real)) num_str += "+ {:+})".format(int(c.imag)) else: num_str = "{:+}".format(int(c)) if abs(c) == 1 and k > 0: num_str = num_str[:-1] tex_str += num_str if k == 0: continue elif k == 1: tex_str += "z" else: tex_str += f"z^{{{k}}}" return tex_str def get_figure(image_name, person_name, year_text, height=3, label_direction=DOWN): image = ImageMobject(image_name) image.set_height(height) rect = SurroundingRectangle(image, buff=0) rect.set_stroke(WHITE, 2) name = Text(f"{person_name}", font_size=36) name.set_color(GREY_A) year_label = Text(f"{year_text}", font_size=30) year_label.match_color(name) year_label.next_to(name, DOWN, buff=0.2) VGroup(name, year_label).next_to(image, label_direction) return Group(rect, image, name, year_label) class NewtonFractal(Mobject): CONFIG = { "shader_folder": "newton_fractal", "data_dtype": [ ('point', np.float32, (3,)), ], "colors": ROOT_COLORS_DEEP, "coefs": [1.0, -1.0, 1.0, 0.0, 0.0, 1.0], "scale_factor": 1.0, "offset": ORIGIN, "n_steps": 30, "julia_highlight": 0.0, "max_degree": 5, "saturation_factor": 0.0, "opacity": 1.0, "black_for_cycles": False, "is_parameter_space": False, } def __init__(self, plane, **kwargs): super().__init__( scale_factor=plane.get_x_unit_size(), offset=plane.n2p(0), **kwargs, ) self.replace(plane, stretch=True) def init_data(self): self.set_points([UL, DL, UR, DR]) def init_uniforms(self): super().init_uniforms() self.set_colors(self.colors) self.set_julia_highlight(self.julia_highlight) self.set_coefs(self.coefs) self.set_scale(self.scale_factor) self.set_offset(self.offset) self.set_n_steps(self.n_steps) self.set_saturation_factor(self.saturation_factor) self.set_opacity(self.opacity) self.uniforms["black_for_cycles"] = float(self.black_for_cycles) self.uniforms["is_parameter_space"] = float(self.is_parameter_space) def set_colors(self, colors): self.uniforms.update({ f"color{n}": np.array(color_to_rgba(color)) for n, color in enumerate(colors) }) return self def set_julia_highlight(self, value): self.uniforms["julia_highlight"] = value def set_coefs(self, coefs, reset_roots=True): full_coefs = [*coefs] + [0] * (self.max_degree - len(coefs) + 1) self.uniforms.update({ f"coef{n}": np.array([coef.real, coef.imag], dtype=np.float64) for n, coef in enumerate(map(complex, full_coefs)) }) if reset_roots: self.set_roots(coefficients_to_roots(coefs), False) self.coefs = coefs return self def set_roots(self, roots, reset_coefs=True): self.uniforms["n_roots"] = float(len(roots)) full_roots = [*roots] + [0] * (self.max_degree - len(roots)) self.uniforms.update({ f"root{n}": np.array([root.real, root.imag], dtype=np.float64) for n, root in enumerate(map(complex, full_roots)) }) if reset_coefs: self.set_coefs(roots_to_coefficients(roots), False) self.roots = roots return self def set_scale(self, scale_factor): self.uniforms["scale_factor"] = scale_factor return self def set_offset(self, offset): self.uniforms["offset"] = np.array(offset) return self def set_n_steps(self, n_steps): self.uniforms["n_steps"] = float(n_steps) return self def set_saturation_factor(self, saturation_factor): self.uniforms["saturation_factor"] = float(saturation_factor) return self def set_opacities(self, *opacities): for n, opacity in enumerate(opacities): self.uniforms[f"color{n}"][3] = opacity return self def set_opacity(self, opacity, recurse=True): self.set_opacities(*len(self.roots) * [opacity]) return self class MetaNewtonFractal(NewtonFractal): CONFIG = { "coefs": [-1.0, 0.0, 0.0, 1.0], "colors": [*ROOT_COLORS_DEEP[::2], BLACK, BLACK], "fixed_roots": [-1, 1], "n_roots": 3, "black_for_cycles": True, "is_parameter_space": True, "n_steps": 300, } def init_uniforms(self): super().init_uniforms() self.set_fixed_roots(self.fixed_roots) def set_fixed_roots(self, roots): super().set_roots(roots, reset_coefs=False) self.uniforms["n_roots"] = 3.0 # Scenes class AmbientRootFinding(Scene): def construct(self): pass class PragmaticOrigins(Scene): title = "Pragmatic origins" include_pi = False def construct(self): # Title title = Text(self.title, font_size=72) title.set_stroke(BLACK, 5, background=True) title.to_edge(UP, buff=MED_SMALL_BUFF) underline = Underline(title, buff=-0.05) underline.insert_n_curves(30) underline.set_stroke(BLUE, width=[0, 3, 3, 3, 0]) underline.scale(1.5) # Axes axes = NumberPlane( x_range=(-3, 3), y_range=(-4, 4), width=6, height=8, background_line_style={ "stroke_color": GREY_A, "stroke_width": 1, } ) axes.set_height(5.0) axes.to_corner(DL) axes.shift(0.5 * UP) coefs = np.array([2, -3, 1, -2, -1, 1], dtype=np.float) roots = [ r.real for r in coefficients_to_roots(coefs) if abs(r.imag) < 1e-2 ] roots.sort() coefs *= 0.2 solve = OldTexText("Solve $f(x) = 0$", font_size=36) solve.next_to(axes, UP, aligned_edge=LEFT) expr = OldTex("f(x) = x^5 - x^4 - 2x^3 + x^2 -3x + 2") expr.match_width(axes) expr.next_to(axes, DOWN) graph_x_range = (-2, 2.4) graph = axes.get_graph( lambda x: poly(x, coefs), x_range=graph_x_range ) graph.set_stroke(BLUE, [0, *50 * [4], 0]) root_dots = VGroup(*( glow_dot(axes.c2p(root, 0)) for root in roots )) root_eqs = VGroup() root_groups = VGroup() for i, root, dot in zip(it.count(1), roots, root_dots): lhs = OldTex(f"x_{i} = ") rhs = DecimalNumber(root, num_decimal_places=3) rhs.set_color(YELLOW) eq = VGroup(lhs, rhs) eq.arrange(RIGHT, aligned_edge=DOWN) rhs.align_to(lhs.family_members_with_points()[0], DOWN) root_eqs.add(eq) root_groups.add(VGroup(eq, dot)) root_eqs.arrange(RIGHT, buff=LARGE_BUFF) root_eqs.next_to(axes, RIGHT, aligned_edge=UP) self.add(axes) self.add(solve) self.add(expr) # Pi if self.include_pi: morty = Mortimer(height=2) morty.to_corner(DR) self.play(PiCreatureSays( morty, "How do you\nfind theses?", target_mode="tease", bubble_config={ "width": 4, "height": 2.5, } )) # Animations self.add(underline, title) self.play( ShowCreation(underline), ) self.wait() alphas = [inverse_interpolate(*graph_x_range, root) for root in roots] self.play( ShowCreation(graph, rate_func=linear), *( FadeIn( rg, rate_func=squish_rate_func(rush_from, a, min(a + 0.2, 1)) ) for rg, a in zip(root_groups, alphas) ), run_time=4, ) self.wait() class SeekingRoots(PragmaticOrigins): title = "Seeking roots" include_pi = True class AskAboutComplexity(Scene): def construct(self): self.add(FullScreenRectangle()) question = Text("What does this complexity reflect?") question.set_width(FRAME_WIDTH - 2) question.to_edge(UP) self.add(question) screen = ScreenRectangle() screen.set_height(6.0) screen.set_fill(BLACK, 1) screen.next_to(question, DOWN) self.add(screen) class WhoCares(TeacherStudentsScene): def construct(self): self.students.refresh_triangulation() screen = self.screen screen.set_height(4, about_edge=UL) screen.set_fill(BLACK, 1) image = ImageMobject("RealNewtonStill") image.replace(screen) self.add(screen) self.add(image) self.wait() self.play(LaggedStart( PiCreatureSays( self.students[1], "Ooh, quintics...", target_mode="thinking", look_at=self.screen, bubble_config={ "direction": LEFT, "width": 4, "height": 2, } ), self.teacher.change("happy"), self.students[0].change("thinking", screen), self.students[2].change("sassy", screen), lag_ratio=0.1, )) self.wait(3) self.play(LaggedStart( PiCreatureSays( self.students[2], "Who cares?", target_mode="tired", bubble_config={ "direction": LEFT, "width": 4, "height": 3, } ), self.teacher.change("guilty"), self.students[0].change("confused", screen), RemovePiCreatureBubble( self.students[1], look_at=self.students[2].eyes, target_mode="erm", ), lag_ratio=0.1, )) self.wait(2) self.teacher_says( "Who doesn't", target_mode="hooray", bubble_config={"height": 3, "width": 4}, added_anims=[self.change_students("pondering", "pondering", "confused")] ) self.wait(3) class SphereExample(Scene): def construct(self): # Shape axes = ThreeDAxes(z_range=(-4, 4)) axes.shift(IN) sphere = Sphere(radius=1.0) # sphere = TexturedSurface(sphere, "EarthTextureMap", "NightEarthTextureMap") sphere.move_to(axes.c2p(0, 0, 0)) sphere.set_gloss(1.0) sphere.set_opacity(0.5) sphere.sort_faces_back_to_front(DOWN) mesh = SurfaceMesh(sphere, resolution=(21, 11)) mesh.set_stroke(BLUE, 0.5, 0.5) sphere = Group(sphere, mesh) frame = self.camera.frame frame.reorient(20, 80) frame.move_to(2 * RIGHT) light = self.camera.light_source self.add(axes) self.add(sphere) frame.add_updater( lambda m, dt: m.increment_theta(1 * dt * DEGREES) ) # Expression equation = OldTex( "1.00", "\\,x^2", "+", "1.00", "\\,y^2", "+", "1.00", "\\,z^2", "=", "1.00", ) decimals = VGroup() for i in range(0, len(equation), 3): decimal = DecimalNumber(1.0, edge_to_fix=RIGHT) decimal.replace(equation[i]) equation.replace_submobject(i, decimal) decimals.add(decimal) decimal.add_updater(lambda m: m.fix_in_frame()) equation.fix_in_frame() equation.to_corner(UR) self.add(equation) # Animations light.move_to([-10, -10, 20]) self.wait() self.play( ChangeDecimalToValue(decimals[3], 9.0), VFadeInThenOut(SurroundingRectangle(decimals[3]).fix_in_frame()), sphere.animate.scale(3), run_time=3 ) self.wait() self.play( ChangeDecimalToValue(decimals[2], 4.0), VFadeInThenOut(SurroundingRectangle(decimals[2]).fix_in_frame()), sphere.animate.stretch(0.5, 2), run_time=3 ) self.wait() self.play( ChangeDecimalToValue(decimals[0], 9.0), VFadeInThenOut(SurroundingRectangle(decimals[0]).fix_in_frame()), sphere.animate.stretch(1 / 3, 0), run_time=3 ) self.wait(10) class ExamplePixels(Scene): def construct(self): pixels = Square().get_grid(5, 5, buff=0) pixels.set_height(2) pixels.to_corner(UL) pixels.set_stroke(WHITE, 1) pixels.set_fill(BLACK, 1) self.add(pixels) y, x = 1066, 1360 endpoint = np.array([x, -y, 0], dtype=np.float) endpoint *= FRAME_HEIGHT / 2160 endpoint += np.array([-FRAME_WIDTH / 2, FRAME_HEIGHT / 2, 0]) lines = VGroup( Line(pixels.get_corner(UR), endpoint), Line(pixels.get_corner(DL), endpoint), ) lines.set_stroke(WHITE, 2) self.add(lines) def match_values(pixels, values): for pixel, value in zip(pixels, it.chain(*values)): value = value[::-1] pixel.set_fill(rgb_to_color(value / 255)) values = np.load( os.path.join(get_directories()["data"], "sphere_pixel_values.npy") ) match_values(pixels, values[0]) # for value in values[60::60]: for value in values[1:]: # pixels.generate_target() # match_values(pixels.target, value) # self.play(MoveToTarget(pixels)) match_values(pixels, value) self.wait(1 / 60) class CurvesDefiningFonts(Scene): def construct(self): # Setup frame = self.camera.frame chars = OldTexText("When a computer\\\\renders text...")[0] chars.set_width(FRAME_WIDTH - 3) chars.refresh_triangulation() filled_chars = chars.copy() filled_chars.insert_n_curves(50) chars.set_stroke(WHITE, 0.5) chars.set_fill(opacity=0.0) dot_groups = VGroup() line_groups = VGroup() for char in chars: dots = VGroup() lines = VGroup() for a1, h, a2 in char.get_bezier_tuples(): for pair in (a1, h), (h, a2): lines.add(Line( *pair, stroke_width=0.25, # dash_length=0.0025, stroke_color=YELLOW, )) for point in (a1, h, a2): dots.add(Dot(point, radius=0.005)) dot_groups.add(dots) line_groups.add(lines) dot_groups.set_fill(BLUE, opacity=0) self.play(ShowIncreasingSubsets(filled_chars, run_time=1, rate_func=linear)) self.wait() # Zoom in on one letter char_index = 2 char = chars[char_index] lines = line_groups[char_index] dots = dot_groups[char_index] char.refresh_bounding_box() frame.generate_target() frame.target.set_height(char.get_height() * 2) frame.target.move_to(char.get_bottom(), DOWN) frame.target.shift(0.1 * char.get_height() * DOWN) self.play( MoveToTarget(frame), filled_chars.animate.set_opacity(0.2), FadeIn(chars), ShowCreation(line_groups, rate_func=linear), dot_groups.animate.set_opacity(1), run_time=5, ) for group in (line_groups, dot_groups): group.remove(*group[0:char_index - 1]) group.remove(*group[char_index + 2:]) self.wait() # Pull out one curve char.become(CurvesAsSubmobjects(char)) index = 26 curve = char[index] sublines = lines[2 * index:2 * index + 2] subdots = dots[3 * index:3 * index + 3] curve_group = VGroup(curve, sublines, subdots) curve_group.set_stroke(background=True) curve_group.generate_target() curve_group.save_state() curve_group.target.scale(3) curve_group.target.next_to(frame.get_top(), DOWN, buff=0.15) curve_group.target.shift(0.3 * LEFT) for dot in curve_group.target[2]: dot.scale(1 / 2) labels = VGroup(*( OldTex(f"P_{i}").set_height(0.05) for i in range(3) )) for label, dot, vect in zip(labels, curve_group.target[2], [LEFT, UP, UP]): label.insert_n_curves(20) label.next_to(dot, vect, buff=0.025) label.match_color(dot) self.play( MoveToTarget(curve_group), *( GrowFromPoint(label, curve_group.get_center()) for label in labels ) ) equation = OldTex( "(1-t)^{2} P_0 +2(1-t)t P_1 +t^2 P_2", tex_to_color_map={ "P_0": BLUE, "P_1": BLUE, "P_2": BLUE, } ) equation.set_height(0.07) equation.next_to(curve_group, RIGHT, buff=0.25) equation.insert_n_curves(20) poly_label = Text("Polynomial") poly_label.insert_n_curves(20) poly_label.set_width(2) poly_label.apply_function( lambda p: [ p[0], p[1] - 0.2 * p[0]**2, p[2], ] ) poly_label.rotate(30 * DEGREES) poly_label.match_height(curve_group) poly_label.scale(0.8) poly_label.move_to(curve, DR) poly_label.shift(0.01 * UL) self.play( ShowCreationThenDestruction(curve.copy().set_color(PINK), run_time=2), Write(poly_label, stroke_width=0.5) ) self.play( LaggedStart(*( TransformFromCopy( labels[i], equation.get_part_by_tex(f"P_{i}").copy(), remover=True ) for i in range(3) )), FadeIn(equation, rate_func=squish_rate_func(smooth, 0.5, 1)), run_time=2, ) self.wait() self.add(curve_group.copy()) self.play(Restore(curve_group)) self.wait() class PlayingInFigma(ExternallyAnimatedScene): pass class RasterizingBezier(Scene): def construct(self): # Add curve and pixels self.add(FullScreenRectangle()) curve = SVGMobject("bezier_example")[0] curve.set_width(FRAME_WIDTH - 3) curve.set_stroke(WHITE, width=1.0) curve.set_fill(opacity=0) curve.to_edge(DOWN, buff=1) curve.insert_n_curves(10) # To better uniformize it thick_curve = curve.copy() thick_curve.set_stroke(YELLOW, 30.0) thick_curve.reverse_points() pixels = Square().get_grid(90 // 2, 160 // 2, buff=0, fill_rows_first=False) pixels.set_height(FRAME_HEIGHT) pixels.set_stroke(WHITE, width=0.25) # I fully recognize the irony is implementing this without # solving polynomials, but I'm happy to be inificient on runtime # to just code up the quickest thing I can think of. samples = np.array([curve.pfp(x) for x in np.linspace(0, 1, 100)]) sw_tracker = ValueTracker(0.15) get_sw = sw_tracker.get_value for pixel in pixels: diffs = samples - pixel.get_center() dists = np.apply_along_axis(lambda p: np.dot(p, p), 1, diffs) index = np.argmin(dists) if index == 0 or index == len(samples) - 1: pixel.dist = np.infty else: pixel.dist = dists[index] def update_pixels(pixels): for pixel in pixels: pixel.set_fill( YELLOW, 0.5 * clip(10 * (get_sw() - pixel.dist), 0, 1) ) update_pixels(pixels) fake_pixels = pixels.copy() fake_pixels.set_stroke(width=0) fake_pixels.set_fill(GREY_E, 1) self.add(thick_curve) self.wait() self.add(fake_pixels, pixels) self.play( FadeIn(fake_pixels), ShowCreation(pixels), lag_ratio=10 / len(pixels), run_time=4 ) self.remove(thick_curve) self.wait() # Pixel pixel = pixels[725].deepcopy() pixel.set_fill(opacity=0) label = OldTexText("Pixel $\\vec{\\textbf{p}}$") label.refresh_triangulation() label.set_fill(YELLOW) label.set_stroke(BLACK, 4, background=True) label.next_to(pixel, UL, buff=LARGE_BUFF) label.shift_onto_screen() arrow = Arrow(label, pixel, buff=0.1, stroke_width=3.0) arrow.set_color(YELLOW) self.play( FadeIn(label), ShowCreation(arrow), pixel.animate.set_stroke(YELLOW, 2.0), ) pixels.add_updater(update_pixels) self.play(sw_tracker.animate.set_value(2.0), run_time=2) self.play(sw_tracker.animate.set_value(0.2), run_time=2) pixels.suspend_updating() self.play(ShowCreation(curve)) # Show P(t) value ct = VGroup(OldTex("\\vec{\\textbf{c}}(")[0], DecimalNumber(0), OldTex(")")[0]) ct.arrange(RIGHT, buff=0) ct.add_updater(lambda m: m.set_stroke(BLACK, 4, background=True)) t_tracker = ValueTracker(0) get_t = t_tracker.get_value P_dot = Dot(color=GREEN) ct[1].add_updater(lambda m: m.set_value(get_t())) ct[1].next_to(ct[0], RIGHT, buff=0) P_dot.add_updater(lambda m: m.move_to(curve.pfp(get_t() / 2))) ct.add_updater(lambda m: m.move_to(P_dot).shift( (0.3 - 0.5 * get_t() * (1 - get_t())) * rotate_vector(np.array([-3, 1, 0]), -0.8 * get_t() * PI) )) curve_copy = curve.copy() curve_copy.pointwise_become_partial(curve, 0, 0.5) curve_copy.set_points(curve_copy.get_points_without_null_curves()) curve_copy.set_stroke(YELLOW, 3.0) self.play( VFadeIn(ct), ApplyMethod(t_tracker.set_value, 1.0, run_time=3), ShowCreation(curve_copy, run_time=3), VFadeIn(P_dot), ) new_ct = OldTex("\\vec{\\textbf{c}}(", "t", ")") new_ct.move_to(ct, LEFT) new_ct.set_stroke(BLACK, 4, background=True) self.play(FadeTransformPieces(ct, new_ct)) ct = new_ct self.wait() # Show distance graph_group = self.get_corner_graph_group(pixel, curve) bg_rect, axes, y_label, graph = graph_group t_tracker = ValueTracker(0) dist_line = Line() dist_line.set_stroke(TEAL, 5) dist_line.add_updater(lambda l: l.put_start_and_end_on( pixel.get_center(), curve_copy.pfp(t_tracker.get_value()) )) dist_lines = VGroup() graph_v_lines = VGroup() for t in np.linspace(0, 1, 20): t_tracker.set_value(t) dist_lines.add(dist_line.update().copy().clear_updaters()) graph_v_lines.add(axes.get_v_line( axes.input_to_graph_point(t, graph) )) dist_lines.set_stroke(RED, 1, opacity=1.0) graph_v_lines.set_stroke(RED, 1, opacity=1.0) t_tracker.set_value(0) self.play( *map(FadeIn, graph_group[:-1]), ) self.play( FadeIn(dist_lines, lag_ratio=1), FadeIn(graph_v_lines, lag_ratio=1), run_time=4 ) self.wait() t_tracker.set_value(0.0) self.play( VFadeIn(dist_line, rate_func=squish_rate_func(smooth, 0, 0.25)), ApplyMethod(t_tracker.set_value, 1.0), ShowCreation(graph), run_time=3, ) self.play(dist_line.animate.set_stroke(RED, 1.0)) self.wait() # Show width again pixels.resume_updating() self.play(sw_tracker.animate.set_value(1.5), run_time=2) self.play(sw_tracker.animate.set_value(0.5), run_time=1) pixels.suspend_updating() self.wait() # Show derivative deriv_graph_group = self.get_deriv_graph_group(graph_group) d_graph = deriv_graph_group[-1] d_graph.set_points_smoothly([d_graph.pfp(x) for x in np.linspace(0, 1, 20)]) deriv_axes = deriv_graph_group[1] t_tracker = ValueTracker(0) get_t = t_tracker.get_value tan_line = always_redraw( lambda: axes.get_tangent_line( get_t(), graph, length=3, ).set_stroke( color=MAROON_B, width=1.0, opacity=clip(20 * get_t() * (1 - get_t()), 0, 1) ) ) self.play(*map(FadeIn, deriv_graph_group[:-1])) self.add(tan_line) self.play( t_tracker.animate.set_value(1), ShowCreation(d_graph), run_time=4 ) self.remove(tan_line) self.wait() points = graph.get_points() min_point = points[np.argmin([p[1] for p in points])] min_line = Line(min_point, [min_point[0], deriv_axes.c2p(0, 0)[1], 0]) min_line.set_stroke(WHITE, 1) question = Text("What is\nthis value?", font_size=30) question.to_corner(DR) arrow = Arrow( question.get_left(), min_line.get_bottom(), stroke_width=3, buff=0.1 ) self.play(ShowCreation(min_line)) self.play( Write(question), ShowCreation(arrow), ) self.wait() def get_corner_graph_group(self, pixel, curve, t_range=(0, 0.5)): axes = Axes( x_range=(0, 1, 0.2), y_range=(0, 20, 5), height=3, width=5, axis_config={"include_tip": False} ) axes.to_corner(UR, buff=SMALL_BUFF) y_label = OldTex( "&\\text{Distance}^2\\\\", "&||\\vec{\\textbf{p}} - \\vec{\\textbf{c}}(t)||^2", font_size=24, ) # For future transition y_label = VGroup(VectorizedPoint(y_label.get_left()), *y_label) y_label.next_to(axes.y_axis.get_top(), RIGHT, aligned_edge=UP) y_label.shift_onto_screen(buff=MED_SMALL_BUFF) graph = axes.get_graph(lambda t: get_norm( pixel.get_center() - curve.pfp(interpolate(*t_range, t)) )**2) graph.set_stroke(RED, 2) bg_rect = BackgroundRectangle(axes, buff=SMALL_BUFF) result = VGroup(bg_rect, axes, y_label, graph) return result def get_deriv_graph_group(self, graph_group): top_bg_rect, top_axes, top_y_label, top_graph = graph_group axes = Axes( x_range=top_axes.x_range, y_range=(-60, 60, 10), height=top_axes.get_height(), width=top_axes.get_width(), axis_config={"include_tip": False} ) axes.to_corner(DR, buff=SMALL_BUFF) axes.shift((top_axes.c2p(0, 0) - axes.c2p(0, 0))[0] * RIGHT) dt = 1e-5 f = top_graph.underlying_function graph = axes.get_graph(lambda t: (f(t + dt) - f(t)) / dt) graph.set_stroke(MAROON_B) # Dumb hack, not sure why it's needed graph.get_points()[:133] += 0.015 * UP y_label = VGroup(OldTex("\\frac{d}{dt}", font_size=24), top_y_label[2].copy()) y_label.arrange(RIGHT, buff=0.05) y_label.next_to(axes.y_axis.get_top(), RIGHT, buff=2 * SMALL_BUFF) bg_rect = BackgroundRectangle(VGroup(axes, graph), buff=SMALL_BUFF) bg_rect.stretch(1.05, 1, about_edge=DOWN) result = VGroup(bg_rect, axes, y_label, graph) return result class WriteThisIsPolynomial(Scene): def construct(self): text = OldTexText("(Some polynomial in $t$)", font_size=24) self.play(Write(text)) self.wait() class DontWorryAboutDetails(TeacherStudentsScene): CONFIG = { "background_color": BLACK, } def construct(self): screen = self.screen screen.set_height(4, about_edge=UL) screen.set_fill(BLACK, 1) image1, image2 = [ ImageMobject(f"RasterizingBezier_{i}").replace(screen) for i in range(1, 3) ] frame = self.camera.frame frame.save_state() frame.replace(image1) self.add(screen, image1) self.play(Restore(frame)) # Student asks about what the function is. self.student_says( OldTexText("Wait, what is that\\\\function exactly?"), look_at=image1, index=2, added_anims=[ self.students[0].change("confused", image1), self.students[1].change("confused", image1), ] ) self.play(self.teacher.change("tease")) self.wait(2) self.play( self.students[0].change("maybe", image1), ) self.play( self.students[1].change("erm", image1), ) self.wait(3) self.teacher_says( OldTexText("Just some\\\\polynomial"), bubble_config={ "width": 4, "height": 3, }, added_anims=[self.change_students("confused", "maybe", "pondering")] ) self.wait() self.look_at(image1) self.play( frame.animate.replace(image1), RemovePiCreatureBubble(self.teacher), run_time=2 ) self.wait() # Image 2 self.remove(image1) self.add(image2) self.play(Restore(frame)) self.play_all_student_changes( "confused", look_at=image1, ) self.teacher_says( OldTex("P(x) = 0"), target_mode="tease", bubble_config={ "width": 3, "height": 3, } ) self.wait(4) self.play( RemovePiCreatureBubble(self.teacher, target_mode="raise_right_hand", look_at=image1), self.change_students( *3 * ["pondering"], look_at=image1, ), FadeOut(image2), ) self.wait(4) class ShowManyGraphs(Scene): def construct(self): # Add plots root_groups = [ (-2, 6), (-5, 0, 3), (-7, -2, 3, 8), (-5, 1, 5, complex(0, 1), complex(0, -1)), ] coef_groups = list(map(roots_to_coefficients, root_groups)) scalars = [0.5, 0.2, 0.01, -0.01] colors = [BLUE_C, BLUE_D, BLUE_B, RED] plots = Group(*( self.get_plot(coefs, scalar, color) for coefs, scalar, color in zip(coef_groups, scalars, colors) )) plots.arrange_in_grid(v_buff=0.5) axes, graphs, root_dots = [ Group(*(plot[i] for plot in plots)) for i in range(3) ] self.play( LaggedStartMap(FadeIn, axes, lag_ratio=0.3), LaggedStartMap(ShowCreation, graphs, lag_ratio=0.3), run_time=3, ) self.play( LaggedStart(*( FadeIn(dot, scale=0.1) for dot in it.chain(*root_dots) ), lag_ratio=0.1) ) self.add(plots) self.wait() quadratic, cubic, quartic, quintic = plots for plot in plots: plot.save_state() # Show quadratic kw = {"tex_to_color_map": { "{a}": BLUE_B, "{b}": BLUE_C, "{c}": BLUE_D, "{d}": TEAL_E, "{e}": TEAL_D, "{f}": TEAL_C, "{p}": BLUE_B, "{q}": BLUE_C, "\\text{root}": YELLOW, "r_1": YELLOW, "r_2": YELLOW, "+": WHITE, "-": WHITE, }} quadratic.generate_target() quadratic.target.set_height(6) quadratic.target.center().to_edge(LEFT) equation = OldTex("{a}x^2 + {b}x + {c} = 0", **kw) equation.next_to(quadratic.target, UP) form = OldTex( "r_1, r_2 = {-{b} \\pm \\sqrt{\\,{b}^2 - 4{a}{c}} \\over 2{a}}", **kw ) form.next_to(quadratic.target, RIGHT, buff=MED_LARGE_BUFF) form_name = Text("Quadratic formula") form_name.match_width(form) form_name.next_to(form, UP, LARGE_BUFF) randy = Randolph(height=2) randy.flip() randy.next_to(form, RIGHT) randy.align_to(quadratic.target, DOWN) randy.shift_onto_screen() self.play( MoveToTarget(quadratic), Write(equation), *map(FadeOut, plots[1:]), FadeIn(randy), ) self.play(randy.change("hooray")) self.play( TransformMatchingShapes( VGroup(*( equation.get_part_by_tex(f"{{{c}}}") for c in "abc" )).copy(), form, lag_ratio=0, run_time=2, ), randy.animate.look_at(form), FadeIn(form_name), FlashAround(form_name), ) self.play(Blink(randy)) self.wait() # Coco sidenote form_group = VGroup(form_name, form) form_group.save_state() form_group.set_stroke(BLACK, 5, background=True) plot_group = Group(quadratic, equation) plot_group.save_state() self.play( plot_group.animate.shift(4 * LEFT).set_opacity(0), form_group.animate.to_corner(UR), FadeOut(randy), ) pixar_image = ImageMobject("PixarCampus") pixar_image.set_height(FRAME_HEIGHT + 4) pixar_image.to_corner(UL, buff=0) pixar_image.shift(LEFT) pixar_image.add_updater(lambda m, dt: m.shift(0.1 * dt * LEFT)) coco_logo = ImageMobject("Coco_logo") coco_logo.set_width(4) coco_logo.match_y(form) coco_logo.to_edge(RIGHT, buff=LARGE_BUFF) arrow = Arrow(form.copy().to_edge(LEFT), coco_logo, buff=0.3, stroke_width=10) self.add(pixar_image, *self.mobjects) self.play(FadeIn(pixar_image)) self.wait(6) self.add(coco_logo, *self.mobjects) self.play( FadeOut(pixar_image), form_group.animate.to_corner(UL), FadeIn(randy), ShowCreation(arrow), FadeIn(coco_logo), ) over_trillion = OldTex("> 1{,}000{,}000{,}000{,}000")[0] over_trillion.next_to(form, RIGHT) over_trillion.shift(3 * DOWN) form_copies = form[4:].replicate(50) self.play( ShowIncreasingSubsets(over_trillion, run_time=1), randy.change("thinking", over_trillion), LaggedStart(*( FadeOut(form_copy, 4 * DOWN) for form_copy in form_copies ), lag_ratio=0.15, run_time=5) ) self.play( FadeOut(over_trillion), FadeOut(coco_logo), FadeOut(arrow), randy.change("happy"), Restore(form_group), Restore(plot_group), ) self.embed() # Cubic low_fade_rect = BackgroundRectangle( Group(quartic, quintic), buff=0.01, fill_opacity=0.95, ) cubic_eq = OldTex("x^3 + {p}x + {q} = 0", **kw) cubic_eq.next_to(cubic, LEFT, LARGE_BUFF, aligned_edge=UP) cubic_eq.shift_onto_screen() cubic_name = OldTexText("Cubic\\\\", "Formula") cubic_name.to_corner(UL) cubic_form = OldTex( "\\text{root}", "=", "\\sqrt[3]{\\,-{{q} \\over 2} + \\sqrt{\\, {{q}^2 \\over 4} + {{p}^3 \\over 27}} }+", "\\sqrt[3]{\\,-{{q} \\over 2} - \\sqrt{\\, {{q}^2 \\over 4} + {{p}^3 \\over 27}} }", **kw, ) cubic_form.set_width(7) cubic_form.next_to(cubic_eq, DOWN, buff=1.25) cubic_form.to_edge(LEFT) cubic_arrow = Arrow( cubic_eq, cubic_form, stroke_width=5, buff=0.1, ) self.add(*plots, randy) self.play( Restore(quadratic), *map(FadeIn, plots[1:]), FadeOut(form), FadeOut(form_name), FadeOut(equation), randy.change("plain"), ) self.play(randy.change("erm", cubic)) self.wait() self.play( FadeOut(quadratic), FadeIn(low_fade_rect), Write(cubic_eq), FadeIn(cubic_name), ) self.play( ShowCreation(cubic_arrow), FadeIn(cubic_form, DOWN), randy.change("confused", cubic_name), ) self.play(Blink(randy)) # Quartic quartic_name = OldTexText("Quartic ", "Formula") quartic_name.move_to(quartic).to_edge(UP) cubic_fade_rect = BackgroundRectangle(cubic, buff=0.01, fill_opacity=0.95) quartic_eq = OldTex("{a}x^4 + {b}x^3 + {c}x^2 + {d}x + {e} = 0", **kw) quartic_eq.next_to(quartic, UP) main_form = OldTex(r"r_{i}&=-\frac{b}{4 a}-S \pm \frac{1}{2} \sqrt{-4 S^{2}-2 p \pm \frac{q}{S}}") details = OldTex(r""" &\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\\\\ """) main_form.match_width(quartic_eq) main_form.move_to(VGroup(quartic_name, quartic_eq)) details.scale(0.5) details.to_corner(UR) details.set_stroke(BLACK, 3, background=True) self.play( FadeOut(cubic_eq), FadeOut(cubic_form), FadeOut(cubic_arrow), FadeIn(cubic_fade_rect), FadeTransform(cubic_name[0], quartic_name[0]), FadeTransform(cubic_name[1], quartic_name[1]), randy.change("erm", quartic_name), low_fade_rect.animate.replace(quintic, stretch=True).scale(1.01), FadeIn(quartic_eq), ) self.play(Write(main_form)) self.wait() self.play( randy.change("horrified", details), Write(details, run_time=5) ) self.play(randy.animate.look_at(details.get_bottom())) self.play(Blink(randy)) self.wait() # Quintic quintic.generate_target() quintic.target.set_height(5) quintic.target.to_corner(UL).shift(DOWN) quintic_eq = OldTex( "{a}x^5 + {b}x^4 + {c}x^3 + {d}x^2 + {e}x + {f}", **kw ) quintic_eq.match_width(quintic.target) quintic_eq.next_to(quintic.target, UP) quintic_name = Text("Quintic formula?", font_size=60) quintic_name.move_to(3 * RIGHT) quintic_name.to_edge(UP) subwords = VGroup( OldTexText("There is none.", "$^*$"), OldTexText("And there never can be."), ) subwords.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) subwords.next_to(quintic_name, DOWN, LARGE_BUFF, aligned_edge=LEFT) footnote = OldTex( "^*\\text{Using }", "+,\\,", "-,\\,", "\\times,\\,", "/,\\,", "\\sqrt[n]{\\quad},\\,", "\\text{exp},\\,", "\\log,\\,", "\\sin,\\,", "\\cos,\\,", "etc.\\\\", font_size=36, alignment="", ) footnote.set_color(GREY_A) footnote.next_to(subwords, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT) footnote.shift_onto_screen(buff=MED_SMALL_BUFF) self.play( FadeOut(cubic), FadeOut(quartic), FadeOut(quartic_eq), FadeOut(main_form), FadeOut(details), FadeTransform(quartic_name, quintic_name), MoveToTarget(quintic), UpdateFromFunc( low_fade_rect, lambda m: m.replace(quintic, stretch=True), ), VFadeOut(low_fade_rect), randy.change("tease", quintic_name), FadeIn(quintic_eq), ) self.play(Blink(randy)) self.wait() self.play( FadeIn(subwords[0][0], 0.5 * DOWN), randy.change("erm", subwords), ) self.wait() self.play(FadeIn(subwords[1], 0.5 * DOWN)) self.wait() self.play( FadeIn(subwords[0][1]), LaggedStartMap(FadeIn, footnote, run_time=6, lag_ratio=0.5), randy.change("pondering", footnote) ) self.play(Blink(randy)) self.wait() def get_plot(self, coefs, scalar=1.0, color=YELLOW, stroke_width=3, height=3.5, bound=10): axes = NumberPlane( (-bound, bound, 5), (-bound, bound, 5), faded_line_ratio=4, background_line_style={ "stroke_width": 1.0, "stroke_color": GREY_A, } ) axes.set_height(height) axes.add_coordinate_labels( x_values=[-5, 0, 5, 10], y_values=[-5, 5, 10], font_size=16, excluding=[], ) def f(x): return scalar * poly(x, coefs) x_min = binary_search( lambda x: abs(f(x)), bound, -bound, 0 ) x_max = binary_search( lambda x: abs(f(x)), bound, 0, bound, ) graph = axes.get_graph(f, x_range=(x_min, x_max)) graph.set_stroke(color, stroke_width) roots = [ root.real for root in coefficients_to_roots(coefs) if np.isclose(root.imag, 0) ] def get_glow_dot(point): result = DotCloud([point] * 10) result.set_radii([ interpolate(0.03, 0.06, t**2) for t in np.linspace(0, 1, 10) ]) result.set_opacity(0.2) result.set_color(YELLOW) return result root_dots = Group(*( get_glow_dot(axes.c2p(root, 0)) for root in roots )) result = Group(axes, graph, root_dots) return result class ComingVideoWrapper(VideoWrapper): animate_boundary = False title = "Unsolvability of the Quintic (future topic?)" class QuinticAppletPlay(ExternallyAnimatedScene): pass class AskAboutFractals(TeacherStudentsScene): def construct(self): self.screen.set_height(4, about_edge=UL) self.screen.set_fill(BLACK, 1) self.add(self.screen) self.student_says( "Fractals?", target_mode="raise_right_hand", index=2, added_anims=[ self.students[0].change("confused"), self.students[1].change("sassy"), ] ) self.wait() self.teacher_says( OldTexText("We're getting\\\\there"), bubble_config={ "height": 3, "width": 4, }, target_mode="happy" ) self.play_all_student_changes( "pondering", look_at=self.screen ) self.wait(2) class RealNewtonsMethod(Scene): coefs = [-0.2, -1, 1, 0, 0, 1] poly_tex = "x^5 + x^2 - x - 0.2" dpoly_tex = "5x^4 + 2x - 1" seed = 1.3 graph_x_range = (-1.5, 1.5) axes_config = { "x_range": (-2, 2, 0.2), "y_range": (-2, 6, 0.2), "height": 8, "width": 8, "axis_config": { "tick_size": 0.05, "longer_tick_multiple": 2.0, "tick_offset": 0, # Change name "big_tick_numbers": list(range(-2, 3)), "include_tip": False, } } graph_color = BLUE_C guess_color = YELLOW rule_font_size = 42 n_search_steps = 5 def construct(self): self.add_graph() self.add_title(self.axes) self.draw_graph() self.highlight_roots() self.preview_iterative_root_finding() self.introduce_step() self.find_root() def add_graph(self): axes = self.axes = Axes(**self.axes_config) axes.to_edge(RIGHT) axes.add_coordinate_labels( np.arange(*self.axes.x_range[:2]), np.arange(self.axes.y_range[0] + 1, self.axes.y_range[1]), ) self.add(axes) graph = self.graph = axes.get_graph( lambda x: poly(x, self.coefs), x_range=self.graph_x_range, ) graph.set_color(self.graph_color) self.add(graph) def add_title(self, axes, opacity=0): title = OldTexText("Newton's method", font_size=60) title.move_to(midpoint(axes.get_left(), LEFT_SIDE)) title.to_edge(UP) title.set_opacity(opacity) poly = OldTex(f"P({self.poly_tex[0]}) = ", self.poly_tex, "= 0 ") poly.match_width(title) poly.next_to(title, DOWN, buff=MED_LARGE_BUFF) poly.set_fill(GREY_A) title.add(poly) self.title = title self.poly = poly self.add(title) def draw_graph(self): rect = SurroundingRectangle(self.poly[:-1]) rect.set_stroke(self.graph_color, 2) self.play( FlashAround(self.poly[:-1], color=self.graph_color, run_time=2), ShowCreation(rect, run_time=2), ShowCreation(self.graph, run_time=4), ) self.wait() self.play( rect.animate.replace(self.poly[-1], stretch=True).scale(1.2) ) self.wait() self.play(FadeOut(rect)) def highlight_roots(self): roots = coefficients_to_roots(self.coefs) real_roots = [ root.real for root in roots if abs(root.imag) < 1e-6 ] real_roots.sort() dots = VGroup(*( # Dot(self.axes.c2p(r, 0), radius=0.05) glow_dot(self.axes.c2p(r, 0)) for r in real_roots )) squares = VGroup(*[ Square().set_height(0.25).move_to(dot) for dot in dots ]) squares.set_stroke(YELLOW, 3) squares.set_fill(opacity=0) self.play( LaggedStart( *[ FadeIn(dot, scale=0.1) for dot in dots ] + [ VShowPassingFlash(square, time_width=2.0, run_time=2) for square in squares ], lag_ratio=0.15 ), ) self.wait() # Show values numerically root_strs = ["{0:.4}".format(root) for root in real_roots] equations = VGroup(*( OldTex( "P(", root_str, ")", "=", "0", font_size=self.rule_font_size ).set_color_by_tex(root_str, YELLOW) for root_str in root_strs )) equations.arrange(DOWN, buff=0.5, aligned_edge=LEFT) equations.next_to(self.poly, DOWN, LARGE_BUFF, aligned_edge=LEFT) question = Text("How do you\ncompute these?") question.next_to(equations, RIGHT, buff=LARGE_BUFF) question.set_color(YELLOW) arrows = VGroup(*( Arrow( question.get_corner(UL) + 0.2 * DL, eq[1].get_corner(UR) + 0.25 * LEFT, path_arc=arc, stroke_width=3, buff=0.2, ) for eq, arc in zip(equations, [0.7 * PI, 0.5 * PI, 0.0 * PI]) )) arrows.set_color(YELLOW) self.play( LaggedStartMap(FadeIn, equations, lag_ratio=0.25), LaggedStart(*( FadeTransform(dot.copy(), eq[1]) for dot, eq in zip(dots, equations) ), lag_ratio=0.25) ) self.wait() self.play( Write(question), Write(arrows) ) self.wait() self.play(LaggedStart( FadeOut(dots), FadeOut(question), FadeOut(arrows), FadeOut(equations), lag_ratio=0.25 )) self.wait() def preview_iterative_root_finding(self): axes = self.axes axis = axes.x_axis coefs = self.coefs n_steps = 5 root_seekers = VGroup(*( ArrowTip().set_height(0.2).rotate(-PI / 2).move_to(axis.n2p(x), DOWN) for x in np.arange(-2, 2.0, 0.2)[:-1] )) root_seekers.set_stroke(YELLOW, 2, opacity=0.5) root_seekers.set_fill(YELLOW, opacity=0.3) words = Text("Approximate\nSolutions", alignment="\\flushleft") words.move_to(axes.c2p(0, 3)) words.align_to(axis, LEFT) words.set_color(YELLOW) self.play( FadeIn(root_seekers, lag_ratio=0.1), Write(words), ) for n in range(n_steps): for rs in root_seekers: rs.generate_target() x = axis.p2n(rs.get_center()) if n == 0 and abs(x - 0.4) < 0.1: x = 0.6 new_x = x - poly(x, coefs) / dpoly(x, coefs) rs.target.set_x(axis.n2p(new_x)[0]) self.play(*map(MoveToTarget, root_seekers), run_time=1.0) self.wait() values = VGroup(*( DecimalNumber( axis.p2n(rs.get_center()), num_decimal_places=5, show_ellipsis=True, ).next_to(rs, UP, SMALL_BUFF) for rs in root_seekers[0::len(root_seekers) // 2] )) values.set_fill(YELLOW) values.set_stroke(BLACK, 8, background=True) last_value = VMobject() for value in values: self.play( FadeIn(value), FadeOut(last_value) ) self.wait(0.5) last_value = value self.play(FadeOut(last_value)) self.play( FadeOut(words), FadeOut(root_seekers), ) def introduce_step(self): axes = self.axes graph = self.graph # Add labels guess_label = OldTex( "\\text{Guess: } x_0 = " + f"{self.seed}", tex_to_color_map={"x_0": YELLOW} ) guess_label.next_to(self.poly, DOWN, LARGE_BUFF) guess_marker, guess_value, guess_tracker = self.get_guess_group() get_guess = guess_tracker.get_value self.play(self.title.animate.set_opacity(1)) self.wait() self.play(Write(guess_label)) self.play( FadeTransform( guess_label[1].copy(), VGroup(guess_marker, guess_value) ) ) self.wait() # Add lines v_line = axes.get_v_line(axes.i2gp(get_guess(), graph)) tan_line = self.get_tan_line(get_guess()) v_line_label = OldTex("P(x_0)", font_size=30, fill_color=GREY_A) v_line_label.next_to(v_line, RIGHT, SMALL_BUFF) self.add(v_line, guess_marker, guess_value) self.play(ShowCreation(v_line)) self.play(FadeIn(v_line_label, 0.2 * RIGHT)) self.wait() self.play( ShowCreation(tan_line), graph.animate.set_stroke(width=2), ) # Mention next guess next_guess_label = Text("Next guess", font_size=30) next_guess_label.set_color(RED) next_guess_label.next_to(axes.c2p(0, 0), RIGHT, MED_LARGE_BUFF) next_guess_label.shift(UP) next_guess_arrow = Arrow(next_guess_label, tan_line.get_start(), buff=0.1) next_guess_arrow.set_stroke(RED, 3) coord = axes.coordinate_labels[0][-1] coord_copy = coord.copy() coord.set_opacity(0) self.play( coord_copy.animate.scale(0), ShowCreation(next_guess_arrow), FadeIn(next_guess_label), ) self.wait() # Show derivative dpoly = OldTex("P'(x) = ", self.dpoly_tex) dpoly.match_height(self.poly) dpoly.match_style(self.poly) dpoly.next_to(self.poly, DOWN, aligned_edge=LEFT) self.play( FadeIn(dpoly, 0.5 * DOWN), guess_label.animate.shift(0.25 * DOWN) ) self.play(FlashAround(dpoly)) self.wait() # Show step step_arrow = Arrow(v_line.get_start(), tan_line.get_start(), buff=0) step_arrow.set_stroke(GREY_A, 3) step_arrow.shift(0.1 * UP) step_word = Text("Step", font_size=24) step_word.set_stroke(BLACK, 3, background=True) step_word.next_to(step_arrow, UP, SMALL_BUFF) self.play( ShowCreation(step_arrow), FadeIn(step_word) ) self.wait() # Show slope slope_eq_texs = [ "P'(x_0) = {P(x_0) \\over -\\text{Step}}", "\\text{Step} = -{P(x_0) \\over P'(x_0)}", ] slope_eqs = [ OldTex( tex, isolate=[ "P'(x_0)", "P(x_0)", "\\text{Step}", "-" ], font_size=self.rule_font_size, ) for tex in slope_eq_texs ] for slope_eq in slope_eqs: slope_eq.set_fill(GREY_A) slope_eq.set_color_by_tex("Step", WHITE) slope_eq.next_to(guess_label, DOWN, LARGE_BUFF) rule = self.rule = self.get_update_rule() rule.next_to(guess_label, DOWN, LARGE_BUFF) for line in [v_line, Line(tan_line.get_start(), v_line.get_start())]: self.play( VShowPassingFlash( Line(line.get_start(), line.get_end()).set_stroke(YELLOW, 10).insert_n_curves(20), time_width=1.0, run_time=1.5 ) ) self.wait() self.play( FadeTransform(v_line_label.copy(), slope_eqs[0].get_part_by_tex("P(x_0)")), FadeTransform(step_word.copy(), slope_eqs[0].get_part_by_tex("\\text{Step}")), FadeIn(slope_eqs[0][3:5]), ) self.wait() self.play(FadeIn(slope_eqs[0][:2])) self.wait() self.play(TransformMatchingTex(*slope_eqs, path_arc=PI / 2)) self.wait() self.play( FadeIn(rule), slope_eqs[1].animate.to_edge(DOWN) ) self.wait() # Transition to x1 self.add(tan_line, guess_value) self.play( FadeOut(next_guess_label), FadeOut(next_guess_arrow), FadeOut(step_word), FadeOut(step_arrow), FadeOut(v_line), FadeOut(v_line_label), guess_tracker.animate.set_value(self.get_next_guess(get_guess())), ) self.play(FadeOut(tan_line)) def find_root(self, cycle_run_time=1.0): for n in range(self.n_search_steps): self.play(*self.cycle_rule_entries_anims(), run_time=cycle_run_time) self.step_towards_root() def step_towards_root(self, fade_tan_with_vline=False, added_anims=None): guess = self.guess_tracker.get_value() next_guess = self.get_next_guess(guess) v_line = self.axes.get_v_line(self.axes.i2gp(guess, self.graph)) tan_line = self.get_tan_line(guess) self.add(v_line, tan_line, self.guess_marker, self.guess_value) self.play( ShowCreation(v_line), GrowFromCenter(tan_line) ) anims = [ FadeOut(v_line), self.guess_tracker.animate.set_value(next_guess) ] if added_anims is not None: anims += added_anims tan_fade = FadeOut(tan_line) if fade_tan_with_vline: self.play(*anims, tan_fade) else: self.play(*anims) self.play(tan_fade) # def get_guess_group(self): axes = self.axes guess_tracker = ValueTracker(self.seed) get_guess = guess_tracker.get_value guess_marker = Triangle(start_angle=PI / 2) guess_marker.set_height(0.1) guess_marker.set_width(0.1, stretch=True) guess_marker.set_fill(self.guess_color, 1) guess_marker.set_stroke(width=0) guess_marker.add_updater(lambda m: m.move_to( axes.c2p(get_guess(), 0), UP )) guess_value = DecimalNumber(0, num_decimal_places=3, font_size=24) def update_guess_value(gv): gv.set_value(get_guess()) gv.next_to(guess_marker, DOWN, SMALL_BUFF) gv.set_fill(self.guess_color) gv.set_stroke(BLACK, 3, background=True) return gv guess_value.add_updater(update_guess_value) self.guess_tracker = guess_tracker self.guess_marker = guess_marker self.guess_value = guess_value return (guess_marker, guess_value, guess_tracker) def get_next_guess(self, curr_guess): x = curr_guess return x - poly(x, self.coefs) / dpoly(x, self.coefs) def get_tan_line(self, curr_guess): next_guess = self.get_next_guess(curr_guess) start = self.axes.c2p(next_guess, 0) end = self.axes.i2gp(curr_guess, self.graph) line = Line(start, start + 2 * (end - start)) line.set_stroke(RED, 3) return line def get_update_rule(self, char="x"): rule = OldTex( """ z_1 = z_0 - {P(z_0) \\over P'(z_0)} """.replace("z", char), tex_to_color_map={ f"{char}_1": self.guess_color, f"{char}_0": self.guess_color }, font_size=self.rule_font_size, ) rule.n = 0 rule.zns = rule.get_parts_by_tex(f"{char}_0") rule.znp1 = rule.get_parts_by_tex(f"{char}_1") return rule def cycle_rule_entries_anims(self): rule = self.rule rule.n += 1 char = rule.get_tex().strip()[0] zns = VGroup() for old_zn in rule.zns: zn = OldTex(f"{char}_{{{rule.n}}}", font_size=self.rule_font_size) zn[0][1:].set_max_width(0.2, about_edge=DL) zn.move_to(old_zn) zn.match_color(old_zn) zns.add(zn) znp1 = OldTex(f"{char}_{{{rule.n + 1}}}", font_size=self.rule_font_size) znp1.move_to(rule.znp1) znp1.match_color(rule.znp1[0]) result = ( FadeOut(rule.zns), FadeTransformPieces(rule.znp1, zns), FadeIn(znp1, 0.5 * RIGHT) ) rule.zns = zns rule.znp1 = znp1 return result class FasterNewtonExample(RealNewtonsMethod): coefs = [0.1440, -1.0, 1.2, 1] poly_tex = "x^3 + 1.2x^2 - x + 0.144" dpoly_tex = "3x^2 + 2.4x - 1" n_search_steps = 6 graph_x_range = (-2, 2) seed = 1.18 axes_config = { "x_range": (-2, 2, 0.2), "y_range": (-1, 3, 0.2), "height": 8, "width": 8, "axis_config": { "tick_size": 0.05, "longer_tick_multiple": 2.0, "tick_offset": 0, # Change name "big_tick_numbers": list(range(-2, 3)), "include_tip": False, } } def construct(self): self.add_graph() self.add_title(self.axes) self.draw_graph() self.introduce_step() self.find_root() def find_root(self, cycle_run_time=1.0): for n in range(self.n_search_steps): self.step_towards_root( added_anims=self.cycle_rule_entries_anims(), fade_tan_with_vline=True ) class AssumingItsGood(TeacherStudentsScene): def construct(self): self.pi_creatures.refresh_triangulation() self.teacher_says( OldTexText("Assuming this\\\\approximation\\\\is decent...", font_size=42), bubble_config={ "height": 3, "width": 4, } ) self.play_student_changes( "pondering", "pondering", "tease", look_at=self.screen ) self.pi_creatures.refresh_triangulation() self.wait(3) class PauseAndPonder(TeacherStudentsScene): def construct(self): self.teacher_says("Pause and\nponder", target_mode="hooray") self.play_all_student_changes("thinking", look_at=self.screen) self.wait(4) class AltPauseAndPonder(Scene): def construct(self): morty = Mortimer(height=2) morty.flip().to_corner(DL) self.play(PiCreatureSays( morty, OldTexText("Pause and\\\\Ponder", font_size=36), target_mode="hooray", bubble_config={ "height": 2, "width": 3, } )) self.play(Blink(morty)) self.wait(2) self.play(morty.change("thinking")) self.play(Blink(morty)) self.wait() class WhatIsThis(Scene): def construct(self): words = Text("What is this", color=RED) arrow = Vector(UR) arrow.set_color(RED) words.next_to(ORIGIN, DOWN) self.play(FadeIn(words, lag_ratio=0.1), ShowCreation(arrow)) self.wait() class GutCheckFormula(RealNewtonsMethod): seed = 5.0 def construct(self): self.add_axes_and_graph() self.add_rule() self.add_guess() self.sample_values() def add_axes_and_graph(self): axes = NumberPlane( (-2, 15), (-2, 8), faded_line_ratio=1, background_line_style={ "stroke_opacity": 0.5, "stroke_color": GREY, } ) axes.to_corner(DL, buff=0) axes.add_coordinate_labels(font_size=16, fill_opacity=0.5) axes.x_axis.numbers.next_to(axes.x_axis, UP, buff=0.05) self.add(axes) roots = [-1, 3, 4.5] coefs = 0.1 * np.array(roots_to_coefficients(roots)) graph = axes.get_graph(lambda x: poly(x, coefs)) graph.set_stroke(BLUE, 3) self.add(graph) self.root_point = axes.c2p(roots[-1], 0) self.axes = axes self.graph = graph def add_rule(self): rule = OldTex( "x_{n + 1}", "=", "x_{n}", " - ", "{P(x) ", "\\over ", "P'(x)}" ) rule.set_stroke(BLACK, 5, background=True) rule.to_corner(UR) step_box = SurroundingRectangle(rule[3:], buff=0.1) step_box.set_stroke(YELLOW, 1.0) step_word = Text("Step size", font_size=36) step_word.set_color(YELLOW) step_word.next_to(step_box, DOWN) self.add(rule) self.add(step_box) self.add(step_word) self.rule = rule self.step_box = step_box self.step_word = step_word def add_guess(self, include_px=True): guess_group = self.get_guess_group() marker, value, tracker = guess_group self.guess_tracker = tracker def update_v_line(v_line): x = tracker.get_value() graph_point = self.graph.pfp( inverse_interpolate(*self.graph.x_range[:2], x) ) v_line.put_start_and_end_on( self.axes.c2p(x, 0), graph_point, ) v_line = Line() v_line.set_stroke(WHITE, 2) v_line.add_updater(update_v_line) self.add(*guess_group) self.add(v_line) if include_px: px_label = OldTex("P(x)", font_size=36) px_label.add_updater(lambda m: m.next_to(v_line, RIGHT, buff=0.05)) self.add(px_label) def sample_values(self): box = self.step_box rule = self.rule tracker = self.guess_tracker graph = self.graph words = Text("Gut check!") words.next_to(self.step_word, DOWN, LARGE_BUFF) words.shift(2 * LEFT) arrow = Arrow(words, self.rule) self.play( Write(words, run_time=1), ShowCreation(arrow), ) self.wait() self.play( FadeOut(words), FadeOut(arrow), FadeOut(self.step_word), box.animate.replace(rule[4], stretch=True).scale(1.2).set_stroke(width=2.0), ) self.play( tracker.animate.set_value(6.666), run_time=3, ) arrow = Arrow( self.axes.c2p(tracker.get_value(), 0), self.root_point, buff=0, stroke_color=RED, ) self.play(ShowCreation(arrow)) self.wait() # Large p_prime self.play( FadeOut(arrow), tracker.animate.set_value(5.0), ) self.play( graph.animate.stretch(8, 1, about_point=self.axes.c2p(0, 0)), box.animate.replace(self.rule[-1]).scale(1.2), run_time=3 ) self.wait() tan_line = self.get_tan_line(graph, tracker.get_value(), 15) self.play(ShowCreation(tan_line)) self.wait() def get_tan_line(self, graph, x, length=5, epsilon=1e-3): alpha = inverse_interpolate(*graph.x_range[:2], x) tan_line = Line( graph.pfp(alpha - epsilon), graph.pfp(alpha + epsilon), ) tan_line.set_length(length) tan_line.set_stroke(RED, 5) return tan_line class HistoryWithNewton(Scene): def construct(self): # Add title title = Text("Newton's method", font_size=60) title.to_edge(UP) self.add(title) # Add timeline time_range = (1620, 2020) timeline = NumberLine( (*time_range, 1), tick_size=0.025, longer_tick_multiple=4, big_tick_numbers=range(*time_range, 10), ) timeline.stretch(0.2 / timeline.get_unit_size(), 0) timeline_center = 2 * DOWN timeline.move_to(timeline_center) timeline.to_edge(RIGHT) timeline.add_numbers( range(*time_range, 10), group_with_commas=False, ) timeline.shift(timeline_center - timeline.n2p(1680)) self.add(timeline) # Newton newton = get_figure("Newton", "Isaac Newton", "1669") newton.next_to(title, DOWN, buff=0.5) newton.to_edge(LEFT, buff=1.5) newton_point = timeline.n2p(1669) newton_arrow = Arrow(newton_point, newton[0].get_right() + DOWN, path_arc=PI / 3) newton_words = Text("Overly\ncomplicated", font_size=36) newton_words.next_to(newton[0], RIGHT) raphson_point = timeline.n2p(1690) raphson = get_figure("Newton", "Joseph Raphson", "1690") raphson.move_to(newton) raphson.set_x(raphson_point[0] + 2) raphson[1].set_opacity(0) raphson_arrow = Arrow(raphson_point, raphson[0].get_left() + DOWN, path_arc=-PI / 3) raphson_word = Text("Simplified", font_size=36) raphson_word.next_to(raphson[0], LEFT) no_image_group = VGroup( Text("No image"), Text("(sorry)"), # Randolph(mode="shruggie", height=1) ) no_image_group[:2].set_fill(GREY) no_image_group.arrange(DOWN, buff=0.5) no_image_group.set_width(raphson[0].get_width() - 0.5) no_image_group.move_to(raphson[0]) self.add(newton, newton_arrow) frame = self.camera.frame frame.save_state() title.fix_in_frame() frame.move_to(timeline, RIGHT) self.play( frame.animate.match_width(timeline).set_x(timeline.get_center()[0]), run_time=2 ) self.play(Restore(frame, run_time=2)) # self.play( # GrowFromPoint(newton, newton_point), # ShowCreation(newton_arrow) # ) self.wait() self.play(Write(newton_words)) self.wait() self.play( GrowFromPoint(raphson, raphson_point), ShowCreation(raphson_arrow), ) self.play(LaggedStartMap(FadeIn, no_image_group, lag_ratio=0.2)) self.play(FadeIn(raphson_word)) self.wait() new_title = Text("Newton-Raphson method", font_size=60) new_title.to_edge(UP) self.play( FadeOut(title), TransformFromCopy( newton[2].get_part_by_text("Newton"), new_title.get_part_by_text("Newton"), ), TransformFromCopy( raphson[2].get_part_by_text("Raphson"), new_title.get_part_by_text("Raphson"), ), TransformFromCopy( title.get_part_by_text("method"), new_title.get_part_by_text("method"), ), FadeIn(new_title.get_part_by_text("-")) ) self.play(FlashAround(new_title, run_time=2)) self.wait() class CalcHomework(GutCheckFormula): seed = 3.0 def construct(self): # Title old_title = Text("Newton-Raphson method", font_size=60) old_title.to_edge(UP) title = Text("Calc 1", font_size=72) title.to_edge(UP, buff=MED_SMALL_BUFF) line = Underline(title) line.scale(2) line.set_stroke(WHITE, 2) self.add(old_title) # Axes axes = NumberPlane( x_range=(-5, 5, 1), y_range=(-8, 10, 2), height=6.5, width=FRAME_WIDTH, faded_line_ratio=4, background_line_style={ "stroke_color": GREY_C, "stroke_width": 1, } ) axes.to_edge(DOWN, buff=0) axes.add_coordinate_labels(font_size=18) self.add(axes) # Homework hw = OldTexText( "Homework:\\\\", "\\quad Approximate $\\sqrt{7}$ by hand using\\\\", "\\quad the ", "Newton-Raphson method.", alignment="", font_size=36, color=GREY_A, ) hw[1:].shift(MED_SMALL_BUFF * RIGHT + SMALL_BUFF * DOWN) hw.add_to_back( BackgroundRectangle(hw, fill_opacity=0.8, buff=0.25) ) hw.move_to(axes, UL) hw.to_edge(LEFT, buff=0) self.wait() self.play( FadeIn(hw, lag_ratio=0.1, run_time=2), FadeTransform( old_title, hw[-1] ), FadeIn(title), ShowCreation(line), ) self.wait() # Graph graph = axes.get_graph( lambda x: x**2 - 7, x_range=(-math.sqrt(17), math.sqrt(17)) ) graph.set_stroke(BLUE, 2) graph_label = OldTex("x^2 - 7", font_size=36) graph_label.set_color(BLUE) graph_label.next_to(graph.pfp(0.99), LEFT) self.add(graph, hw) self.play(ShowCreation(graph, run_time=3)) self.play(FadeIn(graph_label)) self.wait() # Marker axes.x_axis.numbers.remove(axes.x_axis.numbers[-3]) self.axes = axes self.graph = graph self.add_guess(include_px=False) self.wait() # Update tan_line = self.get_tan_line(graph, 3) tan_line.set_stroke(width=3) update_tex = OldTex( "3 \\rightarrow 3 - {3^2 - 7 \\over 2 \\cdot 3}", tex_to_color_map={"3": YELLOW}, font_size=28 ) update_tex.next_to(axes.c2p(1.2, 0), UR, buff=SMALL_BUFF) self.add(tan_line, self.guess_marker, self.guess_value) self.play( GrowFromCenter(tan_line), FadeIn(update_tex), ) self.wait() self.play( self.guess_tracker.animate.set_value(8 / 3), run_time=2 ) class RealNewtonsMethodHigherGraph(FasterNewtonExample): coefs = [1, -1, 1, 0, 0, 0.99] poly_tex = "x^5 + x^2 - x + 1" n_search_steps = 20 class FactorPolynomial(RealNewtonsMethodHigherGraph): def construct(self): self.add_graph() self.add_title(self.axes) self.show_factors() def show_factors(self): poly = self.poly colors = color_gradient((BLUE, YELLOW), 5) factored = OldTex( "P(x) = ", *( f"(x - r_{n})" for n in range(5) ), tex_to_color_map={ f"r_{n}": color for n, color in enumerate(colors) } ) factored.match_height(poly[0]) factored.next_to(poly, DOWN, LARGE_BUFF, LEFT) self.play( FadeTransform(poly.copy(), factored) ) self.wait() words = OldTexText("Potentially complex\\\\", "$r_n = a_n + b_n i$") words.set_color(GREY_A) words.next_to(factored, DOWN, buff=1.5) words.shift(LEFT) lines = VGroup(*( Line(words, part, buff=0.15).set_stroke(part.get_color(), 2) for n in range(5) for part in [factored.get_part_by_tex(f"r_{n}")] )) self.play( FadeIn(words[0]), Write(lines), ) self.play(FadeIn(words[1], 0.5 * DOWN)) self.wait() class TransitionToComplexPlane(RealNewtonsMethodHigherGraph): poly_tex = "z^5 + z^2 - z + 1" def construct(self): self.add_graph() self.add_title(self.axes) self.poly.save_state() self.poly.to_corner(UL) self.center_graph() self.show_example_point() self.separate_input_and_output() self.move_input_around_plane() def center_graph(self): shift_vect = DOWN - self.axes.c2p(0, 0) self.play( self.axes.animate.shift(shift_vect), self.graph.animate.shift(shift_vect), ) self.wait() def show_example_point(self): axes = self.axes input_tracker = ValueTracker(1) get_x = input_tracker.get_value def get_px(): return poly(get_x(), self.coefs) def get_graph_point(): return axes.c2p(get_x(), get_px()) marker = ArrowTip().set_height(0.1) input_marker = marker.copy().rotate(PI / 2) input_marker.set_color(YELLOW) output_marker = marker.copy() output_marker.set_color(MAROON_B) input_marker.add_updater(lambda m: m.move_to(axes.x_axis.n2p(get_x()), UP)) output_marker.add_updater(lambda m: m.shift(axes.y_axis.n2p(get_px()) - m.get_start())) v_line = always_redraw( lambda: axes.get_v_line(get_graph_point(), line_func=Line).set_stroke(YELLOW, 1) ) h_line = always_redraw( lambda: axes.get_h_line(get_graph_point(), line_func=Line).set_stroke(MAROON_B, 1) ) self.add( input_tracker, input_marker, output_marker, v_line, h_line, ) self.play(input_tracker.animate.set_value(-0.5), run_time=3) self.play(input_tracker.animate.set_value(1.0), run_time=3) self.play(ShowCreationThenFadeOut( axes.get_tangent_line(get_x(), self.graph).set_stroke(RED, 3) )) self.input_tracker = input_tracker self.input_marker = input_marker self.output_marker = output_marker self.v_line = v_line self.h_line = h_line def separate_input_and_output(self): axes = self.axes x_axis, y_axis = axes.x_axis, axes.y_axis graph = self.graph input_marker = self.input_marker output_marker = self.output_marker v_line = self.v_line h_line = self.h_line 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.x_axis, UP) output_word.rotate(PI / 2) output_word.next_to(out_plane.y_axis, RIGHT, buff=0.5) cl_copy = axes.coordinate_labels.copy() axes.coordinate_labels.set_opacity(0) self.play( *map(FadeOut, (v_line, h_line, graph, cl_copy)), ) for axis1, axis2 in [(x_axis, in_plane.x_axis), (y_axis, out_plane.y_axis)]: axis1.generate_target() axis1.target.scale(axis2.get_unit_size() / axis1.get_unit_size()) axis1.target.shift(axis2.n2p(0) - axis1.target.n2p(0)) self.play( MoveToTarget(x_axis), MoveToTarget(y_axis), FadeIn(input_word), FadeIn(output_word), ) self.wait() self.add(in_plane, input_marker) self.play( input_word.animate.next_to(in_plane, UP), x_axis.animate.set_stroke(width=0), Write(in_plane, lag_ratio=0.03), ) self.play( Rotate( VGroup(y_axis, output_word, output_marker), -PI / 2, about_point=out_plane.n2p(0) ) ) self.add(out_plane, output_marker) self.play( output_word.animate.next_to(out_plane, UP), y_axis.animate.set_stroke(width=0), Write(out_plane, lag_ratio=0.03), ) self.wait() self.in_plane = in_plane self.out_plane = out_plane self.input_word = input_word self.output_word = output_word def move_input_around_plane(self): in_plane = self.in_plane out_plane = self.out_plane input_marker = self.input_marker output_marker = self.output_marker in_dot, out_dot = [ Dot(radius=0.05).set_fill(marker.get_fill_color()).move_to(marker.get_start()) for marker in (input_marker, output_marker) ] in_dot.set_fill(YELLOW, 1) in_tracer = TracingTail(in_dot, stroke_color=in_dot.get_color()) out_tracer = TracingTail(out_dot, stroke_color=out_dot.get_color()) self.add(in_tracer, out_tracer) out_dot.add_updater(lambda m: m.move_to(out_plane.n2p( poly(in_plane.p2n(in_dot.get_center()), self.coefs) ))) z_label = OldTex("z", font_size=24) z_label.set_fill(YELLOW) z_label.add_background_rectangle() z_label.add_updater(lambda m: m.next_to(in_dot, UP, SMALL_BUFF)) pz_label = OldTex("P(z)", font_size=24) pz_label.set_fill(MAROON_B) pz_label.add_background_rectangle() pz_label.add_updater(lambda m: m.next_to(out_dot, UP, SMALL_BUFF)) self.play( *map(FadeOut, (input_marker, output_marker)), *map(FadeIn, (in_dot, out_dot)), FadeIn(z_label), FlashAround(z_label), ) self.play( FadeTransform(z_label.copy(), pz_label) ) z_values = [ complex(-0.5, 0.5), complex(-0.5, -0.5), complex(-0.25, 0.25), complex(0.5, -0.5), complex(0.5, 0.5), complex(1, 0.25), ] for z in z_values: self.play( in_dot.animate.move_to(in_plane.n2p(z)), run_time=2, path_arc=PI / 2 ) self.wait() self.remove(in_tracer, out_tracer) in_plane.generate_target() in_dot.generate_target() group = VGroup(in_plane.target, in_dot.target) group.set_height(8).center().to_edge(RIGHT, buff=0), self.play( MoveToTarget(in_plane), MoveToTarget(in_dot), FadeOut(self.input_word), FadeOut(self.output_word), FadeOut(out_plane), FadeOut(out_dot), FadeOut(pz_label), self.poly.animate.restore().shift(0.32 * RIGHT), ) class ComplexNewtonsMethod(RealNewtonsMethod): coefs = [1, -1, 1, 0, 0, 1] poly_tex = "z^5 + z^2 - z + 1" plane_config = { "x_range": (-2, 2), "y_range": (-2, 2), "height": 8, "width": 8, } seed = complex(-0.5, 0.5) seed_tex = "-0.5 + 0.5i" guess_color = YELLOW pz_color = MAROON_B step_arrow_width = 5 step_arrow_opacity = 1.0 step_arrow_len = None n_search_steps = 9 def construct(self): self.add_plane() self.add_title() self.add_z0_def() self.add_pz_dot() self.add_rule() self.find_root() def add_plane(self): plane = ComplexPlane(**self.plane_config) plane.add_coordinate_labels(font_size=24) plane.to_edge(RIGHT, buff=0) self.plane = plane self.add(plane) def add_title(self, opacity=1): super().add_title(self.plane, opacity) def add_z0_def(self): seed_text = Text("(Arbitrary seed)") z0_def = OldTex( f"z_0 = {self.seed_tex}", tex_to_color_map={"z_0": self.guess_color}, font_size=self.rule_font_size ) z0_group = VGroup(seed_text, z0_def) z0_group.arrange(DOWN) z0_group.next_to(self.title, DOWN, buff=LARGE_BUFF) guess_dot = Dot(self.plane.n2p(self.seed), color=self.guess_color) guess = DecimalNumber(self.seed, num_decimal_places=3, font_size=30) guess.add_updater( lambda m: m.set_value(self.plane.p2n( guess_dot.get_center() )).set_fill(self.guess_color).add_background_rectangle() ) guess.add_updater(lambda m: m.next_to(guess_dot, UP, buff=0.15)) self.play( Write(seed_text, run_time=1), FadeIn(z0_def), ) self.play( FadeTransform(z0_def[0].copy(), guess_dot), FadeIn(guess), ) self.wait() self.z0_group = z0_group self.z0_def = z0_def self.guess_dot = guess_dot self.guess = guess def add_pz_dot(self): plane = self.plane guess_dot = self.guess_dot def get_pz(): z = plane.p2n(guess_dot.get_center()) return poly(z, self.coefs) pz_dot = Dot(color=self.pz_color) pz_dot.add_updater(lambda m: m.move_to(plane.n2p(get_pz()))) pz_label = OldTex("P(z)", font_size=24) pz_label.set_color(self.pz_color) pz_label.add_background_rectangle() pz_label.add_updater(lambda m: m.next_to(pz_dot, UL, buff=0)) self.play( FadeTransform(self.poly[0].copy(), pz_label), FadeIn(pz_dot), ) self.wait() def add_rule(self): self.rule = rule = self.get_update_rule("z") rule.next_to(self.z0_group, DOWN, buff=LARGE_BUFF) self.play( FadeTransformPieces(self.z0_def[0].copy(), rule.zns), FadeIn(rule), ) self.wait() def find_root(self): for x in range(self.n_search_steps): self.root_search_step() def root_search_step(self): dot = self.guess_dot dot_step_anims = self.get_dot_step_anims(VGroup(dot)) diff_rect = SurroundingRectangle( self.rule.slice_by_tex("-"), buff=0.1, stroke_color=GREY_A, stroke_width=1, ) self.play( ShowCreation(diff_rect), dot_step_anims[0], ) self.play( dot_step_anims[1], FadeOut(diff_rect), *self.cycle_rule_entries_anims(), run_time=2 ) self.wait() def get_dot_step_anims(self, dots): plane = self.plane arrows = VGroup() dots.generate_target() for dot, dot_target in zip(dots, dots.target): try: z0 = plane.p2n(dot.get_center()) pz = poly(z0, self.coefs) dpz = dpoly(z0, self.coefs) if abs(pz) < 1e-3: z1 = z0 else: if dpz == 0: dpz = 0.1 # ??? z1 = z0 - pz / dpz if np.isnan(z1): z1 = z0 arrow = Arrow( plane.n2p(z0), plane.n2p(z1), buff=0, stroke_width=self.step_arrow_width, storke_opacity=self.step_arrow_opacity, ) if self.step_arrow_len is not None: if arrow.get_length() > self.step_arrow_len: arrow.set_length(self.step_arrow_len, about_point=arrow.get_start()) if not hasattr(dot, "history"): dot.history = [dot.get_center().copy()] dot.history.append(plane.n2p(z1)) arrows.add(arrow) dot_target.move_to(plane.n2p(z1)) except ValueError: pass return [ ShowCreation(arrows, lag_ratio=0), AnimationGroup( MoveToTarget(dots), FadeOut(arrows), ) ] class OutputIsZero(Scene): def construct(self): words = OldTexText("Output $\\approx 0$") words.set_stroke(BLACK, 5, background=True) arrow = Vector(0.5 * UL) words.next_to(arrow, DR) words.shift(0.5 * LEFT) self.play( Write(words), ShowCreation(arrow) ) self.wait() class FunPartWords(Scene): def construct(self): text = OldTexText("Now here's \\\\ the fun part", font_size=72) self.add(text) class ComplexNewtonsMethodManySeeds(ComplexNewtonsMethod): dot_radius = 0.035 dot_color = WHITE dot_opacity = 0.8 step_arrow_width = 3 step_arrow_opacity = 0.1 step_arrow_len = 0.15 plane_config = { "x_range": (-2, 2), "y_range": (-2, 2), "height": 8, "width": 8, } step = 0.2 n_search_steps = 20 colors = ROOT_COLORS_BRIGHT def construct(self): self.add_plane() self.add_title() self.add_z0_def() self.add_rule() self.add_true_root_circles() self.find_root() self.add_color() def add_z0_def(self): seed_text = Text("Many seeds: ") z0_def = OldTex( "z_0", tex_to_color_map={"z_0": self.guess_color}, font_size=self.rule_font_size ) z0_group = VGroup(seed_text, z0_def) z0_group.arrange(RIGHT) z0_group.next_to(self.title, DOWN, buff=LARGE_BUFF) x_range = self.plane_config["x_range"] y_range = self.plane_config["y_range"] step = self.step x_vals = np.arange(x_range[0], x_range[1] + step, step) y_vals = np.arange(y_range[0], y_range[1] + step, step) guess_dots = VGroup(*( Dot( self.plane.c2p(x, y), radius=self.dot_radius, fill_opacity=self.dot_opacity, ) for i, x in enumerate(x_vals) for y in (y_vals if i % 2 == 0 else reversed(y_vals)) )) guess_dots.set_submobject_colors_by_gradient(WHITE, GREY_B) guess_dots.set_fill(opacity=self.dot_opacity) guess_dots.set_stroke(BLACK, 2, background=True) self.play( Write(seed_text, run_time=1), FadeIn(z0_def), ) self.play( LaggedStart(*( FadeTransform(z0_def[0].copy(), guess_dot) for guess_dot in guess_dots ), lag_ratio=0.1 / len(guess_dots)), run_time=3 ) self.add(guess_dots) self.wait() self.z0_group = z0_group self.z0_def = z0_def self.guess_dots = guess_dots def add_true_root_circles(self): roots = coefficients_to_roots(self.coefs) root_points = list(map(self.plane.n2p, roots)) colors = self.colors root_circles = VGroup(*( Dot(radius=0.1).set_fill(color, opacity=0.75).move_to(rp) for rp, color in zip(root_points, colors) )) self.play( LaggedStart(*( FadeIn(rc, scale=0.5) for rc in root_circles ), lag_ratio=0.7, run_time=1), ) self.wait() self.root_circles = root_circles def root_search_step(self): dots = self.guess_dots dot_step_anims = self.get_dot_step_anims(dots) self.play(dot_step_anims[0], run_time=0.25) self.play( dot_step_anims[1], *self.cycle_rule_entries_anims(), run_time=1 ) def add_color(self): root_points = [circ.get_center() for circ in self.root_circles] colors = [circ.get_fill_color() for circ in self.root_circles] dots = self.guess_dots dots.generate_target() for dot, dot_target in zip(dots, dots.target): dc = dot.get_center() dot_target.set_color(colors[ np.argmin([get_norm(dc - rp) for rp in root_points]) ]) rect = SurroundingRectangle(self.rule) rect.set_fill(BLACK, 1) rect.set_stroke(width=0) self.play( FadeIn(rect), MoveToTarget(dots) ) self.wait() len_history = max([len(dot.history) for dot in dots if hasattr(dot, "history")], default=0) for n in range(len_history): dots.generate_target() for dot, dot_target in zip(dots, dots.target): try: dot_target.move_to(dot.history[len_history - n - 1]) except Exception: pass self.play(MoveToTarget(dots, run_time=0.5)) class ZeroStepColoring(ComplexNewtonsMethodManySeeds): n_search_steps = 0 class ComplexNewtonsMethodManySeedsHigherRes(ComplexNewtonsMethodManySeeds): step = 0.05 class IntroNewtonFractal(Scene): coefs = [1.0, -1.0, 1.0, 0.0, 0.0, 1.0] plane_config = { "x_range": (-4, 4), "y_range": (-4, 4), "height": 16, "width": 16, "background_line_style": { "stroke_color": GREY_A, "stroke_width": 1.0, }, "axis_config": { "stroke_width": 1.0, } } n_steps = 30 def construct(self): self.init_fractal(root_colors=ROOT_COLORS_BRIGHT) fractal, plane, root_dots = self.group # Transition from last scene frame = self.camera.frame frame.shift(plane.n2p(2) - RIGHT_SIDE) blocker = BackgroundRectangle(plane, fill_opacity=1) blocker.move_to(plane.n2p(-2), RIGHT) self.add(blocker) self.play( frame.animate.center(), FadeOut(blocker), run_time=2, ) self.wait() self.play( fractal.animate.set_colors(ROOT_COLORS_DEEP), *( dot.animate.set_fill(interpolate_color(color, WHITE, 0.2)) for dot, color in zip(root_dots, ROOT_COLORS_DEEP) ) ) self.wait() # Zoom in fractal.set_n_steps(40) zoom_points = [ [-3.12334879, 1.61196545, 0.], [1.21514006, 0.01415811, 0.], ] for point in zoom_points: self.play( frame.animate.set_height(2e-3).move_to(point), run_time=25, rate_func=bezier(2 * [0] + 6 * [1]) ) self.wait() self.play( frame.animate.center().set_height(8), run_time=10, rate_func=bezier(6 * [0] + 2 * [1]) ) # Allow for play self.tie_fractal_to_root_dots(fractal) fractal.set_n_steps(12) def init_fractal(self, root_colors=ROOT_COLORS_DEEP): plane = self.get_plane() fractal = self.get_fractal( plane, colors=root_colors, n_steps=self.n_steps, ) root_dots = self.get_root_dots(plane, fractal) self.tie_fractal_to_root_dots(fractal) self.plane = plane self.fractal = fractal self.group = Group(fractal, plane, root_dots) self.add(*self.group) def get_plane(self): plane = ComplexPlane(**self.plane_config) plane.add_coordinate_labels(font_size=24) self.plane = plane return plane def get_fractal(self, plane, colors=ROOT_COLORS_DEEP, n_steps=30): return NewtonFractal( plane, colors=colors, coefs=self.coefs, n_steps=n_steps, ) def get_root_dots(self, plane, fractal): self.root_dots = VGroup(*( Dot(plane.n2p(root), color=color) for root, color in zip( coefficients_to_roots(fractal.coefs), fractal.colors ) )) self.root_dots.set_stroke(BLACK, 5, background=True) return self.root_dots def tie_fractal_to_root_dots(self, fractal): fractal.add_updater(lambda f: f.set_roots([ self.plane.p2n(dot.get_center()) for dot in self.root_dots ])) def on_mouse_press(self, point, button, mods): super().on_mouse_press(point, button, mods) mob = self.point_to_mobject(point, search_set=self.root_dots) if mob is None: return self.mouse_drag_point.move_to(point) mob.add_updater(lambda m: m.move_to(self.mouse_drag_point)) self.unlock_mobject_data() self.lock_static_mobject_data() def on_mouse_release(self, point, button, mods): super().on_mouse_release(point, button, mods) self.root_dots.clear_updaters() class ChaosOnBoundary(TeacherStudentsScene): def construct(self): self.teacher_says( OldTexText("Chaos at\\\\the boundary"), bubble_config={ "height": 3, "width": 3, } ) self.play_all_student_changes("pondering", look_at=self.screen) self.wait(3) class DeepZoomFractal(IntroNewtonFractal): coefs = [-1.0, 0.0, 0.0, 1.0, 0.0, 1.0] plane_config = { "x_range": (-4, 4), "y_range": (-4, 4), "height": 16 * 1, "width": 16 * 1, "background_line_style": { "stroke_color": GREY_A, "stroke_width": 1.0, }, "axis_config": { "stroke_width": 1.0, } } def construct(self): self.init_fractal(root_colors=ROOT_COLORS_DEEP) fractal, plane, root_dots = self.group he_tracker = ValueTracker(0) frame = self.camera.frame zoom_point = np.array([ # -1.91177811, 0.52197285, 0. 0.72681252, -0.66973296, 0. ], dtype=np.float64) initial_fh = FRAME_HEIGHT frame.add_updater(lambda m: m.set_height( initial_fh * 2**(-he_tracker.get_value()), )) # rd_height = root_dots.get_height() # root_dots.add_updater(lambda m: m.set_height( # rd_height * 2**(he_tracker.get_value() / 8), # about_point=zoom_point # )) self.add(frame) self.play( UpdateFromAlphaFunc( frame, lambda m, a: m.move_to(zoom_point * a), run_time=15, ), ApplyMethod( he_tracker.set_value, 14, run_time=30, rate_func=bezier([0, 0, 1, 1]), ), ) self.wait() class IncreasingStepsNewtonFractal(IntroNewtonFractal): play_mode = False def construct(self): self.init_fractal() fractal, plane, root_dots = self.group fractal.set_n_steps(0) steps_label = VGroup(Integer(0, edge_to_fix=RIGHT), Text("Steps")) steps_label.arrange(RIGHT, aligned_edge=UP) steps_label.next_to(ORIGIN, UP).to_edge(LEFT) steps_label.set_stroke(BLACK, 5, background=True) self.add(steps_label) step_tracker = ValueTracker(0) get_n_steps = step_tracker.get_value fractal.add_updater(lambda m: m.set_n_steps(int(get_n_steps()))) steps_label[0].add_updater( lambda m: m.set_value(int(get_n_steps())) ) steps_label[0].add_updater(lambda m: m.set_stroke(BLACK, 5, background=True)) if self.play_mode: self.wait(20) for n in range(20): step_tracker.set_value(n) if n == 1: self.wait(15) elif n == 2: self.wait(10) else: self.wait() else: self.play( step_tracker.animate.set_value(20), run_time=10 ) class ManyQuestions(Scene): def construct(self): self.add(FullScreenRectangle()) questions = VGroup( Text("Lower order polynomials?"), Text("Do points ever cycle?"), Text("Fractal dimension?"), Text("Connection to Mandelbrot?"), ) screens = VGroup(*(ScreenRectangle() for q in questions)) screens.arrange_in_grid( v_buff=1.5, h_buff=3.0, ) screens.set_fill(BLACK, 1) questions.match_width(screens[0]) for question, screen in zip(questions, screens): question.next_to(screen, UP) screen.add(question) screens.set_height(FRAME_HEIGHT - 0.5) screens.center() self.play(LaggedStartMap( FadeIn, screens, lag_ratio=0.9, ), run_time=8) self.wait() class WhatsGoingOn(TeacherStudentsScene): def construct(self): self.screen.set_height(4, about_edge=UL) self.screen.set_fill(BLACK, 1) self.add(self.screen) self.student_says( "What the %$!* is\ngoing on?", target_mode="angry", look_at=self.screen, index=2, added_anims=[LaggedStart(*( pi.change("guilty", self.students[2].eyes) for pi in [self.teacher, *self.students[:2]] ), run_time=2)] ) self.wait(4) class EquationToFrame(Scene): def construct(self): self.add(FullScreenRectangle()) screens = self.get_screens() arrow = Arrow(*screens) equation = get_newton_rule() equation.next_to(screens[0], UP) title = OldTexText("Unreasonable intricacy") title.next_to(screens[1], UP) self.wait() self.add(screens) self.add(equation) self.play( ShowCreation(arrow), FadeTransform(equation.copy(), title), ) self.wait() def get_screens(self): screens = Square().get_grid(1, 2) screens.set_height(6) screens.set_width(FRAME_WIDTH - 1, stretch=True) screens.set_stroke(WHITE, 3) screens.set_fill(BLACK, 1) screens.arrange(RIGHT, buff=2.0) screens.to_edge(DOWN) return screens class RepeatedNewton(Scene): coefs = [1.0, -1.0, 1.0, 0.0, 0.0, 1.0] plane_config = { "x_range": (-4, 4), "y_range": (-2, 2), "height": 8, "width": 16, } dots_config = { "radius": 0.05, "color": GREY_A, "gloss": 0.4, "shadow": 0.1, "opacity": 0.5, } arrow_style = { "stroke_color": WHITE, "stroke_opacity": 0.5, } dot_density = 5.0 points_scalar = 1.0 n_steps = 10 colors = ROOT_COLORS_BRIGHT show_coloring = True show_arrows = True highlight_equation = False corner_group_height = 2.0 step_run_time = 1.0 show_fractal_background = False def construct(self): self.add_plane() self.add_true_roots() self.add_labels() if self.show_fractal_background: self.add_fractal_background() self.add_dots() self.run_iterations() if self.show_coloring: self.color_points() self.revert_to_original_positions() def add_plane(self): plane = self.plane = ComplexPlane(**self.plane_config) plane.add_coordinate_labels(font_size=24) self.add(plane) def add_labels(self): eq_label = self.eq_label = OldTex( "P(z) = " + coefs_to_poly_string(self.coefs), font_size=36 ) rule_label = self.rule_label = get_newton_rule() rule_label.next_to(eq_label, DOWN, MED_LARGE_BUFF) corner_rect = SurroundingRectangle( VGroup(eq_label, rule_label), buff=MED_SMALL_BUFF ) corner_rect.set_fill(BLACK, 0.9) corner_rect.set_stroke(WHITE, 1) self.corner_group = VGroup( corner_rect, eq_label, rule_label, ) self.corner_group.set_height(self.corner_group_height) self.corner_group.to_corner(UL, buff=0) self.add(self.corner_group) def add_true_roots(self): roots = self.roots = coefficients_to_roots(self.coefs) root_dots = self.root_dots = VGroup(*( glow_dot(self.plane.n2p(root), color=color, opacity_mult=2.0) for root, color in zip(roots, self.colors) )) self.add(root_dots) def add_dots(self): dots = self.dots = DotCloud( self.get_original_points(), **self.dots_config ) self.add(dots, self.corner_group) self.play(ShowCreation(dots)) def get_original_points(self): step = 1.0 / self.dot_density return self.points_scalar * np.array([ self.plane.c2p(x, y) for x in np.arange(*self.plane.x_range[:2], step) for y in np.arange(*self.plane.y_range[:2], step) ]) def run_iterations(self): self.points_history = [] for x in range(self.n_steps): self.points_history.append(self.dots.get_points().copy()) self.take_step(run_time=self.step_run_time) def update_z(self, z, epsilon=1e-6): denom = dpoly(z, self.coefs) if abs(denom) < epsilon: denom = epsilon return z - poly(z, self.coefs) / denom def take_step(self, run_time=1.0): plane = self.plane points = self.dots.get_points() zs = map(plane.p2n, points) new_zs = map(self.update_z, zs) new_points = list(map(plane.n2p, new_zs)) added_anims = [] if self.show_arrows: arrows = [] max_len = 0.5 * plane.get_x_unit_size() / self.dot_density for p1, p2 in zip(points, new_points): vect = p2 - p1 norm = get_norm(vect) if norm > max_len: vect = normalize(vect) * max_len arrows.append(Vector(vect, **self.arrow_style).shift(p1)) arrows = VGroup(*arrows) self.add(arrows, self.dots, self.corner_group) self.play(ShowCreation(arrows, lag_ratio=0)) added_anims.append(FadeOut(arrows)) self.play( self.dots.animate.set_points(new_points), *added_anims, run_time=run_time, ) self.dots.filter_out(lambda p: get_norm(p) > FRAME_WIDTH) def color_points(self): root_points = [rd.get_center() for rd in self.root_dots] rgbas = list(map(color_to_rgba, self.colors)) def get_rgba(point): norms = [get_norm(point - rp) for rp in root_points] return rgbas[np.argmin(norms)] rgbas = list(map(get_rgba, self.dots.get_points())) fractal = NewtonFractal( self.plane, coefs=self.coefs, colors=self.colors, n_steps=0, ) fractal.set_opacity(0) self.add(fractal, self.plane, self.dots, self.corner_group) radius = self.dots.get_radius() self.play( fractal.animate.set_opacity(0.5), self.dots.animate.set_rgba_array(rgbas).set_radius(1.5 * radius), ) self.play( fractal.animate.set_opacity(0), self.dots.animate.set_radius(radius), ) self.remove(fractal) def revert_to_original_positions(self): for ph in self.points_history[::-1]: self.play( self.dots.animate.set_points(ph), run_time=0.5, ) def reveal_fractal(self, **kwargs): plane = self.plane fractal = self.fractal = self.get_fractal(**kwargs) root_dot_backs = VGroup(*(Dot(rd.get_center(), radius=0.1) for rd in self.root_dots)) root_dot_backs.set_stroke(BLACK, 2) root_dot_backs.set_fill(opacity=0) plane.generate_target(use_deepcopy=True) for lines in plane.target.background_lines, plane.target.faded_lines: lines.set_stroke(WHITE) for line in lines.family_members_with_points(): line.set_opacity(line.get_stroke_opacity() * 0.5) self.root_dots.generate_target() for rd, color in zip(self.root_dots.target, fractal.colors): rd.set_fill(color) self.add(fractal, *self.mobjects, root_dot_backs) self.play( FadeIn(fractal), FadeOut(self.dots), FadeIn(root_dot_backs), MoveToTarget(plane), MoveToTarget(self.root_dots), ) self.wait() def get_fractal(self, **kwargs): if "colors" not in kwargs: kwargs["colors"] = self.colors self.fractal = NewtonFractal(self.plane, coefs=self.coefs, **kwargs) return self.fractal def add_fractal_background(self): fractal = self.get_fractal() fractal.set_opacity(0.1) fractal.set_n_steps(12) boundary = self.fractal_boundary = fractal.copy() boundary.set_colors(5 * [WHITE]) boundary.set_julia_highlight(1e-4) boundary.set_opacity(0.25) self.add(fractal, boundary, *self.mobjects) class AmbientQuinticSolving(RepeatedNewton): coefs = [-23.125, -11.9375, -6.875, 0.3125, 2.5, 1] show_fractal_background = True dots_config = { "radius": 0.03, "color": GREY_A, "gloss": 0.4, "shadow": 0.1, "opacity": 0.5, } dot_density = 10.0 def add_labels(self): super().add_labels() self.corner_group.set_opacity(0) class WhyNotThisWrapper(VideoWrapper): title = "Why not something like this?" animate_boundary = False title_config = { "font_size": 60, "color": RED, } wait_time = 2 class SimplyTendingToNearestRoot(RepeatedNewton): def update_z(self, z): norms = [abs(r - z) for r in self.roots] nearest_root = self.roots[np.argmin(norms)] norm = min(norms) step_size = np.log(1 + norm * 3) / 3 return z + step_size * (nearest_root - z) class UnrelatedIdeas(TeacherStudentsScene): def construct(self): self.screen.set_height(4, about_edge=UL) self.add(self.screen) self.play_student_changes( "tease", "thinking", "raise_right_hand", look_at=self.screen, added_anims=[self.teacher.change("happy")] ) self.wait(2) self.teacher_says( OldTexText("Unrelated\\\\ideas"), bubble_config={ "height": 3, "width": 4, }, added_anims=[ s.change("sassy", self.teacher.eyes) for s in self.students ] ) self.play(LaggedStart( self.students[2].change("angry"), self.teacher.change("guilty"), lag_ratio=0.7, )) self.wait(2) self.embed() class RepeatedNewtonCubic(RepeatedNewton): coefs = [-1, 0, 0, 1] # colors = [RED_E, GREEN_E, BLUE_E] colors = ROOT_COLORS_DEEP[::2] def construct(self): super().construct() self.reveal_fractal() frame = self.camera.frame self.play( frame.animate.move_to([0.86579359, -0.8322599, 0.]).set_height(0.0029955), rate_func=bezier([0, 0, 1, 1, 1, 1, 1, 1]), run_time=10, ) class RepeatedNewtonQuadratic(RepeatedNewton): coefs = [-1, 0, 1] colors = [RED, BLUE] n_steps = 10 class SimpleFractalScene(IntroNewtonFractal): colors = ROOT_COLORS_DEEP display_polynomial_label = False display_root_values = False n_steps = 25 def construct(self): self.init_fractal(root_colors=self.colors) if self.display_polynomial_label: self.add_polynomial_label() if self.display_root_values: self.add_root_labels() def add_polynomial_label(self): n = len(self.fractal.roots) t2c = { f"r_{i + 1}": interpolate_color(self.colors[i], WHITE, 0.5) for i in range(n) } label = OldTex( "p(z) = ", *( f"(z - r_{i})" for i in range(1, n + 1) ), tex_to_color_map=t2c, font_size=36 ) label.to_corner(UL) label.set_stroke(BLACK, 5, background=True) self.add(label) def add_root_labels(self): for n, root_dot in zip(it.count(1), self.root_dots): self.add(self.get_root_label(root_dot, n)) def get_root_label(self, root_dot, n): def get_z(): return self.plane.p2n(root_dot.get_center()) label = VGroup( OldTex(f"r_{n} = "), DecimalNumber(get_z(), include_sign=True), ) label.scale(0.5) label.set_stroke(BLACK, 3, background=True) def update_label(label): label.arrange(RIGHT, buff=0.1) label[0].shift(0.1 * label[0].get_height() * DOWN) label.next_to(root_dot, UR, SMALL_BUFF) label[1].set_value(get_z()) label.add_updater(update_label) return label class TwoRootFractal(SimpleFractalScene): coefs = [-1.0, 0.0, 1.0] colors = [ROOT_COLORS_DEEP[0], ROOT_COLORS_DEEP[4]] n_steps = 0 # Doesn't really matter, does it? class TwoRootFractalWithLabels(TwoRootFractal): display_polynomial_label = True display_root_values = True class ThreeRootFractal(SimpleFractalScene): coefs = [-1.0, 0.0, 0.0, 1.0] colors = ROOT_COLORS_DEEP[::2] n_steps = 30 class ThreeRootFractalWithLabels(ThreeRootFractal): display_polynomial_label = True display_root_values = True class FromTwoToThree(EquationToFrame): def construct(self): self.add(FullScreenRectangle()) screens = self.get_screens() arrow = Arrow(*screens) quadratic = OldTex("x^2 + c_1 x + c_0") cubic = OldTex("x^3 + c_2 x^2 + c_1 x + c_0") quadratic.next_to(screens[0], UP) cubic.next_to(screens[1], UP) self.add(screens) self.add(quadratic, cubic) self.play(ShowCreation(arrow)) self.wait() class StudentAsksAboutComplexity(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("Why is it\\\\so complicated?"), index=0, bubble_config={ "height": 3, "width": 4, }, added_anims=[ self.students[1].change("confused", self.teacher.eyes), self.students[2].change("erm", self.teacher.eyes), ], ) self.wait() self.play( self.teacher.change("shruggie"), ) self.wait() self.play(LaggedStart( PiCreatureSays( self.teacher, OldTexText("Math is what\\\\it is"), target_mode="well", bubble_config={ "height": 3, "width": 4, } ), self.students[1].change("maybe"), self.students[2].change("sassy"), lag_ratio=0.7, )) self.wait(2) why = self.students[0].bubble.content[0][:3] question = Text("Is this meaningful?") question.to_corner(UL) question.set_color(YELLOW) arrow = Arrow(question, why) arrow.set_stroke(YELLOW, 5) self.play( why.animate.set_color(YELLOW), Write(question), ShowCreation(arrow), LaggedStart(*( pi.change(mode, question) for pi, mode in zip(self.pi_creatures, ("well", "erm", "sassy", "hesitant")) )) ) self.wait(2) cross = Cross(question) cross.set_stroke(RED, [1, *4 * [8], 1]) words = Text("Surprisingly answerable!") words.next_to(question, RIGHT, LARGE_BUFF) new_arrow = Arrow(words[:10], why) new_arrow.set_stroke(WHITE, 5) self.play( RemovePiCreatureBubble(self.teacher, target_mode="erm"), ShowCreation(cross), FadeIn(words), ShowCreation(new_arrow), ) self.wait(2) class NextVideoWrapper(VideoWrapper): title = "Next video" class PeculiarBoundaryProperty(Scene): coefs = [-1, 0, 0, 1] colors = [RED_E, TEAL_E, BLUE_E] def construct(self): # Title title = Text("Peculiar property", font_size=60) title.to_edge(UP, buff=MED_SMALL_BUFF) title.set_stroke(BLACK, 5, background=True) underline = Underline(title, buff=-0.05) underline.set_width(title.get_width() + 1) underline.insert_n_curves(20) underline.set_stroke(BLUE, [1, *5 * [3], 1]) subtitle = OldTexText( "Boundary of one color", " = " "Boundary of any other", tex_to_color_map={ "one color": BLUE_D, "any other": RED_D, } ) subtitle.next_to(underline, DOWN, MED_LARGE_BUFF) # Setup for planes grid = VGroup(*( ComplexPlane( x_range=(-3, 3), y_range=(-2, 2), ) for n in range(6) )) grid.arrange_in_grid(2, 3, v_buff=2, h_buff=3) grid.set_width(FRAME_WIDTH - 2) grid.to_edge(DOWN, buff=MED_LARGE_BUFF) arrows = VGroup() bound_words = VGroup() for p1, p2 in zip(grid[:3], grid[3:]): arrow = Arrow(p1, p2, stroke_width=4, buff=0.1) arrows.add(arrow) bound_word = Text("Boundary", font_size=24) bound_word.next_to(arrow, RIGHT, buff=SMALL_BUFF) bound_words.add(bound_word) low_equals = VGroup( OldTex("=").move_to(grid[3:5]), OldTex("=").move_to(grid[4:6]), ) # Fractals fractals = Group(*( NewtonFractal(plane, coefs=self.coefs, colors=self.colors) for plane in grid )) alpha = 0.2 for k in 0, 3: fractals[0 + k].set_opacities(alpha, 1, alpha) fractals[1 + k].set_opacities(alpha, alpha, 1) fractals[2 + k].set_opacities(1, alpha, alpha) boxes = VGroup(*( SurroundingRectangle(fractal, buff=0) for fractal in fractals )) boxes.set_stroke(GREY_B, 1) # Initial fractal big_plane = grid[0].deepcopy() big_plane.set_height(6.5) big_plane.center().to_edge(DOWN) big_fractal = NewtonFractal(big_plane, coefs=self.coefs, colors=self.colors) big_julia = big_fractal.copy() big_julia.set_julia_highlight(1e-3) big_julia.set_colors(3 * [WHITE]) self.add(big_fractal) # Animations def get_show_border_anims(fractal): f_copy = fractal.copy() fractal.set_julia_highlight(5e-3) fractal.set_colors(3 * [WHITE]) return (FadeOut(f_copy), GrowFromCenter(fractal)) def high_to_low_anims(index): return ( ShowCreation(arrows[index]), FadeIn(bound_words[index]), TransformFromCopy(fractals[index], fractals[index + 3]), TransformFromCopy(boxes[index], boxes[index + 3]), ) self.add(underline, title) self.play( ShowCreation(underline), GrowFromCenter(big_julia, run_time=4) ) self.play( big_julia.animate.set_julia_highlight(0.02).set_colors(CUBIC_COLORS).set_opacity(0) ) self.wait() self.play( big_fractal.animate.set_opacities(alpha, alpha, 1) ) self.wait() self.play( ReplacementTransform(big_fractal, fractals[1]), FadeIn(subtitle[:2]), ReplacementTransform( boxes[1].copy().replace(big_fractal).set_opacity(0), boxes[1], ), ) self.play(*high_to_low_anims(1)) self.play(*get_show_border_anims(fractals[4])) self.wait(2) subtitle[2:].set_opacity(0) self.add(subtitle[2:]) for i in 2, 0: self.play( FadeIn(fractals[i]), FadeIn(boxes[i]), subtitle[2:].animate.set_opacity(1), ) self.play(*high_to_low_anims(i)) self.play(*get_show_border_anims(fractals[i + 3])) self.wait() self.play(Write(low_equals)) class DefineBoundary(Scene): def construct(self): # Add set blob = VMobject() blob.set_fill(BLUE_E, 1) blob.set_stroke(width=0) blob.set_points_as_corners([ (1 + 0.3 * random.random()) * p for p in compass_directions(12) ]) blob.close_path() blob.set_height(3) blob.set_width(1.0, stretch=True) blob.move_to(2 * RIGHT) blob.apply_complex_function(np.exp) blob.make_smooth() blob.rotate(90 * DEGREES) blob.center() blob.set_height(4) blob.insert_n_curves(50) set_text = Text("Set", font_size=72) set_text.set_stroke(BLACK, 3, background=True) set_text.move_to(interpolate(blob.get_top(), blob.get_bottom(), 0.35)) self.add(blob) self.add(set_text) # Preview boundary point = Dot(radius=0.05) point.move_to(blob.get_start()) boundary_word = Text("Boundary") boundary_word.set_color(YELLOW) boundary_word.next_to(blob, LEFT) outline = blob.copy() outline.set_fill(opacity=0) outline.set_stroke(YELLOW, 2) self.add(point) kw = { "rate_func": bezier([0, 0, 1, 1]), "run_time": 5, } self.play( FadeIn(boundary_word), ShowCreation(outline, **kw), MoveAlongPath(point, blob, **kw) ) self.play(FadeOut(outline)) # Mention formality boundary_word.generate_target() boundary_word.target.to_corner(UL) formally_word = Text("More formally") formally_word.next_to(boundary_word.target, DOWN, aligned_edge=LEFT) self.play( MoveToTarget(boundary_word), FadeTransform(boundary_word.copy(), formally_word) ) self.wait() # Draw circle circle = Circle() circle.move_to(point) circle.set_stroke(TEAL, 3.0) self.play( ShowCreation(circle), point.animate.scale(0.5), ) self.wait() group = VGroup(blob, set_text) self.add(group, point, circle) self.play( ApplyMethod( group.scale, 2, {"about_point": point.get_center()}, run_time=4 ), ApplyMethod( circle.set_height, 0.5, run_time=2, ), ) # Labels inside_words = Text("Points inside", font_size=36) outside_words = Text("Points outside", font_size=36) inside_words.next_to(circle, DOWN, buff=0.5).shift(0.5 * LEFT) outside_words.next_to(circle, UP, buff=0.5).shift(0.5 * RIGHT) inside_arrow = Arrow( inside_words, point, stroke_width=3, buff=0.1, ) outside_arrow = Arrow( outside_words, point, stroke_width=3, buff=0.1, ) self.play( FadeIn(inside_words), ShowCreation(inside_arrow) ) self.play( FadeIn(outside_words), ShowCreation(outside_arrow) ) self.wait() # Show interior point_group = VGroup(point, circle) self.play( point_group.animate.shift(circle.get_height() * DOWN / 4), LaggedStartMap( FadeOut, VGroup(inside_words, inside_arrow, outside_words, outside_arrow) ) ) self.wait() self.play(circle.animate.set_height(0.2)) self.wait() # Show exterior point_group.generate_target() point_group.target.move_to(blob.get_start() + 0.25 * UP) point_group.target[1].set_height(1.0) self.play(MoveToTarget(point_group)) self.wait() self.play(circle.animate.set_height(0.2)) self.wait() # Back to boundary self.play(point_group.animate.move_to(blob.get_start())) frame = self.camera.frame frame.generate_target() frame.target.set_height(0.2) frame.target.move_to(point) point_group.generate_target() point_group.target.set_height(0.2 / 8) point_group.target[1].set_stroke(width=0.1) self.play(MoveToTarget(point_group)) self.play( MoveToTarget(frame), run_time=4 ) class VariousCirclesOnTheFractal(SimpleFractalScene): coefs = [-1.0, 0.0, 0.0, 1.0] colors = CUBIC_COLORS sample_density = 0.02 def construct(self): super().construct() frame = self.camera.frame plane = self.plane fractal = self.fractal frame.save_state() # Setup samples n_steps = 20 density = self.sample_density samples = np.array([ [complex(x, y), 0] for x in np.arange(0, 2, density) for y in np.arange(0, 2, density) ]) roots = coefficients_to_roots(self.coefs) for i in range(len(samples)): z = samples[i, 0] for n in range(n_steps): z = z - poly(z, self.coefs) / dpoly(z, self.coefs) norms = [abs(z - root) for root in roots] samples[i, 1] = np.argmin(norms) unit_size = plane.get_x_unit_size() circle = Circle() circle.set_stroke(WHITE, 3.0) circle.move_to(2 * UR) words = VGroup( Text("#Colors inside: "), Integer(3), ) words.arrange(RIGHT) words[1].align_to(words[0][-2], DOWN) height_ratio = words.get_height() / FRAME_HEIGHT def get_interior_count(circle): radius = circle.get_height() / 2 / unit_size norms = abs(samples[:, 0] - plane.p2n(circle.get_center())) true_result = len(set(samples[norms < radius, 1])) # In principle this would work, but the samples are not perfect return 3 if true_result > 1 else 1 def get_frame_ratio(): return frame.get_height() / FRAME_HEIGHT def update_words(words): words.set_height(height_ratio * frame.get_height()) ratio = get_frame_ratio() words.next_to(circle, UP, buff=SMALL_BUFF * ratio) count = get_interior_count(circle) words[1].set_value(count) words.set_stroke(BLACK, 5 * ratio, background=True) return words words.add_updater(update_words) circle.add_updater(lambda m: m.set_stroke(width=3.0 * get_frame_ratio())) self.play(ShowCreation(circle)) self.play(FadeIn(words)) self.wait() self.play(circle.animate.set_height(0.25)) self.wait() point = plane.c2p(0.5, 0.5) self.play(circle.animate.move_to(point)) self.play(frame.animate.set_height(2).move_to(point)) self.wait() point = plane.c2p(0.25, 0.4) self.play(circle.animate.move_to(point).set_height(0.1)) self.wait() for xy in (0.6, 0.4), (0.2, 0.6): self.play( circle.animate.move_to(plane.c2p(*xy)), run_time=4 ) self.wait() # Back to larger self.play( Restore(frame), circle.animate.set_height(0.5) ) self.wait() # Show smooth boundary count_tracker = ValueTracker(3) words.add_updater(lambda m: m[1].set_value(count_tracker.get_value())) def change_count_at(new_value, alpha): curr_value = count_tracker.get_value() return UpdateFromAlphaFunc( count_tracker, lambda m, a: m.set_value(curr_value if a < alpha else new_value) ) fractal.set_n_steps(10) self.play( fractal.animate.set_n_steps(3), run_time=2 ) self.play( circle.animate.move_to(plane.c2p(0, 0.3)), change_count_at(2, 0.75), run_time=2 ) self.wait() self.play( circle.animate.move_to(plane.c2p(0, 0)), change_count_at(3, 0.5), run_time=2 ) self.wait() self.play( circle.animate.move_to(plane.c2p(-0.6, 0.2)), Succession( change_count_at(2, 0.9), change_count_at(3, 0.7), ), run_time=3 ) self.play( circle.animate.set_height(0.1).move_to(plane.c2p(-0.6, 0.24)), change_count_at(2, 0.8), frame.animate.set_height(2.5).move_to(plane.c2p(-0.5, 0.5)), run_time=3 ) self.wait(2) self.play( fractal.animate.set_n_steps(20), change_count_at(3, 0.1), run_time=3, ) self.wait() # Just show boundary boundary = fractal.copy() boundary.set_colors(3 * [WHITE]) boundary.add_updater( lambda m: m.set_julia_highlight(get_frame_ratio() * 1e-3) ) boundary.set_n_steps(50) frame.generate_target() frame.target.set_height(0.0018), frame.target.move_to([-1.15535091, 0.23001433, 0.]) self.play( FadeOut(circle), FadeOut(words), FadeOut(self.root_dots), GrowFromCenter(boundary, run_time=3), fractal.animate.set_opacity(0.35), MoveToTarget( frame, run_time=10, rate_func=bezier([0, 0, 1, 1, 1, 1, 1]) ), ) self.wait() class ArtPuzzle(Scene): def construct(self): words = VGroup( Text("Art Puzzle:", font_size=60), OldTexText("- Use $\\ge 3$ colors"), OldTexText("- Boundary of one color = Boundary of all"), ) words.set_color(BLACK) words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) words[1:].shift(0.5 * DOWN + 0.5 * RIGHT) words.to_corner(UL) for word in words: self.play(FadeIn(word, lag_ratio=0.1)) self.wait() class ZoomInOnCubic(ThreeRootFractal): colors = CUBIC_COLORS coefs = [complex(0, -1), 0, 0, 1] n_steps = 30 def construct(self): super().construct() frame = self.camera.frame height_exp_tracker = ValueTracker() get_height_exp = height_exp_tracker.get_value center_tracker = VectorizedPoint(ORIGIN) frame.add_updater(lambda m: m.move_to(center_tracker)) frame.add_updater(lambda m: m.set_height(FRAME_HEIGHT * 2**(-get_height_exp()))) self.play( ApplyMethod(center_tracker.move_to, [0.2986952, 1.11848235, 0], run_time=4), ApplyMethod( height_exp_tracker.set_value, 7, run_time=15, rate_func=bezier([0, 0, 1, 1]), ), ) self.wait() class BlobsOnBlobsOnBlobs(Scene): def construct(self): words = OldTexText( "Blobs", *( " on blobs " + ("\\\\" if n == 2 else "") for n in range(6) ), "..." ) words.set_width(FRAME_WIDTH - 2) words.to_edge(UP) words.set_color(BLACK) self.add(words[0]) for word in words[1:]: self.play(FadeIn(word, 0.25 * UP)) self.wait() class FractalDimensionWords(Scene): def construct(self): text = OldTexText("Fractal dimension $\\approx$ 1.44", font_size=60) text.to_corner(UL) self.play(Write(text)) self.wait() class ThinkAboutWhatPropertyMeans(TeacherStudentsScene): def construct(self): self.screen.set_height(4, about_edge=UL) self.add(self.screen) image = ImageMobject("NewtonBoundaryProperty") image.replace(self.screen) self.add(image) self.teacher_says( OldTexText("Think about what\\\\this tells us."), bubble_config={ "height": 3, "width": 4, } ) self.play_student_changes( "pondering", "thinking", "pondering", look_at=self.screen ) self.wait(4) class InterpretBoundaryProperty(RepeatedNewton): plane_config = { "x_range": (-4, 4), "y_range": (-2, 2), "height": 12, "width": 24, } n_steps = 15 def construct(self): self.add_plane() plane = self.plane plane.shift(2 * RIGHT) self.add_true_roots() self.add_labels() self.add_fractal_background() # Show sensitive point point = plane.c2p(-0.8, 0.4) dots = self.dots = DotCloud() dots.set_points([ [r * math.cos(theta), r * math.sin(theta), 0] for r in np.linspace(0, 1, 20) for theta in np.linspace(0, TAU, int(r * 20)) + random.random() * TAU ]) dots.set_height(2).center() dots.filter_out(lambda p: get_norm(p) > 1) dots.set_height(0.3) dots.set_radius(0.04) dots.make_3d() dots.set_color(GREY_A) dots.move_to(point) sensitive_words = Text("Sensitive area") sensitive_words.next_to(dots, RIGHT, buff=SMALL_BUFF) sensitive_words.set_stroke(BLACK, 5, background=True) def get_arrows(): root_dots = self.root_dots if plane.p2n(dots.get_center()).real < -1.25: root_dots = [root_dots[4]] return VGroup(*( Arrow( dots, root_dot, buff=0.1, stroke_color=root_dot[0].get_color() ) for root_dot in root_dots )) arrows = get_arrows() self.play( FadeIn(dots, scale=2), FadeIn(sensitive_words, shift=0.25 * UP) ) self.wait() self.play(ShowCreation(arrows[2])) self.play(ShowCreation(arrows[4])) self.wait() self.play( FadeOut(sensitive_words), LaggedStartMap(ShowCreation, VGroup(*( arrows[i] for i in (0, 1, 3) ))) ) self.wait() arrows.add_updater(lambda m: m.become(get_arrows())) self.add(arrows) self.play(dots.animate.move_to(plane.c2p(-1.4, 0.4)), run_time=3) self.wait() self.play(dots.animate.move_to(point), run_time=3) self.wait() not_allowed = Text("Not allowed!") not_allowed.set_color(RED) not_allowed.set_stroke(BLACK, 8, background=True) not_allowed.next_to(dots, RIGHT, SMALL_BUFF) arrows.clear_updaters() self.play( arrows[:2].animate.set_opacity(0), FadeIn(not_allowed, scale=0.7) ) self.wait() self.play(FadeOut(arrows), FadeOut(not_allowed)) # For fun self.run_iterations() class CommentsOnNaming(Scene): def construct(self): self.setup_table() self.show_everyone() def setup_table(self): titles = VGroup( OldTexText("How it started", font_size=60), OldTexText("How it's going", font_size=60), ) titles.to_edge(UP, buff=MED_SMALL_BUFF) titles.set_color(GREY_A) titles[0].set_x(-FRAME_WIDTH / 4) titles[1].set_x(FRAME_WIDTH / 4) h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) h_line.next_to(titles, DOWN).set_x(0) v_line = Line(UP, DOWN).set_height(FRAME_HEIGHT) lines = VGroup(h_line, v_line) lines.set_stroke(WHITE, 2) self.left_point = [-FRAME_WIDTH / 4, -1, 0] self.right_point = [FRAME_WIDTH / 4, -1, 0] self.add(titles, lines) def show_everyone(self): # Newton newton = get_figure( "Newton", "Isaac Newton", "1643-1727", height=4, ) newton.move_to(self.left_point) newton_formula = get_newton_rule(var="x") newton_formula.next_to(newton, UP) nf_label = OldTexText("``Newton's'' fractal") nf_label.align_to(newton_formula, UP) nf_label.set_x(self.right_point[0]) self.play( FadeIn(newton_formula), LaggedStartMap(FadeIn, newton) ) self.wait() self.play(Write(nf_label)) self.wait(2) # Hamilton hamilton = get_figure( "Hamilton", "William Rowan Hamilton", "1805 - 1865", height=4, ) hamilton.move_to(self.left_point) hamiltons_equation = OldTex( r"\frac{\mathrm{d} \boldsymbol{q}}{\mathrm{d} t}=\frac{\partial \mathcal{H}}{\partial \boldsymbol{p}}, \quad \frac{\mathrm{d} \boldsymbol{p}}{\mathrm{d} t}=-\frac{\partial \mathcal{H}}{\partial \boldsymbol{q}}" ) hamiltons_equation.match_width(hamilton[0]) hamiltons_equation.next_to(hamilton, UP) hamiltonians = Text("Hamiltonians") hamiltonians.move_to(nf_label) self.play( LaggedStart( FadeOut(newton, shift=0.25 * LEFT), FadeOut(newton_formula, shift=0.25 * LEFT), FadeOut(nf_label, shift=0.25 * RIGHT), ), LaggedStart( FadeIn(hamilton, shift=0.25 * LEFT), FadeIn(hamiltons_equation, shift=0.25 * LEFT), FadeIn(hamiltonians, shift=0.25 * RIGHT), ) ) self.wait(2) # Fourier fourier = get_figure( "Joseph Fourier", "Joseph Fourier", "1768-1830", height=4 ) fourier.move_to(self.left_point) fourier_transform = OldTex( r"f(t)=\int_{0}^{\infty}(a(\lambda) \cos (2 \pi \lambda t)+b(\lambda) \sin (2 \pi \lambda t)) d \lambda" ) fourier_transform.set_width(fourier.get_width() * 1.5) fourier_transform.next_to(fourier, UP) FFT = Text("FFT") FFT.move_to(hamiltonians) FFT_diagram = ImageMobject("FFT_Diagram") FFT_diagram.move_to(self.right_point), self.play( LaggedStart( FadeOut(hamilton, shift=0.25 * LEFT), FadeOut(hamiltons_equation, shift=0.25 * LEFT), FadeOut(hamiltonians, shift=0.25 * RIGHT), ), LaggedStart( FadeIn(fourier, shift=0.25 * LEFT), FadeIn(fourier_transform, shift=0.25 * LEFT), FadeIn(FFT, shift=0.25 * RIGHT), ), FadeIn(FFT_diagram), ) self.wait(2) # Everyone people = Group(newton, hamilton, fourier) people.generate_target() people.target.arrange(DOWN, buff=LARGE_BUFF) people.target.set_height(6.4) people.target.move_to(self.left_point) people.target.to_edge(DOWN, buff=SMALL_BUFF) self.play( FadeOut(fourier_transform), FadeOut(FFT), MoveToTarget(people, run_time=2), FFT_diagram.animate.scale(1 / 3).match_y(people.target[2]), ) arrow = Arrow( fourier, FFT_diagram, buff=1.0, stroke_width=8 ) arrows = VGroup( arrow.copy().match_y(newton), arrow.copy().match_y(hamilton), arrow, ) self.play(LaggedStartMap(ShowCreation, arrows, lag_ratio=0.5, run_time=3)) self.wait() class MakeFunOfNextVideo(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("``Next part''...I've\\\\heard that before."), target_mode="sassy", index=2, added_anims=[LaggedStart( self.teacher.change("guilty"), self.students[0].change("sassy"), self.students[1].change("hesitant"), )] ) self.wait() self.teacher_says( OldTexText("Wait, for real\\\\this time!"), bubble_config={ "height": 3, "width": 3, }, target_mode="speaking", added_anims=[ self.students[0].change("hesitant"), ] ) self.wait(3) class Part1EndScroll(PatreonEndScreen): CONFIG = { "title_text": "", "scroll_time": 60, "show_pis": False, } class Thanks(Scene): def construct(self): morty = Mortimer(mode="happy") thanks = Text("Thank you") thanks.next_to(morty, LEFT) self.play( morty.change("gracious"), FadeIn(thanks, lag_ratio=0.1) ) for n in range(5): self.play(morty.animate.look([DL, DR][n % 2])) self.wait(random.random() * 5) self.play(Blink(morty)) class HolomorphicDynamics(Scene): def construct(self): self.ask_about_property() self.repeated_functions() def ask_about_property(self): back_plane = FullScreenRectangle() self.add(back_plane) image = ImageMobject("NewtonBoundaryProperty") border = SurroundingRectangle(image, buff=0) border.set_stroke(WHITE, 2) image = Group(border, image) image.set_height(FRAME_HEIGHT) image.generate_target() image.target.set_height(6) image.target.to_corner(DL) question = Text("Why is this true?") question.to_corner(UR) arrow = Arrow( question.get_left(), image.target.get_top() + RIGHT, path_arc=45 * DEGREES ) self.play( image.animate.set_height(6).to_corner(DL), Write(question), ShowCreation(arrow, rate_func=squish_rate_func(smooth, 0.5, 1), run_time=2) ) self.wait() title = self.title = Text("Holomorphic Dynamics", font_size=60) title.to_edge(UP) self.play( image.animate.set_height(1).to_corner(DL), FadeOut(question, shift=DL, scale=0.2), FadeOut(arrow, shift=DL, scale=0.2), FadeIn(title, shift=3 * DL, scale=0.5), FadeOut(back_plane), ) self.wait() self.image = image def repeated_functions(self): basic_expr = OldTex( "z", "\\rightarrow ", " f(z)" ) fz = basic_expr.get_part_by_tex("f(z)") basic_expr.next_to(self.title, DOWN, LARGE_BUFF) basic_expr.to_edge(LEFT, buff=LARGE_BUFF) brace = Brace(fz, DOWN) newton = OldTex("z - {P(z) \\over P'(z)}") newton.next_to(brace, DOWN) newton.align_to(basic_expr[1], LEFT) newton_example = OldTex("z - {z^3 + z - 1 \\over 3z^2 + 1}") eq = OldTex("=").rotate(PI / 2) eq.next_to(newton, DOWN) newton_example.next_to(eq, DOWN) newton_group = VGroup(newton, eq, newton_example) newton_group.generate_target() newton_group.target[1].rotate(-PI / 2) newton_group.target.arrange(RIGHT, buff=0.2) newton_group.target[2].shift(SMALL_BUFF * UP) newton_group.target.scale(0.7) newton_group.target.to_corner(DL) mandelbrot = OldTex("z^2 + c") mandelbrot.next_to(brace, DOWN) exponential = OldTex("a^z") exponential.next_to(brace, DOWN) self.play( FadeIn(basic_expr), FadeOut(self.image) ) self.wait() self.describe_holomorphic(fz, brace) self.wait() self.play( FadeIn(newton), ) self.play( FadeIn(eq), FadeIn(newton_example), ) self.wait() self.play( MoveToTarget(newton_group), FadeIn(mandelbrot, DOWN), ) self.wait() self.play( mandelbrot.animate.scale(0.7).next_to(newton, UP, LARGE_BUFF, LEFT), FadeIn(exponential, DOWN) ) self.wait() # Show fractals rhss = VGroup(exponential, mandelbrot, newton) f_eqs = VGroup() lhss = VGroup() for rhs in rhss: rhs.generate_target() if rhs is not exponential: rhs.target.scale(1 / 0.7) lhs = OldTex("f(z) = ") lhs.next_to(rhs.target, LEFT) f_eqs.add(VGroup(lhs, rhs.target)) lhss.add(lhs) f_eqs.arrange(RIGHT, buff=1.5) f_eqs.next_to(self.title, DOWN, MED_LARGE_BUFF) rects = ScreenRectangle().replicate(3) rects.arrange(DOWN, buff=0.5) rects.set_height(6.5) rects.next_to(ORIGIN, RIGHT, MED_LARGE_BUFF) rects.to_edge(DOWN, MED_SMALL_BUFF) rects.set_stroke(WHITE, 1) arrows = VGroup() for rect, f_eq in zip(rects, f_eqs): arrow = Vector(0.7 * RIGHT) arrow.next_to(rect, LEFT) arrows.add(arrow) f_eq.next_to(arrow, LEFT) self.play( LaggedStartMap(MoveToTarget, rhss), LaggedStartMap(Write, lhss), LaggedStartMap(FadeIn, rects), LaggedStartMap(ShowCreation, arrows), FadeOut(brace), basic_expr.animate.to_edge(UP), FadeOut(newton_group[1:]), ) self.wait() def describe_holomorphic(self, fz, brace): self.title.set_stroke(BLACK, 5, background=True) word = self.title.get_part_by_text("Holomorphic") underline = Underline(word, buff=-0.05) underline.scale(1.2) underline.insert_n_curves(40) underline.set_stroke(YELLOW, [1, *6 * [3], 1]) self.add(underline, self.title) self.play( word.animate.set_fill(YELLOW), ShowCreation(underline) ) in_words = Text("Complex\ninputs", font_size=36) in_words.to_corner(UL) in_arrow = Arrow( in_words.get_right(), fz[2].get_top(), path_arc=-80 * DEGREES, buff=0.2, ) VGroup(in_words, in_arrow).set_color(YELLOW) out_words = Text("Complex\noutputs", font_size=36) out_words.next_to(brace, DOWN) out_words.set_color(YELLOW) f_prime = OldTexText("$f'(z)$ exists") f_prime.set_color(YELLOW) f_prime.next_to(underline, DOWN, MED_LARGE_BUFF) f_prime.match_y(fz) self.wait() self.play( Write(in_words), ShowCreation(in_arrow), run_time=1, ) self.play( GrowFromCenter(brace), FadeIn(out_words, lag_ratio=0.05) ) self.wait() self.play(FadeIn(f_prime, 0.5 * DOWN)) self.wait() self.play( LaggedStartMap(FadeOut, VGroup( in_words, in_arrow, out_words, f_prime, underline, )), word.animate.set_fill(WHITE) ) class AmbientRepetition(Scene): n_steps = 30 def construct(self): plane = ComplexPlane((-2, 2), (-2, 2)) plane.set_height(FRAME_HEIGHT) plane.add_coordinate_labels(font_size=24) self.add(plane) font_size = 36 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) self.add(dot, z_label) def func(z): return z**2 + complex(-0.6436875, -0.441) 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, UP, SMALL_BUFF) 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), ) self.embed() class BriefMandelbrot(Scene): n_iterations = 30 def construct(self): self.add_plane() self.add_process_description() self.show_iterations() self.wait(10) # Time to play self.add_mandelbrot_image() def add_plane(self): plane = self.plane = ComplexPlane((-2, 1), (-2, 2)) plane.set_height(4) plane.scale(FRAME_HEIGHT / 2.307) plane.next_to(2 * LEFT, RIGHT, buff=0) plane.add_coordinate_labels(font_size=24) self.add(plane) 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("z_0 = 0", **kw), OldTex("{c} \\text{ can be changed}", **kw), ) terms.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) terms.next_to(self.plane, LEFT, MED_LARGE_BUFF) self.add(terms) def show_iterations(self): plane = self.plane c0 = complex(-0.2, 0.95) 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)) lines = VGroup() lines.set_stroke(background=True) 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(self.n_iterations): 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) update_lines(lines) self.add(lines[:2], c_dot) last_dot = Dot(plane.n2p(0)).scale(0) for line, dot in zip(lines[0:20:2], lines[1:20:2]): self.add(line, dot, c_dot) self.play( ShowCreation(line), TransformFromCopy(last_dot, dot) ) last_dot = dot self.remove(*lines) lines.add_updater(update_lines) self.add(lines, c_dot) def add_mandelbrot_image(self): image = ImageMobject("MandelbrotSet") image.set_height(FRAME_HEIGHT) image.shift(self.plane.n2p(-0.7) - image.get_center()) rect = FullScreenFadeRectangle() rect.set_fill(BLACK, 1) rect.next_to(self.plane, LEFT, buff=0) self.add(image, rect, *self.mobjects) self.play( FadeIn(image, run_time=2), self.plane.animate.set_opacity(0.5) ) def on_mouse_press(self, point, button, mods): # TODO, copy-pasted, should factor out super().on_mouse_press(point, button, mods) mob = self.point_to_mobject(point, search_set=[self.c_dot]) if mob is None: return self.mouse_drag_point.move_to(point) mob.add_updater(lambda m: m.move_to(self.mouse_drag_point)) self.unlock_mobject_data() self.lock_static_mobject_data() def on_mouse_release(self, point, button, mods): super().on_mouse_release(point, button, mods) self.c_dot.clear_updaters() class CyclicAttractor(RepeatedNewton): coefs = [2, -2, 0, 1] n_steps = 20 show_coloring = False def construct(self): super().construct() def add_plane(self): super().add_plane() self.plane.axes.set_stroke(GREY_B, 1) 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, 0.2, 10) for theta in np.linspace(0, TAU, int(50 * r)) + TAU * np.random.random() ] 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 MontelCorrolaryScreenGrab(Scene): def construct(self): pass 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, ) 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.generate_target() frame.target.move_to(point1) frame.target.set_height(height1) self.play( MoveToTarget(frame), run_time=10, rate_func=bezier([0, 0, 1, 1]) ) self.wait() 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 ) class Thumbnail2(SimpleFractalScene): def construct(self): super().construct() fractal = self.fractal fractal.set_saturation_factor(4.5) self.remove(self.plane) self.remove(self.root_dots) frame = self.camera.frame frame.set_height(4) fc = fractal.copy() fc.set_saturation_factor(2) fc.set_julia_highlight(0.01) self.add(fc) # self.clear() # back = fractal.copy() # back.set_saturation_factor(0) # back.set_opacity(0.1) # self.add(back) # N = 20 # for x in np.linspace(np.log(1e-3), np.log(0.1), N): # jh = np.exp(x) # fc = fractal.copy() # fc.set_saturation_factor(1) # fc.set_julia_highlight(jh) # fc.set_opacity(2 / N) # self.add(fc) self.embed()