mirror of
https://github.com/3b1b/videos.git
synced 2025-08-05 16:48:47 +00:00
1269 lines
38 KiB
Python
1269 lines
38 KiB
Python
![]() |
from manim_imports_ext import *
|
||
|
|
||
|
from _2021.quintic import coefficients_to_roots
|
||
|
from _2021.quintic import roots_to_coefficients
|
||
|
from _2021.quintic import dpoly
|
||
|
from _2021.quintic import poly
|
||
|
|
||
|
|
||
|
ROOT_COLORS_BRIGHT = [RED, GREEN, BLUE, YELLOW, MAROON_B]
|
||
|
ROOT_COLORS_DEEP = ["#440154", "#3b528b", "#21908c", "#5dc963", "#29abca"]
|
||
|
|
||
|
|
||
|
class PolyFractal(Mobject):
|
||
|
CONFIG = {
|
||
|
"shader_folder": "poly_fractal",
|
||
|
"shader_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,
|
||
|
}
|
||
|
|
||
|
def init_data(self):
|
||
|
self.data = {
|
||
|
"points": np.array([UL, DL, UR, DR]),
|
||
|
}
|
||
|
|
||
|
def init_uniforms(self):
|
||
|
super().init_uniforms()
|
||
|
self.set_colors(self.colors)
|
||
|
self.set_coefs(self.coefs)
|
||
|
self.set_scale(self.scale_factor)
|
||
|
self.set_offset(self.offset)
|
||
|
self.set_n_steps(self.n_steps)
|
||
|
|
||
|
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_coefs(self, coefs, reset_roots=True):
|
||
|
self.uniforms["n_roots"] = float(len(coefs))
|
||
|
self.uniforms.update({
|
||
|
f"coef{n}": np.array([coef.real, coef.imag])
|
||
|
for n, coef in enumerate(map(complex, coefs))
|
||
|
})
|
||
|
if reset_roots:
|
||
|
self.set_roots(coefficients_to_roots(coefs), False)
|
||
|
return self
|
||
|
|
||
|
def set_roots(self, roots, reset_coefs=True):
|
||
|
self.uniforms["n_roots"] = float(len(roots))
|
||
|
self.uniforms.update({
|
||
|
f"root{n}": np.array([root.real, root.imag])
|
||
|
for n, root in enumerate(map(complex, roots))
|
||
|
})
|
||
|
if reset_coefs:
|
||
|
self.set_coefs(roots_to_coefficients(roots), False)
|
||
|
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
|
||
|
|
||
|
|
||
|
# Scenes
|
||
|
|
||
|
class HelloPatrons(Scene):
|
||
|
def construct(self):
|
||
|
self.add(FullScreenRectangle())
|
||
|
morty = Mortimer()
|
||
|
morty.to_edge(DOWN).shift(3 * RIGHT)
|
||
|
self.play(PiCreatureSays(morty, "Hello patrons!", target_mode="wave_1"))
|
||
|
self.play(Blink(morty))
|
||
|
|
||
|
|
||
|
class ComingVideoWrapper(VideoWrapper):
|
||
|
animate_boundary = False
|
||
|
title = "Upcoming: Unsolvabillity of the Quintic"
|
||
|
|
||
|
|
||
|
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
|
||
|
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
|
||
|
"numbers_with_elongated_ticks": 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.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)
|
||
|
)
|
||
|
graph.set_color(self.graph_color)
|
||
|
|
||
|
self.add(graph)
|
||
|
|
||
|
def add_title(self, axes, opacity=0):
|
||
|
title = TexText("Newton's method", font_size=60)
|
||
|
title.move_to(midpoint(axes.get_left(), LEFT_SIDE))
|
||
|
title.to_edge(UP)
|
||
|
title.set_opacity(opacity)
|
||
|
|
||
|
poly = Tex(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):
|
||
|
underline = Underline(self.poly[:-1])
|
||
|
underline.match_style(self.graph)
|
||
|
|
||
|
self.play(
|
||
|
FlashAround(self.poly[:-1], color=self.graph_color),
|
||
|
ShowCreation(self.graph),
|
||
|
run_time=3
|
||
|
)
|
||
|
self.wait()
|
||
|
|
||
|
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)
|
||
|
for r in real_roots
|
||
|
))
|
||
|
dots.set_fill(YELLOW, 1)
|
||
|
dots.set_stroke(BLACK, 2, background=True)
|
||
|
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, shift=DOWN, scale=0.25)
|
||
|
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(*(
|
||
|
Tex(
|
||
|
"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 introduce_step(self):
|
||
|
axes = self.axes
|
||
|
graph = self.graph
|
||
|
|
||
|
# Add labels
|
||
|
guess_label = Tex(
|
||
|
"\\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 = Tex("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 = Tex("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.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 = [
|
||
|
Tex(
|
||
|
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):
|
||
|
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)
|
||
|
]
|
||
|
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.add_background_rectangle()
|
||
|
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 = Tex(
|
||
|
"""
|
||
|
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 = Tex(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 = Tex(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 AssumingItsGood(TeacherStudentsScene):
|
||
|
def construct(self):
|
||
|
self.pi_creatures.refresh_triangulation()
|
||
|
self.teacher_says(
|
||
|
TexText("Assuming this\\\\approximation\\\\is decent...", font_size=42),
|
||
|
bubble_kwargs={
|
||
|
"height": 3, "width": 4,
|
||
|
}
|
||
|
)
|
||
|
self.change_student_modes(
|
||
|
"pondering", "pondering", "tease",
|
||
|
look_at_arg=self.screen
|
||
|
)
|
||
|
self.pi_creatures.refresh_triangulation()
|
||
|
self.wait(3)
|
||
|
|
||
|
|
||
|
class RealNewtonsMethodHigherGraph(RealNewtonsMethod):
|
||
|
coefs = [1, -1, 1, 0, 0, 0.99]
|
||
|
poly_tex = "x^5 + x^2 - x + 1"
|
||
|
n_search_steps = 20
|
||
|
|
||
|
def find_root(self, cycle_run_time=0.5):
|
||
|
super().find_root(cycle_run_time)
|
||
|
|
||
|
def step_towards_root(self, fade_tan_with_vline=True):
|
||
|
super().step_towards_root(fade_tan_with_vline)
|
||
|
|
||
|
|
||
|
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 = Tex(
|
||
|
"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 = TexText("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 = Tex("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 = Tex("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),
|
||
|
Restore(self.poly),
|
||
|
)
|
||
|
|
||
|
|
||
|
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 = Tex(
|
||
|
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 = Tex("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 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 = Tex(
|
||
|
"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.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(LaggedStartMap(DrawBorderThenFill, root_circles))
|
||
|
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")])
|
||
|
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 ComplexNewtonsMethodManySeedsHigherRes(ComplexNewtonsMethodManySeeds):
|
||
|
step = 0.05
|
||
|
|
||
|
|
||
|
class IntroPolyFractal(Scene):
|
||
|
def construct(self):
|
||
|
plane = self.get_plane()
|
||
|
fractal = self.get_fractal(plane)
|
||
|
root_dots = self.get_root_dots(plane, fractal)
|
||
|
|
||
|
self.add(fractal)
|
||
|
self.add(plane)
|
||
|
self.add(root_dots)
|
||
|
|
||
|
# 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 get_plane(self):
|
||
|
plane = ComplexPlane(
|
||
|
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,
|
||
|
}
|
||
|
)
|
||
|
plane.add_coordinate_labels(font_size=24)
|
||
|
self.plane = plane
|
||
|
return plane
|
||
|
|
||
|
def get_fractal(self, plane, colors=ROOT_COLORS_BRIGHT):
|
||
|
fractal = PolyFractal(
|
||
|
scale_factor=get_norm(plane.n2p(1) - plane.n2p(0)),
|
||
|
offset=plane.n2p(0),
|
||
|
colors=colors,
|
||
|
)
|
||
|
fractal.replace(plane, stretch=True)
|
||
|
return fractal
|
||
|
|
||
|
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 IncreasingStepsPolyFractal(IntroPolyFractal):
|
||
|
play_mode = False
|
||
|
|
||
|
def construct(self):
|
||
|
plane = self.get_plane()
|
||
|
fractal = self.get_fractal(plane, colors=ROOT_COLORS_DEEP)
|
||
|
fractal.set_n_steps(0)
|
||
|
root_dots = self.get_root_dots(plane, fractal)
|
||
|
self.tie_fractal_to_root_dots(fractal)
|
||
|
|
||
|
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(fractal)
|
||
|
self.add(plane)
|
||
|
self.add(root_dots)
|
||
|
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
|
||
|
)
|