From b58b7716788c41e971f20e2f542c4411ea03c057 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 20 Mar 2018 17:19:29 -0700 Subject: [PATCH 1/4] EquationSolver2d restructure, en route to implementing BFS --- active_projects/WindingNumber.py | 49 ++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 5399b4b7..655b9e3a 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -922,6 +922,20 @@ class PiWalkerCircle(PiWalker): self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)] PiWalker.setup(self) +class EquationSolver2dNode(object): + def __init__(self, first_anim, children = []): + self.first_anim = first_anim + self.children = children + + def display_in_series(self): + return Succession(self.first_anim, *map(EquationSolver2dNode.display_in_series, self.children)) + + def display_in_parallel(self): + return Succession(self.first_anim, AnimationGroup(*map(EquationSolver2dNode.display_in_parallel, self.children))) + + def display_in_bfs(self): + print "Error! Not yet implemented bfs display!" + class EquationSolver2d(ColorMappedObjectsScene): CONFIG = { "camera_config" : {"use_z_coordinate_for_display_order": True}, @@ -931,7 +945,11 @@ class EquationSolver2d(ColorMappedObjectsScene): "initial_upper_y" : 3, "num_iterations" : 1, "num_checkpoints" : 10, + + # Should really merge this into one enum-style variable "display_in_parallel" : False, + "display_in_bfs" : False, + "use_fancy_lines" : True, # TODO: Consider adding a "find_all_roots" flag, which could be turned off # to only explore one of the two candidate subrectangles when both are viable @@ -991,7 +1009,7 @@ class EquationSolver2d(ColorMappedObjectsScene): print "Solver at depth: " + str(cur_depth) if cur_depth >= self.num_iterations: - return empty_animation + return EquationSolver2dNode(empty_animation) def draw_line_return_wind(start, end, start_wind, should_linger = False, draw_line = True): alpha_winder = make_alpha_winder(clockwise_val_func, start, end, self.num_checkpoints) @@ -1057,14 +1075,14 @@ class EquationSolver2d(ColorMappedObjectsScene): # their "Nothing here" status? # Or draw a large X or something fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = DARK_GREY) - return Succession(anim, FadeIn(fill_rect)) + return EquationSolver2dNode(Succession(anim, FadeIn(fill_rect))) else: (sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split) if dim_to_split == 0: sub_rect_and_sides = [(sub_rect1, 1), (sub_rect2, 3)] else: sub_rect_and_sides = [(sub_rect1, 2), (sub_rect2, 0)] - sub_anims = [ + children = [ Animate2dSolver( cur_depth = cur_depth + 1, rect = sub_rect, @@ -1077,14 +1095,8 @@ class EquationSolver2d(ColorMappedObjectsScene): mid_line_coords = rect.split_line_on_dim(dim_to_split) mid_line_points = [num_plane.coords_to_point(x, y) + 2 * IN for (x, y) in mid_line_coords] mid_line = DashedLine(*mid_line_points) - if self.display_in_parallel: - recursive_anim = AnimationGroup(*sub_anims) - else: - recursive_anim = Succession(*sub_anims) - return Succession(anim, - ShowCreation(mid_line), - recursive_anim - ) + + return EquationSolver2dNode(Succession(anim, ShowCreation(mid_line)), children) lower_x = self.initial_lower_x upper_x = self.initial_upper_x @@ -1098,7 +1110,7 @@ class EquationSolver2d(ColorMappedObjectsScene): print "Starting to compute anim" - anim = Animate2dSolver( + node = Animate2dSolver( cur_depth = 0, rect = rect, dim_to_split = 0, @@ -1108,6 +1120,13 @@ class EquationSolver2d(ColorMappedObjectsScene): print "Done computing anim" + if self.display_in_parallel: + anim = node.display_in_parallel() + elif self.display_in_bfs: + anim = node.display_in_bfs() + else: + anim = node.display_in_series() + # Keep timing details here in sync with details above rect_points = [ rect.get_top_left(), @@ -1937,6 +1956,12 @@ class PreviewClip(EquationSolver2d): "use_fancy_lines" : True, } +class QuickPreview(PreviewClip): + CONFIG = { + "num_iterations" : 2, + "display_in_parallel" : False, + } + # TODO: Borsuk-Ulam visuals # Note: May want to do an ordinary square scene, then MappingCamera it into a circle # class BorsukUlamScene(PiWalker): From 10637e1d970dafb72ef4db0f869e6bcc55834dc0 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Tue, 20 Mar 2018 17:35:57 -0700 Subject: [PATCH 2/4] BFS now implemented on EquationSolver2d --- active_projects/WindingNumber.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 655b9e3a..2815d178 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -927,14 +927,34 @@ class EquationSolver2dNode(object): self.first_anim = first_anim self.children = children + def depth(self): + if len(self.children) == 0: + return 0 + + return 1 + max(map(lambda n : n.depth(), self.children)) + + def nodes_at_depth(self, n): + if n == 0: + return [self] + + return sum(map(lambda c : c.nodes_at_depth(n - 1), self.children), []) + + # This is definitely NOT the efficient way to do BFS, but I'm just trying to write something + # quick without thinking that gets the job done on small instances for now + def hacky_bfs(self): + depth = self.depth() + + return sum(map(lambda i : self.nodes_at_depth(i), range(depth + 1)), []) + def display_in_series(self): - return Succession(self.first_anim, *map(EquationSolver2dNode.display_in_series, self.children)) + return Succession(self.first_anim, *map(lambda n : n.display_in_series(), self.children)) def display_in_parallel(self): - return Succession(self.first_anim, AnimationGroup(*map(EquationSolver2dNode.display_in_parallel, self.children))) + return Succession(self.first_anim, AnimationGroup(*map(lambda n : n.display_in_parallel(), self.children))) def display_in_bfs(self): - print "Error! Not yet implemented bfs display!" + bfs_nodes = self.hacky_bfs() + return Succession(*map(lambda n : n.first_anim, bfs_nodes)) class EquationSolver2d(ColorMappedObjectsScene): CONFIG = { @@ -1960,6 +1980,7 @@ class QuickPreview(PreviewClip): CONFIG = { "num_iterations" : 2, "display_in_parallel" : False, + "display_in_bfs" : True } # TODO: Borsuk-Ulam visuals From dccdca41b2fbbf75056d02912d97bfde34cbc3b9 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 22 Mar 2018 11:54:08 -0700 Subject: [PATCH 3/4] Incremental --- active_projects/WindingNumber.py | 276 +++++++++++++++++++++---------- topics/geometry.py | 4 + 2 files changed, 190 insertions(+), 90 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 2815d178..c03d6d56 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -452,66 +452,6 @@ def make_alpha_winder(func, start, end, num_checkpoints): return resit_near(func(x), check_points[index]) return return_func -def split_interval((a, b)): - mid = (a + b)/2.0 - return ((a, mid), (mid, b)) - -# I am surely reinventing some wheel here, but what's done is done... -class RectangleData(): - def __init__(self, x_interval, y_interval): - self.rect = (x_interval, y_interval) - - def get_top_left(self): - return np.array((self.rect[0][0], self.rect[1][1])) - - def get_top_right(self): - return np.array((self.rect[0][1], self.rect[1][1])) - - def get_bottom_right(self): - return np.array((self.rect[0][1], self.rect[1][0])) - - def get_bottom_left(self): - return np.array((self.rect[0][0], self.rect[1][0])) - - def get_top(self): - return (self.get_top_left(), self.get_top_right()) - - def get_right(self): - return (self.get_top_right(), self.get_bottom_right()) - - def get_bottom(self): - return (self.get_bottom_right(), self.get_bottom_left()) - - def get_left(self): - return (self.get_bottom_left(), self.get_top_left()) - - def splits_on_dim(self, dim): - x_interval = self.rect[0] - y_interval = self.rect[1] - - # TODO: Can refactor the following; will do later - if dim == 0: - return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)] - elif dim == 1: - return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)[::-1]] - else: - print "RectangleData.splits_on_dim passed illegitimate dimension!" - - return tuple(return_data) - - def split_line_on_dim(self, dim): - x_interval = self.rect[0] - y_interval = self.rect[1] - - if dim == 0: - 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]) - # The various inconsistent choices of what datatype to use where are a bit of a mess, # but I'm more keen to rush this video out now than to sort this out. @@ -638,6 +578,7 @@ def walker_animation_with_display( number_update_func = None, show_arrows = True, scale_arrows = False, + num_decimal_points = 1, **kwargs ): @@ -652,7 +593,7 @@ def walker_animation_with_display( if number_update_func != None: display = DecimalNumber(0, - num_decimal_points = 1, + num_decimal_points = num_decimal_points, fill_color = WHITE, include_background_rectangle = True) display.background_rectangle.fill_opacity = 0.5 @@ -792,12 +733,14 @@ class PiWalker(ColorMappedByFuncScene): "step_run_time" : 1, "scale_arrows" : False, "display_wind" : True, + "wind_reset_indices" : [], "display_size" : False, "display_odometer" : False, "color_foreground_not_background" : False, "show_num_plane" : False, "draw_lines" : True, "num_checkpoints" : 10, + "num_decimal_points" : 1 } def construct(self): @@ -833,7 +776,7 @@ class PiWalker(ColorMappedByFuncScene): clockwise_val_func = lambda p : -point_to_rev(self.func(p)) alpha_winder = make_alpha_winder(clockwise_val_func, start_coords, end_coords, self.num_checkpoints) number_update_func = lambda alpha, alpha_winder = alpha_winder, start_wind = start_wind: alpha_winder(alpha) - alpha_winder(0) + start_wind - start_wind = number_update_func(1) + start_wind = 0 if i + 1 in self.wind_reset_indices else number_update_func(1) elif self.display_size: # We need to do this roundabout default argument thing to get the closure we want, # so the next iteration changing val_alpha_func doesn't change this closure @@ -851,7 +794,8 @@ class PiWalker(ColorMappedByFuncScene): scale_arrows = self.scale_arrows, number_update_func = number_update_func, run_time = self.step_run_time, - walker_stroke_color = WALKER_LIGHT_COLOR if self.color_foreground_not_background else BLACK + walker_stroke_color = WALKER_LIGHT_COLOR if self.color_foreground_not_background else BLACK, + num_decimal_points = self.num_decimal_points, ) if self.display_odometer: @@ -897,6 +841,9 @@ class PiWalkerRect(PiWalker): "walk_height" : 2, "func" : plane_func_from_complex_func(lambda c: c**2), "double_up" : False, + + # New default for the scenes using this: + "display_wind" : True } def setup(self): @@ -922,6 +869,76 @@ class PiWalkerCircle(PiWalker): self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)] PiWalker.setup(self) +def split_interval((a, b)): + mid = (a + b)/2.0 + return ((a, mid), (mid, b)) + +# I am surely reinventing some wheel here, but what's done is done... +class RectangleData(): + def __init__(self, x_interval, y_interval): + self.rect = (x_interval, y_interval) + + def get_top_left(self): + return np.array((self.rect[0][0], self.rect[1][1])) + + def get_top_right(self): + return np.array((self.rect[0][1], self.rect[1][1])) + + def get_bottom_right(self): + return np.array((self.rect[0][1], self.rect[1][0])) + + def get_bottom_left(self): + return np.array((self.rect[0][0], self.rect[1][0])) + + def get_top(self): + return (self.get_top_left(), self.get_top_right()) + + def get_right(self): + return (self.get_top_right(), self.get_bottom_right()) + + def get_bottom(self): + return (self.get_bottom_right(), self.get_bottom_left()) + + def get_left(self): + return (self.get_bottom_left(), self.get_top_left()) + + def get_center(self): + return interpolate(self.get_top_left(), self.get_bottom_right(), 0.5) + + def get_width(self): + return self.rect[0][1] - self.rect[0][0] + + def get_height(self): + return self.rect[1][1] - self.rect[1][0] + + def splits_on_dim(self, dim): + x_interval = self.rect[0] + y_interval = self.rect[1] + + # TODO: Can refactor the following; will do later + if dim == 0: + return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)] + elif dim == 1: + return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)[::-1]] + else: + print "RectangleData.splits_on_dim passed illegitimate dimension!" + + return tuple(return_data) + + def split_line_on_dim(self, dim): + x_interval = self.rect[0] + y_interval = self.rect[1] + + if dim == 0: + 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]) + + class EquationSolver2dNode(object): def __init__(self, first_anim, children = []): self.first_anim = first_anim @@ -937,6 +954,8 @@ class EquationSolver2dNode(object): if n == 0: return [self] + # Not the efficient way to flatten lists, because Python + is linear in list size, + # but we have at most two children, so no big deal here return sum(map(lambda c : c.nodes_at_depth(n - 1), self.children), []) # This is definitely NOT the efficient way to do BFS, but I'm just trying to write something @@ -944,6 +963,8 @@ class EquationSolver2dNode(object): def hacky_bfs(self): depth = self.depth() + # Not the efficient way to flatten lists, because Python + is linear in list size, + # but this IS hacky_bfs... return sum(map(lambda i : self.nodes_at_depth(i), range(depth + 1)), []) def display_in_series(self): @@ -986,7 +1007,9 @@ class EquationSolver2d(ColorMappedObjectsScene): "show_winding_numbers" : True, # Used for UhOhScene; - "manual_wind_override" : None + "manual_wind_override" : None, + + "show_cursor" : False, } def construct(self): @@ -1012,6 +1035,9 @@ class EquationSolver2d(ColorMappedObjectsScene): linger_rate = squish_rate_func(lambda t : t, 0, fdiv(run_time_base, run_time_with_lingering)) + cursor_base = TextMobject("?") + cursor_base.scale(2) + # Helper functions for manual_wind_override def head(m): if m == None: @@ -1081,6 +1107,24 @@ class EquationSolver2d(ColorMappedObjectsScene): draw_line = i in sides_to_draw) anim = Succession(anim, next_anim) + if self.show_cursor: + cursor = cursor_base.copy() + center_x, center_y = rect.get_center() + width = rect.get_width() + height = rect.get_height() + + cursor.move_to(num_plane.coords_to_point(center_x, center_y)) + cursor.scale(min(width, height)) + + # Do a quick FadeIn, wait, and quick FadeOut on the cursor, matching rectangle-drawing time + cursor_anim = Succession( + FadeIn(cursor, run_time = 0.1), + Animation(cursor, run_time = 3.8), + FadeOut(cursor, run_time = 0.1) + ) + + anim = AnimationGroup(anim, cursor_anim) + total_wind = head(manual_wind_override) or round(wind_so_far) if total_wind == 0: @@ -1860,22 +1904,24 @@ class LoopSplitScene(ColorMappedObjectsScene): def fader_widthmob(start, end, mob): return UpdateFromAlphaFunc(mob, lambda m, a : m.set_stroke(width = interpolate(start, end, a) * stroke_width)) - def indicate_circle(x): - circle = Circle(color = WHITE) - circle.surround(x) - # if x.get_slope == 0: - # circle.stretch(0, 0.3) - # else: - # circle.stretch(0.3, 0) - circle.stretch(0.3, 0.3) + def indicate_circle(x, double_horizontal_stretch = False): + circle = Circle(color = WHITE, radius = 2 * np.sqrt(2)) + circle.move_to(x.get_center()) + + if x.get_slope() == 0: + circle.stretch(0.2, 1) + if double_horizontal_stretch: + circle.stretch(2, 0) + else: + circle.stretch(0.2, 0) return circle tl_line_trip = pl(tl, tm) - left_mid_trip = pl(tm, bm) + midline_left_trip = pl(tm, bm) bl_line_trip = pl(bm, bl) left_line_trip = pl(bl, tl) - left_square_trips = [tl_line_trip, left_mid_trip, bl_line_trip, left_line_trip] + left_square_trips = [tl_line_trip, midline_left_trip, bl_line_trip, left_line_trip] left_square_lines = [x[0] for x in left_square_trips] left_square_lines_vmobject = VMobject(*left_square_lines) left_square_bullets = [x[1] for x in left_square_trips] @@ -1884,31 +1930,68 @@ class LoopSplitScene(ColorMappedObjectsScene): tr_line_trip = pl(tm, tr) right_line_trip = pl(tr, br) br_line_trip = pl(br, bm) - right_midline_trip = pl(bm, tm) + midline_right_trip = pl(bm, tm) - right_square_trips = [tr_line_trip, right_line_trip, br_line_trip, right_midline_trip] + right_square_trips = [tr_line_trip, right_line_trip, br_line_trip, midline_right_trip] right_square_lines = [x[0] for x in right_square_trips] right_square_lines_vmobject = VMobject(*right_square_lines) right_square_bullets = [x[1] for x in right_square_trips] right_square_anims = [x[2] for x in right_square_trips] + midline_trips = [midline_left_trip, midline_right_trip] + midline_lines = [x[0] for x in midline_trips] + midline_lines_vmobject = VMobject(*midline_lines) + midline_bullets = [x[1] for x in midline_trips] + midline_anims = [x[1] for x in midline_trips] + + left_line = left_line_trip[0] + right_line = right_line_trip[0] + # Hm... still some slight bug with this. for b in left_square_bullets + right_square_bullets: b.set_fill(opacity = 0) + def play_fade_left(a, b): + self.play(fader_widthmob(a, b, left_square_lines_vmobject), *fader_blist(a, b, left_square_bullets)) + + def play_fade_right(a, b): + self.play(fader_widthmob(a, b, right_square_lines_vmobject), *fader_blist(a, b, right_square_bullets)) + + def play_fade_mid(a, b): + self.play(fader_widthmob(a, b, midline_lines_vmobject), *fader_blist(a, b, midline_bullets)) + + def flash_circles(circles): + # self.play(*map(FadeIn, circles)) + self.play(LaggedStart(FadeIn, VGroup(circles))) + self.play(*map(FadeOut, circles)) + self.add(*left_square_anims) - self.play(fader_widthmob(0, 1, left_square_lines_vmobject), *fader_blist(0, 1, left_square_bullets)) + VGroup(left_square_bullets).set_fill(opacity = 0) + play_fade_left(0, 1) self.add(*right_square_anims) - self.play(fader_widthmob(0, 1, right_square_lines_vmobject), *fader_blist(0, 1, right_square_bullets)) + VGroup(right_square_bullets).set_fill(opacity = 0) + play_fade_right(0, 1) - self.play(fader_widthmob(1, faded, right_square_lines_vmobject), *fader_blist(1, faded, right_square_bullets)) + play_fade_right(1, faded) + flash_circles([indicate_circle(l) for l in left_square_lines]) + play_fade_right(faded, 1) - # left_circlers = [indicate_circle(l) for l in left_square_lines] - # self.play(*map(FadeIn, left_circlers)) - # self.play(*map(FadeOut, left_circlers)) + play_fade_left(1, faded) + flash_circles([indicate_circle(l) for l in right_square_lines]) + play_fade_left(faded, 1) - self.play(ShowCreation(indicate_circle(left_square_lines[1]))) + outside_circlers = [ + indicate_circle(left_line), + indicate_circle(right_line), + indicate_circle(top_line, double_horizontal_stretch = True), + indicate_circle(bottom_line, double_horizontal_stretch = True) + ] + flash_circles(outside_circlers) + + inner_circle = indicate_circle(midline_lines[0]) + self.play(FadeIn(inner_circle)) + self.play(FadeOut(inner_circle), fader_widthmob(1, 0, midline_lines_vmobject), *fader_blist(1, 0, midline_bullets)) self.wait() @@ -1960,14 +2043,16 @@ class SolveX5MinusXMinus1(EquationSolver2d): "use_fancy_lines" : True, } -class SolveX5MinusXMinus1Parallel(EquationSolver2d): +class SolveX5MinusXMinus1Parallel(SolveX5MinusXMinus1): CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 5, - "use_fancy_lines" : True, "display_in_parallel" : True } +class SolveX5MinusXMinus1BFS(SolveX5MinusXMinus1): + CONFIG = { + "display_in_bfs" : True + } + class PreviewClip(EquationSolver2d): CONFIG = { "func" : example_plane_func, @@ -1978,9 +2063,10 @@ class PreviewClip(EquationSolver2d): class QuickPreview(PreviewClip): CONFIG = { - "num_iterations" : 2, + "num_iterations" : 3, "display_in_parallel" : False, - "display_in_bfs" : True + "display_in_bfs" : True, + "show_cursor" : True } # TODO: Borsuk-Ulam visuals @@ -2507,6 +2593,12 @@ class OneFifthTwoFifthWinder(SpecifiedWinder): "step_size" : 0.01, "show_num_plane" : False, "step_run_time" : 6, + "num_decimal_points" : 2, + } + +class OneFifthOneFifthWinderWithReset(OneFifthTwoFifthWinder): + CONFIG = { + "wind_reset_indices" : [1] } class OneFifthTwoFifthWinderOdometer(OneFifthTwoFifthWinder): @@ -2539,6 +2631,8 @@ class CWColorWalk(PiWalkerRect): "walk_width" : 2, "walk_height" : 2, "draw_lines" : False, + "display_wind" : False, + "step_run_time" : 2 } class CWColorWalkOdometer(CWColorWalk): @@ -2560,7 +2654,8 @@ class CCWColorWalkOdometer(CCWColorWalk): class ThreeTurnWalker(PiWalkerRect): CONFIG = { "func" : plane_func_from_complex_func(lambda c: c**3 * complex(1, 1)**3), - "double_up" : True + "double_up" : True, + "wind_reset_indices" : [4] } class ThreeTurnWalkerOdometer(ThreeTurnWalker): @@ -2600,6 +2695,7 @@ class ZeroTurnWalkerOdometer(ZeroTurnWalker): class NegOneTurnWalker(PiWalkerRect): CONFIG = { + "step_run_time" : 2, "func" : plane_func_by_wind_spec((0, 0, -1)) } diff --git a/topics/geometry.py b/topics/geometry.py index 5e2fe5a1..303cd63e 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -159,7 +159,11 @@ class Circle(Arc): def surround(self, mobject, dim_to_match = 0, stretch = False, buffer_factor = 1.2): # Ignores dim_to_match and stretch; result will always be a circle # TODO: Perhaps create an ellipse class to handle singele-dimension stretching + + # Something goes wrong here when surrounding lines? + # TODO: Figure out and fix self.replace(mobject, dim_to_match, stretch) + self.scale_to_fit_width(np.sqrt(mobject.get_width()**2 + mobject.get_height()**2)) self.scale(buffer_factor) From b69d8a9f97754818cef8c55b6ba95e36b5e7fe97 Mon Sep 17 00:00:00 2001 From: Sridhar Ramesh Date: Thu, 22 Mar 2018 15:02:20 -0700 Subject: [PATCH 4/4] Incremental --- active_projects/WindingNumber.py | 124 ++++++++++++++++--------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 1e584e9f..296f0c59 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -1017,6 +1017,8 @@ class EquationSolver2d(ColorMappedObjectsScene): "manual_wind_override" : None, "show_cursor" : False, + + "linger_parameter" : 0.2, } def construct(self): @@ -1037,7 +1039,7 @@ class EquationSolver2d(ColorMappedObjectsScene): obj1.color_using_background_image(bg) run_time_base = 1 - run_time_with_lingering = run_time_base + 0.2 + run_time_with_lingering = run_time_base + self.linger_parameter base_rate = lambda t : t linger_rate = squish_rate_func(lambda t : t, 0, fdiv(run_time_base, run_time_with_lingering)) @@ -1229,8 +1231,8 @@ class EquationSolver2d(ColorMappedObjectsScene): self.wait() - # In case we wish to reference these earlier, as had been done in the SearchByPerimeterNotArea - # scene at one point + # In case we wish to reference these later, as had been done in the + # WindingNumber_G/SearchSpacePerimeterVsArea scene at one point self.node = node self.anim = anim @@ -1851,10 +1853,12 @@ class DemonstrateColorMapping(ColorMappedObjectsScene): self.play(ReplacementTransform(background, bright_background)) self.wait() +# Everything in this is manually kept in sync with WindingNumber_G/TransitionFromPathsToBoundaries class LoopSplitScene(ColorMappedObjectsScene): CONFIG = { - # TODO: Change this to something more colorful down the midline - "func" : plane_func_by_wind_spec((0.1/2, 1.1/2, 1), (-3.1/2, -1.3/2, 2), (1.8/2, -2.1/2, -1)), + "func" : plane_func_by_wind_spec( + (-2, 0, 2), (2, 0, 1) + ), "use_fancy_lines" : True, } @@ -1864,7 +1868,7 @@ class LoopSplitScene(ColorMappedObjectsScene): num_bullets = 4, pulse_time = 1, **kwargs): - line = Line(start, end, color = WHITE, **kwargs) + line = Line(start, end, color = WHITE, stroke_width = 4, **kwargs) if self.use_fancy_lines: line.color_using_background_image(self.background_image_file) anim = LinePulser( @@ -1903,19 +1907,6 @@ class LoopSplitScene(ColorMappedObjectsScene): def pl(a, b): return self.PulsedLine(a, b, default_bullet) - faded = 0.3 - - # Workaround for FadeOut/FadeIn not playing well with ContinualAnimations due to - # Transforms making copies no longer identified with the ContinualAnimation's tracked mobject - def fader_bullet(start, end, mob): - return UpdateFromAlphaFunc(mob, lambda m, a : m.set_fill(opacity = interpolate(start, end, a))) - - def fader_blist(start, end, blist): - return map(lambda b : fader_bullet(start, end, b), blist) - - def fader_widthmob(start, end, mob): - return UpdateFromAlphaFunc(mob, lambda m, a : m.set_stroke(width = interpolate(start, end, a) * stroke_width)) - def indicate_circle(x, double_horizontal_stretch = False): circle = Circle(color = WHITE, radius = 2 * np.sqrt(2)) circle.move_to(x.get_center()) @@ -1959,39 +1950,62 @@ class LoopSplitScene(ColorMappedObjectsScene): left_line = left_line_trip[0] right_line = right_line_trip[0] - # Hm... still some slight bug with this. for b in left_square_bullets + right_square_bullets: b.set_fill(opacity = 0) - def play_fade_left(a, b): - self.play(fader_widthmob(a, b, left_square_lines_vmobject), *fader_blist(a, b, left_square_bullets)) + faded = 0.3 - def play_fade_right(a, b): - self.play(fader_widthmob(a, b, right_square_lines_vmobject), *fader_blist(a, b, right_square_bullets)) + # Workaround for FadeOut/FadeIn not playing well with ContinualAnimations due to + # Transforms making copies no longer identified with the ContinualAnimation's tracked mobject + def bullet_fade(start, end, mob): + return UpdateFromAlphaFunc(mob, lambda m, a : m.set_fill(opacity = interpolate(start, end, a))) - def play_fade_mid(a, b): - self.play(fader_widthmob(a, b, midline_lines_vmobject), *fader_blist(a, b, midline_bullets)) + def bullet_list_fade(start, end, bullet_list): + return map(lambda b : bullet_fade(start, end, b), bullet_list) + + def line_fade(start, end, mob): + return UpdateFromAlphaFunc(mob, lambda m, a : m.set_stroke(width = interpolate(start, end, a) * stroke_width)) + + def play_combined_fade(start, end, lines_vmobject, bullets): + self.play( + line_fade(start, end, lines_vmobject), + *bullet_list_fade(start, end, bullets) + ) + + def play_fade_left(start, end): + play_combined_fade(start, end, left_square_lines_vmobject, left_square_bullets) + + def play_fade_right(start, end): + play_combined_fade(start, end, right_square_lines_vmobject, right_square_bullets) + + def play_fade_mid(start, end): + play_combined_fade(start, end, midline_lines_vmobject, midline_bullets) def flash_circles(circles): - # self.play(*map(FadeIn, circles)) self.play(LaggedStart(FadeIn, VGroup(circles))) self.play(*map(FadeOut, circles)) + self.add(left_square_lines_vmobject, right_square_lines_vmobject) + self.remove(*midline_lines) + self.wait() + self.play(ShowCreation(midline_lines[0])) + self.add(midline_lines_vmobject) + self.wait() + self.add(*left_square_anims) - VGroup(left_square_bullets).set_fill(opacity = 0) - play_fade_left(0, 1) + self.play(line_fade(1, faded, right_square_lines_vmobject), *bullet_list_fade(0, 1, left_square_bullets)) + self.wait() + flash_circles([indicate_circle(l) for l in left_square_lines]) + self.play(line_fade(faded, 1, right_square_lines_vmobject), *bullet_list_fade(1, 0, left_square_bullets)) self.add(*right_square_anims) - VGroup(right_square_bullets).set_fill(opacity = 0) - play_fade_right(0, 1) - - play_fade_right(1, faded) - flash_circles([indicate_circle(l) for l in left_square_lines]) - play_fade_right(faded, 1) - - play_fade_left(1, faded) + self.play(line_fade(1, faded, left_square_lines_vmobject), *bullet_list_fade(0, 1, right_square_bullets)) + self.wait() flash_circles([indicate_circle(l) for l in right_square_lines]) - play_fade_left(faded, 1) + self.play(line_fade(faded, 1, left_square_lines_vmobject), *bullet_list_fade(1, 0, right_square_bullets)) + + self.wait() + self.play(*bullet_list_fade(0, 1, left_square_bullets + right_square_bullets)) outside_circlers = [ indicate_circle(left_line), @@ -2003,30 +2017,9 @@ class LoopSplitScene(ColorMappedObjectsScene): inner_circle = indicate_circle(midline_lines[0]) self.play(FadeIn(inner_circle)) - self.play(FadeOut(inner_circle), fader_widthmob(1, 0, midline_lines_vmobject), *fader_blist(1, 0, midline_bullets)) + self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - self.wait() - - return - - highlight_circle = Circle(color = WHITE) # Perhaps make this a dashed circle? - highlight_circle.surround(mid_lines) - highlight_circle.stretch(0.3, 0) - 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() + self.wait(3) # Is there a way to abstract this into a general process to derive a new mapped scene from an old scene? class LoopSplitSceneMapped(LoopSplitScene): @@ -2081,6 +2074,15 @@ class QuickPreview(PreviewClip): "show_cursor" : True } +class LongEquationSolver(EquationSolver2d): + CONFIG = { + "func" : example_plane_func, + "num_iterations" : 10, + "display_in_bfs" : True, + "linger_parameter" : 0.4, + "show_cursor" : True, + } + # TODO: Borsuk-Ulam visuals # Note: May want to do an ordinary square scene, then MappingCamera it into a circle # class BorsukUlamScene(PiWalker):