From a4881d2d07be7d4a3170f9e518e1bf14985601f2 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Wed, 24 Jan 2018 13:11:25 -0800 Subject: [PATCH 1/4] Removed UnsyncedParallel, which I realized was just reimplementing the functionality of AnimationGroup (and erroneously); I had misinterpreted the nature of the 'squish'ing in AnimationGroup --- animation/simple_animations.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 853f218f..566cd9f7 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -460,11 +460,3 @@ class AnimationGroup(Animation): def update_mobject(self, alpha): for anim in self.sub_anims: anim.update(alpha) - -# Parallel animations where shorter animations are not stretched out to match the longest -class UnsyncedParallel(AnimationGroup): - def __init__(self, *sub_anims, **kwargs): - digest_config(self, kwargs, locals()) - self.run_time = max([a.run_time for a in sub_anims]) - everything = Mobject(*[a.mobject for a in sub_anims]) - Animation.__init__(self, everything, **kwargs) \ No newline at end of file From 10cc6469ef574436142dc5f4c9db6ebe1ea5fba7 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Wed, 24 Jan 2018 13:11:44 -0800 Subject: [PATCH 2/4] Incremental progress on WindingNumber --- active_projects/WindingNumber.py | 525 ++++++++++++++++++++----------- 1 file changed, 333 insertions(+), 192 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 33478038..46257a5b 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -256,52 +256,12 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene): self.drawGraph() self.solveEquation() -class FirstSqrtScene(EquationSolver1d): - CONFIG = { - "x_min" : 0, - "x_max" : 2.5, - "y_min" : 0, - "y_max" : 2.5**2, - "graph_origin" : 2*DOWN + 5 * LEFT, - "x_axis_width" : 12, - "zoom_factor" : 3, - "zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT, - "func" : lambda x : x**2, - "targetX" : np.sqrt(2), - "targetY" : 2, - "initial_lower_x" : 1, - "initial_upper_x" : 2, - "num_iterations" : 10, - "iteration_at_which_to_start_zoom" : 3, - "graph_label" : "y = x^2", - "show_target_line" : True, - } - -class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene): - - 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: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly # popping out and in? # # TODO: Perhaps have bullets change color corresponding to a function of their coordinates? +# This could involve some merging of functoinality with PiColorWakler class LinePulser(ContinualAnimation): def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs): self.line = line @@ -318,154 +278,6 @@ class LinePulser(ContinualAnimation): self.bullets[i].move_to(interpolate(start, end, np.true_divide((i + alpha),(self.num_bullets)))) -class LoopSplitScene(Scene): - - def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs): - line = Line(start, end, **kwargs) - anim = LinePulser(line, bullet_template, num_bullets, pulse_time, **kwargs) - return [VGroup(line, *anim.bullets), anim] - - def construct(self): - num_plane = NumberPlane(color = LIGHT_GREY, stroke_width = 1) - num_plane.axes.set_stroke(color = WHITE, width = 2) - num_plane.fade() - self.add(num_plane) - - scale_factor = 2 - shift_term = 0 - - # Original loop - tl = scale_factor * (UP + LEFT) + shift_term - tm = scale_factor * UP + shift_term - tr = scale_factor * (UP + RIGHT) + shift_term - mr = scale_factor * RIGHT + shift_term - br = scale_factor * (DOWN + RIGHT) + shift_term - bm = scale_factor * DOWN + shift_term - bl = scale_factor * (DOWN + LEFT) + shift_term - lm = scale_factor * LEFT + shift_term - - loop_color = BLUE - - default_bullet = PiCreature(color = RED) - default_bullet.scale(0.15) - - modified_bullet = PiCreature(color = PINK) - modified_bullet.scale(0.15) - - def SGroup(*args): - return VGroup(*[arg[0] for arg in args]) - - top_line = self.PulsedLine(tl, tr, default_bullet, color = BLUE) - right_line = self.PulsedLine(tr, br, modified_bullet, color = BLUE) - bottom_line = self.PulsedLine(br, bl, default_bullet, color = BLUE) - left_line = self.PulsedLine(bl, tl, default_bullet, color = BLUE) - line_list = [top_line, right_line, bottom_line, left_line] - loop = SGroup(*line_list) - for line in line_list: - self.add(*line) - self.wait() - - # Splits in middle - split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5)) - self.play(ShowCreation(split_line)) - - self.remove(*split_line) - mid_line_left = self.PulsedLine(tm, bm, default_bullet, color = loop_color) - mid_line_right = self.PulsedLine(bm, tm, modified_bullet, color = loop_color) - self.add(*mid_line_left) - self.add(*mid_line_right) - - top_line_left_half = self.PulsedLine(tl, tm, default_bullet, 2, 1, color = loop_color) - top_line_right_half = self.PulsedLine(tm, tr, modified_bullet, 2, 1, color = loop_color) - - bottom_line_left_half = self.PulsedLine(bm, bl, default_bullet, 2, 1, color = loop_color) - bottom_line_right_half = self.PulsedLine(br, bm, modified_bullet, 2, 1, color = loop_color) - - self.remove(*top_line) - self.add(*top_line_left_half) - self.add(*top_line_right_half) - self.remove(*bottom_line) - self.add(*bottom_line_left_half) - self.add(*bottom_line_right_half) - - left_open_loop = SGroup(top_line_left_half, left_line, bottom_line_left_half) - left_closed_loop = VGroup(left_open_loop, mid_line_left[0]) - right_open_loop = SGroup(top_line_right_half, right_line, bottom_line_right_half) - right_closed_loop = VGroup(right_open_loop, mid_line_right[0]) - - # self.play( - # ApplyMethod(left_closed_loop.shift, LEFT), - # ApplyMethod(right_closed_loop.shift, RIGHT) - # ) - - self.wait() - - # self.play( - # ApplyMethod(left_open_loop.shift, LEFT), - # ApplyMethod(right_open_loop.shift, RIGHT) - # ) - - self.wait() - - mid_lines = SGroup(mid_line_left, mid_line_right) - - highlight_circle = Circle(color = YELLOW_E) # Perhaps make this a dashed circle? - highlight_circle.surround(mid_lines) - self.play(Indicate(mid_lines), ShowCreation(highlight_circle, run_time = 0.5)) - - self.wait() - - self.play(FadeOut(highlight_circle), FadeOut(mid_lines)) - # Because FadeOut didn't remove the continual pulsers, we remove them manually - self.remove(mid_line_left[1], mid_line_right[1]) - - # Brings loop back together; keep in sync with motions which bring loop apart above - # self.play( - # ApplyMethod(left_open_loop.shift, 2 * RIGHT), - # ApplyMethod(right_open_loop.shift, 2 * LEFT) - # ) - - self.wait() - -class LoopSplitSceneMapped(LoopSplitScene): - - def setup(self): - left_camera = Camera(**self.camera_config) - right_camera = MappingCamera( - mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3), - **self.camera_config) - split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) - self.camera = split_screen_camera - -class NumberLineScene(Scene): - def construct(self): - num_line = NumberLine() - self.add(num_line) - self.wait() - - interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1), - stroke_color = RED, stroke_width = 10) - self.play(ShowCreation(interval_1d)) - self.wait() - - num_plane = NumberPlane() - - random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT] - - interval_2d = Polygon( - *random_points, - stroke_color = RED, - stroke_width = 10) - # TODO: Turn this into a more complicated, curvy loop? - # TODO: Illustrate borders and filled interiors with a particular color - # on both 1d and 2d region? - - self.play( - FadeOut(num_line), - FadeIn(num_plane), - ReplacementTransform(interval_1d, interval_2d)) - - self.wait() def color_func(alpha): alpha = alpha % 1 @@ -503,7 +315,7 @@ class FuncRotater(Animation): "rotate_func" : lambda x : x # Func from alpha to revolutions } - # Perhaps abstract this out into an "Animation from base object" class + # Perhaps abstract this out into an "Animation updating from original object" class def update_submobject(self, submobject, starting_submobject, alpha): submobject.points = np.array(starting_submobject.points) @@ -512,6 +324,7 @@ class FuncRotater(Animation): angle_revs = self.rotate_func(alpha) self.mobject.rotate( angle_revs * 2 * np.pi, + about_point = ORIGIN ) self.mobject.set_color(color_func(angle_revs)) @@ -646,7 +459,7 @@ empty_animation = Animation(Mobject()) def EmptyAnimation(): return empty_animation -# TODO: Perhaps restructure this to avoid using AnimationGroup/UnsyncedParallels, and instead +# TODO: Perhaps restructure this to avoid using AnimationGroup, and instead # use lists of animations or lists or other such data, to be merged and processed into parallel # animations later class EquationSolver2d(Scene): @@ -729,7 +542,7 @@ class EquationSolver2d(Scene): return Succession(anim, ShowCreation(mid_line), FadeOut(mid_line), - UnsyncedParallel(*sub_anims) + AnimationGroup(*sub_anims) ) lower_x = self.initial_lower_x @@ -752,3 +565,331 @@ class EquationSolver2d(Scene): self.wait() +class PiColorWalker(Scene): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**2), + "start_x" : -1, + "start_y" : 1, + "walk_width" : 2, + "walk_height" : 2, + } + + def construct(self): + + rev_func = lambda p : point_to_rev(self.func(p)) + + num_plane = NumberPlane() + num_plane.fade() + self.add(num_plane) + + class WalkerAnimation(Animation): + CONFIG = { + "remover" : True, + "rate_func" : None, + "start_coords" : None, + "end_coords" : None, + } + + def __init__(self, start_coords, end_coords, **kwargs): + self.start_coords = start_coords + self.end_coords = end_coords + self.compound_walker = VGroup() + self.compound_walker.walker = PiCreature(color = RED) + self.compound_walker.walker.scale(0.35) + self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0) + self.compound_walker.digest_mobject_attrs() + Animation.__init__(self, self.compound_walker, **kwargs) + + # Perhaps abstract this out into an "Animation updating from original object" class + def update_submobject(self, submobject, starting_submobject, alpha): + submobject.points = np.array(starting_submobject.points) + + def update_mobject(self, alpha): + Animation.update_mobject(self, alpha) + cur_x, cur_y = cur_coords = interpolate(self.start_coords, self.end_coords, alpha) + self.mobject.walker.move_to(num_plane.coords_to_point(cur_x, cur_y)) + rev = rev_func(cur_coords) + self.mobject.walker.set_color(color_func(rev)) + self.mobject.arrow.set_color(color_func(rev)) + self.mobject.arrow.rotate( + rev * 2 * np.pi, + about_point = ORIGIN #self.mobject.arrow.get_start() + ) + + + TL = np.array((self.start_x, self.start_y)) + TR = TL + (self.walk_width, 0) + BR = TR + (0, -self.walk_height) + BL = BR + (-self.walk_width, 0) + + walk_coords = [TL, TR, BR, BL] + for i in range(len(walk_coords)): + start_x, start_y = start_coords = walk_coords[i] + start_point = num_plane.coords_to_point(start_x, start_y) + end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)] + end_point = num_plane.coords_to_point(end_x, end_y) + self.play( + WalkerAnimation( + start_coords = start_coords, + end_coords = end_coords, + remover = (i < len(walk_coords) - 1) + ), + ShowCreation(Line(start_point, end_point))) + + + self.wait() + +############# +# Above are mostly general tools; here, we list, in order, finished or near-finished scenes + +class FirstSqrtScene(EquationSolver1d): + CONFIG = { + "x_min" : 0, + "x_max" : 2.5, + "y_min" : 0, + "y_max" : 2.5**2, + "graph_origin" : 2*DOWN + 5 * LEFT, + "x_axis_width" : 12, + "zoom_factor" : 3, + "zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT, + "func" : lambda x : x**2, + "targetX" : np.sqrt(2), + "targetY" : 2, + "initial_lower_x" : 1, + "initial_upper_x" : 2, + "num_iterations" : 10, + "iteration_at_which_to_start_zoom" : 3, + "graph_label" : "y = x^2", + "show_target_line" : True, + } + +# TODO: Pi creatures intrigued + +class ComplexPlaneIs2d(Scene): + def construct(self): + com_plane = ComplexPlane() + self.add(com_plane) + # TODO: Add labels to axes, specific complex points + self.wait() + +class NumberLineScene(Scene): + def construct(self): + num_line = NumberLine() + self.add(num_line) + # TODO: Add labels, arrows, specific points + self.wait() + + interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1), + stroke_color = RED, stroke_width = 10) + self.play(ShowCreation(interval_1d)) + self.wait() + + num_plane = NumberPlane() + + random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT] + + interval_2d = Polygon( + *random_points, + stroke_color = RED, + stroke_width = 10) + # TODO: Turn this into a more complicated, curvy loop? + # TODO: Illustrate borders and filled interiors with a particular color + # on both 1d and 2d region? + + self.play( + FadeOut(num_line), + FadeIn(num_plane), + ReplacementTransform(interval_1d, interval_2d)) + + self.wait() + +# TODO: Split screen illustration of 2d function (before domain coloring) + +# TODO: Illustrations for introducing domain coloring + +# 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() + +class LoopSplitScene(Scene): + + def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs): + line = Line(start, end, **kwargs) + anim = LinePulser(line, bullet_template, num_bullets, pulse_time, **kwargs) + return [VGroup(line, *anim.bullets), anim] + + def construct(self): + num_plane = NumberPlane(color = LIGHT_GREY, stroke_width = 1) + + # We actually don't want to highlight + num_plane.axes.set_stroke(color = WHITE, width = 2) + num_plane.fade() + self.add(num_plane) + + scale_factor = 2 + shift_term = 0 + + # Original loop + tl = scale_factor * (UP + LEFT) + shift_term + tm = scale_factor * UP + shift_term + tr = scale_factor * (UP + RIGHT) + shift_term + mr = scale_factor * RIGHT + shift_term + br = scale_factor * (DOWN + RIGHT) + shift_term + bm = scale_factor * DOWN + shift_term + bl = scale_factor * (DOWN + LEFT) + shift_term + lm = scale_factor * LEFT + shift_term + + loop_color = BLUE + + default_bullet = PiCreature(color = RED) + default_bullet.scale(0.15) + + modified_bullet = PiCreature(color = PINK) + modified_bullet.scale(0.15) + + def SGroup(*args): + return VGroup(*[arg[0] for arg in args]) + + top_line = self.PulsedLine(tl, tr, default_bullet, color = BLUE) + right_line = self.PulsedLine(tr, br, modified_bullet, color = BLUE) + bottom_line = self.PulsedLine(br, bl, default_bullet, color = BLUE) + left_line = self.PulsedLine(bl, tl, default_bullet, color = BLUE) + line_list = [top_line, right_line, bottom_line, left_line] + loop = SGroup(*line_list) + for line in line_list: + self.add(*line) + self.wait() + + # Splits in middle + split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5)) + self.play(ShowCreation(split_line)) + + self.remove(*split_line) + mid_line_left = self.PulsedLine(tm, bm, default_bullet, color = loop_color) + mid_line_right = self.PulsedLine(bm, tm, modified_bullet, color = loop_color) + self.add(*mid_line_left) + self.add(*mid_line_right) + + top_line_left_half = self.PulsedLine(tl, tm, default_bullet, 2, 1, color = loop_color) + top_line_right_half = self.PulsedLine(tm, tr, modified_bullet, 2, 1, color = loop_color) + + bottom_line_left_half = self.PulsedLine(bm, bl, default_bullet, 2, 1, color = loop_color) + bottom_line_right_half = self.PulsedLine(br, bm, modified_bullet, 2, 1, color = loop_color) + + self.remove(*top_line) + self.add(*top_line_left_half) + self.add(*top_line_right_half) + self.remove(*bottom_line) + self.add(*bottom_line_left_half) + self.add(*bottom_line_right_half) + + left_open_loop = SGroup(top_line_left_half, left_line, bottom_line_left_half) + left_closed_loop = VGroup(left_open_loop, mid_line_left[0]) + right_open_loop = SGroup(top_line_right_half, right_line, bottom_line_right_half) + right_closed_loop = VGroup(right_open_loop, mid_line_right[0]) + + # self.play( + # ApplyMethod(left_closed_loop.shift, LEFT), + # ApplyMethod(right_closed_loop.shift, RIGHT) + # ) + + self.wait() + + # self.play( + # ApplyMethod(left_open_loop.shift, LEFT), + # ApplyMethod(right_open_loop.shift, RIGHT) + # ) + + self.wait() + + mid_lines = SGroup(mid_line_left, mid_line_right) + + highlight_circle = Circle(color = YELLOW_E) # Perhaps make this a dashed circle? + highlight_circle.surround(mid_lines) + self.play(Indicate(mid_lines), ShowCreation(highlight_circle, run_time = 0.5)) + + self.wait() + + self.play(FadeOut(highlight_circle), FadeOut(mid_lines)) + # Because FadeOut didn't remove the continual pulsers, we remove them manually + self.remove(mid_line_left[1], mid_line_right[1]) + + # Brings loop back together; keep in sync with motions which bring loop apart above + # self.play( + # ApplyMethod(left_open_loop.shift, 2 * RIGHT), + # ApplyMethod(right_open_loop.shift, 2 * LEFT) + # ) + + self.wait() + +class LoopSplitSceneMapped(LoopSplitScene): + + def setup(self): + left_camera = Camera(**self.camera_config) + right_camera = MappingCamera( + mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3), + allow_object_intrusion = False + **self.camera_config) + split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) + self.camera = split_screen_camera + +# TODO: Perhaps do extra illustration of zooming out and winding around a large circle, +# 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)), + } + +# TODO: Borsuk-Ulam visuals + +# TODO: "Good enough" property visuals (swinging arrows and odometer, like in odometer scene above) + +# TODO: Brouwer's fixed point theorem visuals + +# TODO: Pi creatures wide-eyed in amazement + +################# + +# TODOs, from easiest to hardest: + +# Minor fiddling with little things in each animation; placements, colors, timing + +# Writing new Pi creature walker scenes off of general template + +# Odometer/swinging arrows stuff + +# Pi creature emotion stuff + +# Split screen illustration of 2d function (before domain coloring) + +# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out + +# BFT visuals + +# Borsuk-Ulam visuals + +# Domain coloring + +# FIN From ab45afba8aa132c69d39a86ca4b8cdf06314268e Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Fri, 26 Jan 2018 12:26:30 -0800 Subject: [PATCH 3/4] Incremental progress on WindingNumber --- active_projects/WindingNumber.py | 342 +++++++++++++++++++++---------- 1 file changed, 236 insertions(+), 106 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 46257a5b..1fed87e4 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -128,7 +128,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene): downBrace, upBrace = yBraces = TexMobject("||") yBraces.stretch(2, 0) - yBraces.rotate(np.pi/2) + yBraces.rotate(TAU/4) lowerX = self.initial_lower_x lowerY = self.func(lowerX) @@ -261,13 +261,14 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene): # popping out and in? # # TODO: Perhaps have bullets change color corresponding to a function of their coordinates? -# This could involve some merging of functoinality with PiColorWakler +# This could involve some merging of functoinality with PiWalker class LinePulser(ContinualAnimation): - def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs): + def __init__(self, line, bullet_template, num_bullets, pulse_time, color_func = None, **kwargs): self.line = line self.num_bullets = num_bullets self.pulse_time = pulse_time self.bullets = [bullet_template.copy() for i in range(num_bullets)] + self.color_func = color_func ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) def update_mobject(self, dt): @@ -275,8 +276,11 @@ class LinePulser(ContinualAnimation): start = self.line.get_start() end = self.line.get_end() for i in range(self.num_bullets): - self.bullets[i].move_to(interpolate(start, end, - np.true_divide((i + alpha),(self.num_bullets)))) + position = interpolate(start, end, + np.true_divide((i + alpha),(self.num_bullets))) + self.bullets[i].move_to(position) + if self.color_func: + self.bullets.set_color(self.color_func(position)) def color_func(alpha): @@ -298,7 +302,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 * 2 * np.pi) + x.rotate(revs * TAU) x.set_color(color_func(revs)) return x @@ -322,8 +326,9 @@ class FuncRotater(Animation): def update_mobject(self, alpha): Animation.update_mobject(self, alpha) angle_revs = self.rotate_func(alpha) + # We do a clockwise rotation self.mobject.rotate( - angle_revs * 2 * np.pi, + -angle_revs * TAU, about_point = ORIGIN ) self.mobject.set_color(color_func(angle_revs)) @@ -336,21 +341,40 @@ class TestRotater(Scene): rotate_func = lambda x : x % 0.25, run_time = 10)) +# TODO: Be careful about clockwise vs. counterclockwise convention throughout! +# Make sure this is correct everywhere in resulting video. class OdometerScene(Scene): CONFIG = { - "rotate_func" : lambda x : np.sin(x * 2 * np.pi), - "run_time" : 5 + "rotate_func" : lambda x : np.sin(x * TAU), + "run_time" : 5, + "dashed_line_angle" : None, + "biased_display_start" : None } def construct(self): - base_arrow = Arrow(ORIGIN, RIGHT) - circle = Circle(center = ORIGIN, radius = 1.3) + radius = 1.3 + circle = Circle(center = ORIGIN, radius = radius) self.add(circle) + + if self.dashed_line_angle: + dashed_line = DashedLine(ORIGIN, radius * RIGHT) + # Clockwise rotation + dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN) + self.add(dashed_line) + num_display = DecimalNumber(0) num_display.move_to(2 * DOWN) + + display_val_bias = 0 + if self.biased_display_start != None: + display_val_bias = self.biased_display_start - self.rotate_func(0) + display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias + + base_arrow = Arrow(ORIGIN, RIGHT, buff = 0) + self.play( FuncRotater(base_arrow, rotate_func = self.rotate_func), - ChangingDecimal(num_display, self.rotate_func), + ChangingDecimal(num_display, display_func), run_time = self.run_time, rate_func = None) @@ -361,7 +385,7 @@ def point_to_rev((x, y)): if (x, y) == (0, 0): print "Error! Angle of (0, 0) computed!" return None - return np.true_divide(np.arctan2(y, x), 2 * np.pi) + return np.true_divide(np.arctan2(y, x), TAU) # Returns the value with the same fractional component as x, closest to m def resit_near(x, m): @@ -441,6 +465,8 @@ class RectangleData(): sides = (self.get_top(), self.get_bottom()) elif dim == 1: sides = (self.get_left(), self.get_right()) + else: + print "RectangleData.split_line_on_dim passed illegitimate dimension!" return tuple([mid(x, y) for (x, y) in sides]) @@ -455,10 +481,120 @@ def plane_poly_with_roots(*points): def plane_func_from_complex_func(f): return lambda (x, y) : complex_to_pair(f(complex(x,y))) +def point_func_from_complex_func(f): + return lambda (x, y, z): complex_to_R3(f(complex(x, y))) + empty_animation = Animation(Mobject()) def EmptyAnimation(): return empty_animation +class WalkerAnimation(Animation): + CONFIG = { + "walk_func" : None, # Must be initialized to use + "remover" : True, + "rate_func" : None, + "coords_to_point" : None + } + + def __init__(self, walk_func, rev_func, coords_to_point, **kwargs): + self.walk_func = walk_func + self.rev_func = rev_func + self.coords_to_point = coords_to_point + self.compound_walker = VGroup() + self.compound_walker.walker = PiCreature(color = RED) + self.compound_walker.walker.scale(0.35) + self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0) + self.compound_walker.digest_mobject_attrs() + Animation.__init__(self, self.compound_walker, **kwargs) + + # Perhaps abstract this out into an "Animation updating from original object" class + def update_submobject(self, submobject, starting_submobject, alpha): + submobject.points = np.array(starting_submobject.points) + + def update_mobject(self, alpha): + Animation.update_mobject(self, alpha) + cur_x, cur_y = cur_coords = self.walk_func(alpha) + self.mobject.walker.move_to(self.coords_to_point(cur_x, cur_y)) + rev = self.rev_func(cur_coords) + self.mobject.walker.set_color(color_func(rev)) + self.mobject.arrow.set_color(color_func(rev)) + self.mobject.arrow.rotate( + rev * TAU, + about_point = ORIGIN #self.mobject.arrow.get_start() + ) + +def LinearWalker(start_coords, end_coords, coords_to_point, rev_func, **kwargs): + walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha) + return WalkerAnimation( + walk_func = walk_func, + coords_to_point = coords_to_point, + rev_func = rev_func, + **kwargs) + +class PiWalker(Scene): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**2), + "walk_coords" : [], + "step_run_time" : 1 + } + + def construct(self): + + rev_func = lambda p : point_to_rev(self.func(p)) + + num_plane = NumberPlane() + num_plane.fade() + self.add(num_plane) + + walk_coords = self.walk_coords + for i in range(len(walk_coords)): + start_x, start_y = start_coords = walk_coords[i] + start_point = num_plane.coords_to_point(start_x, start_y) + end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)] + end_point = num_plane.coords_to_point(end_x, end_y) + self.play( + LinearWalker( + start_coords = start_coords, + end_coords = end_coords, + coords_to_point = num_plane.coords_to_point, + rev_func = rev_func, + remover = (i < len(walk_coords) - 1) + ), + ShowCreation(Line(start_point, end_point)), + run_time = self.step_run_time) + + + self.wait() + +class PiWalkerRect(PiWalker): + CONFIG = { + "start_x" : -1, + "start_y" : 1, + "walk_width" : 2, + "walk_height" : 2, + } + + def setup(self): + TL = np.array((self.start_x, self.start_y)) + TR = TL + (self.walk_width, 0) + BR = TR + (0, -self.walk_height) + BL = BR + (-self.walk_width, 0) + self.walk_coords = [TL, TR, BR, BL] + PiWalker.setup(self) + +class PiWalkerCircle(PiWalker): + CONFIG = { + "radius" : 1, + "num_steps" : 100, + "step_run_time" : 0.01 + } + + def setup(self): + r = self.radius + N = self.num_steps + self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)] + PiWalker.setup(self) + # TODO: Perhaps restructure this to avoid using AnimationGroup, and instead # use lists of animations or lists or other such data, to be merged and processed into parallel # animations later @@ -495,8 +631,17 @@ class EquationSolver2d(Scene): color = RED) thin_line = line.copy() thin_line.set_stroke(width = 1) + walker_anim = LinearWalker( + start_coords = start, + end_coords = end, + coords_to_point = num_plane.coords_to_point, + rev_func = rev_func, + remover = True + ) + line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim, + run_time = 2) anim = Succession( - ShowCreation, line, + line_draw_anim, Transform, line, thin_line ) return (anim, rebased_winder(1)) @@ -523,6 +668,8 @@ class EquationSolver2d(Scene): rect.get_bottom_left() ] points = [num_plane.coords_to_point(x, y) for (x, y) in coords] + # TODO: Maybe use diagonal lines or something to fill in rectangles indicating + # their "Nothing here" status? fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED) return Succession(anim, FadeIn(fill_rect)) else: @@ -541,7 +688,7 @@ class EquationSolver2d(Scene): mid_line = DashedLine(*mid_line_points) return Succession(anim, ShowCreation(mid_line), - FadeOut(mid_line), + # FadeOut(mid_line), # TODO: Can change timing so this fades out at just the time it would be overdrawn AnimationGroup(*sub_anims) ) @@ -565,80 +712,6 @@ class EquationSolver2d(Scene): self.wait() -class PiColorWalker(Scene): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**2), - "start_x" : -1, - "start_y" : 1, - "walk_width" : 2, - "walk_height" : 2, - } - - def construct(self): - - rev_func = lambda p : point_to_rev(self.func(p)) - - num_plane = NumberPlane() - num_plane.fade() - self.add(num_plane) - - class WalkerAnimation(Animation): - CONFIG = { - "remover" : True, - "rate_func" : None, - "start_coords" : None, - "end_coords" : None, - } - - def __init__(self, start_coords, end_coords, **kwargs): - self.start_coords = start_coords - self.end_coords = end_coords - self.compound_walker = VGroup() - self.compound_walker.walker = PiCreature(color = RED) - self.compound_walker.walker.scale(0.35) - self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0) - self.compound_walker.digest_mobject_attrs() - Animation.__init__(self, self.compound_walker, **kwargs) - - # Perhaps abstract this out into an "Animation updating from original object" class - def update_submobject(self, submobject, starting_submobject, alpha): - submobject.points = np.array(starting_submobject.points) - - def update_mobject(self, alpha): - Animation.update_mobject(self, alpha) - cur_x, cur_y = cur_coords = interpolate(self.start_coords, self.end_coords, alpha) - self.mobject.walker.move_to(num_plane.coords_to_point(cur_x, cur_y)) - rev = rev_func(cur_coords) - self.mobject.walker.set_color(color_func(rev)) - self.mobject.arrow.set_color(color_func(rev)) - self.mobject.arrow.rotate( - rev * 2 * np.pi, - about_point = ORIGIN #self.mobject.arrow.get_start() - ) - - - TL = np.array((self.start_x, self.start_y)) - TR = TL + (self.walk_width, 0) - BR = TR + (0, -self.walk_height) - BL = BR + (-self.walk_width, 0) - - walk_coords = [TL, TR, BR, BL] - for i in range(len(walk_coords)): - start_x, start_y = start_coords = walk_coords[i] - start_point = num_plane.coords_to_point(start_x, start_y) - end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)] - end_point = num_plane.coords_to_point(end_x, end_y) - self.play( - WalkerAnimation( - start_coords = start_coords, - end_coords = end_coords, - remover = (i < len(walk_coords) - 1) - ), - ShowCreation(Line(start_point, end_point))) - - - self.wait() - ############# # Above are mostly general tools; here, we list, in order, finished or near-finished scenes @@ -679,31 +752,62 @@ class NumberLineScene(Scene): # TODO: Add labels, arrows, specific points self.wait() - interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1), - stroke_color = RED, stroke_width = 10) - self.play(ShowCreation(interval_1d)) + border_color = PURPLE_E + inner_color = RED + stroke_width = 10 + + left_point = num_line.number_to_point(-1) + right_point = num_line.number_to_point(1) + interval_1d = Line(left_point, right_point, + stroke_color = inner_color, stroke_width = stroke_width) + 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) + self.play(ShowCreation(full_1d)) self.wait() num_plane = NumberPlane() - random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT] + random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT] - interval_2d = Polygon( + border_2d = Polygon( *random_points, - stroke_color = RED, - stroke_width = 10) - # TODO: Turn this into a more complicated, curvy loop? - # TODO: Illustrate borders and filled interiors with a particular color - # on both 1d and 2d region? + stroke_color = border_color, + stroke_width = stroke_width) + + filling_2d = Polygon( + *random_points, + fill_color = inner_color, + fill_opacity = 0.8, + stroke_width = stroke_width) + full_2d = VGroup(filling_2d, border_2d) self.play( FadeOut(num_line), FadeIn(num_plane), - ReplacementTransform(interval_1d, interval_2d)) + ReplacementTransform(full_1d, full_2d)) self.wait() -# TODO: Split screen illustration of 2d function (before domain coloring) +class Initial2dFuncScene(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)), + **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() + 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) + self.play(ShowCreation(line)) # TODO: Illustrations for introducing domain coloring @@ -844,13 +948,13 @@ class LoopSplitScene(Scene): self.wait() +# Is there a way to abstract this into a general process to derive a new mapped scene from an old scene? class LoopSplitSceneMapped(LoopSplitScene): def setup(self): left_camera = Camera(**self.camera_config) right_camera = MappingCamera( mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3), - allow_object_intrusion = False **self.camera_config) split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) self.camera = split_screen_camera @@ -860,11 +964,35 @@ class LoopSplitSceneMapped(LoopSplitScene): class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)), + "num_iterations" : 1, } # TODO: Borsuk-Ulam visuals +# Note: May want to do an ordinary square scene, then mapping func it into a circle +# class BorsukUlamScene(PiWalker): -# TODO: "Good enough" property visuals (swinging arrows and odometer, like in odometer scene above) +# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere +left_func = lambda x : x**2 - x + 1 +diff_func = lambda x : np.cos(1.4 * (x - 0.1) * (np.log(x + 0.1) - 0.3) * TAU)/2.1 + +class LeftOdometer(OdometerScene): + CONFIG = { + "rotate_func" : left_func, + "biased_display_start" : 0 + } + +class RightOdometer(OdometerScene): + CONFIG = { + "rotate_func" : lambda x : left_func(x) + diff_func(x), + "biased_display_start" : 0 + } + +class DiffOdometer(OdometerScene): + CONFIG = { + "rotate_func" : diff_func, + "dashed_line_angle" : 0.5, + "biased_display_start" : 0 + } # TODO: Brouwer's fixed point theorem visuals @@ -876,16 +1004,18 @@ class FundThmAlg(EquationSolver2d): # Minor fiddling with little things in each animation; placements, colors, timing -# Writing new Pi creature walker scenes off of general template - # Odometer/swinging arrows stuff -# Pi creature emotion stuff +# Writing new Pi creature walker scenes off of general template # Split screen illustration of 2d function (before domain coloring) # Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out +# ---- + +# Pi creature emotion stuff + # BFT visuals # Borsuk-Ulam visuals From ad14222d1cd94c82ed83cdf3dd782109fd543831 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Fri, 26 Jan 2018 13:54:20 -0800 Subject: [PATCH 4/4] Reorganized file structure of Camera module --- camera/__init__.py | 1 + camera.py => camera/camera.py | 0 2 files changed, 1 insertion(+) create mode 100644 camera/__init__.py rename camera.py => camera/camera.py (100%) diff --git a/camera/__init__.py b/camera/__init__.py new file mode 100644 index 00000000..ad6fed0b --- /dev/null +++ b/camera/__init__.py @@ -0,0 +1 @@ +from camera import * diff --git a/camera.py b/camera/camera.py similarity index 100% rename from camera.py rename to camera/camera.py