From ad330cc95b7bbc75656b21f032d57333ba830c97 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 21 Mar 2018 13:41:15 -0700 Subject: [PATCH] TransitionFromPathsToBoundaries --- active_projects/WindingNumber_G.py | 527 +++++++++++++++++++++-------- 1 file changed, 394 insertions(+), 133 deletions(-) diff --git a/active_projects/WindingNumber_G.py b/active_projects/WindingNumber_G.py index e6e093fd..73ed02b9 100644 --- a/active_projects/WindingNumber_G.py +++ b/active_projects/WindingNumber_G.py @@ -2127,6 +2127,188 @@ class PathContainingZero(InputOutputScene, PiCreatureScene): self.play(morty.change, "hooray") self.wait(3) +class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): + CONFIG = { + "func" : plane_func_by_wind_spec( + (-2, 0, 2), (2, 0, 1) + ) + } + def construct(self): + ColorMappedObjectsScene.construct(self) + + #Setup paths + squares, joint_rect = self.get_squares_and_joint_rect() + left_square, right_square = squares + + path1, path2 = paths = VGroup(*[ + Line(square.get_corner(UP+LEFT), square.get_corner(UP+RIGHT)) + for square in squares + ]) + joint_path = Line(path1.get_start(), path2.get_end()) + + for mob in it.chain(paths, [joint_path]): + mob.set_stroke(WHITE, 4) + mob.color_using_background_image(self.background_image_file) + + dot = self.get_dot_and_add_continual_animations() + + #Setup path braces + for mob, tex in (path1, "x"), (path2, "y"), (joint_path, "x+y"): + mob.brace = Brace(mob, DOWN) + label = TextMobject("Winding =", "$%s$"%tex) + label.next_to(mob.brace, DOWN) + mob.brace.add(label) + + #Setup region labels + + for square, tex in (left_square, "x"), (right_square, "y"), (joint_rect, "x+y \\, ?"): + square.label = TextMobject("Winding = ", "$%s$"%tex) + square.label.move_to(square) + + #Add paths + self.position_dot(path1.get_start()) + for path in path1, path2: + self.position_dot(path.get_start()) + self.play( + MoveAlongPath(dot, path.copy()), + ShowCreation(path), + run_time = 2 + ) + self.play(GrowFromCenter(path.brace)) + self.wait() + self.position_dot(joint_path.get_start()) + self.play( + MoveAlongPath(dot, joint_path, run_time = 3), + FadeOut(VGroup(path1.brace, path2.brace)), + FadeIn(joint_path.brace), + ) + self.wait() + + #Add regions + self.play( + FadeOut(paths), + FadeOut(joint_path.brace), + dot.move_to, path1.get_start() + ) + for square in squares: + self.position_dot(square.points[0]) + kwargs = { + "run_time" : 4, + "rate_func" : bezier([0, 0, 1, 1]), + } + self.play( + MoveAlongPath(dot, square.copy(), **kwargs), + ShowCreation(square, **kwargs), + Write(square.label, run_time = 2), + ) + self.wait() + self.play( + dot.move_to, joint_rect.points[0], + FadeOut(squares), + FadeIn(joint_rect), + ) + self.position_dot(joint_rect.points[0]) + self.play( + Transform(left_square.label[0], joint_rect.label[0]), + Transform( + left_square.label[1], joint_rect.label[1][0], + path_arc = TAU/6 + ), + FadeIn(joint_rect.label[1][1]), + FadeIn(joint_rect.label[1][3]), + FadeOut(right_square.label[0]), + Transform( + right_square.label[1], joint_rect.label[1][2], + path_arc = TAU/6 + ), + MoveAlongPath( + dot, joint_rect, + run_time = 6, + rate_func = bezier([0, 0, 1, 1]) + ) + ) + self.wait() + + ### + + def get_squares_and_joint_rect(self): + squares = VGroup(*[ + Square(side_length = 4).next_to(ORIGIN, vect, buff = 0) + for vect in LEFT, RIGHT + ]) + joint_rect = SurroundingRectangle(squares, buff = 0) + for mob in it.chain(squares, [joint_rect]): + mob.set_stroke(WHITE, 4) + mob.color_using_background_image(self.background_image_file) + return squares, joint_rect + + def get_dot_and_add_continual_animations(self): + #Define important functions for updates + get_output = lambda : self.func(tuple(dot.get_center()[:2])) + get_output_color = lambda : rgba_to_color(point_to_rgba(get_output())) + get_output_rev = lambda : -point_to_rev(get_output()) + self.get_output_rev = get_output_rev + + self.start_rev = 0 + self.curr_winding = 0 + def get_total_winding(dt = 0): + rev = (get_output_rev() - self.start_rev)%1 + possible_windings = [ + np.floor(self.curr_winding)+k+rev + for k in -1, 0, 1 + ] + i = np.argmin([abs(pw - self.curr_winding) for pw in possible_windings]) + self.curr_winding = possible_windings[i] + return self.curr_winding + + + #Setup dot, arrow and label + dot = self.dot = Dot(radius = 0.1) + dot.set_stroke(WHITE, 1) + update_dot_color = ContinualUpdateFromFunc( + dot, lambda d : d.set_fill(get_output_color()) + ) + + label = DecimalNumber(0, num_decimal_points = 1) + label_upadte = ContinualChangingDecimal( + label, get_total_winding, + position_update_func = lambda l : l.next_to(dot, UP+LEFT, SMALL_BUFF) + ) + + arrow_length = 0.75 + arrow = Vector(arrow_length*RIGHT) + arrow.set_stroke(WHITE, 1) + def arrow_update_func(arrow): + arrow.set_fill(get_output_color(), 1) + arrow.rotate(-TAU*get_output_rev() - arrow.get_angle()) + arrow.scale(arrow_length/arrow.get_length()) + arrow.shift(dot.get_center() - arrow.get_start()) + return arrow + update_arrow = ContinualUpdateFromFunc(arrow, arrow_update_func) + + self.add(update_arrow, update_dot_color, label_upadte) + return dot + + def position_dot(self, point): + self.dot.move_to(point) + self.start_rev = self.get_output_rev() + self.curr_winding = 0 + +class BreakDownLoopWithNonzeroWinding(TransitionFromPathsToBoundaries): + def construct(self): + zero_point = 2*LEFT + + squares, joint_rect = self.get_squares_and_joint_rect() + left_square, right_square = squares + VGroup(squares, joint_rect).shift(SMALL_BUFF*UP) + + dot = self.get_dot_and_add_continual_animations() + + for rect in (left_square, "x"), (right_square, "y"), (joint_rect, "3"): + pass + + + class BackToEquationSolving(AltTeacherStudentsScene): def construct(self): self.teacher_says( @@ -2139,6 +2321,218 @@ class BackToEquationSolving(AltTeacherStudentsScene): ]) self.wait(3) +class MonomialTerm(PathContainingZero): + CONFIG = { + "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5), + "full_func_label" : "f(x) = x^5", + "func_label" : "x^5", + "loop_radius" : 1.1, + "label_buff" : 0.3, + "label_move_to_corner" : ORIGIN, + "should_end_with_rescaling" : True, + } + def construct(self): + self.setup_planes() + self.relabel_planes() + self.add_function_label() + self.show_winding() + if self.should_end_with_rescaling: + self.rescale_output_plane() + + def relabel_planes(self): + for plane in self.input_plane, self.output_plane: + for mob in plane: + if isinstance(mob, TexMobject): + plane.remove(mob) + + if hasattr(plane, "numbers_to_show"): + _range = plane.numbers_to_show + else: + _range = range(-2, 3) + for x in _range: + if x == 0: + continue + label = TexMobject(str(x)) + label.scale(0.5) + point = plane.coords_to_point(x, 0) + label.next_to(point, DOWN, MED_SMALL_BUFF) + plane.add(label) + self.add_foreground_mobject(label) + tick = Line(SMALL_BUFF*DOWN, SMALL_BUFF*UP) + tick.move_to(point) + plane.add(tick) + for y in _range: + if y == 0: + continue + label = TexMobject("%di"%y) + label.scale(0.5) + point = plane.coords_to_point(0, y) + label.next_to(point, LEFT, MED_SMALL_BUFF) + plane.add(label) + self.add_foreground_mobject(label) + tick = Line(SMALL_BUFF*LEFT, SMALL_BUFF*RIGHT) + tick.move_to(point) + plane.add(tick) + self.add(self.input_plane, self.output_plane) + + def add_function_label(self): + label = TexMobject(self.full_func_label) + label.add_background_rectangle(opacity = 1, buff = SMALL_BUFF) + arrow = Arrow( + 2*LEFT, 2*RIGHT, path_arc = -TAU/3, + use_rectangular_stem = False + ) + arrow.pointwise_become_partial(arrow, 0, 0.95) + label.next_to(arrow, UP) + VGroup(arrow, label).to_edge(UP) + self.add(label, arrow) + + def show_winding(self): + loop = Arc(color = WHITE, angle = 1.02*TAU, num_anchors = 42) + loop.scale(self.loop_radius) + loop.match_background_image_file(self.input_coloring) + loop.move_to(self.input_plane.coords_to_point(0, 0)) + + out_loop = loop.copy() + out_loop.apply_function(self.point_function) + out_loop.match_background_image_file(self.output_coloring) + + get_in_point = lambda : loop.points[-1] + get_out_point = lambda : out_loop.points[-1] + in_origin = self.input_plane.coords_to_point(0, 0) + out_origin = self.output_plane.coords_to_point(0, 0) + + dot = Dot() + update_dot = UpdateFromFunc(dot, lambda d : d.move_to(get_in_point())) + + out_dot = Dot() + update_out_dot = UpdateFromFunc(out_dot, lambda d : d.move_to(get_out_point())) + + buff = self.label_buff + def generate_label_update(label, point_func, origin): + return UpdateFromFunc( + label, lambda m : m.move_to( + (1+buff)*point_func() - buff*origin, + self.label_move_to_corner + ) + ) + x = TexMobject("x") + fx = TexMobject(self.func_label) + update_x = generate_label_update(x, get_in_point, in_origin) + update_fx = generate_label_update(fx, get_out_point, out_origin) + + morty = self.pi_creature + + kwargs = { + "run_time" : 15, + "rate_func" : None, + } + self.play( + ShowCreation(loop, **kwargs), + ShowCreation(out_loop, **kwargs), + update_dot, + update_out_dot, + update_x, + update_fx, + ApplyMethod(morty.change, "pondering", out_dot), + ) + self.play( + FadeOut(VGroup(dot, out_dot, x, fx)) + ) + self.loop = loop + self.out_loop = out_loop + + def rescale_output_plane(self): + output_stuff = VGroup(self.output_plane, self.output_coloring) + self.play(*map(FadeOut, [self.loop, self.out_loop])) + self.play( + output_stuff.scale, 3.0/50, run_time = 2 + ) + self.wait() + + ### + + def func(self, coords): + return self.non_renormalized_func(coords) + +class PolynomialTerms(MonomialTerm): + CONFIG = { + "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5 - z - 1), + "full_func_label" : "f(x) = x^5 - x - 1", + "func_label" : "x^5 + \\cdots", + "loop_radius" : 2.0, + "label_buff" : 0.15, + "label_move_to_corner" : DOWN+LEFT, + "should_end_with_rescaling" : False, + } + def construct(self): + self.pi_creature.change("pondering", VectorizedPoint(ORIGIN)) + MonomialTerm.construct(self) + self.cinch_loop() + # self.sweep_through_loop_interior() + + def relabel_planes(self): + self.output_plane.x_radius = 50 + self.output_plane.y_radius = 50 + self.output_plane.numbers_to_show = range(-45, 50, 15) + MonomialTerm.relabel_planes(self) + + def sweep_through_loop_interior(self): + loop = self.loop + morty = self.pi_creature + + line, line_target = [ + Line( + loop.get_left(), loop.get_right(), + path_arc = u*TAU/2, + n_arc_anchors = 40, + background_image_file = self.input_coloring.background_image_file , + stroke_width = 4, + ) + for u in -1, 1 + ] + out_line = line.copy() + update_out_line = UpdateFromFunc( + out_line, + lambda m : m.set_points(line.points).apply_function(self.point_function), + ) + + self.play( + Transform( + line, line_target, + run_time = 10, + rate_func = there_and_back + ), + update_out_line, + morty.change, "hooray" + ) + self.wait() + + def cinch_loop(self): + loop = self.loop + out_loop = self.out_loop + morty = self.pi_creature + + update_out_loop = UpdateFromFunc( + out_loop, + lambda m : m.set_points(loop.points).apply_function(self.point_function) + ) + + self.add( + loop.copy().set_stroke(width = 1), + out_loop.copy().set_stroke(width = 1), + ) + self.play( + ApplyMethod( + loop.scale, 0, {"about_point" : self.input_plane.coords_to_point(0.2, 1)}, + run_time = 12, + rate_func = bezier([0, 0, 1, 1]) + ), + update_out_loop, + morty.change, "hooray" + ) + self.wait() + class SearchSpacePerimeterVsArea(EquationSolver2d): CONFIG = { "func" : plane_func_by_wind_spec( @@ -2206,139 +2600,6 @@ class SearchSpacePerimeterVsArea(EquationSolver2d): self.play(FadeOut(full_rect)) self.wait() -class MonomialTerm(PathContainingZero): - CONFIG = { - "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5), - "func_label" : "x^5", - "loop_radius" : 1.1, - "label_buff" : 0.3, - } - def construct(self): - self.setup_planes() - self.relabel_planes() - self.add_function_label() - self.show_winding() - - def relabel_planes(self): - for plane in self.input_plane, self.output_plane: - for mob in plane: - if isinstance(mob, TexMobject): - plane.remove(mob) - - if hasattr(plane, "numbers_to_show"): - _range = plane.numbers_to_show - else: - _range = range(-2, 3) - for x in _range: - if x == 0: - continue - label = TexMobject(str(x)) - label.scale(0.5) - point = plane.coords_to_point(x, 0) - label.next_to(point, DOWN, MED_SMALL_BUFF) - plane.add(label) - tick = Line(SMALL_BUFF*DOWN, SMALL_BUFF*UP) - tick.move_to(point) - plane.add(tick) - for y in _range: - if y == 0: - continue - label = TexMobject("%di"%y) - label.scale(0.5) - point = plane.coords_to_point(0, y) - label.next_to(point, LEFT, MED_SMALL_BUFF) - plane.add(label) - tick = Line(SMALL_BUFF*LEFT, SMALL_BUFF*RIGHT) - tick.move_to(point) - plane.add(tick) - - def add_function_label(self): - label = TexMobject("f(x) = %s"%self.func_label) - label.add_background_rectangle(opacity = 1) - arrow = Arrow( - 2*LEFT, 2*RIGHT, path_arc = -TAU/3, - use_rectangular_stem = False - ) - arrow.pointwise_become_partial(arrow, 0, 0.95) - label.next_to(arrow, UP) - VGroup(arrow, label).to_edge(UP) - self.add(label, arrow) - - def show_winding(self): - loop = Arc(color = WHITE, angle = 1.02*TAU, num_anchors = 42) - loop.scale(self.loop_radius) - loop.match_background_image_file(self.input_coloring) - loop.move_to(self.input_plane.coords_to_point(0, 0)) - - out_loop = loop.copy() - out_loop.apply_function(self.point_function) - out_loop.match_background_image_file(self.output_coloring) - - get_in_point = lambda : loop.points[-1] - get_out_point = lambda : out_loop.points[-1] - in_origin = self.input_plane.coords_to_point(0, 0) - out_origin = self.output_plane.coords_to_point(0, 0) - - dot = Dot() - update_dot = UpdateFromFunc(dot, lambda d : d.move_to(get_in_point())) - - out_dot = Dot() - update_out_dot = UpdateFromFunc(out_dot, lambda d : d.move_to(get_out_point())) - - buff = self.label_buff - def generate_label_update(label, point_func, origin): - return UpdateFromFunc( - label, lambda m : m.move_to( - (1+buff)*point_func() - buff*origin, - ) - ) - x = TexMobject("x") - fx = TexMobject(self.func_label) - update_x = generate_label_update(x, get_in_point, in_origin) - update_fx = generate_label_update(fx, get_out_point, out_origin) - - morty = self.pi_creature - - kwargs = { - "run_time" : 15, - "rate_func" : None, - } - self.play( - ShowCreation(loop, **kwargs), - ShowCreation(out_loop, **kwargs), - update_dot, - update_out_dot, - update_x, - update_fx, - Succession( - morty.change, "pondering", - Blink(morty), - Animation(morty, run_time = 4), - Blink(morty), - Animation(morty, run_time = 4), - Blink(morty), - ) - ) - - ### - - def func(self, coords): - return self.non_renormalized_func(coords) - -class PolynomialTerms(MonomialTerm): - CONFIG = { - "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5 - z - 1), - "func_label" : "x^5 - x - 1", - "loop_radius" : 2.0, - "label_buff" : 0.3, - } - - def relabel_planes(self): - self.output_plane.x_radius = 50 - self.output_plane.y_radius = 50 - self.output_plane.numbers_to_show = range(-45, 50, 10) - MonomialTerm.relabel_planes(self) - class EndingCredits(Scene): def construct(self): text = TextMobject(