diff --git a/.gitignore b/.gitignore index fda3b94b..4f3ead67 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ ben_cairo_test.py *.xml *.iml + +primes.py diff --git a/active_projects/WindingNumber.py b/active_projects/WindingNumber.py index 7f070458..296f0c59 100644 --- a/active_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -30,6 +30,9 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * +import mpmath +mpmath.mp.dps = 7 + # Useful constants to play around with UL = UP + LEFT UR = UP + RIGHT @@ -46,6 +49,9 @@ cw_circle = Circle(color = WHITE).stretch(-1, 0) # Used when walker animations are on black backgrounds, in EquationSolver2d and PiWalker WALKER_LIGHT_COLOR = DARK_GREY +ODOMETER_RADIUS = 1.5 +ODOMETER_STROKE_WIDTH = 20 + # 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 @@ -112,7 +118,6 @@ positive_color = rev_to_color(0) negative_color = rev_to_color(0.5) neutral_color = rev_to_color(0.25) -# This, the first animation I made, has the messiest code, full of cruft, but so it goes class EquationSolver1d(GraphScene, ZoomedScene): CONFIG = { "camera_config" : @@ -129,7 +134,7 @@ class EquationSolver1d(GraphScene, ZoomedScene): "graph_label" : None, "show_target_line" : True, "base_line_y" : 0, # The y coordinate at which to draw our x guesses - "show_y_as_deviation" : False, # Displays y-values as deviations from target + "show_y_as_deviation" : False, # Displays y-values as deviations from target, } def drawGraph(self): @@ -213,7 +218,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel, lambda alpha : self.point_to_coords(leftBrace.get_center())[0], tracked_mobject = leftBrace) - self.add(leftBraceLabelAnimation) rightBrace.move_to(self.coords_to_point(upperX, self.base_line_y)) #, aligned_edge = LEFT) rightBraceLabel = DecimalNumber(upperX) @@ -221,7 +225,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel, lambda alpha : self.point_to_coords(rightBrace.get_center())[0], tracked_mobject = rightBrace) - self.add(rightBraceLabelAnimation) downBrace.move_to(self.coords_to_point(0, lowerY)) #, aligned_edge = UP) downBraceLabel = DecimalNumber(lowerY) @@ -229,7 +232,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel, lambda alpha : self.point_to_coords(downBrace.get_center())[1] + y_bias, tracked_mobject = downBrace) - self.add(downBraceLabelAnimation) upBrace.move_to(self.coords_to_point(0, upperY)) #, aligned_edge = DOWN) upBraceLabel = DecimalNumber(upperY) @@ -237,7 +239,6 @@ class EquationSolver1d(GraphScene, ZoomedScene): upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel, lambda alpha : self.point_to_coords(upBrace.get_center())[1] + y_bias, tracked_mobject = upBrace) - self.add(upBraceLabelAnimation) lowerDotPoint = self.input_to_graph_point(lowerX, self.graph) lowerDotXPoint = self.coords_to_point(lowerX, self.base_line_y) @@ -250,14 +251,11 @@ class EquationSolver1d(GraphScene, ZoomedScene): lowerXLine = Line(lowerDotXPoint, lowerDotPoint, color = lower_color) upperXLine = Line(upperDotXPoint, upperDotPoint, color = upper_color) - lowerYLine = Line(lowerDotYPoint, lowerDotPoint, color = lower_color) - upperYLine = Line(upperDotYPoint, upperDotPoint, color = upper_color) - self.add(lowerXLine, upperXLine, lowerYLine, upperYLine) - - self.add_foreground_mobjects(xBraces, yBraces, lowerDot, upperDot) + lowerYLine = Line(lowerDotPoint, lowerDotYPoint, color = lower_color) + upperYLine = Line(upperDotPoint, upperDotYPoint, color = upper_color) x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = WHITE, stroke_width = 10) - self.add(x_guess_line) + lowerGroup = Group( lowerDot, @@ -277,7 +275,35 @@ class EquationSolver1d(GraphScene, ZoomedScene): initialUpperXDot = Dot(upperDotXPoint + OUT, color = upper_color) initialLowerYDot = Dot(lowerDotYPoint + OUT, color = lower_color) initialUpperYDot = Dot(upperDotYPoint + OUT, color = upper_color) - self.add_foreground_mobjects(initialLowerXDot, initialUpperXDot, initialLowerYDot, initialUpperYDot) + + # All the initial adds and ShowCreations are here now: + self.play(FadeIn(initialLowerXDot), FadeIn(leftBrace), FadeIn(leftBraceLabel)) + self.add_foreground_mobjects(initialLowerXDot, leftBrace) + self.add(leftBraceLabelAnimation) + self.play(ShowCreation(lowerXLine)) + self.add_foreground_mobject(lowerDot) + self.play(ShowCreation(lowerYLine)) + self.play(FadeIn(initialLowerYDot), FadeIn(downBrace), FadeIn(downBraceLabel)) + self.add_foreground_mobjects(initialLowerYDot, downBrace) + self.add(downBraceLabelAnimation) + + self.wait() + + self.play(FadeIn(initialUpperXDot), FadeIn(rightBrace), FadeIn(rightBraceLabel)) + self.add_foreground_mobjects(initialUpperXDot, rightBrace) + self.add(rightBraceLabelAnimation) + self.play(ShowCreation(upperXLine)) + self.add_foreground_mobject(upperDot) + self.play(ShowCreation(upperYLine)) + self.play(FadeIn(initialUpperYDot), FadeIn(upBrace), FadeIn(upBraceLabel)) + self.add_foreground_mobjects(initialUpperYDot, upBrace) + self.add(upBraceLabelAnimation) + + self.wait() + + self.play(FadeIn(x_guess_line)) + + self.wait() for i in range(self.num_iterations): if i == self.iteration_at_which_to_start_zoom: @@ -426,83 +452,12 @@ 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)) - -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. def complex_to_pair(c): return np.array((c.real, c.imag)) -# def complex_poly_with_roots(*roots): -# def poly(c): -# return np.prod([c - z for z in roots]) -# return poly - -# def complex_func_by_critical_points(roots, poles = []): -# numerator = complex_poly_with_roots(*map(pair_to_complex, roots)) -# denominator = complex_poly_with_roots(*map(pair_to_complex, poles)) -# def rational_func(c): -# return numerator(c)/denominator(c) -# return rational_func - def plane_func_from_complex_func(f): return lambda (x, y) : complex_to_pair(f(complex(x,y))) @@ -515,6 +470,20 @@ def point3d_func_from_plane_func(f): def point3d_func_from_complex_func(f): return point3d_func_from_plane_func(plane_func_from_complex_func(f)) +def plane_zeta((x, y)): + CLAMP_SIZE = 1000 + z = complex(x, y) + try: + answer = mpmath.zeta(z) + except ValueError: + return (CLAMP_SIZE, 0) + if abs(answer) > CLAMP_SIZE: + answer = answer/abs(answer) * CLAMP_SIZE + return (float(answer.real), float(answer.imag)) + +def rescaled_plane_zeta((x, y)): + return plane_zeta((x/SPACE_WIDTH, 8*y)) + # Returns a function from 2-ples to 2-ples # This function is specified by a list of (x, y, z) tuples, # and has winding number z (or total of all specified z) around each (x, y) @@ -543,6 +512,9 @@ def plane_func_by_wind_spec(*specs): return plane_func_from_complex_func(complex_func) +def scale_func(func, scale_factor): + return lambda x : func(x) * scale_factor + # Used in Initial2dFunc scenes, VectorField scene, and ExamplePlaneFunc example_plane_func_spec = [(-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, -1)] example_plane_func = plane_func_by_wind_spec(*example_plane_func_spec) @@ -613,6 +585,7 @@ def walker_animation_with_display( number_update_func = None, show_arrows = True, scale_arrows = False, + num_decimal_points = 1, **kwargs ): @@ -627,7 +600,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 @@ -693,11 +666,15 @@ class ColorMappedByFuncScene(Scene): self.input_to_pos_func = lambda p : p self.pos_to_color_func = self.func + self.pixel_pos_to_color_func = lambda (x, y) : self.pos_to_color_func( + self.num_plane.point_to_coords_cheap(np.array([x, y, 0])) + ) + jitter_val = 0.1 line_coords = np.linspace(-10, 10) + jitter_val func_hash_points = it.product(line_coords, line_coords) def mini_hasher(p): - rgba = point_to_rgba(self.pos_to_color_func(p)) + rgba = point_to_rgba(self.pixel_pos_to_color_func(p)) if rgba[3] != 1.0: print "Warning! point_to_rgba assigns fractional alpha", rgba[3] return tuple(rgba) @@ -722,10 +699,7 @@ class ColorMappedByFuncScene(Scene): if self.in_background_pass: self.camera.set_background_from_func( lambda (x, y): point_to_rgba( - self.pos_to_color_func( - # Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])), - # but for cheapness, we'll go with just (x, y), having never altered - # any num_plane's from default settings so far + self.pixel_pos_to_color_func( (x, y) ) ) @@ -765,9 +739,15 @@ class PiWalker(ColorMappedByFuncScene): "walk_coords" : [], "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): @@ -788,8 +768,9 @@ class PiWalker(ColorMappedByFuncScene): polygon.color_using_background_image(self.background_image_file) total_run_time = len(points) * self.step_run_time polygon_anim = ShowCreation(polygon, run_time = total_run_time, rate_func = None) - walker_anim = EmptyAnimation + walker_anim = empty_animation + start_wind = 0 for i in range(len(walk_coords)): start_coords = walk_coords[i] end_coords = walk_coords[(i + 1) % len(walk_coords)] @@ -798,7 +779,12 @@ class PiWalker(ColorMappedByFuncScene): # so the next iteration changing start_coords, end_coords doesn't change this closure val_alpha_func = lambda a, start_coords = start_coords, end_coords = end_coords : self.func(interpolate(start_coords, end_coords, a)) - if self.display_size: + if self.display_wind: + 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 = 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 number_update_func = lambda a, val_alpha_func = val_alpha_func : point_to_rescaled_size(val_alpha_func(a)) # We only use this for diagnostics @@ -815,7 +801,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: @@ -838,9 +825,18 @@ class PiWalker(ColorMappedByFuncScene): # use point_from_proportion to get points along the way if self.display_odometer: + color_wheel = Circle(radius = ODOMETER_RADIUS) + color_wheel.stroke_width = ODOMETER_STROKE_WIDTH + color_wheel.color_using_background_image(self.short_path_to_long_path("pure_color_map.png")) # Manually inserted here; this is unclean + self.add(color_wheel) self.play(walker_anim) else: - self.play(polygon_anim, walker_anim) + if self.draw_lines: + self.play(polygon_anim, walker_anim) + else: + # (Note: Turns out, play is unhappy playing empty_animation, as had been + # previous approach to this toggle; should fix that) + self.play(walker_anim) self.wait() @@ -850,7 +846,11 @@ class PiWalkerRect(PiWalker): "start_y" : 1, "walk_width" : 2, "walk_height" : 2, - "func" : plane_func_from_complex_func(lambda c: c**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): @@ -859,6 +859,8 @@ class PiWalkerRect(PiWalker): BR = TR + (0, -self.walk_height) BL = BR + (-self.walk_width, 0) self.walk_coords = [TL, TR, BR, BL] + if self.double_up: + self.walk_coords = self.walk_coords + self.walk_coords PiWalker.setup(self) class PiWalkerCircle(PiWalker): @@ -874,6 +876,114 @@ 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 + 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] + + # 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 + # quick without thinking that gets the job done on small instances for now + 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): + 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(lambda n : n.display_in_parallel(), self.children))) + + def display_in_bfs(self): + bfs_nodes = self.hacky_bfs() + return Succession(*map(lambda n : n.first_anim, bfs_nodes)) + class EquationSolver2d(ColorMappedObjectsScene): CONFIG = { "camera_config" : {"use_z_coordinate_for_display_order": True}, @@ -883,7 +993,11 @@ class EquationSolver2d(ColorMappedObjectsScene): "initial_upper_y" : 3, "num_iterations" : 1, "num_checkpoints" : 10, - "display_in_parallel" : True, + + # 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 @@ -900,7 +1014,11 @@ class EquationSolver2d(ColorMappedObjectsScene): "show_winding_numbers" : True, # Used for UhOhScene; - "manual_wind_override" : None + "manual_wind_override" : None, + + "show_cursor" : False, + + "linger_parameter" : 0.2, } def construct(self): @@ -921,11 +1039,14 @@ 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)) + cursor_base = TextMobject("?") + cursor_base.scale(2) + # Helper functions for manual_wind_override def head(m): if m == None: @@ -943,7 +1064,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) @@ -995,6 +1116,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: @@ -1009,14 +1148,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, @@ -1029,14 +1168,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 @@ -1050,7 +1183,7 @@ class EquationSolver2d(ColorMappedObjectsScene): print "Starting to compute anim" - anim = Animate2dSolver( + node = Animate2dSolver( cur_depth = 0, rect = rect, dim_to_split = 0, @@ -1060,6 +1193,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(), @@ -1091,6 +1231,11 @@ class EquationSolver2d(ColorMappedObjectsScene): self.wait() + # 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 + # TODO: Perhaps have option for bullets (pulses) to fade out and in at ends of line, instead of # jarringly popping out and in? # @@ -1173,20 +1318,27 @@ class TestRotater(Scene): class OdometerScene(ColorMappedObjectsScene): CONFIG = { # "func" : lambda p : 100 * p # Full coloring, essentially - "rotate_func" : lambda x : np.sin(x * TAU), # This is given in terms of CW revs - "run_time" : 5, + "rotate_func" : lambda x : 2 * np.sin(2 * x * TAU), # This is given in terms of CW revs + "run_time" : 40, "dashed_line_angle" : None, - "biased_display_start" : None + "biased_display_start" : None, + "pure_odometer_background" : False } def construct(self): ColorMappedObjectsScene.construct(self) - radius = 1.3 + radius = ODOMETER_RADIUS circle = Circle(center = ORIGIN, radius = radius) + circle.stroke_width = ODOMETER_STROKE_WIDTH circle.color_using_background_image(self.background_image_file) self.add(circle) + if self.pure_odometer_background: + # Just display this background circle, for compositing in Premiere with PiWalker odometers + self.wait() + return + if self.dashed_line_angle: dashed_line = DashedLine(ORIGIN, radius * RIGHT) # Clockwise rotation @@ -1235,6 +1387,7 @@ class FirstSqrtScene(EquationSolver1d): "iteration_at_which_to_start_zoom" : 3, "graph_label" : "y = x^2", "show_target_line" : True, + "x_tick_frequency" : 0.25 } class TestFirstSqrtScene(FirstSqrtScene): @@ -1315,7 +1468,7 @@ class RewriteEquation(Scene): class SignsExplanation(Scene): def construct(self): - num_line = NumberLine(stroke_width = 1) + num_line = NumberLine() largest_num = 10 num_line.add_numbers(*range(-largest_num, largest_num + 1)) self.add(num_line) @@ -1410,7 +1563,7 @@ class HasItsLimitations(Scene): # play well with z coordinates. input_dot = Dot(base_point + DOT_Z, color = dot_color) - input_label = TexMobject("Input", fill_color = dot_color) + input_label = TextMobject("Input", fill_color = dot_color) input_label.next_to(input_dot, UP + LEFT) input_label.add_background_rectangle() self.add_foreground_mobject(input_dot) @@ -1425,7 +1578,7 @@ class HasItsLimitations(Scene): self.play(ShowCreation(curved_arrow)) output_dot = Dot(base_point + 2 * RIGHT + DOT_Z, color = dot_color) - output_label = TexMobject("Output", fill_color = dot_color) + output_label = TextMobject("Output", fill_color = dot_color) output_label.next_to(output_dot, UP + RIGHT) output_label.add_background_rectangle() @@ -1635,44 +1788,77 @@ class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase): class DemonstrateColorMapping(ColorMappedObjectsScene): CONFIG = { - "show_num_plane" : False + "show_num_plane" : False, + "show_full_color_map" : True } def construct(self): ColorMappedObjectsScene.construct(self) + # Doing this in Premiere now instead + # output_plane_label = TextMobject("Output Plane", color = WHITE) + # output_plane_label.move_to(3 * UP) + # self.add_foreground_mobject(output_plane_label) + + if self.show_full_color_map: + bright_background = Rectangle(width = 2 * SPACE_WIDTH + 1, height = 2 * SPACE_HEIGHT + 1, fill_opacity = 1) + bright_background.color_using_background_image(self.background_image_file) + dim_background = bright_background.copy() + dim_background.fill_opacity = 0.3 + + background = bright_background.copy() + self.add(background) + self.wait() + self.play(ReplacementTransform(background, dim_background)) + + self.wait() + + ray = Line(ORIGIN, 10 * LEFT) + + circle = cw_circle.copy() circle.color_using_background_image(self.background_image_file) + self.play(ShowCreation(circle)) + + self.wait() + + scale_up_factor = 5 + scale_down_factor = 20 + self.play(ApplyMethod(circle.scale, fdiv(1, scale_down_factor))) + + self.play(ApplyMethod(circle.scale, scale_up_factor * scale_down_factor)) + + self.play(ApplyMethod(circle.scale, fdiv(1, scale_up_factor))) + + self.wait() + self.remove(circle) + ray = Line(ORIGIN, 10 * LEFT) ray.color_using_background_image(self.background_image_file) - self.play(ShowCreation(circle)) - self.play(ShowCreation(ray)) - scale_up_factor = 5 - scale_down_factor = 20 - self.play(ApplyMethod(circle.scale, scale_up_factor)) + self.wait() - self.play(ApplyMethod(circle.scale, fdiv(1, scale_up_factor * scale_down_factor))) + self.play(Rotating(ray, about_point = ORIGIN, radians = -TAU/2)) - self.play(ApplyMethod(circle.scale, scale_down_factor)) + self.wait() - self.play(Rotating(ray, about_point = ORIGIN, radians = -TAU)) + self.play(Rotating(ray, about_point = ORIGIN, radians = -TAU/2)) -# TODO: Illustrations for introducing domain coloring + self.wait() -# TODO: Bunch of Pi walker scenes - -# TODO: An odometer scene when introducing winding numbers -# (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) + if self.show_full_color_map: + 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), (-4.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, } @@ -1682,7 +1868,7 @@ class LoopSplitScene(ColorMappedObjectsScene): num_bullets = 4, pulse_time = 1, **kwargs): - line = Line(start, end, **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( @@ -1692,7 +1878,7 @@ class LoopSplitScene(ColorMappedObjectsScene): pulse_time = pulse_time, output_func = self.func, **kwargs) - return [VGroup(line, *anim.bullets), anim] + return (line, VMobject(*anim.bullets), anim) def construct(self): ColorMappedObjectsScene.construct(self) @@ -1703,102 +1889,137 @@ class LoopSplitScene(ColorMappedObjectsScene): # TODO: Change all this to use a wider than tall loop, made of two squares # 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 + tl = (UP + 2 * LEFT) * scale_factor + tm = UP * scale_factor + tr = (UP + 2 * RIGHT) * scale_factor + bl = (DOWN + 2 * LEFT) * scale_factor + bm = DOWN * scale_factor + br = (DOWN + 2 * RIGHT) * scale_factor - loop_color = WHITE + top_line = Line(tl, tr) # Invisible; only used for surrounding circle + bottom_line = Line(br, bl) # Invisible; only used for surrounding circle - default_bullet = PiCreature(color = RED) + stroke_width = top_line.stroke_width + + default_bullet = PiCreature() default_bullet.scale(0.15) - modified_bullet = PiCreature(color = PINK) - modified_bullet.scale(0.15) + def pl(a, b): + return self.PulsedLine(a, b, default_bullet) - def SGroup(*args): - return VGroup(*[arg[0] for arg in args]) + def indicate_circle(x, double_horizontal_stretch = False): + circle = Circle(color = WHITE, radius = 2 * np.sqrt(2)) + circle.move_to(x.get_center()) - top_line = self.PulsedLine(tl, tr, default_bullet, color = loop_color) - right_line = self.PulsedLine(tr, br, modified_bullet, color = loop_color) - bottom_line = self.PulsedLine(br, bl, default_bullet, color = loop_color) - left_line = self.PulsedLine(bl, tl, default_bullet, color = loop_color) - line_list = [top_line, right_line, bottom_line, left_line] - loop = SGroup(*line_list) - for line in line_list: - self.add(*line) + 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) + midline_left_trip = pl(tm, bm) + bl_line_trip = pl(bm, bl) + left_line_trip = pl(bl, tl) + + 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] + left_square_anims = [x[2] for x in left_square_trips] + + tr_line_trip = pl(tm, tr) + right_line_trip = pl(tr, br) + br_line_trip = pl(br, bm) + midline_right_trip = pl(bm, tm) + + 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] + + for b in left_square_bullets + right_square_bullets: + b.set_fill(opacity = 0) + + 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 bullet_fade(start, end, mob): + return UpdateFromAlphaFunc(mob, lambda m, a : m.set_fill(opacity = interpolate(start, end, a))) + + 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(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() - # Splits in middle - if self.use_fancy_lines: - split_line = Line(tm, bm) - split_line.color_using_background_image(self.background_image_file) - else: - split_line = DashedLine(tm, bm) - 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.add(*left_square_anims) + 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.play( - # ApplyMethod(left_open_loop.shift, LEFT), - # ApplyMethod(right_open_loop.shift, RIGHT) - # ) - + self.add(*right_square_anims) + self.play(line_fade(1, faded, left_square_lines_vmobject), *bullet_list_fade(0, 1, right_square_bullets)) self.wait() - - mid_lines = SGroup(mid_line_left, mid_line_right) - - 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)) + flash_circles([indicate_circle(l) for l in right_square_lines]) + 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)) - 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]) + 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) - # 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) - # ) + inner_circle = indicate_circle(midline_lines[0]) + self.play(FadeIn(inner_circle)) + self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - 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): @@ -1817,10 +2038,26 @@ class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)), "num_iterations" : 2, - "display_in_parallel" : True, "use_fancy_lines" : True, } +class SolveX5MinusXMinus1(EquationSolver2d): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), + "num_iterations" : 5, + "use_fancy_lines" : True, + } + +class SolveX5MinusXMinus1Parallel(SolveX5MinusXMinus1): + CONFIG = { + "display_in_parallel" : True + } + +class SolveX5MinusXMinus1BFS(SolveX5MinusXMinus1): + CONFIG = { + "display_in_bfs" : True + } + class PreviewClip(EquationSolver2d): CONFIG = { "func" : example_plane_func, @@ -1829,6 +2066,23 @@ class PreviewClip(EquationSolver2d): "use_fancy_lines" : True, } +class QuickPreview(PreviewClip): + CONFIG = { + "num_iterations" : 3, + "display_in_parallel" : False, + "display_in_bfs" : True, + "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): @@ -1946,23 +2200,45 @@ class CombineInterval2(Scene): self.wait() -tiny_loop_func = plane_func_by_wind_spec((-1, -2), (1, 1), (1, 1)) -class TinyLoopInInputPlane(ColorMappedByFuncScene): +tiny_loop_func = scale_func(plane_func_by_wind_spec((-1, -2), (1, 1), (1, 1)), 0.3) + +class TinyLoopScene(ColorMappedByFuncScene): CONFIG = { "func" : tiny_loop_func, "show_num_plane" : False, + "loop_point" : ORIGIN, + "circle_scale" : 0.7 } def construct(self): ColorMappedByFuncScene.construct(self) - self.wait() circle = cw_circle.copy() - circle.move_to(UP + RIGHT) + circle.scale(self.circle_scale) + circle.move_to(self.loop_point) self.play(ShowCreation(circle)) + self.wait() -class TinyLoopInOutputPlane(TinyLoopInInputPlane): +class TinyLoopInInputPlaneAroundNonZero(TinyLoopScene): + CONFIG = { + "loop_point" : 0.5 * RIGHT + } + +class TinyLoopInInputPlaneAroundZero(TinyLoopScene): + CONFIG = { + "loop_point" : UP + RIGHT + } + +class TinyLoopInOutputPlaneAroundNonZero(TinyLoopInInputPlaneAroundNonZero): + CONFIG = { + "camera_class" : MappingCamera, + "camera_config" : {"mapping_func" : point3d_func_from_plane_func(tiny_loop_func)}, + "show_output" : True, + "show_num_plane" : False, + } + +class TinyLoopInOutputPlaneAroundZero(TinyLoopInInputPlaneAroundZero): CONFIG = { "camera_class" : MappingCamera, "camera_config" : {"mapping_func" : point3d_func_from_plane_func(tiny_loop_func)}, @@ -2071,6 +2347,28 @@ class PiWalkerExamplePlaneFunc(PiWalkerRect): "walk_height" : 6, } +class NoticeHowOnThisLoop(PiWalkerRect): + CONFIG = { + "show_num_plane" : False, + "func" : example_plane_func, + # These are just manually entered, not + # automatically kept in sync with example_plane_func: + "start_x" : 0.5, + "start_y" : -0.5, + "walk_width" : -1, # We trace from bottom-right clockwise on this one, to start at a red point + "walk_height" : -1, + } + +class ButOnThisLoopOverHere(NoticeHowOnThisLoop): + CONFIG = { + # These are just manually entered, not + # automatically kept in sync with example_plane_func: + "start_x" : -1, + "start_y" : 0, + "walk_width" : 1, + "walk_height" : 1, + } + class PiWalkerExamplePlaneFuncWithScaling(PiWalkerExamplePlaneFunc): CONFIG = { "scale_arrows" : True, @@ -2086,8 +2384,8 @@ class TinyLoopOfBasicallySameColor(PureColorMap): self.wait() def uhOhFunc((x, y)): - x = np.clip(x, -5, 5)/5 - y = np.clip(y, -3, 3)/3 + x = -np.clip(x, -5, 5)/5 + y = -np.clip(y, -3, 3)/3 alpha = 0.5 # Most things will return green @@ -2113,12 +2411,22 @@ class UhOhFuncTest(PureColorMap): class UhOhScene(EquationSolver2d): CONFIG = { "func" : uhOhFunc, - "display_in_parallel" : True, - "manual_wind_override" : (1, (1, (1, None, None), None), None), # Tailored to UhOhFunc above + "manual_wind_override" : (1, None, (1, None, (1, None, None))), # Tailored to UhOhFunc above "show_winding_numbers" : False, "num_iterations" : 5, } +class UhOhSceneWithWindingNumbers(UhOhScene): + CONFIG = { + "show_winding_numbers" : True, + } + +class UhOhSceneWithWindingNumbersNoOverride(UhOhSceneWithWindingNumbers): + CONFIG = { + "manual_wind_override" : None, + "num_iterations" : 2 + } + class UhOhSalientStill(ColorMappedObjectsScene): CONFIG = { "func" : uhOhFunc @@ -2222,4 +2530,192 @@ class PiWalkerFancyLineTest(PiWalkerExamplePlaneFunc): CONFIG = { "color_foreground_not_background" : True } + +class NotFoundScene(Scene): + def construct(self): + self.add(TextMobject("SCENE NOT FOUND!")) + self.wait() + +criticalStripYScale = 100 +criticalStrip = Axes(x_min = -0.5, x_max = 1.5, x_axis_config = {"unit_size" : SPACE_WIDTH, + "number_at_center" : 0.5}, + y_min = -criticalStripYScale, y_max = criticalStripYScale, + y_axis_config = {"unit_size" : fdiv(SPACE_HEIGHT, criticalStripYScale)}) + +class ZetaViz(PureColorMap): + CONFIG = { + "func" : plane_zeta, + #"num_plane" : criticalStrip, + "show_num_plane" : True + } + +class TopLabel(Scene): + CONFIG = { + "text" : "Text" + } + def construct(self): + label = TextMobject(self.text) + label.move_to(3 * UP) + self.add(label) + self.wait() + +# This is a giant hack that doesn't handle rev wrap-around correctly; should use +# make_alpha_winder instead +class SpecifiedWinder(PiWalker): + CONFIG = { + "start_x" : 0, + "start_y" : 0, + "x_wind" : 1, # Assumed positive + "y_wind" : 1, # Assumed positive + "step_size" : 0.1 + } + + def setup(self): + rev_func = lambda p : point_to_rev(self.func(p)) + start_pos = np.array((self.start_x, self.start_y)) + cur_pos = start_pos.copy() + start_rev = rev_func(start_pos) + + mid_rev = start_rev + while (abs(mid_rev - start_rev) < self.x_wind): + cur_pos += (self.step_size, 0) + mid_rev = rev_func(cur_pos) + + print "Reached ", cur_pos, ", with rev ", mid_rev - start_rev + mid_pos = cur_pos.copy() + + end_rev = mid_rev + while (abs(end_rev - mid_rev) < self.y_wind): + cur_pos -= (0, self.step_size) + end_rev = rev_func(cur_pos) + + end_pos = cur_pos.copy() + + print "Reached ", cur_pos, ", with rev ", end_rev - mid_rev + + self.walk_coords = [start_pos, mid_pos, end_pos] + print "Walk coords: ", self.walk_coords + PiWalker.setup(self) + +class OneFifthTwoFifthWinder(SpecifiedWinder): + CONFIG = { + "func" : example_plane_func, + "start_x" : -2.0, + "start_y" : 1.0, + "x_wind" : 0.2, + "y_wind" : 0.2, + "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): + CONFIG = { + "display_odometer" : True, + } + +class ForwardBackWalker(PiWalker): + CONFIG = { + "func" : example_plane_func, + "walk_coords" : [np.array((-2, 1)), np.array((1, 1))], + "step_run_time" : 3, + } + +class ForwardBackWalkerOdometer(ForwardBackWalker): + CONFIG = { + "display_odometer" : True, + } + +class PureOdometerBackground(OdometerScene): + CONFIG = { + "pure_odometer_background" : True + } + +class CWColorWalk(PiWalkerRect): + CONFIG = { + "func" : example_plane_func, + "start_x" : example_plane_func_spec[0][0] - 1, + "start_y" : example_plane_func_spec[0][1] + 1, + "walk_width" : 2, + "walk_height" : 2, + "draw_lines" : False, + "display_wind" : False, + "step_run_time" : 2 + } + +class CWColorWalkOdometer(CWColorWalk): + CONFIG = { + "display_odometer" : True, + } + +class CCWColorWalk(CWColorWalk): + CONFIG = { + "start_x" : example_plane_func_spec[2][0] - 1, + "start_y" : example_plane_func_spec[2][1] + 1, + } + +class CCWColorWalkOdometer(CCWColorWalk): + CONFIG = { + "display_odometer" : True, + } + +class ThreeTurnWalker(PiWalkerRect): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c: c**3 * complex(1, 1)**3), + "double_up" : True, + "wind_reset_indices" : [4] + } + +class ThreeTurnWalkerOdometer(ThreeTurnWalker): + CONFIG = { + "display_odometer" : True, + } + +class FourTurnWalker(PiWalkerRect): + CONFIG = { + "func" : plane_func_by_wind_spec((0, 0, 4)) + } + +class FourTurnWalkerOdometer(FourTurnWalker): + CONFIG = { + "display_odometer" : True, + } + +class OneTurnWalker(PiWalkerRect): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : np.exp(c) + c) + } + +class OneTurnWalkerOdometer(OneTurnWalker): + CONFIG = { + "display_odometer" : True, + } + +class ZeroTurnWalker(PiWalkerRect): + CONFIG = { + "func" : plane_func_by_wind_spec((2, 2, 1), (-1, 2, -1)) + } + +class ZeroTurnWalkerOdometer(ZeroTurnWalker): + CONFIG = { + "display_odometer" : True, + } + +class NegOneTurnWalker(PiWalkerRect): + CONFIG = { + "step_run_time" : 2, + "func" : plane_func_by_wind_spec((0, 0, -1)) + } + +class NegOneTurnWalkerOdometer(NegOneTurnWalker): + CONFIG = { + "display_odometer" : True, + } + # FIN \ No newline at end of file diff --git a/active_projects/WindingNumber_G.py b/active_projects/WindingNumber_G.py index 2b5a3934..5089cbc4 100644 --- a/active_projects/WindingNumber_G.py +++ b/active_projects/WindingNumber_G.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from helpers import * from mobject.tex_mobject import TexMobject @@ -25,11 +27,14 @@ from topics.complex_numbers import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene from scene.zoomed_scene import * +from scene.moving_camera_scene import * from camera import * from mobject.svg_mobject import * from mobject.tex_mobject import * from topics.graph_scene import * +from topics.common_scenes import * +from old_projects.uncertainty import Flash from active_projects.WindingNumber import * class AltTeacherStudentsScene(TeacherStudentsScene): @@ -48,24 +53,511 @@ class IntroSceneWrapper(PiCreatureScene): "height" : 2, }, "default_pi_creature_start_corner" : DOWN+LEFT, - # "default_pi_creature_height" : 1, } def construct(self): + self.introduce_two_words() + self.describe_main_topic() + self.describe_meta_topic() + + def introduce_two_words(self): morty = self.pi_creature rect = ScreenRectangle(height = 5) rect.to_corner(UP+RIGHT) self.add(rect) - main_topic, meta_topic = toipcs = VGroup( + h_line = Line(LEFT, RIGHT).scale(2) + h_line.to_corner(UP+LEFT) + h_line.shift(0.5*DOWN) + + main_topic, meta_topic = topics = VGroup( TextMobject("Main topic"), TextMobject("Meta topic"), ) - topics.arrange_submobjects(DOWN, aligned_edge = LEFT) + topics.next_to(morty, UP) + topics.shift_onto_screen() + + self.play( + morty.change, "raise_left_hand", + FadeInFromDown(main_topic) + ) + self.wait() + self.play( + morty.change, "raise_right_hand", + main_topic.next_to, meta_topic.get_top(), UP, MED_SMALL_BUFF, + FadeInFromDown(meta_topic) + ) + self.wait() + self.play( + morty.change, "happy", + main_topic.next_to, h_line, UP, + meta_topic.set_fill, {"opacity" : 0.2}, + ) + self.play(ShowCreation(h_line)) + self.wait() + + self.set_variables_as_attrs(h_line, main_topic, meta_topic) + + def describe_main_topic(self): + h_line = self.h_line + morty = self.pi_creature + main_topic = self.main_topic + meta_topic = self.meta_topic + + solver = TextMobject("2d equation solver") + solver.match_width(h_line) + solver.next_to(h_line, DOWN) + rainbow_solver1 = solver.copy() + rainbow_solver2 = solver.copy() + colors = ["RED", "ORANGE", "YELLOW", "GREEN", BLUE, "PURPLE", PINK] + rainbow_solver1.gradient_highlight(*colors) + rainbow_solver2.gradient_highlight(*reversed(colors)) + xy_equation = TexMobject(""" + \\left[\\begin{array}{c} + ye^x \\\\ + \\sin(|xy|) + \\end{array}\\right] = + \\left[\\begin{array}{c} + y^2 \\\\ + 3y + \\end{array}\\right] + """) + # xy_equation.highlight_by_tex_to_color_map({ + # "x" : BLUE, + # "y" : YELLOW + # }) + xy_equation.scale(0.8) + xy_equation.next_to(solver, DOWN, MED_LARGE_BUFF) + z_equation = TexMobject("z", "^5", "+", "z", "+", "1", "=", "0") + z_equation.highlight_by_tex("z", GREEN) + z_equation.move_to(xy_equation, UP) + zeta = TexMobject("\\zeta(s) = 0") + zeta[2].highlight(GREEN) + zeta.next_to(z_equation, DOWN, MED_LARGE_BUFF) + self.play(Write(solver)) + self.play( + LaggedStart(FadeIn, xy_equation, run_time = 1), + morty.change, "pondering" + ) + self.wait(2) + self.play( + FadeOut(xy_equation), + FadeIn(z_equation) + ) + self.wait() + self.play(Write(zeta)) + self.wait() + solver.save_state() + for rainbow_solver in rainbow_solver1, rainbow_solver2: + self.play(Transform( + solver, rainbow_solver, + run_time = 2, + submobject_mode = "lagged_start" + )) + self.play(solver.restore) + self.wait() + + self.play(LaggedStart( + FadeOut, VGroup(solver, z_equation, zeta) + )) + self.play( + main_topic.move_to, meta_topic, + main_topic.set_fill, {"opacity" : 0.2}, + meta_topic.move_to, main_topic, + meta_topic.set_fill, {"opacity" : 1}, + morty.change, "hesitant", + path_arc = TAU/8, + ) + + def describe_meta_topic(self): + h_line = self.h_line + morty = self.pi_creature + + words = TextMobject("Seek constructs which \\\\ compose nicely") + words.scale(0.7) + words.next_to(h_line, DOWN) + + self.play(Write(words)) + self.play(morty.change, "happy") + self.wait(3) + +class Introduce1DFunctionCase(Scene): + CONFIG = { + "search_range_rect_height" : 0.15, + "arrow_opacity" : 1, + "show_dotted_line_to_f" : True, + "arrow_config": { + "max_stem_width_to_tip_width_ratio" : 0.5, + "max_tip_length_to_length_ratio" : 0.5, + }, + "show_midpoint_value" : True, + } + def construct(self): + self.show_axes_one_at_a_time() + self.show_two_graphs() + self.transition_to_sqrt_2_case() + self.show_example_binary_search() + + def show_axes_one_at_a_time(self): + axes = Axes( + x_min = -1, x_max = 3.2, + x_axis_config = { + "unit_size" : 3, + "tick_frequency" : 0.25, + "numbers_with_elongated_ticks" : range(-1, 4) + }, + y_min = -2, y_max = 4.5, + ) + axes.to_corner(DOWN+LEFT) + axes.x_axis.add_numbers(*range(-1, 4)) + axes.y_axis.label_direction = LEFT + axes.y_axis.add_numbers(-1, *range(1, 5)) + + inputs = TextMobject("Inputs") + inputs.next_to(axes.x_axis, UP, aligned_edge = RIGHT) + + outputs = TextMobject("Outputs") + outputs.next_to(axes.y_axis, UP, SMALL_BUFF) + + self.play( + ShowCreation(axes.x_axis), + Write(inputs) + ) + self.wait() + self.play( + ShowCreation(axes.y_axis), + FadeOut(axes.x_axis.numbers[1], rate_func = squish_rate_func(smooth, 0, 0.2)), + Write(outputs) + ) + self.wait() + + self.axes = axes + self.inputs_label = inputs + self.outputs_label = outputs + + def show_two_graphs(self): + axes = self.axes + f_graph = axes.get_graph( + lambda x : 2*x*(x - 0.75)*(x - 1.5) + 1, + color = BLUE + ) + g_graph = axes.get_graph( + lambda x : 1.8*np.cos(TAU*x/2), + color = YELLOW + ) + + label_x_corod = 2 + f_label = TexMobject("f(x)") + f_label.match_color(f_graph) + f_label.next_to(axes.input_to_graph_point(label_x_corod, f_graph), LEFT) + + g_label = TexMobject("g(x)") + g_label.match_color(g_graph) + g_label.next_to( + axes.input_to_graph_point(label_x_corod, g_graph), UP, SMALL_BUFF + ) + + solution = 0.24 + cross_point = axes.input_to_graph_point(solution, f_graph) + l_v_line, r_v_line, v_line = [ + DashedLine( + axes.coords_to_point(x, 0), + axes.coords_to_point(x, f_graph.underlying_function(solution)), + ) + for x in axes.x_min, axes.x_max, solution + ] + + equation = TexMobject("f(x)", "=", "g(x)") + equation[0].match_color(f_label) + equation[2].match_color(g_label) + equation.next_to(cross_point, UP, buff = 1.5, aligned_edge = LEFT) + equation_arrow = Arrow( + equation.get_bottom(), cross_point, + buff = SMALL_BUFF, + color = WHITE + ) + equation.target = TexMobject("x^2", "=", "2") + equation.target.match_style(equation) + equation.target.to_edge(UP) + + for graph, label in (f_graph, f_label), (g_graph, g_label): + self.play( + ShowCreation(graph), + Write(label, rate_func = squish_rate_func(smooth, 0.5, 1)), + run_time = 2 + ) + self.wait() + self.play( + ReplacementTransform(r_v_line.copy().fade(1), v_line), + ReplacementTransform(l_v_line.copy().fade(1), v_line), + run_time = 2 + ) + self.play( + ReplacementTransform(f_label.copy(), equation[0]), + ReplacementTransform(g_label.copy(), equation[2]), + Write(equation[1]), + GrowArrow(equation_arrow), + ) + for x in range(4): + self.play( + FadeOut(v_line.copy()), + ShowCreation(v_line, rate_func = squish_rate_func(smooth, 0.5, 1)), + run_time = 1.5 + ) + self.wait() + self.play( + MoveToTarget(equation, replace_mobject_with_target_in_scene = True), + *map(FadeOut, [equation_arrow, v_line]) + ) + + self.set_variables_as_attrs( + f_graph, f_label, g_graph, g_label, + equation = equation.target + ) + + def transition_to_sqrt_2_case(self): + f_graph = self.f_graph + f_label = VGroup(self.f_label) + g_graph = self.g_graph + g_label = VGroup(self.g_label) + axes = self.axes + for label in f_label, g_label: + for x in range(2): + label.add(VectorizedPoint(label.get_center())) + for number in axes.y_axis.numbers: + number.add_background_rectangle() + + squared_graph = axes.get_graph(lambda x : x**2) + squared_graph.match_style(f_graph) + two_graph = axes.get_graph(lambda x : 2) + two_graph.match_style(g_graph) + + squared_label = TexMobject("f(x)", "=", "x^2") + squared_label.next_to( + axes.input_to_graph_point(2, squared_graph), RIGHT + ) + squared_label.match_color(squared_graph) + two_label = TexMobject("g(x)", "=", "2") + two_label.next_to( + axes.input_to_graph_point(3, two_graph), UP, + ) + two_label.match_color(two_graph) + + find_sqrt_2 = self.find_sqrt_2 = TextMobject("(Find $\\sqrt{2}$)") + find_sqrt_2.next_to(self.equation, DOWN) + + self.play( + ReplacementTransform(f_graph, squared_graph), + ReplacementTransform(f_label, squared_label), + ) + self.play( + ReplacementTransform(g_graph, two_graph), + ReplacementTransform(g_label, two_label), + Animation(axes.y_axis.numbers) + ) + self.wait() + self.play(Write(find_sqrt_2)) + self.wait() + + self.set_variables_as_attrs( + squared_graph, two_graph, + squared_label, two_label, + ) + + def show_example_binary_search(self): + self.binary_search( + self.squared_graph, self.two_graph, + x0 = 1, x1 = 2, + n_iterations = 8 + ) + + ## + + def binary_search( + self, + f_graph, g_graph, + x0, x1, + n_iterations, + n_iterations_with_sign_mention = 0, + zoom = False, + ): + + axes = self.axes + rect = self.rect = Rectangle() + rect.set_stroke(width = 0) + rect.set_fill(YELLOW, 0.5) + rect.replace(Line( + axes.coords_to_point(x0, 0), + axes.coords_to_point(x1, 0), + ), dim_to_match = 0) + rect.stretch_to_fit_height(self.search_range_rect_height) + + #Show first left and right + mention_signs = n_iterations_with_sign_mention > 0 + kwargs = {"mention_signs" : mention_signs} + leftovers0 = self.compare_graphs_at_x(f_graph, g_graph, x0, **kwargs) + self.wait() + leftovers1 = self.compare_graphs_at_x(f_graph, g_graph, x1, **kwargs) + self.wait() + self.play(GrowFromCenter(rect)) + self.wait() + + all_leftovers = VGroup(leftovers0, leftovers1) + end_points = [x0, x1] + if mention_signs: + sign_word0 = leftovers0.sign_word + sign_word1 = leftovers1.sign_word + + midpoint_line = Line(MED_SMALL_BUFF*UP, ORIGIN, color = YELLOW) + midpoint_line_update = UpdateFromFunc( + midpoint_line, lambda l : l.move_to(rect) + ) + decimal = DecimalNumber( + 0, + num_decimal_points = 3, + show_ellipsis = True, + ) + decimal.scale(0.7) + decimal_update = ChangingDecimal( + decimal, lambda a : axes.x_axis.point_to_number(rect.get_center()), + position_update_func = lambda m : m.next_to( + midpoint_line, DOWN, SMALL_BUFF, + submobject_to_align = decimal[:-1], + ), + ) + if not self.show_midpoint_value: + decimal.set_fill(opacity = 0) + midpoint_line.set_stroke(width = 0) + + #Restrict to by a half each time + kwargs = { + "mention_signs" : False, + "show_decimal" : zoom, + } + for x in range(n_iterations - 1): + x_mid = np.mean(end_points) + leftovers_mid = self.compare_graphs_at_x(f_graph, g_graph, x_mid, **kwargs) + if leftovers_mid.too_high == all_leftovers[0].too_high: + index_to_fade = 0 + else: + index_to_fade = 1 + edge = [RIGHT, LEFT][index_to_fade] + to_fade = all_leftovers[index_to_fade] + all_leftovers.submobjects[index_to_fade] = leftovers_mid + end_points[index_to_fade] = x_mid + + added_anims = [] + if mention_signs: + word = [leftovers0, leftovers1][index_to_fade].sign_word + if x < n_iterations_with_sign_mention: + added_anims = [word.next_to, leftovers_mid[0].get_end(), -edge] + elif word in self.camera.extract_mobject_family_members(self.mobjects): + added_anims = [FadeOut(word)] + + rect.generate_target() + rect.target.stretch(0.5, 0, about_edge = edge) + rect.target.stretch_to_fit_height(self.search_range_rect_height) + self.play( + MoveToTarget(rect), + midpoint_line_update, + decimal_update, + Animation(all_leftovers), + FadeOut(to_fade), + *added_anims + ) + if zoom: + factor = 2.0/rect.get_width() + everything = VGroup(*self.mobjects) + decimal_index = everything.submobjects.index(decimal) + midpoint_line_index = everything.submobjects.index(midpoint_line) + everything.generate_target() + everything.target.scale(factor, about_point = rect.get_center()) + everything.target[decimal_index].scale(1./factor, about_edge = UP) + everything.target[midpoint_line_index].scale(1./factor) + if factor > 1: + self.play( + everything.scale, factor, + {"about_point" : rect.get_center()} + ) + else: + self.wait() + + def compare_graphs_at_x( + self, f_graph, g_graph, x, + mention_signs = False, + show_decimal = False, + ): + axes = self.axes + f_point = axes.input_to_graph_point(x, f_graph) + g_point = axes.input_to_graph_point(x, g_graph) + arrow = Arrow( + g_point, f_point, buff = 0, + **self.arrow_config + ) + too_high = f_point[1] > g_point[1] + if too_high: + arrow.set_fill(GREEN, opacity = self.arrow_opacity) + else: + arrow.set_fill(RED, opacity = self.arrow_opacity) + + leftovers = VGroup(arrow) + leftovers.too_high = too_high + + if self.show_dotted_line_to_f: + v_line = DashedLine(axes.coords_to_point(x, 0), f_point) + self.play(ShowCreation(v_line)) + leftovers.add(v_line) + + added_anims = [] + if show_decimal: + decimal = DecimalNumber( + axes.x_axis.point_to_number(arrow.get_start()), + num_decimal_points = 3, + # show_ellipsis = True, + ) + height = self.rect.get_height() + decimal.scale_to_fit_height(height) + next_to_kwargs = { + "buff" : height, + } + if too_high: + decimal.next_to(arrow, DOWN, **next_to_kwargs) + if hasattr(self, "last_up_arrow_decimal"): + added_anims += [FadeOut(self.last_up_arrow_decimal)] + self.last_up_arrow_decimal = decimal + else: + decimal.next_to(arrow, UP, **next_to_kwargs) + if hasattr(self, "last_down_arrow_decimal"): + added_anims += [FadeOut(self.last_down_arrow_decimal)] + self.last_down_arrow_decimal = decimal + line = Line(decimal, arrow, buff = 0) + # line.match_color(arrow) + line.set_stroke(WHITE, 1) + decimal.add(line) + added_anims += [FadeIn(decimal)] + + if mention_signs: + if too_high: + sign_word = TextMobject("Positive") + sign_word.highlight(GREEN) + sign_word.scale(0.7) + sign_word.next_to(arrow.get_end(), RIGHT) + else: + sign_word = TextMobject("Negative") + sign_word.highlight(RED) + sign_word.scale(0.7) + sign_word.next_to(arrow.get_end(), LEFT) + sign_word.add_background_rectangle() + added_anims += [FadeIn(sign_word)] + leftovers.sign_word = sign_word + + self.play(GrowArrow(arrow), *added_anims) + + return leftovers class PiCreaturesAreIntrigued(AltTeacherStudentsScene): def construct(self): @@ -77,87 +569,175 @@ class PiCreaturesAreIntrigued(AltTeacherStudentsScene): self.look_at(self.screen) self.wait(3) +class TransitionFromEquationSolverToZeroFinder(Introduce1DFunctionCase): + CONFIG = { + "show_dotted_line_to_f" : False, + "arrow_config" : {}, + "show_midpoint_value" : False, + } + def construct(self): + #Just run through these without animating. + self.force_skipping() + self.show_axes_one_at_a_time() + self.show_two_graphs() + self.transition_to_sqrt_2_case() + self.revert_to_original_skipping_status() + ## + + self.transition_to_difference_graph() + self.show_binary_search_with_signs() + + def transition_to_difference_graph(self): + axes = self.axes + equation = x_squared, equals, two = self.equation + for s in "-", "0": + tex_mob = TexMobject(s) + tex_mob.scale(0.01) + tex_mob.fade(1) + tex_mob.move_to(equation.get_right()) + equation.add(tex_mob) + find_sqrt_2 = self.find_sqrt_2 + rect = SurroundingRectangle(VGroup(equation, find_sqrt_2)) + rect.highlight(WHITE) + + f_graph = self.squared_graph + g_graph = self.two_graph + new_graph = axes.get_graph( + lambda x : f_graph.underlying_function(x) - g_graph.underlying_function(x), + color = GREEN + ) + zero_graph = axes.get_graph(lambda x : 0) + zero_graph.set_stroke(BLACK, 0) + + f_label = self.squared_label + g_label = self.two_label + new_label = TexMobject("f(x)", "-", "g(x)") + new_label[0].match_color(f_label) + new_label[2].match_color(g_label) + new_label.next_to( + axes.input_to_graph_point(2, new_graph), + LEFT + ) + + fg_labels = VGroup(f_label, g_label) + fg_labels.generate_target() + fg_labels.target.arrange_submobjects(DOWN, aligned_edge = LEFT) + fg_labels.target.to_corner(UP+RIGHT) + + new_equation = TexMobject("x^2", "-", "2", "=", "0") + new_equation[0].match_style(equation[0]) + new_equation[2].match_style(equation[2]) + new_equation.move_to(equation, RIGHT) + for tex in equation, new_equation: + tex.sort_submobjects_alphabetically() + + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.play( + ReplacementTransform(equation, new_equation, path_arc = TAU/4), + find_sqrt_2.next_to, new_equation, DOWN, + ) + self.play(MoveToTarget(fg_labels)) + self.play( + ReplacementTransform(f_graph, new_graph), + ReplacementTransform(g_graph, zero_graph), + ) + self.play( + ReplacementTransform(f_label[0].copy(), new_label[0]), + ReplacementTransform(g_label[0].copy(), new_label[2]), + Write(new_label[1]), + ) + self.wait() + + self.set_variables_as_attrs(new_graph, zero_graph) + + def show_binary_search_with_signs(self): + self.play(FadeOut(self.axes.x_axis.numbers[2])) + self.binary_search( + self.new_graph, self.zero_graph, + 1, 2, + n_iterations = 9, + n_iterations_with_sign_mention = 2, + zoom = True, + ) + class RewriteEquationWithTeacher(AltTeacherStudentsScene): def construct(self): - equations = VGroup( - TexMobject( - "f(\\text{2d input})", "", "=", - "g(\\text{2d input})", "" - ), - TexMobject( - "f(\\text{2d input})", "-", - "g(\\text{2d input})", "=", "0" - ), - ) - specific_equations = VGroup( + root_two_equations = VGroup( TexMobject("x^2", "", "=", "2", ""), TexMobject("x^2", "-", "2", "=", "0"), ) - for equation in it.chain(equations, specific_equations): + for equation in root_two_equations: equation.sort_submobjects_alphabetically() for part in equation.get_parts_by_tex("text"): part[2:-1].highlight(YELLOW) part[2:-1].scale(0.9) equation.move_to(self.hold_up_spot, DOWN) - self.teacher_holds_up(specific_equations[0]) - self.play(Transform(*specific_equations, path_arc = TAU/4)) + brace = Brace(root_two_equations[1], UP) + f_equals_0 = brace.get_tex("f(x) = 0") + + self.teacher_holds_up(root_two_equations[0]) + self.wait() + self.play(Transform( + *root_two_equations, + run_time = 1.5, + path_arc = TAU/2 + )) self.play(self.get_student_changes(*["pondering"]*3)) - self.play(FadeOut(specific_equations[0]), FadeIn(equations[0])) - self.wait() - self.play(Transform(*equations, path_arc = TAU/4)) - self.change_student_modes(*["happy"]*3) - - # 2d plane - plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) - plane.scale(0.8) - plane.to_corner(UP+LEFT) - plane.add_coordinates() - - dot = Dot(color = YELLOW) - label = TextMobject("Sign?") - label.add_background_rectangle() - label.scale(0.5) - label.next_to(dot, UP, SMALL_BUFF) - dot.add(label) - dot.move_to(plane.coords_to_point(1, 1)) - dot.save_state() - dot.fade(1) - dot.center() - - question = TextMobject( - "Wait...what would \\\\", "+", "and", "\\textminus", " \\, be in 2d?", - ) - question.highlight_by_tex_to_color_map({ - "+" : "green", - "textminus" : "red" - }) - - self.student_says( - question, - target_mode = "sassy", - student_index = 2, - added_anims = [ - equations[0].to_corner, UP+RIGHT, - self.teacher.change, "plain", - ], - bubble_kwargs = {"direction" : LEFT}, - run_time = 1, - ) self.play( - Write(plane, run_time = 1), - self.students[0].change, "confused", - self.students[1].change, "confused", + GrowFromCenter(brace), + self.teacher.change, "happy" ) - self.play(dot.restore) - for coords in (-1, 1), (1, -1), (0, -2), (-2, 1): - self.wait(0.5) - self.play(dot.move_to, plane.coords_to_point(*coords)) + self.play(Write(f_equals_0)) + self.change_student_modes(*["happy"]*3) self.wait() -class DotsHoppingToColor(Scene): + # + to_remove = VGroup(root_two_equations[0], brace, f_equals_0) + two_d_equation = TexMobject(""" + \\left[\\begin{array}{c} + ye^x \\\\ + \\sin(xy) + \\end{array}\\right] = + \\left[\\begin{array}{c} + y^2 + x^3 \\\\ + 3y - x + \\end{array}\\right] + """) + complex_equation = TexMobject("z", "^5 + ", "z", " + 1 = 0") + z_def = TextMobject( + "(", "$z$", " is complex, ", "$a + bi$", ")", + arg_separator = "" + ) + complex_group = VGroup(complex_equation, z_def) + complex_group.arrange_submobjects(DOWN) + for tex in complex_group: + tex.highlight_by_tex("z", GREEN) + complex_group.move_to(self.hold_up_spot, DOWN) + + self.play( + ApplyMethod( + to_remove.next_to, SPACE_WIDTH*RIGHT, RIGHT, + remover = True, + rate_func = running_start, + path_arc = -TAU/4, + ), + self.teacher.change, "hesitant", + self.get_student_changes(*["erm"]*3) + ) + self.teacher_holds_up(two_d_equation) + self.change_all_student_modes("horrified") + self.wait() + self.play( + FadeOut(two_d_equation), + FadeInFromDown(complex_group), + ) + self.change_all_student_modes("confused") + self.wait(3) + +class InputOutputScene(Scene): CONFIG = { - "dot_radius" : 0.05, "plane_width" : 6, "plane_height" : 6, "x_shift" : SPACE_WIDTH/2, @@ -165,17 +745,499 @@ class DotsHoppingToColor(Scene): "output_scalar" : 10, "non_renormalized_func" : plane_func_by_wind_spec( (-2, -1, 2), - (1, 2, 1), - (2, -2, 1), + (1, 1, 1), + (2, -2, -1), ), + } + + ### + + def func(self, coord_pair): + out_coords = np.array(self.non_renormalized_func(coord_pair)) + out_norm = np.linalg.norm(out_coords) + if out_norm > 1: + angle = angle_of_vector(out_coords) + factor = 0.5-0.1*np.cos(4*angle) + target_norm = factor*np.log(out_norm) + out_coords *= target_norm / out_norm + else: + out_coords = (0, 0) + return tuple(out_coords) + + def point_function(self, point): + in_coords = self.input_plane.point_to_coords(point) + out_coords = self.func(in_coords) + return self.output_plane.coords_to_point(*out_coords) + + def get_colorings(self): + in_cmos = ColorMappedObjectsScene( + func = lambda p : self.non_renormalized_func( + (p[0]+self.x_shift, p[1]+self.y_shift) + ) + ) + scalar = self.output_scalar + out_cmos = ColorMappedObjectsScene( + func = lambda p : ( + scalar*(p[0]-self.x_shift), scalar*(p[1]+self.y_shift) + ) + ) + + input_coloring = Rectangle( + height = self.plane_height, + width = self.plane_width, + stroke_width = 0, + fill_color = WHITE, + fill_opacity = 1, + ) + output_coloring = input_coloring.copy() + colorings = VGroup(input_coloring, output_coloring) + vects = [LEFT, RIGHT] + cmos_pair = [in_cmos, out_cmos] + for coloring, vect, cmos in zip(colorings, vects, cmos_pair): + coloring.move_to(self.x_shift*vect + self.y_shift*DOWN) + coloring.color_using_background_image(cmos.background_image_file) + return colorings + + def get_planes(self): + input_plane = self.input_plane = NumberPlane( + x_radius = self.plane_width/2.0, + y_radius = self.plane_height/2.0, + ) + output_plane = self.output_plane = input_plane.copy() + planes = VGroup(input_plane, output_plane) + vects = [LEFT, RIGHT] + label_texts = ["Input", "Output"] + label_colors = [GREEN, RED] + for plane, vect, text, color in zip(planes, vects, label_texts, label_colors): + plane.stretch_to_fit_width(self.plane_width) + plane.add_coordinates(x_vals = range(-2, 3), y_vals = range(-2, 3)) + plane.white_parts = VGroup(plane.axes, plane.coordinate_labels) + plane.lines_to_fade = VGroup(plane.main_lines, plane.secondary_lines) + plane.move_to(vect*SPACE_WIDTH/2 + self.y_shift*DOWN) + label = TextMobject(text) + label.scale(1.5) + label.add_background_rectangle() + label.move_to(plane) + label.to_edge(UP, buff = MED_SMALL_BUFF) + plane.add(label) + plane.label = label + for submob in plane.submobject_family(): + if isinstance(submob, TexMobject) and hasattr(submob, "background_rectangle"): + submob.remove(submob.background_rectangle) + + return planes + + def get_v_line(self): + v_line = Line(UP, DOWN).scale(SPACE_HEIGHT) + v_line.set_stroke(WHITE, 5) + return v_line + + def get_dots(self, input_plane, output_plane): + step = self.dot_density + x_min = -3.0 + x_max = 3.0 + y_min = -3.0 + y_max = 3.0 + dots = VGroup() + for x in np.arange(x_min, x_max + step, step): + for y in np.arange(y_max, y_min - step, -step): + out_coords = self.func((x, y)) + dot = Dot(radius = self.dot_radius) + dot.set_stroke(BLACK, 1) + dot.move_to(input_plane.coords_to_point(x, y)) + dot.original_position = dot.get_center() + dot.generate_target() + dot.target.move_to(output_plane.coords_to_point(*out_coords)) + dot.target_color = rgba_to_color(point_to_rgba( + tuple(self.output_scalar*np.array(out_coords)) + )) + dots.add(dot) + return dots + +class IntroduceInputOutputScene(InputOutputScene): + CONFIG = { + "dot_radius" : 0.05, + "dot_density" : 0.25, + } + def construct(self): + self.setup_planes() + self.map_single_point_to_point() + + def setup_planes(self): + self.input_plane, self.output_plane = self.get_planes() + self.v_line = self.get_v_line() + self.add(self.input_plane, self.output_plane, self.v_line) + + def map_single_point_to_point(self): + input_plane = self.input_plane + output_plane = self.output_plane + + #Dots + dots = self.get_dots() + + in_dot = dots[int(0.55*len(dots))].copy() + out_dot = in_dot.target + for mob in in_dot, out_dot: + mob.scale(1.5) + in_dot.highlight(YELLOW) + out_dot.highlight(PINK) + + input_label_arrow = Vector(DOWN+RIGHT) + input_label_arrow.next_to(in_dot, UP+LEFT, SMALL_BUFF) + input_label = TextMobject("Input point") + input_label.next_to(input_label_arrow.get_start(), UP, SMALL_BUFF) + for mob in input_label, input_label_arrow: + mob.match_color(in_dot) + input_label.add_background_rectangle() + + output_label_arrow = Vector(DOWN+LEFT) + output_label_arrow.next_to(out_dot, UP+RIGHT, SMALL_BUFF) + output_label = TextMobject("Output point") + output_label.next_to(output_label_arrow.get_start(), UP, SMALL_BUFF) + for mob in output_label, output_label_arrow: + mob.match_color(out_dot) + output_label.add_background_rectangle() + + path_arc = -TAU/4 + curved_arrow = Arrow( + in_dot, out_dot, + buff = SMALL_BUFF, + path_arc = path_arc, + use_rectangular_stem = False, + color = WHITE, + ) + curved_arrow.pointwise_become_partial(curved_arrow, 0, 0.95) + function_label = TexMobject("f(", "\\text{2d input}", ")") + function_label.next_to(curved_arrow, UP) + function_label.add_background_rectangle() + + + self.play(LaggedStart(GrowFromCenter, dots)) + self.play(LaggedStart( + MoveToTarget, dots, + path_arc = path_arc + )) + self.wait() + self.play(FadeOut(dots)) + self.play( + GrowFromCenter(in_dot), + GrowArrow(input_label_arrow), + FadeIn(input_label) + ) + self.wait() + self.play( + ShowCreation(curved_arrow), + ReplacementTransform( + in_dot.copy(), out_dot, + path_arc = path_arc + ), + FadeIn(function_label), + ) + self.play( + GrowArrow(output_label_arrow), + FadeIn(output_label) + ) + self.wait() + self.play(*map(FadeOut, [ + input_label_arrow, input_label, + output_label_arrow, output_label, + curved_arrow, function_label, + ])) + + #General movements and wiggles + out_dot_continual_update = self.get_output_dot_continual_update(in_dot, out_dot) + self.add(out_dot_continual_update) + + for vect in UP, RIGHT: + self.play( + in_dot.shift, 0.25*vect, + rate_func = lambda t : wiggle(t, 8), + run_time = 2 + ) + for vect in compass_directions(4, UP+RIGHT): + self.play(Rotating( + in_dot, about_point = in_dot.get_corner(vect), + radians = TAU, + run_time = 1 + )) + self.wait() + for coords in (-2, 2), (-2, -2), (2, -2), (1.5, 1.5): + self.play( + in_dot.move_to, input_plane.coords_to_point(*coords), + path_arc = -TAU/4, + run_time = 2 + ) + self.wait() + + ### + + def get_dots(self): + input_plane = self.input_plane + dots = VGroup() + step = self.dot_density + x_max = input_plane.x_radius + x_min = -x_max + y_max = input_plane.y_radius + y_min = -y_max + + reverse = False + for x in np.arange(x_min+step, x_max, step): + y_range = list(np.arange(x_min+step, x_max, step)) + if reverse: + y_range.reverse() + reverse = not reverse + for y in y_range: + dot = Dot(radius = self.dot_radius) + dot.move_to(input_plane.coords_to_point(x, y)) + dot.generate_target() + dot.target.move_to(self.point_function(dot.get_center())) + dots.add(dot) + return dots + + def get_output_dot_continual_update(self, input_dot, output_dot): + return ContinualUpdateFromFunc( + output_dot, + lambda od : od.move_to(self.point_function(input_dot.get_center())) + ) + +class IntroduceVectorField(IntroduceInputOutputScene): + CONFIG = { + "dot_density" : 0.5, + } + def construct(self): + self.setup_planes() + input_plane, output_plane = self.input_plane, self.output_plane + dots = self.get_dots() + + in_dot = dots[0].copy() + in_dot.move_to(input_plane.coords_to_point(1.5, 1.5)) + out_dot = in_dot.copy() + out_dot_continual_update = self.get_output_dot_continual_update(in_dot, out_dot) + for mob in in_dot, out_dot: + mob.scale(1.5) + in_dot.highlight(YELLOW) + out_dot.highlight(PINK) + + out_vector = Arrow( + LEFT, RIGHT, + color = out_dot.get_color(), + ) + out_vector.set_stroke(BLACK, 1) + continual_out_vector_update = ContinualUpdateFromFunc( + out_vector, lambda ov : ov.put_start_and_end_on( + output_plane.coords_to_point(0, 0), + out_dot.get_center(), + ) + ) + + in_vector = out_vector.copy() + def update_in_vector(in_vector): + Transform(in_vector, out_vector).update(1) + in_vector.scale(0.5) + in_vector.shift(in_dot.get_center() - in_vector.get_start()) + continual_in_vector_update = ContinualUpdateFromFunc( + in_vector, update_in_vector + ) + continual_updates = [ + out_dot_continual_update, + continual_out_vector_update, + continual_in_vector_update + ] + + self.add(in_dot, out_dot) + self.play(GrowArrow(out_vector, run_time = 2)) + self.wait() + self.add_foreground_mobjects(in_dot) + self.play(ReplacementTransform(out_vector.copy(), in_vector)) + self.wait() + self.add(*continual_updates) + path = Square().rotate(-90*DEGREES) + path.replace(Line( + input_plane.coords_to_point(-1.5, -1.5), + input_plane.coords_to_point(1.5, 1.5), + ), stretch = True) + in_vectors = VGroup() + self.add(in_vectors) + for a in np.linspace(0, 1, 25): + self.play( + in_dot.move_to, path.point_from_proportion(a), + run_time = 0.2, + rate_func = None, + ) + in_vectors.add(in_vector.copy()) + + # Full vector field + newer_in_vectors = VGroup() + self.add(newer_in_vectors) + for dot in dots: + self.play(in_dot.move_to, dot, run_time = 1./15) + newer_in_vectors.add(in_vector.copy()) + self.remove(*continual_updates) + self.remove() + self.play(*map(FadeOut, [ + out_dot, out_vector, in_vectors, in_dot, in_vector + ])) + self.wait() + target_length = 0.4 + for vector in newer_in_vectors: + vector.generate_target() + if vector.get_length() == 0: + continue + factor = target_length / vector.get_length() + vector.target.scale(factor, about_point = vector.get_start()) + + self.play(LaggedStart(MoveToTarget, newer_in_vectors)) + self.wait() + +class TwoDScreenInOurThreeDWorld(AltTeacherStudentsScene, ThreeDScene): + def construct(self): + self.ask_about_2d_functions() + self.show_3d() + + def ask_about_2d_functions(self): + in_plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) + in_plane.add_coordinates() + in_plane.scale_to_fit_height(3) + out_plane = in_plane.copy() + + in_text = TextMobject("Input space") + out_text = TextMobject("Output space") + VGroup(in_text, out_text).scale(0.75) + in_text.next_to(in_plane, UP, SMALL_BUFF) + out_text.next_to(out_plane, UP, SMALL_BUFF) + in_plane.add(in_text) + out_plane.add(out_text) + + arrow = Arrow( + LEFT, RIGHT, + path_arc = -TAU/4, + use_rectangular_stem = False, + color = WHITE + ) + arrow.pointwise_become_partial(arrow, 0.0, 0.97) + group = VGroup(in_plane, arrow, out_plane) + group.arrange_submobjects(RIGHT) + arrow.shift(UP) + group.move_to(self.students) + group.to_edge(UP) + + dots = VGroup() + dots_target = VGroup() + for x in np.arange(-2.5, 3.0, 0.5): + for y in np.arange(-2.5, 3.0, 0.5): + dot = Dot(radius = 0.05) + dot.move_to(in_plane.coords_to_point(x, y)) + dot.generate_target() + dot.target.move_to(out_plane.coords_to_point( + x + 0.25*np.cos(5*y), y + 0.25*np.sin(3*x) + )) + dots.add(dot) + dots_target.add(dot.target) + dots.gradient_highlight(YELLOW, RED) + dots_target.gradient_highlight(YELLOW, RED) + + self.play( + self.teacher.change, "raise_right_hand", + Write(in_plane, run_time = 1) + ) + self.play( + ShowCreation(arrow), + ReplacementTransform( + in_plane.copy(), out_plane, + path_arc = -TAU/4, + ) + ) + self.play( + LaggedStart(GrowFromCenter, dots, run_time = 1), + self.get_student_changes(*3*["erm"]), + ) + self.play(LaggedStart(MoveToTarget, dots, path_arc = -TAU/4)) + self.wait(3) + + + def show_3d(self): + laptop = Laptop().scale(2) + laptop.rotate(-TAU/12, DOWN) + laptop.rotate(-5*TAU/24, LEFT) + laptop.rotate(TAU/8, LEFT) + laptop.scale(2.3*SPACE_WIDTH/laptop.screen_plate.get_width()) + laptop.shift(-laptop.screen_plate.get_center() + 0.1*IN) + should_shade_in_3d(laptop) + + everything = VGroup(laptop, *self.mobjects) + everything.generate_target() + # for mob in everything.target.submobject_family(): + # if isinstance(mob, PiCreature): + # mob.change_mode("confused") + everything.target.rotate(TAU/12, LEFT) + everything.target.rotate(TAU/16, UP) + everything.target.shift(4*UP) + + self.move_camera( + distance = 12, + run_time = 4, + added_anims = [MoveToTarget(everything, run_time = 4)], + ) + self.add(AmbientRotation(everything, axis = UP, rate = 3*DEGREES)) + self.wait(10) + +class EveryOutputPointHasAColor(ColorMappedObjectsScene): + CONFIG = { + "func" : lambda p : p, + "dot_spacing" : 0.1, + "dot_radius" : 0.01, + } + def construct(self): + full_rect = FullScreenRectangle() + full_rect.set_fill(WHITE, 1) + full_rect.set_stroke(WHITE, 0) + full_rect.color_using_background_image(self.background_image_file) + + title = TextMobject("Output Space") + title.scale(1.5) + title.to_edge(UP, buff = MED_SMALL_BUFF) + title.set_stroke(BLACK, 1) + # self.add_foreground_mobjects(title) + + plane = NumberPlane() + plane.fade(0.5) + plane.axes.set_stroke(WHITE, 3) + # plane.add(BackgroundRectangle(title)) + self.add(plane) + + + dots = VGroup() + step = self.dot_spacing + for x in np.arange(-SPACE_WIDTH, SPACE_WIDTH+step, step): + for y in np.arange(-SPACE_HEIGHT, SPACE_HEIGHT+step, step): + dot = Dot(color = WHITE) + dot.color_using_background_image(self.background_image_file) + dot.move_to(x*RIGHT + y*UP) + dots.add(dot) + random.shuffle(dots.submobjects) + + m = 3 #exponential factor + n = 1 + dot_groups = VGroup() + while n <= len(dots): + dot_groups.add(dots[n-1:m*n-1]) + n *= m + self.play(LaggedStart( + LaggedStart, dot_groups, + lambda dg : (GrowFromCenter, dg), + run_time = 8, + lag_ratio = 0.2, + )) + +class DotsHoppingToColor(InputOutputScene): + CONFIG = { + "dot_radius" : 0.05, "dot_density" : 0.25, } def construct(self): input_coloring, output_coloring = self.get_colorings() input_plane, output_plane = self.get_planes() - - v_line = Line(UP, DOWN).scale(SPACE_HEIGHT) - v_line.set_stroke(WHITE, 5) + v_line = self.get_v_line() dots = self.get_dots(input_plane, output_plane) @@ -247,33 +1309,28 @@ class DotsHoppingToColor(Scene): inspector.scale(0.15) inspector_image = inspector.copy() - def point_function(point): - in_coords = input_plane.point_to_coords(point) - out_coords = self.func(in_coords) - return output_plane.coords_to_point(*out_coords) - def update_inspector_image(inspector_image): - inspector_image.move_to(point_function(inspector.get_center())) + inspector_image.move_to(self.point_function(inspector.get_center())) inspector_image_update_anim = UpdateFromFunc( inspector_image, update_inspector_image ) - yellow_points_label = TextMobject("Yellow points") - yellow_points_label.scale(0.7) - yellow_points_label.highlight(BLACK) + pink_points_label = TextMobject("Pink points") + pink_points_label.scale(0.7) + pink_points_label.highlight(BLACK) self.play( - inspector.move_to, input_plane.coords_to_point(1.5, 0), + inspector.move_to, input_plane.coords_to_point(-2.75, 2.75), inspector.set_stroke, {"width" : 2}, ) - yellow_points_label.next_to(inspector, UP) + pink_points_label.next_to(inspector, RIGHT) self.play( Rotating( - inspector, about_point = inspector.get_corner(UP+LEFT), + inspector, about_point = inspector.get_center(), rate_func = smooth, run_time = 2, ), - Write(yellow_points_label) + Write(pink_points_label) ) self.wait() self.play(right_half_block.next_to, SPACE_WIDTH*RIGHT, RIGHT) @@ -285,7 +1342,7 @@ class DotsHoppingToColor(Scene): self.play( ApplyMethod( inspector.move_to, - input_plane.coords_to_point(0, 2), + input_plane.coords_to_point(-2, 0), path_arc = -TAU/8, run_time = 3, ), @@ -294,26 +1351,30 @@ class DotsHoppingToColor(Scene): self.play( ApplyMethod( inspector.move_to, - input_plane.coords_to_point(2, 0), - path_arc = TAU/4, + input_plane.coords_to_point(-2.75, 2.75), + path_arc = TAU/8, run_time = 3, ), inspector_image_update_anim ) - self.play(FadeOut(yellow_points_label)) + self.play(FadeOut(pink_points_label)) # Show black zero zeros = tuple(it.starmap(input_plane.coords_to_point, [ - (-2, -1), (1, 2), (2, -2), + (-2., -1), (1, 1), (2, -2), ])) for x in range(2): for zero in zeros: + path = ParametricFunction( + bezier([ + inspector.get_center(), + input_plane.coords_to_point(0, 0), + zero + ]), + t_min = 0, t_max = 1 + ) self.play( - ApplyMethod( - inspector.move_to, zero, - path_arc = -TAU/8, - run_time = 2, - ), + MoveAlongPath(inspector, path, run_time = 2), inspector_image_update_anim, ) self.wait() @@ -360,101 +1421,6 @@ class DotsHoppingToColor(Scene): ) self.wait() - - - ### - - def func(self, coord_pair): - out_coords = np.array(self.non_renormalized_func(coord_pair)) - out_norm = np.linalg.norm(out_coords) - if out_norm > 0.5: - angle = angle_of_vector(out_coords) - factor = 0.5-0.1*np.cos(4*angle) - target_norm = factor*np.log(out_norm) - out_coords *= target_norm / out_norm - return tuple(out_coords) - - def get_colorings(self): - in_cmos = ColorMappedObjectsScene( - func = lambda p : self.non_renormalized_func( - (p[0]+self.x_shift, p[1]+self.y_shift) - ) - ) - scalar = self.output_scalar - out_cmos = ColorMappedObjectsScene( - func = lambda p : ( - scalar*(p[0]-self.x_shift), scalar*(p[1]+self.y_shift) - ) - ) - - input_coloring = Rectangle( - height = self.plane_height, - width = self.plane_width, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - ) - output_coloring = input_coloring.copy() - colorings = [input_coloring, output_coloring] - vects = [LEFT, RIGHT] - cmos_pair = [in_cmos, out_cmos] - for coloring, vect, cmos in zip(colorings, vects, cmos_pair): - coloring.move_to(self.x_shift*vect + self.y_shift*DOWN) - coloring.color_using_background_image(cmos.background_image_file) - return colorings - - def get_planes(self): - input_plane = NumberPlane( - x_radius = self.plane_width/2.0, - y_radius = self.plane_height/2.0, - ) - output_plane = input_plane.copy() - planes = [input_plane, output_plane] - vects = [LEFT, RIGHT] - label_texts = ["Input", "Output"] - label_colors = [GREEN, RED] - for plane, vect, text, color in zip(planes, vects, label_texts, label_colors): - plane.stretch_to_fit_width(self.plane_width) - plane.add_coordinates(x_vals = range(-2, 3), y_vals = range(-2, 3)) - plane.white_parts = VGroup(plane.axes, plane.coordinate_labels) - plane.lines_to_fade = VGroup(plane.main_lines, plane.secondary_lines) - plane.move_to(vect*SPACE_WIDTH/2 + self.y_shift*DOWN) - label = TextMobject(text) - label.scale(1.5) - label.add_background_rectangle() - label.move_to(plane) - label.to_edge(UP, buff = MED_SMALL_BUFF) - plane.add(label) - plane.label = label - for submob in plane.submobject_family(): - if isinstance(submob, TexMobject) and hasattr(submob, "background_rectangle"): - submob.remove(submob.background_rectangle) - - - return planes - - def get_dots(self, input_plane, output_plane): - step = self.dot_density - x_min = -3.0 - x_max = 3.0 - y_min = -3.0 - y_max = 3.0 - dots = VGroup() - for x in np.arange(x_min, x_max + step, step): - for y in np.arange(y_max, y_min - step, -step): - out_coords = self.func((x, y)) - dot = Dot(radius = self.dot_radius) - dot.set_stroke(BLACK, 1) - dot.move_to(input_plane.coords_to_point(x, y)) - dot.original_position = dot.get_center() - dot.generate_target() - dot.target.move_to(output_plane.coords_to_point(*out_coords)) - dot.target_color = rgba_to_color(point_to_rgba( - tuple(self.output_scalar*np.array(out_coords)) - )) - dots.add(dot) - return dots - class SoWeFoundTheZeros(AltTeacherStudentsScene): def construct(self): self.student_says( @@ -470,6 +1436,357 @@ class SoWeFoundTheZeros(AltTeacherStudentsScene): ) self.wait(3) +class Rearrange2DEquation(AltTeacherStudentsScene): + def construct(self): + f_tex, g_tex, h_tex = [ + "%s(\\text{2d point})"%char + for char in "f", "g", "h" + ] + zero_tex = "\\vec{\\textbf{0}}" + equations = VGroup( + TexMobject(g_tex, "", "=", h_tex, ""), + TexMobject(g_tex, "-", h_tex, "=", zero_tex), + ) + equations.move_to(self.hold_up_spot, DOWN) + equations.shift_onto_screen() + + brace = Brace(equations[1], UP) + zero_eq = brace.get_tex("%s = %s"%(f_tex, zero_tex)) + + for equation in equations: + equation.highlight_by_tex(g_tex, BLUE) + equation.highlight_by_tex(h_tex, YELLOW) + equation.sort_submobjects_alphabetically() + + + self.teacher_holds_up(equations[0]) + self.change_all_student_modes("pondering") + self.play(Transform( + *equations, + run_time = 1.5, + path_arc = TAU/2 + )) + self.play( + Succession( + GrowFromCenter(brace), + Write(zero_eq, run_time = 1) + ), + self.get_student_changes(*["happy"]*3) + ) + self.play(*[ + ApplyMethod(pi.change, "thinking", self.screen) + for pi in self.pi_creatures + ]) + self.wait(3) + +class SearchForZerosInInputSpace(ColorMappedObjectsScene): + CONFIG = { + "func" : example_plane_func, + } + def construct(self): + title = TextMobject("Input space") + title.scale(2) + title.to_edge(UP) + title.set_stroke(BLACK, 1) + title.add_background_rectangle() + + plane = NumberPlane() + plane.fade(0.5) + plane.axes.set_stroke(WHITE, 3) + + self.add(plane, title) + + looking_glass = Circle() + looking_glass.set_stroke(WHITE, 3) + looking_glass.set_fill(WHITE, 0.6) + looking_glass.color_using_background_image(self.background_image_file) + question = TextMobject("Which points go to 0?") + question.next_to(looking_glass, DOWN) + question.add_background_rectangle() + + mover = VGroup(looking_glass, question) + mover.move_to(4*LEFT + UP) + + self.play(FadeIn(mover)) + points = [4*RIGHT+UP, 2*RIGHT+2*DOWN, 2*LEFT+2*DOWN, 3*RIGHT+2.5*DOWN] + for point in points: + self.play(mover.move_to, point, run_time = 1.5) + self.wait() + +class OneDRegionBoundary(Scene): + CONFIG = { + "graph_color" : BLUE, + "region_rect_height" : 0.1, + } + def construct(self): + x0 = self.x0 = 3 + x1 = self.x1 = 6 + fx0 = self.fx0 = -2 + fx1 = self.fx1 = 2 + + axes = self.axes = Axes( + x_min = -1, x_max = 10, + y_min = -3, y_max = 3, + ) + axes.center() + axes.set_stroke(width = 2) + + input_word = TextMobject("Input") + input_word.next_to(axes.x_axis, UP, SMALL_BUFF, RIGHT) + output_word = TextMobject("Output") + output_word.next_to(axes.y_axis, UP) + axes.add(input_word, output_word) + self.add(axes) + + graph = self.get_graph_part(1, 1) + alt_graphs = [ + self.get_graph_part(*points) + for points in [ + (-1, -2), + (-1, -1, -1), + (1, 1, 1), + (-0.75, 0, 1.75), + (-3, -2, -1), + ] + ] + + #Region and boundary + line = Line(axes.coords_to_point(x0, 0), axes.coords_to_point(x1, 0)) + region = Rectangle( + stroke_width = 0, + fill_color = YELLOW, + fill_opacity = 0.5, + height = self.region_rect_height + ) + region.match_width(line, stretch = True) + region.move_to(line) + + region_words = TextMobject("Input region") + region_words.scale_to_fit_width(0.8*region.get_width()) + region_words.next_to(region, UP) + + x0_arrow, x1_arrow = arrows = VGroup(*[ + Arrow( + axes.coords_to_point(x, 0), + axes.coords_to_point(x, fx), + color = color, + buff = 0 + ) + for x, fx, color in (x0, fx0, RED), (x1, fx1, GREEN) + ]) + minus = TexMobject("-") + minus.match_color(x0_arrow) + minus.next_to(x0_arrow, UP) + plus = TexMobject("+") + plus.match_color(x1_arrow) + plus.next_to(x1_arrow, DOWN) + signs = VGroup(plus, minus) + + + self.play( + GrowFromCenter(region), + FadeIn(region_words) + ) + self.wait() + self.play(*it.chain( + map(GrowArrow, arrows), + map(Write, signs) + )) + self.wait() + self.play( + ShowCreation(graph), + FadeOut(region_words), + ) + self.wait() + for alt_graph in alt_graphs + alt_graphs: + self.play(Transform(graph, alt_graph, path_arc = 0.1*TAU)) + self.wait() + + + ### + + def get_graph_part(self, *interim_values): + result = VMobject() + result.set_stroke(self.graph_color, 3) + result.set_fill(opacity = 0) + values = [self.fx0] + list(interim_values) + [self.fx1] + result.set_points_smoothly([ + self.axes.coords_to_point(x, fx) + for x, fx in zip( + np.linspace(self.x0, self.x1, len(values)), + values + ) + ]) + return result + +class DirectionOfA2DFunctionAlongABoundary(InputOutputScene): + def construct(self): + colorings = self.get_colorings() + colorings.set_fill(opacity = 0.25) + input_plane, output_plane = planes = self.get_planes() + for plane in planes: + plane.lines_to_fade.set_stroke(width = 0) + v_line = self.get_v_line() + + rect = Rectangle() + rect.set_stroke(WHITE, 5) + rect.set_fill(WHITE, 0) + line = Line( + input_plane.coords_to_point(-0.75, 2.5), + input_plane.coords_to_point(2.5, -1.5), + ) + rect.replace(line, stretch = True) + rect.insert_n_anchor_points(50) + rect.match_background_image_file(colorings[0]) + + rect_image = rect.copy() + rect_image.match_background_image_file(colorings[1]) + def update_rect_image(rect_image): + rect_image.points = np.array(rect.points) + rect_image.apply_function(self.point_function) + rect_image_update_anim = UpdateFromFunc(rect_image, update_rect_image) + + + def get_input_point(): + return rect.points[-1] + + def get_output_coords(): + in_coords = input_plane.point_to_coords(get_input_point()) + return self.func(in_coords) + + def get_angle(): + return angle_of_vector(get_output_coords()) + + def get_color(): + return rev_to_color(get_angle()/TAU) #Negative? + + + out_vect = Vector(RIGHT, color = WHITE) + out_vect_update_anim = UpdateFromFunc( + out_vect, + lambda ov : ov.put_start_and_end_on( + output_plane.coords_to_point(0, 0), + rect_image.points[-1] + ).highlight(get_color()) + ) + + dot = Dot() + dot.set_stroke(BLACK, 1) + dot_update_anim = UpdateFromFunc( + dot, lambda d : d.move_to(get_input_point()).set_fill(get_color()) + ) + + in_vect = Vector(RIGHT) + def update_in_vect(in_vect): + in_vect.put_start_and_end_on(ORIGIN, 0.5*RIGHT) + in_vect.rotate(get_angle()) + in_vect.highlight(get_color()) + in_vect.shift(get_input_point() - in_vect.get_start()) + return in_vect + in_vect_update_anim = UpdateFromFunc(in_vect, update_in_vect) + + self.add(colorings, planes, v_line) + + self.play( + GrowArrow(out_vect), + GrowArrow(in_vect), + Animation(dot), + ) + self.play( + ShowCreation(rect), + ShowCreation(rect_image), + out_vect_update_anim, + in_vect_update_anim, + dot_update_anim, + rate_func = bezier([0, 0, 1, 1]), + run_time = 10, + ) + +class AskAboutHowToGeneralizeSigns(AltTeacherStudentsScene): + def construct(self): + # 2d plane + plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) + plane.scale(0.8) + plane.to_corner(UP+LEFT) + plane.add_coordinates() + + dot = Dot(color = YELLOW) + label = TextMobject("Sign?") + label.add_background_rectangle() + label.scale(0.5) + label.next_to(dot, UP, SMALL_BUFF) + dot.add(label) + dot.move_to(plane.coords_to_point(1, 1)) + dot.save_state() + dot.fade(1) + dot.center() + + question = TextMobject( + "Wait...what would \\\\ positive and negative \\\\ be in 2d?", + ) + # question.highlight_by_tex_to_color_map({ + # "+" : "green", + # "textminus" : "red" + # }) + + + self.student_says( + question, + target_mode = "sassy", + student_index = 2, + added_anims = [ + self.teacher.change, "plain", + ], + bubble_kwargs = {"direction" : LEFT}, + run_time = 1, + ) + self.play( + Write(plane, run_time = 1), + self.students[0].change, "confused", + self.students[1].change, "confused", + ) + self.play(dot.restore) + for coords in (-1, 1), (1, -1), (0, -2), (-2, 1): + self.wait(0.5) + self.play(dot.move_to, plane.coords_to_point(*coords)) + self.wait() + +class HypothesisAboutFullyColoredBoundary(ColorMappedObjectsScene): + CONFIG = { + "func" : plane_func_from_complex_func(lambda z : z**3), + } + def construct(self): + ColorMappedObjectsScene.construct(self) + square = Square(side_length = 4) + square.color_using_background_image(self.background_image_file) + hypothesis = TextMobject( + "Working Hypothesis: \\\\", + "If a 2d function hits outputs of all possible colors \\\\" + + "on the boundary of a 2d region,", + "that region \\\\ contains a zero.", + alignment = "", + ) + hypothesis[0].next_to(hypothesis[1:], UP) + hypothesis[0].highlight(YELLOW) + s = hypothesis[1].get_tex_string() + s = filter(lambda c : c not in string.whitespace, s) + n = s.index("colors") + hypothesis[1][n:n+len("colors")].gradient_highlight( + # RED, GOLD_E, YELLOW, GREEN, BLUE, PINK, + BLUE, PINK, YELLOW, + ) + hypothesis.to_edge(UP) + square.next_to(hypothesis, DOWN, MED_LARGE_BUFF) + + self.add(hypothesis[0]) + self.play( + LaggedStart(FadeIn, hypothesis[1]), + ShowCreation(square, run_time = 8) + ) + self.play(LaggedStart(FadeIn, hypothesis[2])) + self.play(square.set_fill, {"opacity" : 1}, run_time = 2) + self.wait() + class PiCreatureAsksWhatWentWrong(PiCreatureScene): def construct(self): randy = self.pi_creature @@ -491,14 +1808,1196 @@ class PiCreatureAsksWhatWentWrong(PiCreatureScene): ) self.wait(5) - - - - - - - - +class ForeverNarrowingLoop(InputOutputScene): + CONFIG = { + "target_coords" : (1, 1), + "input_plane_corner" : UP+RIGHT, + "shrink_time" : 20, + "circle_start_radius" : 2.25, + "start_around_target" : False, + + # Added as a flag to not mess up one clip already used and fine-timed + # but to make it more convenient to do the other TinyLoop edits + "add_convenient_waits" : False + } + def construct(self): + input_coloring, output_coloring = colorings = VGroup(*self.get_colorings()) + input_plane, output_plane = planes = VGroup(*self.get_planes()) + for plane in planes: + plane.white_parts.highlight(BLACK) + plane.lines_to_fade.set_stroke(width = 0) + + v_line = Line(UP, DOWN).scale(SPACE_HEIGHT) + v_line.set_stroke(WHITE, 5) + + self.add(colorings, v_line, planes) + self.play(*it.chain( + [ + ApplyMethod(coloring.set_fill, {"opacity" : 0.2}) + for coloring in colorings + ], + [ + ApplyMethod(plane.white_parts.highlight, WHITE) + for plane in planes + ] + ), run_time = 2) + + # circle + circle = Circle(color = WHITE, radius = self.circle_start_radius) + circle.flip(axis = RIGHT) + circle.insert_n_anchor_points(50) + if self.start_around_target: + circle.move_to(input_plane.coords_to_point(*self.target_coords)) + else: + circle.next_to( + input_coloring.get_corner(self.input_plane_corner), + -self.input_plane_corner, + SMALL_BUFF + ) + circle.set_stroke(width = 5) + circle_image = circle.copy() + circle.match_background_image_file(input_coloring) + circle_image.match_background_image_file(output_coloring) + + def update_circle_image(circle_image): + circle_image.points = circle.points + circle_image.apply_function(self.point_function) + circle_image.make_smooth() + + circle_image_update_anim = UpdateFromFunc( + circle_image, update_circle_image + ) + + def optional_wait(): + if self.add_convenient_waits: + self.wait() + + optional_wait() + self.play( + ShowCreation(circle), + ShowCreation(circle_image), + run_time = 3, + rate_func = bezier([0, 0, 1, 1]) + ) + optional_wait() + self.play( + circle.scale, 0, + circle.move_to, input_plane.coords_to_point(*self.target_coords), + circle_image_update_anim, + run_time = self.shrink_time, + rate_func = bezier([0, 0, 1, 1]) + ) + +class AltForeverNarrowingLoop(ForeverNarrowingLoop): + CONFIG = { + "target_coords" : (-2, -1), + "input_plane_corner" : DOWN+LEFT, + "shrink_time" : 3, + } + +class TinyLoop(ForeverNarrowingLoop): + CONFIG = { + "circle_start_radius" : 0.5, + "start_around_target" : True, + "shrink_time" : 1, + "add_convenient_waits" : True, + } + +class TinyLoopAroundZero(TinyLoop): + CONFIG = { + "target_coords" : (1, 1), + } + +class TinyLoopAroundBlue(TinyLoop): + CONFIG = { + "target_coords" : (2.4, 0), + } + +class TinyLoopAroundYellow(TinyLoop): + CONFIG = { + "target_coords" : (0, -1.3), + } + +class TinyLoopAroundOrange(TinyLoop): + CONFIG = { + "target_coords" : (0, -0.5), + } + +class TinyLoopAroundRed(TinyLoop): + CONFIG = { + "target_coords" : (-1, 1), + } + +class FailureOfComposition(ColorMappedObjectsScene): + CONFIG = { + "func" : lambda p : ( + np.cos(TAU*p[1]/3.5), + np.sin(TAU*p[1]/3.5) + ) + } + def construct(self): + ColorMappedObjectsScene.construct(self) + + big_square = Square(side_length = 4) + big_square.move_to(ORIGIN, RIGHT) + small_squares = VGroup(*[ + Square(side_length = 2) for x in range(2) + ]) + small_squares.match_width(big_square, stretch = True) + small_squares.arrange_submobjects(DOWN, buff = 0) + small_squares.move_to(big_square) + small_squares.space_out_submobjects(1.1) + all_squares = VGroup(big_square, *small_squares) + all_squares.set_stroke(width = 6) + + for square in all_squares: + square.highlight(WHITE) + square.color_using_background_image(self.background_image_file) + + question = TextMobject("Does my border go through every color?") + question.to_edge(UP) + no_answers = VGroup() + yes_answers = VGroup() + for square in all_squares: + if square is big_square: + square.answer = TextMobject("Yes") + square.answer.highlight(GREEN) + yes_answers.add(square.answer) + else: + square.answer = TextMobject("No") + square.answer.highlight(RED) + no_answers.add(square.answer) + square.answer.move_to(square) + + no_answers_in_equation = no_answers.copy() + yes_answers_in_equation = yes_answers.copy() + plus, equals = plus_equals = TexMobject("+=") + equation = VGroup( + no_answers_in_equation[0], plus, + no_answers_in_equation[1], equals, + yes_answers_in_equation + ) + equation.arrange_submobjects(RIGHT, buff = SMALL_BUFF) + equation.next_to(big_square, RIGHT, MED_LARGE_BUFF) + q_marks = TexMobject("???") + q_marks.next_to(equals, UP) + + + self.add(question) + self.play(LaggedStart(ShowCreation, small_squares, lag_ratio = 0.8)) + self.play(LaggedStart(Write, no_answers)) + self.wait() + self.play( + small_squares.arrange_submobjects, DOWN, {"buff" : 0}, + small_squares.move_to, big_square, + no_answers.space_out_submobjects, 0.9, + ) + self.add(big_square) + no_answers_copy = no_answers.copy() + small_squares.save_state() + self.play( + Transform(no_answers, no_answers_in_equation), + Write(plus_equals), + small_squares.set_stroke, {"width" : 0}, + ) + self.play( + Write(yes_answers), + Write(yes_answers_in_equation), + ) + self.play(LaggedStart(FadeIn, q_marks, run_time = 1, lag_ratio = 0.8)) + self.wait(2) + self.play( + small_squares.restore, + FadeOut(yes_answers), + FadeIn(no_answers_copy), + ) + self.wait() + self.play( + small_squares.set_stroke, {"width" : 0}, + FadeOut(no_answers_copy), + FadeIn(yes_answers), + ) + self.wait() + + # We can find a better notion of what we want + + cross = Cross(question) + + self.play( + ShowCreation(cross, run_time = 2), + FadeOut(equation), + FadeOut(no_answers), + FadeOut(q_marks), + FadeOut(yes_answers), + ) + + x, plus, y = x_plus_y = TexMobject("x+y") + x_plus_y.move_to(big_square) + x_plus_y.save_state() + x.move_to(no_answers_copy[0]) + y.move_to(no_answers_copy[1]) + plus.fade(1) + + for square, char in zip(small_squares, [x, y]): + ghost = square.copy() + ghost.set_stroke(width = 5) + ghost.background_image_file = None + self.play( + small_squares.restore, + ShowPassingFlash(ghost), + Write(char) + ) + self.wait() + ghost = big_square.copy() + ghost.background_image_file = None + self.play( + small_squares.set_stroke, {"width" : 0}, + x_plus_y.restore, + ) + self.play(ShowPassingFlash(ghost)) + self.wait() + +class PathContainingZero(InputOutputScene, PiCreatureScene): + CONFIG = { + "default_pi_creature_kwargs" : { + "flip_at_start" : False, + "height" : 1.5, + }, + "default_pi_creature_start_corner" : DOWN+LEFT, + } + def construct(self): + self.setup_planes() + self.draw_path_hitting_zero() + self.comment_on_zero() + + def setup_planes(self): + colorings = VGroup(*self.get_colorings()) + self.input_coloring, self.output_coloring = colorings + colorings.set_fill(opacity = 0.3) + + planes = VGroup(*self.get_planes()) + self.input_plane, self.output_plane = planes + for plane in planes: + # plane.white_parts.highlight(BLACK) + plane.lines_to_fade.set_stroke(width = 0) + + v_line = Line(UP, DOWN).scale(SPACE_HEIGHT) + v_line.set_stroke(WHITE, 5) + + self.add(colorings, planes) + self.add(v_line) + + def draw_path_hitting_zero(self): + morty = self.pi_creature + + path = self.path = VMobject( + stroke_width = 5, + stroke_color = WHITE, + fill_opacity = 0, + ) + path.match_background_image_file(self.input_coloring) + path.set_points_smoothly(list(it.starmap( + self.input_plane.coords_to_point, + [(1, 2.5), (2.5, 2.5), (2, 0.5), (1, 1), (0.5, 1), (0.5, 2), (1, 2.5)] + ))) + + out_path = self.out_path = path.copy() + out_path.apply_function(self.point_function) + out_path.match_background_image_file(self.output_coloring) + out_path.make_smooth() + + self.play( + Flash( + VectorizedPoint(self.output_plane.coords_to_point(0, 0)), + color = WHITE, + flash_radius = 0.3, + line_length = 0.2, + num_lines = 13, + rate_func = squish_rate_func(smooth, 0.5, 0.6), + ), + morty.change, "pondering", + *[ + ShowCreation(mob, rate_func = bezier([0, 0, 1, 1])) + for mob in path, out_path + ], + run_time = 5 + ) + + def comment_on_zero(self): + morty = self.pi_creature + + words = TextMobject( + "Output is zero \\\\", + "which has no direction" + ) + origin = self.output_plane.coords_to_point(0, 0) + words.to_edge(DOWN, buff = LARGE_BUFF) + background_rect = BackgroundRectangle( + words, buff = SMALL_BUFF, + opacity = 1.0 + ) + background_rect.stretch_to_fit_width(0.1) + + arrow = Arrow(words.get_top(), origin) + + circles = VGroup() + for point in self.input_plane.coords_to_point(1, 1), origin: + circle = Circle(color = BLACK, radius = 0.5, stroke_width = 0) + circle.move_to(point) + circle.generate_target() + circle.target.scale(0) + circle.target.set_stroke(width = 4) + circles.add(circle) + in_circle, out_circle = circles + + new_words = TextMobject( + "But we want $\\vec{\\textbf{x}}$ \\\\", + "where $f(\\vec{\\textbf{x}}) = 0$", + ) + new_words.move_to(words) + + self.play( + FadeIn(background_rect), + Write(words[0]), + GrowArrow(arrow), + ) + self.play( + Write(words[1]), + morty.change, "pleading", + MoveToTarget(out_circle, run_time = 2) + ) + self.wait() + self.play(FadeOut(words)) + self.play( + FadeIn(new_words), + morty.change, "happy" + ) + self.play(MoveToTarget(in_circle, run_time = 2)) + 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(MED_LARGE_BUFF*DOWN) + + dot = self.get_dot_and_add_continual_animations() + + for rect, tex in (left_square, "x"), (right_square, "y"), (joint_rect, "3"): + rect.label = TextMobject("Winding = ", "$%s$"%tex) + rect.label.move_to(rect) + sum_label = TexMobject("x", "+", "y", "=", "3") + x, plus, y, equals, three = sum_label + sum_label.next_to(joint_rect, UP) + + both_cannot_be_zero = TextMobject("These cannot both be 0") + both_cannot_be_zero.move_to(plus) + both_cannot_be_zero.to_edge(UP) + arrows = VGroup(*[ + Arrow(both_cannot_be_zero.get_bottom(), var.get_top(), buff = SMALL_BUFF) + for var in x, y + ]) + + self.position_dot(joint_rect.points[0]) + self.add(joint_rect) + self.play( + MoveAlongPath(dot, joint_rect, rate_func = bezier([0, 0, 1, 1])), + Write(joint_rect.label, rate_func = squish_rate_func(smooth, 0.7, 1)), + run_time = 4 + ) + self.wait() + self.play( + ReplacementTransform(joint_rect.label, left_square.label), + ReplacementTransform(joint_rect.label.copy(), right_square.label), + ReplacementTransform(joint_rect.label[1].copy(), three), + FadeIn(left_square), + FadeIn(right_square), + ) + self.play( + ReplacementTransform(left_square.label[1].copy(), x), + ReplacementTransform(right_square.label[1].copy(), y), + FadeIn(plus), + FadeIn(equals), + ) + self.play( + FadeIn(both_cannot_be_zero), + *map(GrowArrow, arrows) + ) + self.wait() + +class BackToEquationSolving(AltTeacherStudentsScene): + def construct(self): + self.teacher_says( + "Back to solving \\\\ equations" + ) + self.change_all_student_modes("hooray") + self.play(*[ + ApplyMethod(pi.look_at, self.screen) + for pi in self.pi_creatures + ]) + 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( + (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) + ), + "num_iterations" : 15, + "display_in_parallel" : False, + "use_fancy_lines" : True, + } + def construct(self): + self.force_skipping() + EquationSolver2d.construct(self) + self.revert_to_original_skipping_status() + + all_parts = VGroup(*self.get_mobjects()) + path_parts = VGroup() + non_path_parts = VGroup() + for part in all_parts: + if part.get_background_image_file() is not None: + path_parts.add(part) + else: + non_path_parts.add(part) + path_parts.save_state() + path_parts.generate_target() + for path_target in path_parts.target: + if isinstance(path_target, Line): + path_target.rotate(-path_target.get_angle()) + path_parts.target.arrange_submobjects(DOWN, buff = MED_SMALL_BUFF) + alt_path_parts = path_parts.copy() + size = lambda m : m.get_height() + m.get_width() + alt_path_parts.submobjects.sort( + lambda m1, m2 : -cmp(size(m1), size(m2)) + ) + + full_rect = SurroundingRectangle( + path_parts, + stroke_width = 0, + fill_color = WHITE, + fill_opacity = 1, + background_image_file = path_parts[0].background_image_file + ) + full_rect.save_state() + full_rect.stretch(0, 1, about_edge = UP) + + self.play( + FadeOut(non_path_parts), + path_parts.set_stroke, {"width" : 1}, + ) + self.remove(all_parts) + for x in range(2): + alt_path_parts.save_state() + self.play(LaggedStart( + FadeIn, alt_path_parts, + rate_func = there_and_back, + lag_ratio = 0.3, + run_time = 3, + remover = True + )) + alt_path_parts.restore() + self.play( + full_rect.restore, + run_time = 2, + ) + self.wait() + self.play(FadeOut(full_rect)) + self.wait() + +class EndingCredits(Scene): + def construct(self): + text = TextMobject( + "Written and animated by: \\\\", + "Sridhar Ramesh \\\\", + "Grant Sanderson" + ) + text[0].shift(MED_SMALL_BUFF*UP) + text.to_edge(UP) + + pi = PiCreature(color = YELLOW_E, height = 2) + pi.to_edge(DOWN) + pi.change_mode("happy") + self.add(pi) + + self.play(LaggedStart(FadeIn, text), pi.look_at, text) + self.play(pi.change, "wave_1", text) + self.play(Blink(pi)) + self.play(pi.change, "happy") + self.wait() + +class MentionQAndA(Scene): + def construct(self): + title = TextMobject("Q\\&A with ", "Ben", "and", "Sridhar\\\\", "at", "Patreon") + title.highlight_by_tex_to_color_map({ + "Ben" : MAROON, + "Sridhar" : YELLOW, + }) + patreon_logo = VGroup(*PatreonLogo().family_members_with_points()) + patreon_logo.sort_submobjects() + patreon_logo.replace(title.get_parts_by_tex("Patreon")) + patreon_logo.scale(1.3, about_edge = LEFT) + patreon_logo.shift(0.5*SMALL_BUFF*DOWN) + title.submobjects[-1] = patreon_logo + + title.to_edge(UP) + self.add(title) + + questions = VGroup(*map(TextMobject, [ + "If you think of the current videos as short stories, \\\\ what is the novel that you want to write?", + "How did you get into mathematics?", + "What motivated you to join 3b1b?", + "$\\vdots$", + ])) + questions.arrange_submobjects(DOWN, buff = 0.75) + questions.next_to(title, DOWN, LARGE_BUFF) + + self.play(LaggedStart(FadeIn, questions, run_time = 3)) + self.wait(2) + self.play(FadeOut(questions)) + self.wait() + +class TickingClock(Scene): + CONFIG = { + "run_time" : 90, + } + def construct(self): + clock = Clock() + clock.scale_to_fit_height(2*SPACE_HEIGHT - 1) + clock.to_edge(LEFT) + lines = [clock.hour_hand, clock.minute_hand] + def update_line(line): + rev = line.get_angle()/TAU + line.highlight(rev_to_color(rev)) + + for line in lines: + self.add(ContinualUpdateFromFunc(line, update_line)) + + run_time = self.run_time + self.play(ClockPassesTime( + clock, + run_time = run_time, + hours_passed = 0.1*run_time + )) + +class InfiniteListOfTopics(Scene): + def construct(self): + rect = Rectangle(width = 5, height = 7) + rect.to_edge(RIGHT) + title = TextMobject("Infinite list \\\\ of topics") + title.next_to(rect.get_top(), DOWN) + lines = VGroup(*[ + TextMobject(words).scale(0.5) + for words in [ + "Winding number", + "Laplace transform", + "Wallis product", + "Quantum information", + "Elliptic curve cryptography", + "Strange attractors", + "Convolutional neural networks", + "Fixed points", + ] + ] + [TexMobject("\\vdots")]) + lines.arrange_submobjects(DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) + lines.next_to(title, DOWN, MED_LARGE_BUFF) + lines[-1].next_to(lines[-2], DOWN) + + self.add(rect, title) + self.play(LaggedStart(FadeIn, lines, run_time = 5)) + self.wait() + +class ManyIterations(Scene): + def construct(self): + words = VGroup(*[ + TextMobject(word, alignment = "") + for word in [ + "Winding numbers, v1", + "Winding numbers, v2 \\\\ (center on domain coloring)", + "Winding numbers, v3 \\\\ (clarify visuals of 2d functions)", + "Winding numbers, v4 \\\\ (postpone topology examples for part 2)", + "Winding numbers, v5 \\\\ (start down wrong path)", + ] + ]) + words.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) + words.scale(0.75) + words.to_edge(RIGHT) + + self.add(words[0]) + for last_word, word in zip(words, words[1:]): + cross = Cross(last_word) + self.play(ShowCreation(cross)) + self.play(FadeIn(word)) + self.wait() + +class MentionFree(PiCreatureScene): + CONFIG = { + "default_pi_creature_kwargs" : { + "flip_at_start" : False, + }, + "default_pi_creature_start_corner" : DOWN, + } + def construct(self): + morty = self.pi_creature + morty.shift(RIGHT) + + items = VGroup( + TextMobject("Movie:", "$>\\$10.00$"), + TextMobject("College course:", "$>\\$1{,}000.00$"), + TextMobject("YouTube video:", "$=\\$0.00$"), + ) + # items.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + items.next_to(morty, UP, LARGE_BUFF) + right_x = morty.get_right()[0] + for item in items: + item[1].highlight(GREEN) + item.shift((right_x - item[0].get_right()[0])*RIGHT) + + self.play( + morty.change, "raise_right_hand", + FadeInFromDown(items[0]) + ) + self.wait() + self.play( + FadeInFromDown(items[1]), + items[0].shift, UP, + ) + self.wait() + self.play( + items[:2].shift, UP, + FadeInFromDown(items[2]), + morty.change, "surprised" + ) + self.wait(4) + self.play( + morty.change, "raise_left_hand", VectorizedPoint(3*LEFT) + ) + self.wait(4) + self.play(morty.change, "gracious", OUT) + self.wait(4) + +class PatreonScroll(Scene): + CONFIG = { + "specific_patrons" : [ + "Juan Benet", + "Chloe Zhou", + "Ross Garber", + "Desmos", + "Burt Humburg", + "CrypticSwarm", + "Sergei", + "Devin Scott", + "George John", + "Akash Kumar", + "Felix Tripier", + "Arthur Zey", + "David Kedmey", + "Ali Yahya", + "Mayank M. Mehrotra", + "Lukas Biewald", + "Yana Chernobilsky", + "Kaustuv DeBiswas", + "Yu Jun", + "Dave Nicponski", + "Damion Kistler", + "Patrick Mézard", + "Jordan Scales", + "Markus Persson", + "Britt Selvitelle", + "Jonathan Wilson", + "Ryan Atallah", + "Joseph John Cox", + "Luc Ritchie", + "Steven Tomlinson", + "Shìmín Ku$\\overline{\\text{a}}$ng", + "Jameel Syed", + "Bong Choung", + "Ignacio Freiberg", + "Zhilong Yang", + "Karl Niu", + "Dan Esposito (Guardion)", + "Giovanni Filippi", + "Eric Younge", + "Prasant Jagannath", + "Cody Brocious", + "Jacob Kohl", + "James H. Park", + "Norton Wang", + "Kevin Le", + "Alexander Feldman", + "Tianyu Ge", + "David MacCumber", + "Oliver Steele", + "Yaw Etse", + "David B", + "Waleed Hamied", + "George Chiesa", + "supershabam", + "Delton Ding", + "Thomas Tarler", + "Jonathan Eppele", + "Isak Hietala", + "1stViewMaths", + "Jacob Magnuson", + "Mark Govea", + "Clark Gaebel", + "Mathias Jansson", + "David Clark", + "Michael Gardner", + "Mads Elvheim", + "Awoo", + "Dr. David G. Stork", + "Ted Suzman", + "Linh Tran", + "Andrew Busey", + "John Haley", + "Ankalagon", + "Eric Lavault", + "Boris Veselinovich", + "Julian Pulgarin", + "Jeff Linse", + "Cooper Jones", + "Ryan Dahl", + "Robert Teed", + "Jason Hise", + "Meshal Alshammari", + "Bernd Sing", + "James Thornton", + "Mustafa Mahdi", + "Mathew Bramson", + "Jerry Ling", + "Mèngzi Yì", + "Rish Kundalia", + "Achille Brighton", + "Ripta Pasay", + ], + "random_seed" : 1, + } + def construct(self): + patreon_logo = PatreonLogo() + patreon_logo.to_corner(UP+RIGHT) + patreon_logo.shift(SMALL_BUFF*LEFT) + self.add(patreon_logo) + + patrons = VGroup(*map(TextMobject, self.specific_patrons)) + patrons.scale(0.75) + random.shuffle(patrons.submobjects) + patrons.arrange_submobjects(DOWN, aligned_edge = LEFT) + patrons.next_to(ORIGIN, DOWN) + patrons.to_edge(RIGHT) + + # patorons = patrons[:10] ##TO remove + + scroll = AmbientMovement(patrons, direction = UP, rate = 1) + def patrons_opacity_update(patrons): + for patron in patrons: + y = patron.get_center()[1] + if y > 3.5: + patrons.remove(patron) + alpha = smooth(np.clip(2.5 - y, 0, 1)) + patron.set_fill(opacity = alpha) + opacity_update = ContinualUpdateFromFunc(patrons, patrons_opacity_update) + + self.add(scroll, opacity_update) + self.wait(55) + +class EndScreen(PatreonEndScreen, PiCreatureScene): + CONFIG = { + "run_time" : 0, + } + def construct(self): + self.remove(self.pi_creature) + PatreonEndScreen.construct(self) + randy, morty = self.pi_creatures + randy.change("plain") + morty.change("plain") + + for mode in "thinking", "confused", "pondering", "hooray": + self.play(randy.change, mode) + self.wait() + self.play(morty.change, mode) + self.wait(2) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index b26edd43..86c4a07c 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -310,3 +310,24 @@ class ApplyToCenters(Animation): center_mob.get_center()-mobject.get_center() ) +class FadeInAndShiftFromDirection(Transform): + CONFIG = { + "direction" : DOWN, + } + def __init__(self, mobject, **kwargs): + digest_config(self, kwargs) + target = mobject.copy() + mobject.shift(self.direction) + mobject.fade(1) + Transform.__init__(self, mobject, target, **kwargs) + +# Essentially just a more convenient name for the above animation +class FadeInFromDown(FadeInAndShiftFromDirection): + CONFIG = { + "direction" : DOWN, + } + + + + + diff --git a/camera/camera.py b/camera/camera.py index f599eb09..80c067fc 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -11,6 +11,8 @@ from helpers import * from mobject import Mobject, PMobject, VMobject, \ ImageMobject, Group +import time + class Camera(object): CONFIG = { "background_image" : None, @@ -123,14 +125,15 @@ class Camera(object): pixel coordinates), and each output is expected to be an RGBA array of 4 floats. """ - print "Starting set_background_from_func" - + print "Starting set_background; for reference, the current time is ", time.strftime("%H:%M:%S") coords = self.get_coords_of_all_pixels() new_background = np.apply_along_axis( coords_to_colors_func, 2, coords ) + print "Ending set_background; for reference, the current time is ", time.strftime("%H:%M:%S") + return self.convert_pixel_array(new_background, convert_from_floats = True) def set_background_from_func(self, coords_to_colors_func): diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 1a6b4710..5fc1b14e 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -210,10 +210,11 @@ class TexMobject(SVGMobject): self.submobjects.sort(alphabetical_cmp) return self - def add_background_rectangle(self, color = BLACK, opacity = 0.75): + def add_background_rectangle(self, color = BLACK, opacity = 0.75, **kwargs): self.background_rectangle = BackgroundRectangle( self, color = color, - fill_opacity = opacity + fill_opacity = opacity, + **kwargs ) letters = VMobject(*self.submobjects) self.submobjects = [self.background_rectangle, letters] diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 603a2867..f0047dd9 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -65,13 +65,6 @@ class VMobject(Mobject): return self def set_fill(self, color = None, opacity = None, family = True): - probably_meant_to_change_opacity = reduce(op.and_, [ - color is not None, - opacity is None, - self.fill_opacity == 0 - ]) - if probably_meant_to_change_opacity: - opacity = 1 return self.set_style_data( fill_color = color, fill_opacity = opacity, diff --git a/old_projects/pi_day.py b/old_projects/pi_day.py new file mode 100644 index 00000000..dfbb9568 --- /dev/null +++ b/old_projects/pi_day.py @@ -0,0 +1,1129 @@ +# -*- coding: utf-8 -*- + +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.compositions import * +from animation.playground import * +from animation.continual_animation import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.fractals import * +from topics.number_line import * +from topics.combinatorics import * +from topics.numerals import * +from topics.three_dimensions import * +from topics.objects import * +from topics.probability import * +from topics.complex_numbers import * +from scene import Scene +from scene.reconfigurable_scene import ReconfigurableScene +from scene.zoomed_scene import * +from scene.moving_camera_scene import * +from camera import * +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from topics.graph_scene import * +from topics.common_scenes import * + +###### Ben's stuff ######## + + +RADIUS = 2 +RADIUS_BUFF_HOR = 1.3 +RADIUS_BUFF_VER = 0.5 +RADIUS_COLOR = BLUE +CIRCUM_COLOR = YELLOW +DECIMAL_WIDTH = 0.5 + +HIGHLIGHT_COLOR = YELLOW + + +class ArcLengthChange(Animation): + + def __init__(self, mobject = None, new_angle = TAU/3, **kwargs): + + self.old_angle = mobject.angle + self.start_angle = mobject.start_angle + self.new_angle = new_angle + Animation.__init__(self,mobject,**kwargs) + + def update_mobject(self,alpha): + angle = interpolate(self.old_angle, self.new_angle, alpha) + self.mobject.angle = angle + self.mobject.generate_points() + + +class LabelTracksLine(Animation): + + def __init__(self, mobject = None, line = None, buff = 0.2, **kwargs): + + self.line = line + self.buff = buff + Animation.__init__(self,mobject,**kwargs) + + def update_mobject(self,alpha): + line_center = self.line.get_center() + line_end = self.line.points[-1] + v = line_end - line_center + v = v/np.linalg.norm(v) + w = np.array([-v[1],v[0],0]) + self.mobject.move_to(line_center + self.buff * w) + + + + + +class CircleConstants(Scene): + + def radial_label_func(self,a,b,theta): + + theta2 = theta % TAU + slope = (a-b)/(TAU/4) + + if theta2 < TAU/4: + x = a - slope * theta2 + elif theta < TAU/2: + x = b + slope * (theta2 - TAU/4) + elif theta < 3*TAU/4: + x = a - slope * (theta2 - TAU/2) + else: + x = b + slope * (theta2 - 3*TAU/4) + return x + + + def construct(self): + self.setup_circle() + self.change_arc_length(0.004) + self.pi_equals.next_to(self.decimal, LEFT) + self.wait() + self.change_arc_length(TAU/2) + self.wait() + self.change_arc_length(TAU) + self.wait() + self.change_arc_length(TAU/4) + self.wait() + self.change_arc_length(TAU/2) + self.wait() + + + + + def setup_circle(self): + + + self.circle_arc = Arc(angle = 0.004, radius = RADIUS) + self.radius = Line(ORIGIN, RADIUS * RIGHT) + self.radius.highlight(RADIUS_COLOR) + self.circle_arc.highlight(CIRCUM_COLOR) + + self.pi_equals = TexMobject("\pi\\approx", color = CIRCUM_COLOR) + self.decimal = DecimalNumber(0, color = CIRCUM_COLOR) + self.decimal.next_to(self.pi_equals, RIGHT, buff = 0.25) + self.circum_label = VGroup(self.pi_equals, self.decimal) + self.circum_label.next_to(self.radius, RIGHT, buff = RADIUS_BUFF_HOR) + + + self.one = TexMobject("1", color = RADIUS_COLOR) + self.one.next_to(self.radius, UP) + + self.play(ShowCreation(self.radius), FadeIn(self.one)) + self.play( + ShowCreation(self.circle_arc), + Write(self.pi_equals), + Write(self.decimal) + ) + + + def change_arc_length(self, new_angle): + + def decimal_position_update_func(decimal): + + angle = decimal.number + max_radius = RADIUS + RADIUS_BUFF_HOR + min_radius = RADIUS + RADIUS_BUFF_VER + label_radius = self.radial_label_func(max_radius, min_radius, angle) + label_center = label_radius * np.array([np.cos(angle), np.sin(angle),0]) + label_center += 0.5 * RIGHT + # label_center += pi_eq_stencil.get_width() * RIGHT + # print "label_center = ", label_center + decimal.move_to(label_center) + + + self.play( + Rotate(self.radius, new_angle - self.circle_arc.angle, about_point = ORIGIN), + ArcLengthChange(self.circle_arc,new_angle), + ChangeDecimalToValue( + self.decimal, new_angle, + position_update_func = decimal_position_update_func + ), + #MaintainPositionRelativeTo(self.one, self.radius), + MaintainPositionRelativeTo(self.pi_equals, self.decimal), + LabelTracksLine(self.one,self.radius, buff = 0.5), + run_time = 3, + ) + self.wait(2) + + +class AnalysisQuote(Scene): + + def construct(self): + + text = TextMobject('``We therefore set the radius of \\\\'\ + 'the circle\dots to be = 1, and \dots\\\\'\ + 'through approximations the \\\\'\ + 'semicircumference of said circle \\\\'\ + 'has been found to be $= 3.14159\dots$,\\\\'\ + 'for which number, for the sake of \\\\'\ + 'brevity, I will write $\pi$\dots"', + alignment = '') + for char in text.submobjects[12:24]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[42:44]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[75:92]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[120:131]: + char.set_fill(HIGHLIGHT_COLOR) + text.submobjects[-5].set_fill(HIGHLIGHT_COLOR) + + text.to_edge(LEFT, buff = 1) + + self.play(LaggedStart(FadeIn,text), run_time = 5) + self.wait() + self.play(FadeOut(text)) + self.wait() + + +class BernoulliQuote(Scene): + + def construct(self): + + text = TextMobject('``Your most profound investigation of the series \\\\'\ + '$1+{1\over 4}+{1\over 9}+{1\over 16} + $ etc., which I had found to be \\\\'\ + 'one sixth of the square of $\pi$ itself\dots, not only\\\\'\ + ' gave me the greatest pleasure, but also renown \\\\'\ + 'among the whole Academy of St.\ Petersburg."') + text.submobjects[88].set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[41:60]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[79:107]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[127:143]: + char.set_fill(HIGHLIGHT_COLOR) + for char in text.submobjects[151:157]: + char.set_fill(HIGHLIGHT_COLOR) + + self.play(LaggedStart(FadeIn,text), run_time = 5) + self.wait() + self.play(FadeOut(text)) + self.wait + + +class EulerSignature(Scene): + + def construct(self): + + sig = SVGMobject(file_name = "euler-signature") + + self.play( + Write(sig, run_time = 5) + ) + + +########################### + +RESOURCE_DIR = os.path.join(MEDIA_DIR, "3b1b_videos", "π Day 2018", "images") +def get_image(name): + return ImageMobject(os.path.join(RESOURCE_DIR, name)) + +def get_circle_drawing_terms(radius = 1, positioning_func = lambda m : m.center()): + circle = Circle(color = YELLOW, radius = 1.25) + positioning_func(circle) + radius = Line(circle.get_center(), circle.points[0]) + radius.highlight(WHITE) + one = TexMobject("1") + one.scale(0.75) + one_update = UpdateFromFunc( + one, lambda m : m.move_to( + radius.get_center() + \ + 0.25*rotate_vector(radius.get_vector(), TAU/4) + ), + ) + decimal = DecimalNumber(0, num_decimal_points = 4, show_ellipsis = True) + decimal.scale(0.75) + def reposition_decimal(decimal): + vect = radius.get_vector() + unit_vect = vect/np.linalg.norm(vect) + angle = radius.get_angle() + alpha = (-np.cos(2*angle) + 1)/2 + interp_length = interpolate(decimal.get_width(), decimal.get_height(), alpha) + buff = interp_length/2 + MED_SMALL_BUFF + decimal.move_to(radius.get_end() + buff*unit_vect) + decimal.shift(UP*decimal.get_height()/2) + return decimal + + kwargs = {"run_time" : 3, "rate_func" : bezier([0, 0, 1, 1])} + changing_decimal = ChangingDecimal( + decimal, lambda a : a*TAU, + position_update_func = reposition_decimal, + **kwargs + ) + + terms = VGroup(circle, radius, one, decimal) + generate_anims1 = lambda : [ShowCreation(radius), Write(one)] + generate_anims2 = lambda : [ + ShowCreation(circle, **kwargs), + Rotating(radius, about_point = radius.get_start(), **kwargs), + changing_decimal, + one_update, + ] + + return terms, generate_anims1, generate_anims2 + +## + +class PiTauDebate(PiCreatureScene): + def construct(self): + pi, tau = self.pi, self.tau + self.add(pi, tau) + + pi_value = TextMobject("3.1415...!") + pi_value.highlight(BLUE) + tau_value = TextMobject("6.2831...!") + tau_value.highlight(GREEN) + + self.play(PiCreatureSays( + pi, pi_value, + target_mode = "angry", + look_at_arg = tau.eyes, + # bubble_kwargs = {"width" : 3} + )) + self.play(PiCreatureSays( + tau, tau_value, + target_mode = "angry", + look_at_arg = pi.eyes, + bubble_kwargs = {"width" : 3, "height" : 2}, + )) + self.wait() + + # Show tau + terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( + radius = 1.25, + positioning_func = lambda m : m.to_edge(RIGHT, buff = 2).to_edge(UP, buff = 1) + ) + circle, radius, one, decimal = terms + + self.play( + RemovePiCreatureBubble(pi), + RemovePiCreatureBubble(tau), + *generate_anims1() + ) + self.play( + tau.change, "hooray", + pi.change, "sassy", + *generate_anims2() + ) + self.wait() + + + # Show pi + circle = Circle(color = RED, radius = 1.25/2) + circle.rotate(TAU/4) + circle.move_to(pi) + circle.to_edge(UP, buff = MED_LARGE_BUFF) + diameter = Line(circle.get_left(), circle.get_right()) + one = TexMobject("1") + one.scale(0.75) + one.next_to(diameter, UP, SMALL_BUFF) + + circum_line = diameter.copy().scale(np.pi) + circum_line.match_style(circle) + circum_line.next_to(circle, DOWN, buff = MED_LARGE_BUFF) + # circum_line.to_edge(LEFT) + brace = Brace(circum_line, DOWN, buff = SMALL_BUFF) + decimal = DecimalNumber(np.pi, num_decimal_points = 4, show_ellipsis = True) + decimal.scale(0.75) + decimal.next_to(brace, DOWN, SMALL_BUFF) + + self.play( + FadeIn(VGroup(circle, diameter, one)), + tau.change, "confused", + pi.change, "hooray" + ) + self.add(circle.copy().fade(0.5)) + self.play( + ReplacementTransform(circle, circum_line, run_time = 2) + ) + self.play(GrowFromCenter(brace), Write(decimal)) + self.wait(3) + # self.play() + + + def create_pi_creatures(self): + pi = self.pi = Randolph() + pi.to_edge(DOWN).shift(4*LEFT) + tau = self.tau = TauCreature( + # mode = "angry", + file_name_prefix = "TauCreatures", + color = GREEN_E + ).flip() + tau.to_edge(DOWN).shift(4*RIGHT) + return VGroup(pi, tau) + +class HartlAndPalais(Scene): + def construct(self): + hartl_rect = ScreenRectangle( + color = WHITE, + stroke_width = 1, + ) + hartl_rect.scale_to_fit_width(SPACE_WIDTH - 1) + hartl_rect.to_edge(LEFT) + palais_rect = hartl_rect.copy() + palais_rect.to_edge(RIGHT) + + tau_words = TextMobject("$\\tau$ ``tau''") + tau_words.next_to(hartl_rect, UP) + + hartl_words = TextMobject("Michael Hartl's \\\\ ``Tau manifesto''") + hartl_words.next_to(hartl_rect, DOWN) + + palais_words = TextMobject("Robert Palais' \\\\ ``Pi is Wrong!''") + palais_words.next_to(palais_rect, DOWN) + + for words in hartl_words, palais_words: + words.scale(0.7, about_edge = UP) + + three_legged_creature = ThreeLeggedPiCreature(height = 1.5) + three_legged_creature.next_to(palais_rect, UP) + + # self.add(hartl_rect, palais_rect) + self.add(hartl_words) + self.play(Write(tau_words)) + self.wait() + self.play(FadeIn(palais_words)) + self.play(FadeIn(three_legged_creature)) + self.play(three_legged_creature.change_mode, "wave") + self.play(Blink(three_legged_creature)) + self.play(Homotopy( + lambda x, y, z, t : (x + 0.1*np.sin(2*TAU*t)*np.exp(-10*(t-0.5 - 0.5*(y-1.85))**2), y, z), + three_legged_creature, + run_time = 2, + )) + self.wait() + +class ManyFormulas(Scene): + def construct(self): + formulas = VGroup( + TexMobject("\\sin(x + \\tau) = \\sin(x)"), + TexMobject("e^{\\tau i} = 1"), + TexMobject("n! \\approx \\sqrt{\\tau n} \\left(\\frac{n}{e} \\right)^n"), + TexMobject("c_n = \\frac{1}{\\tau} \\int_0^\\tau f(x) e^{inx}dx"), + ) + formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF) + formulas.to_edge(LEFT) + + self.play(LaggedStart(FadeIn, formulas, run_time = 3)) + + circle = Circle(color = YELLOW, radius = 2) + circle.to_edge(RIGHT) + radius = Line(circle.get_center(), circle.get_right()) + radius.highlight(WHITE) + + angle_groups = VGroup() + for denom in 5, 4, 3, 2: + radius_copy = radius.copy() + radius_copy.rotate(TAU/denom, about_point = circle.get_center()) + arc = Arc( + angle = TAU/denom, + stroke_color = RED, + stroke_width = 4, + radius = circle.radius, + ) + arc.shift(circle.get_center()) + mini_arc = arc.copy() + mini_arc.set_stroke(WHITE, 2) + mini_arc.scale(0.15, about_point = circle.get_center()) + tau_tex = TexMobject("\\tau/%d"%denom) + point = mini_arc.point_from_proportion(0.5) + tau_tex.next_to(point, direction = point - circle.get_center()) + angle_group = VGroup(radius_copy, mini_arc, tau_tex, arc) + angle_groups.add(angle_group) + + + angle_group = angle_groups[0] + self.play(*map(FadeIn, [circle, radius])) + self.play( + circle.set_stroke, {"width" : 1,}, + FadeIn(angle_group), + ) + self.wait() + for group in angle_groups[1:]: + self.play(Transform(angle_group, group, path_arc = TAU/8)) + self.wait() + +class HistoryOfOurPeople(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Today: The history \\\\ of our people.", + bubble_kwargs = {"width" : 4, "height" : 3} + ) + self.change_all_student_modes("hooray") + self.wait() + self.play(*[ + ApplyMethod(pi.change, "happy", self.screen) + for pi in self.pi_creatures + ]) + self.wait(4) + +class TauFalls(Scene): + def construct(self): + tau = TauCreature() + bottom = np.min(tau.body.points[:,1])*UP + angle = -0.15*TAU + tau.generate_target() + tau.target.change("angry") + tau.target.rotate(angle, about_point = bottom) + self.play(Rotate(tau, angle, rate_func = rush_into, path_arc = angle, about_point = bottom)) + # self.play(MoveToTarget(tau, rate_func = rush_into, path_arc = angle)) + self.play(MoveToTarget(tau)) + self.wait() + +class EulerWrites628(Scene): + CONFIG = { + "camera_config" : { + "background_alpha" : 255, + } + } + def construct(self): + image = ImageMobject(os.path.join(RESOURCE_DIR, "dalembert_zoom")) + image.scale_to_fit_width(2*SPACE_WIDTH - 1) + image.to_edge(UP, buff = MED_SMALL_BUFF) + image.fade(0.15) + rect = Rectangle( + width = 12, + height = 0.5, + stroke_width = 0, + fill_opacity = 0.3, + fill_color = GREEN, + ) + rect.insert_n_anchor_points(20) + rect.apply_function(lambda p : np.array([p[0], p[1] - 0.005*p[0]**2, p[2]])) + rect.rotate(0.012*TAU) + rect.move_to(image) + rect.shift(0.15*DOWN) + + words = TextMobject( + "``Let", "$\\pi$", "be the", "circumference", + "of a circle whose", "radius = 1''", + ) + words.highlight_by_tex_to_color_map({ + "circumference" : YELLOW, + "radius" : GREEN, + }) + words.next_to(image, DOWN) + pi = words.get_part_by_tex("\\pi").copy() + + terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( + radius = 1, + positioning_func = lambda circ : circ.next_to(words, DOWN, buff = 1.25) + ) + circle, radius, one, decimal = terms + + unwrapped_perimeter = Line(ORIGIN, TAU*RIGHT) + unwrapped_perimeter.match_style(circle) + unwrapped_perimeter.next_to(circle, DOWN) + brace = Brace(unwrapped_perimeter, UP, buff = SMALL_BUFF) + + perimeter = TexMobject( + "\\pi\\epsilon\\rho\\iota\\mu\\epsilon\\tau\\rho\\text{o}\\varsigma", + "\\text{ (perimeter)}", + "=" + ) + perimeter.next_to(brace, UP, submobject_to_align = perimeter[1], buff = SMALL_BUFF) + perimeter[0][0].highlight(GREEN) + + self.play(FadeInFromDown(image)) + self.play( + Write(words), + GrowFromPoint(rect, rect.point_from_proportion(0.9)) + ) + self.wait() + self.play(*generate_anims1()) + self.play(*generate_anims2()) + self.play(terms.shift, UP) + self.play( + pi.scale, 2, + pi.shift, DOWN, + pi.highlight, GREEN + ) + self.wait() + self.play( + GrowFromCenter(brace), + circle.set_stroke, YELLOW, 1, + ReplacementTransform(circle.copy(), unwrapped_perimeter), + decimal.scale, 1.25, + decimal.next_to, perimeter[-1].get_right(), RIGHT, + ReplacementTransform(pi, perimeter[0][0]), + Write(perimeter), + ) + self.wait() + +class HeroAndVillain(Scene): + CONFIG = { + "camera_config" : { + "background_alpha" : 255, + } + } + def construct(self): + good_euler = get_image("Leonhard_Euler_by_Handmann") + bad_euler_pixelated = get_image("Leonard_Euler_pixelated") + bad_euler = get_image("Leonard_Euler_revealed") + pictures = good_euler, bad_euler_pixelated, bad_euler + for mob in pictures: + mob.scale_to_fit_height(5) + + good_euler.move_to(SPACE_WIDTH*LEFT/2) + bad_euler.move_to(SPACE_WIDTH*RIGHT/2) + bad_euler_pixelated.move_to(bad_euler) + + good_euler_label = TextMobject("Leonhard Euler") + good_euler_label.next_to(good_euler, DOWN) + tau_words = TextMobject("Used 6.2831...") + tau_words.next_to(good_euler, UP) + tau_words.highlight(GREEN) + + bad_euler_label = TextMobject("Also Euler...") + bad_euler_label.next_to(bad_euler, DOWN) + pi_words = TextMobject("Used 3.1415...") + pi_words.highlight(RED) + pi_words.next_to(bad_euler, UP) + + self.play( + FadeInFromDown(good_euler), + Write(good_euler_label) + ) + self.play(LaggedStart(FadeIn, tau_words)) + self.wait() + self.play(FadeInFromDown(bad_euler_pixelated)) + self.play(LaggedStart(FadeIn, pi_words)) + self.wait(2) + self.play( + FadeIn(bad_euler), + Write(bad_euler_label), + ) + self.remove(bad_euler_pixelated) + self.wait(2) + +class AnalysisQuote(Scene): + def construct(self): + analysis = get_image("Analysis_page_showing_pi") + analysis.scale_to_fit_height(2*SPACE_HEIGHT) + analysis.to_edge(LEFT, buff = 0) + + text = TextMobject( + "``\\dots set the radius of", + "the circle\\dots to be = 1, \\dots \\\\", + "through approximations the", + "semicircumference \\\\", "of said circle", + "has been found to be", "$= 3.14159\\dots$,\\\\", + "for which number, for the sake of", + "brevity, \\\\ I will write", "$\pi$\\dots''", + alignment = '' + ) + pi_formula = TexMobject( + "\\pi", "=", "{ \\text{semicircumference}", "\\over", "\\text{radius}}" + ) + text.scale_to_fit_width(SPACE_WIDTH) + text.next_to(analysis, RIGHT, LARGE_BUFF) + text.to_edge(UP) + + HIGHLIGHT_COLOR= GREEN + for mob in text, pi_formula: + mob.highlight_by_tex_to_color_map({ + "semicircumference" : HIGHLIGHT_COLOR, + "3.14" : HIGHLIGHT_COLOR, + "\pi" : HIGHLIGHT_COLOR + }) + + terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( + radius = 1, + positioning_func = lambda circ : circ.next_to(text, DOWN, LARGE_BUFF) + ) + terms[0].highlight(HIGHLIGHT_COLOR) + terms[-1].highlight(HIGHLIGHT_COLOR) + + pi_formula.next_to(terms, DOWN, buff = 0) + pi_formula.align_to(text, alignment_vect = RIGHT) + pi_formula[0].scale(2, about_edge = RIGHT) + + self.add(analysis) + self.play(*generate_anims2(), rate_func = lambda t : 0.5*smooth(t)) + self.play(LaggedStart(FadeIn,text), run_time = 5) + self.play(FadeIn(pi_formula)) + self.wait() + +class QuarterTurn(Scene): + def construct(self): + terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( + radius = 2, + positioning_func = lambda circ : circ.move_to(4*RIGHT) + ) + circle, radius, one, decimal = terms + circle_ghost = circle.copy().set_stroke(YELLOW_E, 1) + radius_ghost = radius.copy().set_stroke(WHITE, 1) + + self.add(circle_ghost, radius_ghost) + self.play( + *generate_anims2(), + rate_func = lambda t : 0.25*smooth(t) + ) + self.wait() + + pi_halves = TexMobject("\\pi", "/2") + pi_halves[0].highlight(RED) + tau_fourths = TexMobject("\\tau", "/4") + tau_fourths[0].highlight(GREEN) + for mob in pi_halves, tau_fourths: + mob.next_to(decimal, UP) + + self.play(FadeInFromDown(pi_halves)) + self.wait() + self.play( + pi_halves.shift, 0.75*UP, + FadeInFromDown(tau_fourths) + ) + self.wait() + +class UsingTheta(Scene): + def construct(self): + plane = NumberPlane(x_unit_size = 3, y_unit_size = 3) + # plane.main_lines.fade(0.5) + # plane.secondary_lines.fade(0.5) + plane.fade(0.5) + self.add(plane) + + circle = Circle(radius = 3) + circle.set_stroke(YELLOW, 2) + self.add(circle) + + radius = Line(ORIGIN, circle.get_right()) + arc = Arc(radius = 0.5, angle = TAU, num_anchors = 200) + arc.highlight(GREEN) + start_arc = arc.copy() + + theta = TexMobject("\\theta", "=") + theta[0].match_color(arc) + theta.add_background_rectangle() + update_theta = ContinualUpdateFromFunc( + theta, lambda m : m.shift( + rotate_vector(0.9*RIGHT, radius.get_angle()/2) \ + - m[1][0].get_center() + ) + ) + + angle = Integer(0, unit = "^\\circ") + update_angle = ContinualChangingDecimal( + angle, lambda a : radius.get_angle()*(360/TAU), + position_update_func = lambda a : a.next_to(theta, RIGHT, SMALL_BUFF) + ) + + self.add(update_theta, update_angle) + last_frac = 0 + for frac in 1./4, 1./12, 3./8, 1./6, 5./6: + self.play( + Rotate(radius, angle = (frac-last_frac)*TAU, about_point = ORIGIN), + UpdateFromAlphaFunc( + arc, + lambda m, a : m.pointwise_become_partial( + start_arc, 0, interpolate(last_frac, frac, a) + ) + ), + run_time = 3 + ) + last_frac = frac + self.wait() + +class ThingsNamedAfterEuler(Scene): + def construct(self): + group = VGroup(*map(TextMobject, [ + "Euler's formula (Complex analysis)", + "Euler's formula (Graph theory)", + "Euler's formula (Mechanical engineering)", + "Euler's formula (Analytical number theory)", + "Euler's formula (Continued fractions)", + "Euler-Lagrance equations (mechanics)", + "Euler number (topology)", + "Euler equations (fluid dynamics)", + "Euler angles (rigid-body mechanics)", + "Euler totient function (number theory)", + ])) + group.arrange_submobjects(DOWN, aligned_edge = LEFT) + group.scale_to_fit_height(2*SPACE_HEIGHT - 1) + + self.play(LaggedStart(FadeIn, group, lag_ratio = 0.2, run_time = 12)) + self.wait() + +class EulerThinking(Scene): + def construct(self): + image = get_image("Leonhard_Euler_by_Handmann") + image.scale_to_fit_height(4) + image.to_edge(DOWN) + image.shift(4*LEFT) + bubble = ThoughtBubble(height = 4.5) + bubble.next_to(image, RIGHT) + bubble.to_edge(UP, buff = SMALL_BUFF) + bubble.shift(0.8*LEFT) + bubble.set_fill(BLACK, 0.3) + + pi_vs_tau = TextMobject( + "Should $\\pi$ represent \\\\", "3.1415...", + "or", "6.2831...", "?" + ) + pi_vs_tau.highlight_by_tex_to_color_map({ + "3.14" : GREEN, + "6.28" : RED, + }) + pi_vs_tau.move_to(bubble.get_bubble_center()) + + question = TexMobject( + "\\frac{1}{1} + \\frac{1}{4} + \\frac{1}{9} + \\frac{1}{16} + \\cdots = ", + "\\;???" + ) + question[0].gradient_highlight(BLUE_C, BLUE_B) + question.move_to(bubble.get_bubble_center()) + question.shift(2*SMALL_BUFF*UP) + + cross = Cross(pi_vs_tau) + cross.set_stroke(RED, 8) + + self.add(image) + self.play( + ShowCreation(bubble), + Write(pi_vs_tau) + ) + self.play(ShowCreation(cross)) + self.wait() + self.play( + FadeOut(VGroup(pi_vs_tau, cross)), + FadeIn(question), + ) + self.wait() + + +class WhatIsRight(PiCreatureScene): + CONFIG = { + "default_pi_creature_kwargs" : { + "color" : BLUE_D, + "filp_at_start" : False, + }, + "default_pi_creature_start_corner" : DOWN, + } + def construct(self): + randy = self.pi_creature + randy.scale(0.75, about_edge = DOWN) + title = TextMobject("Which is ``right''?") + title.scale(1.5) + title.to_edge(UP) + self.add(title) + + sum_over_N_converges = TexMobject("1+2+3+\\cdots = -\\frac{1}{12}") + sum_over_N_diverges = TexMobject("1+2+3+\\cdots \\text{ Diverges}") + + literal_derivative = TexMobject( + "f'(x) = \\frac{f(x+dx) - f(x)}{dx}" + ) + limit_derivative = TexMobject( + "f'(x) = \\lim_{h \\to 0}\\frac{f(x+h) - f(x)}{h}" + ) + + divide_by_zero_definable = TexMobject( + "\\frac{1}{0}", "\\text{ has meaning}" + ) + divide_by_zero_undefinable = TexMobject( + "\\frac{1}{0}", "\\text{ is undefined}" + ) + + left_mobs = VGroup( + sum_over_N_converges, literal_derivative, + divide_by_zero_definable + ) + right_mobs = VGroup( + sum_over_N_diverges, limit_derivative, + divide_by_zero_undefinable + ) + + for mob in left_mobs: + mob.next_to(randy, UP) + mob.shift(3.5*LEFT) + for mob in right_mobs: + mob.next_to(randy, UP) + mob.shift(3.5*RIGHT) + + left_mobs.gradient_highlight(YELLOW_C, YELLOW_D) + right_mobs.gradient_highlight(GREEN_C, GREEN_D) + + self.play(randy.change, "pondering", title) + self.wait() + self.play(randy.change, "sassy", title) + self.wait() + + last_terms = VGroup() + for left, right in zip(left_mobs, right_mobs)[:-1]: + right.align_to(left) + self.play( + randy.change, "raise_right_hand", + FadeInFromDown(left), + last_terms.shift, 1.75*UP + ) + self.wait() + self.play( + randy.change, "raise_left_hand", + FadeInFromDown(right) + ) + self.play(randy.change, "plain", right) + last_terms.add(left, right, title) + self.play( + randy.change, "shruggie", + FadeInFromDown(left_mobs[-1]), + FadeInFromDown(right_mobs[-1]), + last_terms.shift, 1.75*UP, + ) + self.wait(4) + +class AskPuzzle(TeacherStudentsScene): + def construct(self): + series = TexMobject( + "\\frac{1}{1} + \\frac{1}{4} + \\frac{1}{9} + \\cdots + " +\ + "\\frac{1}{n^2} + \\cdots = ", "\\,???" + ) + series[0].gradient_highlight(BLUE_C, BLUE_B) + series[1].highlight(YELLOW) + + question = TextMobject( + "How should we think about\\\\", + "$\\displaystyle \\sum_{n=1}^\\infty \\frac{1}{n^s}$", + "for arbitrary $s$?" + ) + question[1].highlight(BLUE) + question[0].shift(SMALL_BUFF*UP) + + response = TextMobject( + "What do you mean by ", + "$\\displaystyle \\sum_{n = 1}^{\\infty}$", + "?" + ) + response[1].highlight(BLUE) + + self.teacher_says(series) + self.change_all_student_modes("pondering", look_at_arg = series) + self.wait(3) + self.play( + FadeOut(self.teacher.bubble), + self.teacher.change, "happy", + series.scale, 0.5, + series.to_corner, UP+LEFT, + PiCreatureSays( + self.students[0], question, + target_mode = "raise_left_hand" + ) + ) + self.change_student_modes( + None, "confused", "confused", + added_anims = [self.students[0].look_at, question] + ) + self.wait(2) + + self.students[0].bubble.content = VGroup() + self.play( + RemovePiCreatureBubble(self.students[0]), + question.scale, 0.5, + question.next_to, series, DOWN, MED_LARGE_BUFF, LEFT, + PiCreatureSays(self.teacher, response) + ) + self.change_all_student_modes("erm") + self.wait(3) + +class ChangeTopic(PiCreatureScene): + def construct(self): + pi, tau = self.pi_creatures + title = TextMobject("Happy $\\pi$ day!") + title.scale(1.5) + title.to_edge(UP, buff = MED_SMALL_BUFF) + self.add(title) + + question = TextMobject( + "Have you ever seen why \\\\", + "$\\displaystyle \\frac{2}{1} \\cdot \\frac{2}{3} \\cdots"+ \ + "\\frac{4}{3} \\cdot \\frac{4}{5} \\cdot" + \ + "\\frac{6}{5} \\cdot \\frac{6}{7} \\cdots = \\frac{\\pi}{2}$", "?" + ) + question[0].shift(MED_SMALL_BUFF*UP) + question[1].gradient_highlight(YELLOW, GREEN) + + self.play( + PiCreatureSays( + tau, "We should \\emph{really} celebrate \\\\ on 6/28!", + target_mode = "angry", + ), + pi.change, "guilty", + ) + self.wait(2) + self.play( + PiCreatureSays(pi, question), + RemovePiCreatureBubble( + tau, target_mode = "pondering", look_at_arg = question, + ) + ) + self.play(pi.change, "pondering", question) + self.wait(4) + + + def create_pi_creatures(self): + tau = TauCreature(color = GREEN_E) + pi = Randolph().flip() + VGroup(pi, tau).scale(0.75) + tau.to_edge(DOWN).shift(3*LEFT) + pi.to_edge(DOWN).shift(3*RIGHT) + return pi, tau + +class SpecialThanks(Scene): + def construct(self): + title = TextMobject("Special thanks to:") + title.to_edge(UP, LARGE_BUFF) + title.scale(1.5) + title.highlight(BLUE) + h_line = Line(LEFT, RIGHT).scale(4) + h_line.next_to(title, DOWN) + h_line.set_stroke(WHITE, 1) + + people = VGroup(*map(TextMobject, [ + "Ben Hambrecht", + "University Library Basel", + "Martin Mattmüller", + "Library of the Institut de France", + ])) + people.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF) + people.next_to(h_line, DOWN) + + self.add(title, h_line, people) + +class EndScene(PatreonEndScreen): + CONFIG = { + "camera_config" : { + "background_alpha" : 255, + } + } + def construct(self): + self.add_title() + title = self.title + basel_screen = ScreenRectangle(height = 2.35) + basel_screen.next_to(title, DOWN) + watch_basel = TextMobject( + "One such actual piece of math", "(quite pretty!)", + ) + watch_basel[0].highlight(YELLOW) + watch_basel.next_to(basel_screen, DOWN, submobject_to_align = watch_basel[0]) + + self.add(watch_basel) + # self.add(basel_screen) + + line = DashedLine(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT) + line.next_to(watch_basel, DOWN) + self.add(line) + + plushie_square = Square(side_length = 2) + plushie_square.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) + plushie_square.shift(UP) + + plushie_words = TextMobject( + "Plushie pi \\\\ creatures \\\\ now available.", + alignment = "" + ) + plushie_words.next_to(plushie_square, RIGHT) + + self.add(plushie_words) + # self.add(plushie_square) + + instagram_line = TextMobject( + "randy\\_the\\_pi" + ) + instagram_logo = ImageMobject("instagram_logo") + instagram_logo.match_height(instagram_line) + instagram_logo.next_to(instagram_line, LEFT, SMALL_BUFF) + instagram = Group(instagram_logo, instagram_line) + instagram.next_to(line, DOWN) + instagram.shift(SPACE_WIDTH*RIGHT/2) + self.add(instagram) + + + pictures = Group(*[ + ImageMobject("randy/randy_%s"%name) + for name in [ + "science", + "cooking", + "in_a_can", + "sandwhich", + "lab", + "fractal", + "flowers", + "group", + "labcoat", + "tennis", + ] + ]) + for i, picture in enumerate(pictures): + picture.scale_to_fit_height(2) + picture.next_to(instagram, DOWN, aligned_edge = RIGHT) + if i%3 != 0: + picture.next_to(last_picture, LEFT, buff = 0) + self.play(FadeIn(picture, run_time = 2)) + last_picture = picture + +class Thumbnail(Scene): + def construct(self): + pi, eq, num = formula = TexMobject( + "\\pi", "=", "6.283185\\dots" + ) + formula.scale(2) + pi.scale(1.5, about_edge = RIGHT) + formula.set_stroke(BLUE, 1) + formula.scale_to_fit_width(2*SPACE_WIDTH - 2) + # formula.shift(0.5*RIGHT) + self.add(formula) + + words = TextMobject("...according to Euler.") + words.scale(1.5) + words.next_to(formula, DOWN, MED_LARGE_BUFF) + self.add(words) + + + + + + + + + + + + + + + + + + + + + + + diff --git a/old_projects/uncertainty.py b/old_projects/uncertainty.py index 96451c52..91b6b502 100644 --- a/old_projects/uncertainty.py +++ b/old_projects/uncertainty.py @@ -34,7 +34,7 @@ from mobject.tex_mobject import * from topics.graph_scene import * from topics.light import * -from active_projects.fourier import * +from old_projects.fourier import * FREQUENCY_COLOR = RED diff --git a/topics/characters.py b/topics/characters.py index f21d9675..7bdb862d 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -29,6 +29,7 @@ MOUTH_INDEX = 5 class PiCreature(SVGMobject): CONFIG = { "color" : BLUE_E, + "file_name_prefix" : "PiCreatures", "stroke_width" : 0, "stroke_color" : BLACK, "fill_opacity" : 1.0, @@ -43,18 +44,19 @@ class PiCreature(SVGMobject): "left_arm_range" : [.34, .462], } def __init__(self, mode = "plain", **kwargs): + digest_config(self, kwargs) self.parts_named = False try: svg_file = os.path.join( PI_CREATURE_DIR, - "PiCreatures_%s.svg"%mode + "%s_%s.svg"%(self.file_name_prefix, mode) ) SVGMobject.__init__(self, file_name = svg_file, **kwargs) except: - warnings.warn("No PiCreature design with mode %s"%mode) + warnings.warn("No %s design with mode %s"%(self.file_name_prefix, mode)) svg_file = os.path.join( FILE_DIR, - "PiCreatures_plain.svg" + "PiCreatures_plain.svg", ) SVGMobject.__init__(self, file_name = svg_file, **kwargs) @@ -210,7 +212,7 @@ class PiCreature(SVGMobject): def get_all_pi_creature_modes(): result = [] - prefix = "PiCreatures_" + prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"] suffix = ".svg" for file in os.listdir(PI_CREATURE_DIR): if file.startswith(prefix) and file.endswith(suffix): @@ -253,6 +255,17 @@ class BabyPiCreature(PiCreature): pupil.scale_in_place(self.pupil_scale_factor) self.look(looking_direction) +class TauCreature(PiCreature): + CONFIG = { + "file_name_prefix" : "TauCreatures" + } + +class ThreeLeggedPiCreature(PiCreature): + CONFIG = { + "file_name_prefix" : "ThreeLeggedPiCreatures" + } + + class Blink(ApplyMethod): CONFIG = { "rate_func" : squish_rate_func(there_and_back) @@ -666,6 +679,9 @@ class TeacherStudentsScene(PiCreatureScene): student = self.get_students()[kwargs.get("student_index", 1)] return self.pi_creature_thinks(student, *content, **kwargs) + def change_all_student_modes(self, mode, **kwargs): + self.change_student_modes(*[mode]*len(self.students), **kwargs) + def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.pop("added_anims", []) self.play( diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 265e210c..8848a125 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -3,7 +3,8 @@ from helpers import * from scene.scene import Scene from animation import Animation -from animation.simple_animations import Write, DrawBorderThenFill, LaggedStart +from animation.simple_animations import Write, DrawBorderThenFill +from animation.compositions import LaggedStart from animation.transform import FadeIn, FadeOut, ApplyMethod from mobject.vectorized_mobject import VGroup from mobject.tex_mobject import TexMobject, TextMobject @@ -132,11 +133,11 @@ class PatreonEndScreen(PatreonThanks): self.scroll_through_patrons() def add_title(self): - title = TextMobject("Clicky Stuffs") + title = self.title = TextMobject("Clicky Stuffs") title.scale(1.5) title.to_edge(UP, buff = MED_SMALL_BUFF) - randy, morty = Randolph(), Mortimer() + randy, morty = self.pi_creatures = VGroup(Randolph(), Mortimer()) for pi, vect in (randy, LEFT), (morty, RIGHT): pi.scale_to_fit_height(title.get_height()) pi.change_mode("thinking") diff --git a/topics/geometry.py b/topics/geometry.py index d2e9363f..00475e8d 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) @@ -450,7 +454,6 @@ class DashedLine(Line): class Arrow(Line): CONFIG = { - "color" : YELLOW_C, "tip_length" : 0.25, "tip_width_to_length_ratio" : 1, "max_tip_length_to_length_ratio" : 0.35, @@ -707,15 +710,6 @@ class BackgroundRectangle(SurroundingRectangle): def get_fill_color(self): return Color(self.color) -class FullScreenFadeRectangle(Rectangle): - CONFIG = { - "height" : 2*SPACE_HEIGHT, - "width" : 2*SPACE_WIDTH, - "stroke_width" : 0, - "fill_color" : BLACK, - "fill_opacity" : 0.7, - } - class ScreenRectangle(Rectangle): CONFIG = { "width_to_height_ratio" : 16.0/9.0, @@ -725,6 +719,18 @@ class ScreenRectangle(Rectangle): self.width = self.width_to_height_ratio * self.height Rectangle.generate_points(self) +class FullScreenRectangle(ScreenRectangle): + CONFIG = { + "height" : 2*SPACE_HEIGHT, + } + +class FullScreenFadeRectangle(FullScreenRectangle): + CONFIG = { + "stroke_width" : 0, + "fill_color" : BLACK, + "fill_opacity" : 0.7, + } + class PictureInPictureFrame(Rectangle): CONFIG = { "height" : 3, diff --git a/topics/number_line.py b/topics/number_line.py index cd8da287..9aae1844 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -261,6 +261,7 @@ class NumberPlane(VMobject): "secondary_color" : BLUE_E, "axes_color" : WHITE, "secondary_stroke_width" : 1, + # TODO: Allow coordinate center of NumberPlane to not be at (0, 0) "x_radius": None, "y_radius": None, "x_unit_size" : 1,