From 00ee9c34977ba0a686f09063e648464bdcf62ce0 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Mon, 29 Jan 2018 13:39:56 -0800 Subject: [PATCH] Incremental progress on WindingNumber --- active_projects/WindingNumber.py | 218 +++++++++++++++++-------------- 1 file changed, 118 insertions(+), 100 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 1fed87e4..190891c3 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -32,64 +32,8 @@ from topics.graph_scene import * # TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code, # (and it will be done, but first I'll figure out what I'm doing with all this...) # -SR - -class DualScene(Scene): - CONFIG = { - "num_needed_anchor_points" : 10 - } - - def setup(self): - split_line = DashedLine(SPACE_HEIGHT * UP, SPACE_HEIGHT * DOWN) - self.num_plane = NumberPlane(x_radius = SPACE_WIDTH/2) - self.num_plane.to_edge(LEFT, buff = 0) - self.num_plane.prepare_for_nonlinear_transform() - self.add(self.num_plane, split_line) - - def apply_function(self, func, run_time = 3): - self.func = func - right_plane = self.num_plane.copy() - right_plane.center() - right_plane.prepare_for_nonlinear_transform() - right_plane.apply_function(func) - right_plane.shift(SPACE_WIDTH/2 * RIGHT) - self.right_plane = right_plane - crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1) - crappy_cropper.stretch_to_fit_width(SPACE_WIDTH) - crappy_cropper.to_edge(LEFT, buff = 0) - self.play( - ReplacementTransform(self.num_plane.copy(), right_plane), - FadeIn(crappy_cropper), - Animation(self.num_plane), - run_time = run_time - ) - - def squash_onto_left(self, object): - object.shift(SPACE_WIDTH/2 * LEFT) - - def squash_onto_right(self, object): - object.shift(SPACE_WIDTH/2 * RIGHT) - - def path_draw(self, input_object, run_time = 3): - output_object = input_object.copy() - if input_object.get_num_anchor_points() < self.num_needed_anchor_points: - input_object.insert_n_anchor_points(self.num_needed_anchor_points) - output_object.apply_function(self.func) - self.squash_onto_left(input_object) - self.squash_onto_right(output_object) - self.play( - ShowCreation(input_object), - ShowCreation(output_object), - run_time = run_time - ) - -class TestDual(DualScene): - def construct(self): - self.force_skipping() - self.apply_function(lambda (x, y, z) : complex_to_R3(complex(x,y)**2)) - self.revert_to_original_skipping_status() - self.path_draw(Line(LEFT + DOWN, RIGHT + DOWN)) -class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene): +class EquationSolver1d(GraphScene, ZoomedScene): CONFIG = { "func" : lambda x : x, "targetX" : 0, @@ -302,7 +246,7 @@ class ArrowCircleTest(Scene): base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT) def rev_rotate(x, revs): - x.rotate(revs * TAU) + x.rotate(revs * TAU, about_point = ORIGIN) x.set_color(color_func(revs)) return x @@ -560,8 +504,11 @@ class PiWalker(Scene): rev_func = rev_func, remover = (i < len(walk_coords) - 1) ), - ShowCreation(Line(start_point, end_point)), + ShowCreation(Line(start_point, end_point), rate_func = None), run_time = self.step_run_time) + + # TODO: Allow smooth paths instead of brekaing them up into lines, and + # use point_from_proportion to get points along the way self.wait() @@ -626,10 +573,10 @@ class EquationSolver2d(Scene): alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints) a0 = alpha_winder(0) rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind - line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), + flashing_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end), stroke_width = 5, color = RED) - thin_line = line.copy() + thin_line = flashing_line.copy() thin_line.set_stroke(width = 1) walker_anim = LinearWalker( start_coords = start, @@ -638,12 +585,12 @@ class EquationSolver2d(Scene): rev_func = rev_func, remover = True ) - line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim, - run_time = 2) - anim = Succession( - line_draw_anim, - Transform, line, thin_line - ) + line_draw_anim = AnimationGroup( + ShowCreation(thin_line), + #ShowPassingFlash(flashing_line), + walker_anim, + rate_func = None) + anim = line_draw_anim return (anim, rebased_winder(1)) wind_so_far = 0 @@ -736,6 +683,29 @@ class FirstSqrtScene(EquationSolver1d): "show_target_line" : True, } +class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene): +# TODO: Don't bother with ReconfigurableScene; just use new config from start +# (But can also use this as written, and just cut into middle in Premiere) + + def setup(self): + FirstSqrtScene.setup(self) + ReconfigurableScene.setup(self) + + def construct(self): + shiftVal = self.targetY + + self.drawGraph() + newOrigin = self.coords_to_point(0, shiftVal) + self.transition_to_alt_config( + func = lambda x : x**2 - shiftVal, + targetY = 0, + graph_label = "y = x^2 - " + str(shiftVal), + y_min = self.y_min - shiftVal, + y_max = self.y_max - shiftVal, + show_target_line = False, + graph_origin = newOrigin) + self.solveEquation() + # TODO: Pi creatures intrigued class ComplexPlaneIs2d(Scene): @@ -758,15 +728,22 @@ class NumberLineScene(Scene): left_point = num_line.number_to_point(-1) right_point = num_line.number_to_point(1) + # TODO: Make this line a thin rectangle interval_1d = Line(left_point, right_point, stroke_color = inner_color, stroke_width = stroke_width) + rect_1d = Rectangle(stroke_width = 0, fill_opacity = 1, fill_color = inner_color) + rect_1d.replace(interval_1d) + rect_1d.stretch_to_fit_height(SMALL_BUFF) left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color) right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color) endpoints_1d = VGroup(left_dot, right_dot) - full_1d = VGroup(interval_1d, endpoints_1d) + full_1d = VGroup(rect_1d, endpoints_1d) self.play(ShowCreation(full_1d)) self.wait() + # TODO: Can polish the morphing above; have dots become left and right sides, and + # only then fill in the top and bottom + num_plane = NumberPlane() random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT] @@ -790,20 +767,78 @@ class NumberLineScene(Scene): self.wait() -class Initial2dFuncScene(Scene): +initial_2d_func = point_func_from_complex_func(lambda c : np.exp(c)) + +class Initial2dFuncSceneMorphing(Scene): + CONFIG = { + "num_needed_anchor_points" : 10, + "func" : initial_2d_func, + } + + def setup(self): + split_line = DashedLine(SPACE_HEIGHT * UP, SPACE_HEIGHT * DOWN) + self.num_plane = NumberPlane(x_radius = SPACE_WIDTH/2) + self.num_plane.to_edge(LEFT, buff = 0) + self.num_plane.prepare_for_nonlinear_transform() + self.add(self.num_plane, split_line) + + def squash_onto_left(self, object): + object.shift(SPACE_WIDTH/2 * LEFT) + + def squash_onto_right(self, object): + object.shift(SPACE_WIDTH/2 * RIGHT) + + def obj_draw(self, input_object): + output_object = input_object.copy() + if input_object.get_num_anchor_points() < self.num_needed_anchor_points: + input_object.insert_n_anchor_points(self.num_needed_anchor_points) + output_object.apply_function(self.func) + self.squash_onto_left(input_object) + self.squash_onto_right(output_object) + self.play( + ShowCreation(input_object), + ShowCreation(output_object) + ) + + def construct(self): + right_plane = self.num_plane.copy() + right_plane.center() + right_plane.prepare_for_nonlinear_transform() + right_plane.apply_function(self.func) + right_plane.shift(SPACE_WIDTH/2 * RIGHT) + self.right_plane = right_plane + crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1) + crappy_cropper.stretch_to_fit_width(SPACE_WIDTH) + crappy_cropper.to_edge(LEFT, buff = 0) + self.play( + ReplacementTransform(self.num_plane.copy(), right_plane), + FadeIn(crappy_cropper), + Animation(self.num_plane), + run_time = 3 + ) + + points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP] + for i in range(len(points) - 1): + line = Line(points[i], points[i + 1], color = RED) + self.obj_draw(line) + +# Alternative to the above, using MappingCameras, but no morphing animation +class Initial2dFuncSceneWithoutMorphing(Scene): def setup(self): left_camera = Camera(**self.camera_config) right_camera = MappingCamera( - mapping_func = point_func_from_complex_func(lambda c : np.exp(c)), + mapping_func = initial_2d_func, **self.camera_config) split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) self.camera = split_screen_camera def construct(self): num_plane = NumberPlane() - num_plane.fade() + num_plane.prepare_for_nonlinear_transform() + #num_plane.fade() self.add(num_plane) + points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP] for i in range(len(points) - 1): line = Line(points[i], points[i + 1], color = RED) @@ -814,28 +849,8 @@ class Initial2dFuncScene(Scene): # TODO: Bunch of Pi walker scenes # TODO: An odometer scene when introducing winding numbers - -class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene): -# TODO: Don't bother with ReconfigurableScene; just use new config from start - - def setup(self): - FirstSqrtScene.setup(self) - ReconfigurableScene.setup(self) - - def construct(self): - shiftVal = self.targetY - - self.drawGraph() - newOrigin = self.coords_to_point(0, shiftVal) - self.transition_to_alt_config( - func = lambda x : x**2 - shiftVal, - targetY = 0, - graph_label = "y = x^2 - " + str(shiftVal), - y_min = self.y_min - shiftVal, - y_max = self.y_max - shiftVal, - show_target_line = False, - graph_origin = newOrigin) - self.solveEquation() +# (Just set up an OdometerScene with function matching the walking of the Pi +# creature from previous scene, then place it as a simultaneous inset with Premiere) class LoopSplitScene(Scene): @@ -963,12 +978,12 @@ class LoopSplitSceneMapped(LoopSplitScene): # to illustrate relation between degree and large-scale winding number class FundThmAlg(EquationSolver2d): CONFIG = { - "func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)), + "func" : plane_poly_with_roots((1, 2), (-1, 2.5), (-1, 2.5)), "num_iterations" : 1, } # TODO: Borsuk-Ulam visuals -# Note: May want to do an ordinary square scene, then mapping func it into a circle +# Note: May want to do an ordinary square scene, then MappingCamera it into a circle # class BorsukUlamScene(PiWalker): # 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere @@ -1002,15 +1017,16 @@ class DiffOdometer(OdometerScene): # TODOs, from easiest to hardest: -# Minor fiddling with little things in each animation; placements, colors, timing +# Minor fiddling with little things in each animation; placements, colors, timing, text -# Odometer/swinging arrows stuff +# Initial odometer scene (simple once previous Pi walker scene is decided upon) -# Writing new Pi creature walker scenes off of general template +# Writing new Pi walker scenes by parametrizing general template -# Split screen illustration of 2d function (before domain coloring) +# Generalizing Pi walker stuff to make bullets on pulsing lines change colors dynamically according to +# function traced out -# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out +# Debugging Pi walker stuff added to EquationSolver2d # ---- @@ -1022,4 +1038,6 @@ class DiffOdometer(OdometerScene): # Domain coloring +# TODO: Ask about tracked mobject, which is probably very useful for our animations + # FIN