From 6f43be49bfff91ac205a2cec06b9615ff924b04a Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 3 Apr 2018 19:38:54 +0200 Subject: [PATCH 01/25] experimental changes to old Pascal's triangle code --- active_projects/eop/pascal.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/active_projects/eop/pascal.py b/active_projects/eop/pascal.py index 087caf6b..0870bef1 100644 --- a/active_projects/eop/pascal.py +++ b/active_projects/eop/pascal.py @@ -1,4 +1,5 @@ from big_ol_pile_of_manim_imports import * +from once_useful_constructs.combinatorics import * nb_levels = 50 @@ -13,11 +14,18 @@ def rainbow_color(alpha): return rainbow[index] +class SampleScene(Scene): + + def construct(self): + + triangle = Polygon() + self.add(triangle) + self.wait() -class PascalScene(Scene): +class PascalNetScene(Scene): def construct(self): @@ -71,7 +79,7 @@ class PascalScene(Scene): -class RescaledPascalScene(Scene): +class RescaledPascalNetScene(Scene): def construct(self): From 9e1216397ae0462dfb631f4233173851527e70c3 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 3 Apr 2018 19:39:24 +0200 Subject: [PATCH 02/25] experimental changes to old Pascal's triangle code --- once_useful_constructs/combinatorics.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/once_useful_constructs/combinatorics.py b/once_useful_constructs/combinatorics.py index 42340dfd..b804ad6e 100644 --- a/once_useful_constructs/combinatorics.py +++ b/once_useful_constructs/combinatorics.py @@ -85,12 +85,18 @@ class CountingScene(Scene): self.number = num_mob return self -class PascalsTriangle(VMobject): +class CombinationTexMobject(TexMobject): + def __init(self,n,k,**kwargs): + TexMobject.__init__(str(choose(n,k)),**kwargs) + + +class GeneralizedPascalsTriangle(VMobject): CONFIG = { "nrows" : 7, "height" : FRAME_HEIGHT - 1, "width" : 1.5*FRAME_X_RADIUS, - "portion_to_fill" : 0.7 + "portion_to_fill" : 0.7, + "submob_class" : CombinationTexMobject, } def generate_points(self): self.cell_height = float(self.height) / self.nrows @@ -98,16 +104,16 @@ class PascalsTriangle(VMobject): self.bottom_left = (self.cell_width * self.nrows / 2.0)*LEFT + \ (self.cell_height * self.nrows / 2.0)*DOWN num_to_num_mob = {} - self.coords_to_mobs = {} + self.coords_to_mobs = {} self.coords = [ (n, k) for n in range(self.nrows) for k in range(n+1) ] for n, k in self.coords: - num = choose(n, k) + num = choose(n, k) center = self.coords_to_center(n, k) - num_mob = TexMobject(str(num)) + num_mob = self.submob_class(n,k) #TexMobject(str(num)) scale_factor = min( 1, self.portion_to_fill * self.cell_height / num_mob.get_height(), @@ -167,7 +173,10 @@ class PascalsTriangle(VMobject): self.add(mob) return self - +class PascalsTriangle(GeneralizedPascalsTriangle): + CONFIG = { + "submob_class" : CombinationTexMobject, + } From 646e1856e8b955b29287973ec9af14bcdc1b39e0 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 3 Apr 2018 20:16:31 +0200 Subject: [PATCH 03/25] revert to earlier version --- .../WindingNumber.py | 278 ++++-------------- .../WindingNumber_G.py | 86 +----- .../complex_transformation_scene.py | 106 +++++++ 3 files changed, 173 insertions(+), 297 deletions(-) rename {old_projects => active_projects}/WindingNumber.py (92%) rename {old_projects => active_projects}/WindingNumber_G.py (97%) diff --git a/old_projects/WindingNumber.py b/active_projects/WindingNumber.py similarity index 92% rename from old_projects/WindingNumber.py rename to active_projects/WindingNumber.py index c2d20fef..2601201a 100644 --- a/old_projects/WindingNumber.py +++ b/active_projects/WindingNumber.py @@ -1,7 +1,5 @@ from big_ol_pile_of_manim_imports import * -import time - import mpmath mpmath.mp.dps = 7 @@ -409,9 +407,7 @@ def resit_near(x, m): # TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating # latter as needed from former? -# -# "cheap" argument only used for diagnostic testing right now -def make_alpha_winder(func, start, end, num_checkpoints, cheap = False): +def make_alpha_winder(func, start, end, num_checkpoints): check_points = [None for i in range(num_checkpoints)] check_points[0] = func(start) step_size = fdiv(end - start, num_checkpoints) @@ -421,15 +417,9 @@ def make_alpha_winder(func, start, end, num_checkpoints, cheap = False): func(start + (i + 1) * step_size), check_points[i]) def return_func(alpha): - if cheap: - return alpha # A test to see if this func is responsible for slowdown - index = clamp(0, num_checkpoints - 1, int(alpha * num_checkpoints)) x = interpolate(start, end, alpha) - if cheap: - return check_points[index] # A more principled test that at least returns a reasonable answer - else: - return resit_near(func(x), check_points[index]) + return resit_near(func(x), check_points[index]) return return_func # The various inconsistent choices of what datatype to use where are a bit of a mess, @@ -566,7 +556,6 @@ def walker_animation_with_display( show_arrows = True, scale_arrows = False, num_decimal_points = 1, - include_background_rectangle = True, **kwargs ): @@ -582,15 +571,13 @@ def walker_animation_with_display( if number_update_func != None: display = DecimalNumber(0, num_decimal_points = num_decimal_points, - fill_color = WHITE if include_background_rectangle else BLACK, - include_background_rectangle = include_background_rectangle) - if include_background_rectangle: - display.background_rectangle.fill_opacity = 0.5 - display.background_rectangle.fill_color = GREY - display.background_rectangle.scale(1.2) + fill_color = WHITE, + include_background_rectangle = True) + display.background_rectangle.fill_opacity = 0.5 + display.background_rectangle.fill_color = GREY + display.background_rectangle.scale(1.2) displaycement = 0.5 * DOWN # How about that pun, eh? - # display.move_to(walker.get_center() + displaycement) - display.next_to(walker, DOWN+RIGHT, SMALL_BUFF) + display.move_to(walker.get_center() + displaycement) display_anim = ChangingDecimal(display, number_update_func, tracked_mobject = walker_anim.compound_walker.walker, @@ -608,7 +595,6 @@ def LinearWalker( number_update_func = None, show_arrows = True, scale_arrows = False, - include_background_rectangle = True, **kwargs ): walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha) @@ -619,7 +605,6 @@ def LinearWalker( number_update_func = number_update_func, show_arrows = show_arrows, scale_arrows = scale_arrows, - include_background_rectangle = include_background_rectangle, **kwargs) class ColorMappedByFuncScene(Scene): @@ -732,8 +717,7 @@ class PiWalker(ColorMappedByFuncScene): "show_num_plane" : False, "draw_lines" : True, "num_checkpoints" : 10, - "num_decimal_points" : 1, - "include_background_rectangle" : False, + "num_decimal_points" : 1 } def construct(self): @@ -789,7 +773,6 @@ class PiWalker(ColorMappedByFuncScene): run_time = self.step_run_time, walker_stroke_color = WALKER_LIGHT_COLOR if self.color_foreground_not_background else BLACK, num_decimal_points = self.num_decimal_points, - include_background_rectangle = self.include_background_rectangle, ) if self.display_odometer: @@ -971,18 +954,6 @@ class EquationSolver2dNode(object): bfs_nodes = self.hacky_bfs() return Succession(*map(lambda n : n.first_anim, bfs_nodes)) - def play_in_bfs(self, scene, border_anim): - bfs_nodes = self.hacky_bfs() - print "Number of nodes: ", len(bfs_nodes) - - if len(bfs_nodes) < 1: - print "Less than 1 node! Aborting!" - return - - scene.play(bfs_nodes[0].first_anim, border_anim) - for node in bfs_nodes[1:]: - scene.play(node.first_anim) - class EquationSolver2d(ColorMappedObjectsScene): CONFIG = { "camera_config" : {"use_z_coordinate_for_display_order": True}, @@ -990,7 +961,7 @@ class EquationSolver2d(ColorMappedObjectsScene): "initial_upper_x" : 5, "initial_lower_y" : -3, "initial_upper_y" : 3, - "num_iterations" : 0, + "num_iterations" : 1, "num_checkpoints" : 10, # Should really merge this into one enum-style variable @@ -998,9 +969,6 @@ class EquationSolver2d(ColorMappedObjectsScene): "display_in_bfs" : False, "use_fancy_lines" : True, - "line_color" : WHITE, # Only used for non-fancy lines - - # 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 @@ -1018,26 +986,18 @@ class EquationSolver2d(ColorMappedObjectsScene): # Used for UhOhScene; "manual_wind_override" : None, - "show_cursor" : True, + "show_cursor" : False, - "linger_parameter" : 0.5, - - "use_separate_plays" : False, - - "use_cheap_winding_numbers" : False, # To use this, make num_checkpoints large + "linger_parameter" : 0.2, } def construct(self): - if self.num_iterations == 0: - print "You forgot to set num_iterations (maybe you meant to subclass something other than EquationSolver2d directly?)" - return - ColorMappedObjectsScene.construct(self) num_plane = self.num_plane clockwise_val_func = lambda p : -point_to_rev(self.func(p)) - base_line = Line(UP, RIGHT, stroke_width = border_stroke_width, color = self.line_color) + base_line = Line(UP, RIGHT, stroke_width = border_stroke_width, color = WHITE) if self.use_fancy_lines: base_line.color_using_background_image(self.background_image_file) @@ -1077,7 +1037,7 @@ class EquationSolver2d(ColorMappedObjectsScene): 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, cheap = self.use_cheap_winding_numbers) + alpha_winder = make_alpha_winder(clockwise_val_func, start, end, self.num_checkpoints) a0 = alpha_winder(0) rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind colored_line = Line(num_plane.coords_to_point(*start) + IN, num_plane.coords_to_point(*end) + IN) @@ -1132,7 +1092,7 @@ class EquationSolver2d(ColorMappedObjectsScene): width = rect.get_width() height = rect.get_height() - cursor.move_to(num_plane.coords_to_point(center_x, center_y) + 10 * IN) + 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 @@ -1144,11 +1104,7 @@ class EquationSolver2d(ColorMappedObjectsScene): anim = AnimationGroup(anim, cursor_anim) - override_wind = head(manual_wind_override) - if override_wind != None: - total_wind = override_wind - else: - total_wind = round(wind_so_far) + total_wind = head(manual_wind_override) or round(wind_so_far) if total_wind == 0: coords = [ @@ -1241,17 +1197,15 @@ class EquationSolver2d(ColorMappedObjectsScene): rate_func = rect_rate ) - print "About to do the big Play; for reference, the current time is ", time.strftime("%H:%M:%S") - - if self.use_separate_plays: - node.play_in_bfs(self, border_anim) - else: - self.play(anim, border_anim) - - print "All done; for reference, the current time is ", time.strftime("%H:%M:%S") + self.play(anim, border_anim) 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? # @@ -1264,7 +1218,7 @@ class LinePulser(ContinualAnimation): self.pulse_time = pulse_time self.bullets = [bullet_template.copy() for i in range(num_bullets)] self.output_func = output_func - ContinualAnimation.__init__(self, VGroup(*self.bullets), **kwargs) + ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) def update_mobject(self, dt): alpha = self.external_time % self.pulse_time @@ -1999,9 +1953,7 @@ class LoopSplitScene(ColorMappedObjectsScene): def flash_circles(circles): self.play(LaggedStart(FadeIn, VGroup(circles))) - self.wait() - self.play(FadeOut(VGroup(circles))) - self.wait() + self.play(*map(FadeOut, circles)) self.add(left_square_lines_vmobject, right_square_lines_vmobject) self.remove(*midline_lines) @@ -2015,17 +1967,15 @@ class LoopSplitScene(ColorMappedObjectsScene): 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.wait() 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() 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.wait() + self.play(*bullet_list_fade(0, 1, left_square_bullets + right_square_bullets)) outside_circlers = [ indicate_circle(left_line), @@ -2037,15 +1987,20 @@ class LoopSplitScene(ColorMappedObjectsScene): inner_circle = indicate_circle(midline_lines[0]) self.play(FadeIn(inner_circle)) - self.wait() self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - self.wait() - - # Repeat for effect, goes well with narration - self.play(FadeIn(inner_circle), line_fade(0, 1, midline_lines_vmobject), *bullet_list_fade(0, 1, midline_bullets)) - self.wait() - 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): + + def setup(self): + left_camera = Camera(**self.camera_config) + right_camera = MappingCamera( + mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3), + **self.camera_config) + split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) + self.camera = split_screen_camera # TODO: Perhaps do extra illustration of zooming out and winding around a large circle, # to illustrate relation between degree and large-scale winding number @@ -2053,121 +2008,16 @@ class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)), "num_iterations" : 2, + "use_fancy_lines" : True, } class SolveX5MinusXMinus1(EquationSolver2d): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 10, - "show_cursor" : True, - "display_in_bfs" : True, - } - -class PureColorMapOfX5Thing(PureColorMap): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - } - -class X5ThingWithRightHalfGreyed(SolveX5MinusXMinus1): - CONFIG = { - "num_iterations" : 3, - "manual_wind_override" : (1, None, (1, (0, None, None), (0, None, None))) - } - -class SolveX5MinusXMinus1_5Iterations(EquationSolver2d): CONFIG = { "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), "num_iterations" : 5, - "show_cursor" : True, - "display_in_bfs" : True, - "manual_wind_override" : (None, None, (None, (0, None, None), (0, None, None))) + "use_fancy_lines" : True, } -class X5_Monster_Red_Lines(SolveX5MinusXMinus1_5Iterations): - CONFIG = { - "use_separate_plays" : True, - "use_fancy_lines" : False, - "line_color" : RED, - } - -class X5_Monster_Green_Lines(X5_Monster_Red_Lines): - CONFIG = { - "line_color" : GREEN, - } - -class X5_Monster_Red_Lines_Long(X5_Monster_Red_Lines): - CONFIG = { - "num_iterations" : 6 - } - -class X5_Monster_Green_Lines_Long(X5_Monster_Green_Lines): - CONFIG = { - "num_iterations" : 6 - } - -class X5_Monster_Red_Lines_Little_More(X5_Monster_Red_Lines_Long): - CONFIG = { - "num_iterations" : 7 - } - -class X5_Monster_Green_Lines_Little_More(X5_Monster_Green_Lines_Long): - CONFIG = { - "num_iterations" : 7 - } - -class X5_Monster_Red_Lines_No_Numbers(X5_Monster_Red_Lines): - CONFIG = { - "num_iterations" : 3, - "show_winding_numbers" : False, - } - -class X5_Monster_Green_Lines_No_Numbers(X5_Monster_Green_Lines): - CONFIG = { - "num_iterations" : 3, - "show_winding_numbers" : False, - } - -class SolveX5MinusXMinus1_3Iterations(EquationSolver2d): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 3, - "show_cursor" : True, - "display_in_bfs" : True, - } - -class Diagnostic(SolveX5MinusXMinus1_3Iterations): - CONFIG = { - # I think the combination of these two makes things slow - "use_separate_plays" : not False, # This one isn't important to set any particular way, so let's leave it like this - "use_fancy_lines" : True, - - # This causes a small slowdown (before rendering, in particular), but not the big one, I think - "show_winding_numbers" : True, - - # This doesn't significantly matter for rendering time, I think - "camera_config" : {"use_z_coordinate_for_display_order" : True} - } - -# All above flags False (meaning not db = False): just under 30 it/s -# not db = True: 30 -# use_fancy_lines = True: 30 at first (if scene.play(bfs_nodes[0].first_anim, border_anim is off), but then drops to 3 (or drops right away if that simultaneous play is on) -# use_z_coordinate = True: 30 -# show_winding_numbers = True: 10 -# winding AND use_fancy_lines: 10 -# not db AND fancy_lines AND z_coords = true, winding = false: 3. Not 30, but 3. Slow. -# db AND use_fancy: 3. Slow. -# fancy AND z_coords: 30. Fast. [Hm, this may have been a mistake; fancy and z_coords is now slow?] -# fancy, winding, AND z_coords, but not (not db): 10 -# not db, winding, AND z_coords, but not fancy: 10 - -# class DiagnosticB(Diagnostic): -# CONFIG = { -# "num_iterations" : 3, -# #"num_checkpoints" : 100, -# #"show_winding_numbers" : False, -# #"use_cheap_winding_numbers" : True, -# } - class SolveX5MinusXMinus1Parallel(SolveX5MinusXMinus1): CONFIG = { "display_in_parallel" : True @@ -2186,37 +2036,6 @@ class PreviewClip(EquationSolver2d): "use_fancy_lines" : True, } -class ParallelClip(EquationSolver2d): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) - ), - "num_iterations" : 5, - "display_in_parallel" : True, - } - -class EquationSolver2dMatchBreakdown(EquationSolver2d): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0.3, 2), (2, -0.2, 1) # Not an exact match, because our breakdown function has a zero along midlines... - ), - "num_iterations" : 5, - "display_in_parallel" : True, - "show_cursor" : True - } - -class EquationSolver2dMatchBreakdown_parallel(EquationSolver2dMatchBreakdown): - CONFIG = { - "display_in_parallel" : True, - "display_in_bfs" : False, - } - -class EquationSolver2dMatchBreakdown_bfs(EquationSolver2dMatchBreakdown): - CONFIG = { - "display_in_parallel" : False, - "display_in_bfs" : True, - } - class QuickPreview(PreviewClip): CONFIG = { "num_iterations" : 3, @@ -2234,11 +2053,6 @@ class LongEquationSolver(EquationSolver2d): "show_cursor" : True, } -class QuickPreviewUnfancy(LongEquationSolver): - CONFIG = { - # "use_fancy_lines" : False, - } - # TODO: Borsuk-Ulam visuals # Note: May want to do an ordinary square scene, then MappingCamera it into a circle # class BorsukUlamScene(PiWalker): @@ -2665,6 +2479,18 @@ class ShowBack(PiWalkerRect): "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)) } +class Diagnostic(Scene): + def construct(self): + testList = map(lambda n : (n, rev_to_rgba(n)), [0, 0.1, 0.2, 0.3, 0.4]) + print "Here you go:", testList + self.wait() + +class DiagnosticColorMap(PureColorMap): + CONFIG = { + "func" : lambda (x, y) : (25 * x, 25 * y), + "show_num_plane" : False, + } + class PiWalkerOdometerTest(PiWalkerExamplePlaneFunc): CONFIG = { "display_odometer" : True diff --git a/old_projects/WindingNumber_G.py b/active_projects/WindingNumber_G.py similarity index 97% rename from old_projects/WindingNumber_G.py rename to active_projects/WindingNumber_G.py index 501fc289..fa6a8354 100644 --- a/old_projects/WindingNumber_G.py +++ b/active_projects/WindingNumber_G.py @@ -1896,20 +1896,6 @@ class TinyLoopAroundRed(TinyLoop): "target_coords" : (-1, 1), } -class ConfusedPiCreature(PiCreatureScene): - def construct(self): - morty = self.pi_creature - morty.set_color(YELLOW_E) - morty.flip() - morty.center() - - self.play(morty.change, "awe", DOWN+3*RIGHT) - self.wait(2) - self.play(morty.change, "confused") - self.wait(2) - self.play(morty.change, "pondering") - self.wait(2) - class FailureOfComposition(ColorMappedObjectsScene): CONFIG = { "func" : lambda p : ( @@ -2162,11 +2148,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): CONFIG = { "func" : plane_func_by_wind_spec( (-2, 0, 2), (2, 0, 1) - ), - "dot_fill_opacity" : 1, - "dot_stroke_width" : 1, - "include_walkers" : True, - "include_question_mark" : True, + ) } def construct(self): ColorMappedObjectsScene.construct(self) @@ -2196,10 +2178,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): #Setup region labels - sum_tex = "x+y" - if self.include_question_mark: - sum_tex += "\\, ?" - for square, tex in (left_square, "x"), (right_square, "y"), (joint_rect, sum_tex): + 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) @@ -2253,7 +2232,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): path_arc = TAU/6 ), FadeIn(joint_rect.label[1][1]), - FadeIn(joint_rect.label[1][3:]), + FadeIn(joint_rect.label[1][3]), FadeOut(right_square.label[0]), Transform( right_square.label[1], joint_rect.label[1][2], @@ -2302,12 +2281,9 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): #Setup dot, arrow and label dot = self.dot = Dot(radius = 0.1) - dot.set_stroke(WHITE, self.dot_stroke_width) + dot.set_stroke(WHITE, 1) update_dot_color = ContinualUpdateFromFunc( - dot, lambda d : d.set_fill( - get_output_color(), - self.dot_fill_opacity - ) + dot, lambda d : d.set_fill(get_output_color()) ) label = DecimalNumber(0, num_decimal_points = 1) @@ -2318,7 +2294,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): arrow_length = 0.75 arrow = Vector(arrow_length*RIGHT) - arrow.set_stroke(WHITE, self.dot_stroke_width) + 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()) @@ -2327,8 +2303,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): return arrow update_arrow = ContinualUpdateFromFunc(arrow, arrow_update_func) - if self.include_walkers: - self.add(update_arrow, update_dot_color, label_upadte) + self.add(update_arrow, update_dot_color, label_upadte) return dot def position_dot(self, point): @@ -2336,17 +2311,6 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): self.start_rev = self.get_output_rev() self.curr_winding = 0 -class TransitionFromPathsToBoundariesArrowless(TransitionFromPathsToBoundaries): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0, 2), (2, 0, 1) - ), - "dot_fill_opacity" : 0, - "dot_stroke_width" : 0, - "include_walkers" : False, - "include_question_mark" : False, - } - class BreakDownLoopWithNonzeroWinding(TransitionFromPathsToBoundaries): def construct(self): zero_point = 2*LEFT @@ -2625,7 +2589,9 @@ class PolynomialTerms(MonomialTerm): class SearchSpacePerimeterVsArea(EquationSolver2d): CONFIG = { - "func" : example_plane_func, + "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, @@ -2688,6 +2654,7 @@ class SearchSpacePerimeterVsArea(EquationSolver2d): self.play(FadeOut(full_rect)) self.wait() +<<<<<<< HEAD:old_projects/WindingNumber_G.py class ShowPolynomialFinalState(SolveX5MinusXMinus1): CONFIG = { "num_iterations" : 15, @@ -2905,6 +2872,8 @@ class AllOfTheVideos(Scene): self.play(LaggedStart(FadeIn, images, run_time = 4)) self.wait() +======= +>>>>>>> parent of feeb9ec... Merge branch 'master' into eop:active_projects/WindingNumber_G.py class EndingCredits(Scene): def construct(self): text = TextMobject( @@ -3218,33 +3187,8 @@ class EndScreen(PatreonEndScreen, PiCreatureScene): self.play(morty.change, mode) self.wait(2) -class Thumbnail(SearchSpacePerimeterVsArea): - CONFIG = { - "num_iterations" : 18, - "func" : plane_func_by_wind_spec( - (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) - ), - } - def construct(self): - self.force_skipping() - EquationSolver2d.construct(self) - self.revert_to_original_skipping_status() - - mobjects = VGroup(*self.get_mobjects()) - lines = VGroup() - rects = VGroup() - get_length = lambda mob : max(mob.get_width(), mob.get_height()) - for mob in mobjects: - if mob.background_image_file is not None: - mob.set_stroke(width = 4*np.sqrt(get_length(mob))) - lines.add(mob) - elif isinstance(mob, Polygon): - rects.add(mob) - else: - self.remove(mob) - - self.clear() - self.add(lines) + + diff --git a/once_useful_constructs/complex_transformation_scene.py b/once_useful_constructs/complex_transformation_scene.py index efbbe21b..7d008b3a 100644 --- a/once_useful_constructs/complex_transformation_scene.py +++ b/once_useful_constructs/complex_transformation_scene.py @@ -156,6 +156,112 @@ class ComplexTransformationScene(Scene): *added_anims ) +<<<<<<< HEAD:once_useful_constructs/complex_transformation_scene.py +======= +##### Unsure about what comes under here... + +def complex_string(complex_num): + return filter(lambda c : c not in "()", str(complex_num)) + +class ComplexPlane(NumberPlane): + CONFIG = { + "color" : BLUE, + "unit_size" : 1, + "line_frequency" : 1, + "faded_line_frequency" : 0.5, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + kwargs.update({ + "x_unit_size" : self.unit_size, + "y_unit_size" : self.unit_size, + "x_line_frequency" : self.line_frequency, + "x_faded_line_frequency" : self.faded_line_frequency, + "y_line_frequency" : self.line_frequency, + "y_faded_line_frequency" : self.faded_line_frequency, + }) + NumberPlane.__init__(self, **kwargs) + + def number_to_point(self, number): + number = complex(number) + return self.coords_to_point(number.real, number.imag) + + def point_to_number(self, point): + x, y = self.point_to_coords(point) + return complex(x, y) + + def get_coordinate_labels(self, *numbers): + # TODO: Should merge this with the code from NumberPlane.get_coordinate_labels + + result = VGroup() + nudge = 0.1*(DOWN+RIGHT) + if len(numbers) == 0: + numbers = range(-int(self.x_radius), int(self.x_radius)+1) + numbers += [ + complex(0, y) + for y in range(-int(self.y_radius), int(self.y_radius)+1) + ] + for number in numbers: + if number == complex(0, 0): + continue + point = self.number_to_point(number) + num_str = str(number).replace("j", "i") + if num_str.startswith("0"): + num_str = "0" + elif num_str in ["1i", "-1i"]: + num_str = num_str.replace("1", "") + num_mob = TexMobject(num_str) + num_mob.add_background_rectangle() + num_mob.scale_to_fit_height(self.written_coordinate_height) + num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF) + result.add(num_mob) + return result + + def add_coordinates(self, *numbers): + self.add(*self.get_coordinate_labels(*numbers)) + return self + + def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6): + # This code no longer works because it has this reference to self.fade_factor + # which is never initialized. Shall we delete this little-used function entirely? + self.fade(self.fade_factor) + config = { + "color" : self.color, + "density" : self.density, + } + for radius in np.arange(circle_freq, SPACE_WIDTH, circle_freq): + self.add(Circle(radius = radius, **config)) + for angle in np.arange(0, 2*np.pi, angle_freq): + end_point = np.cos(angle)*RIGHT + np.sin(angle)*UP + end_point *= SPACE_WIDTH + self.add(Line(ORIGIN, end_point, **config)) + return self + +class ComplexFunction(ApplyPointwiseFunction): + def __init__(self, function, mobject = ComplexPlane, **kwargs): + if "path_func" not in kwargs: + self.path_func = path_along_arc( + np.log(function(complex(1))).imag + ) + ApplyPointwiseFunction.__init__( + self, + lambda (x, y, z) : complex_to_R3(function(complex(x, y))), + instantiate(mobject), + **kwargs + ) + +class ComplexHomotopy(Homotopy): + def __init__(self, complex_homotopy, mobject = ComplexPlane, **kwargs): + """ + Complex Hootopy a function Cx[0, 1] to C + """ + def homotopy(event): + x, y, z, t = event + c = complex_homotopy((complex(x, y), t)) + return (c.real, c.imag, z) + Homotopy.__init__(self, homotopy, mobject, *args, **kwargs) + +>>>>>>> parent of feeb9ec... Merge branch 'master' into eop:topics/complex_numbers.py From d32682ef959299c32b32224fa3ba7c06c01aa36e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 3 Apr 2018 20:16:57 +0200 Subject: [PATCH 04/25] Revert "revert to earlier version" This reverts commit 646e1856e8b955b29287973ec9af14bcdc1b39e0. --- .../WindingNumber.py | 280 ++++++++++++++---- .../WindingNumber_G.py | 86 +++++- .../complex_transformation_scene.py | 106 ------- 3 files changed, 298 insertions(+), 174 deletions(-) rename {active_projects => old_projects}/WindingNumber.py (92%) rename {active_projects => old_projects}/WindingNumber_G.py (97%) diff --git a/active_projects/WindingNumber.py b/old_projects/WindingNumber.py similarity index 92% rename from active_projects/WindingNumber.py rename to old_projects/WindingNumber.py index 2601201a..c2d20fef 100644 --- a/active_projects/WindingNumber.py +++ b/old_projects/WindingNumber.py @@ -1,5 +1,7 @@ from big_ol_pile_of_manim_imports import * +import time + import mpmath mpmath.mp.dps = 7 @@ -407,7 +409,9 @@ def resit_near(x, m): # TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating # latter as needed from former? -def make_alpha_winder(func, start, end, num_checkpoints): +# +# "cheap" argument only used for diagnostic testing right now +def make_alpha_winder(func, start, end, num_checkpoints, cheap = False): check_points = [None for i in range(num_checkpoints)] check_points[0] = func(start) step_size = fdiv(end - start, num_checkpoints) @@ -417,9 +421,15 @@ def make_alpha_winder(func, start, end, num_checkpoints): func(start + (i + 1) * step_size), check_points[i]) def return_func(alpha): + if cheap: + return alpha # A test to see if this func is responsible for slowdown + index = clamp(0, num_checkpoints - 1, int(alpha * num_checkpoints)) x = interpolate(start, end, alpha) - return resit_near(func(x), check_points[index]) + if cheap: + return check_points[index] # A more principled test that at least returns a reasonable answer + else: + return resit_near(func(x), check_points[index]) return return_func # The various inconsistent choices of what datatype to use where are a bit of a mess, @@ -556,6 +566,7 @@ def walker_animation_with_display( show_arrows = True, scale_arrows = False, num_decimal_points = 1, + include_background_rectangle = True, **kwargs ): @@ -571,13 +582,15 @@ def walker_animation_with_display( if number_update_func != None: display = DecimalNumber(0, num_decimal_points = num_decimal_points, - fill_color = WHITE, - include_background_rectangle = True) - display.background_rectangle.fill_opacity = 0.5 - display.background_rectangle.fill_color = GREY - display.background_rectangle.scale(1.2) + fill_color = WHITE if include_background_rectangle else BLACK, + include_background_rectangle = include_background_rectangle) + if include_background_rectangle: + display.background_rectangle.fill_opacity = 0.5 + display.background_rectangle.fill_color = GREY + display.background_rectangle.scale(1.2) displaycement = 0.5 * DOWN # How about that pun, eh? - display.move_to(walker.get_center() + displaycement) + # display.move_to(walker.get_center() + displaycement) + display.next_to(walker, DOWN+RIGHT, SMALL_BUFF) display_anim = ChangingDecimal(display, number_update_func, tracked_mobject = walker_anim.compound_walker.walker, @@ -595,6 +608,7 @@ def LinearWalker( number_update_func = None, show_arrows = True, scale_arrows = False, + include_background_rectangle = True, **kwargs ): walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha) @@ -605,6 +619,7 @@ def LinearWalker( number_update_func = number_update_func, show_arrows = show_arrows, scale_arrows = scale_arrows, + include_background_rectangle = include_background_rectangle, **kwargs) class ColorMappedByFuncScene(Scene): @@ -717,7 +732,8 @@ class PiWalker(ColorMappedByFuncScene): "show_num_plane" : False, "draw_lines" : True, "num_checkpoints" : 10, - "num_decimal_points" : 1 + "num_decimal_points" : 1, + "include_background_rectangle" : False, } def construct(self): @@ -773,6 +789,7 @@ class PiWalker(ColorMappedByFuncScene): run_time = self.step_run_time, walker_stroke_color = WALKER_LIGHT_COLOR if self.color_foreground_not_background else BLACK, num_decimal_points = self.num_decimal_points, + include_background_rectangle = self.include_background_rectangle, ) if self.display_odometer: @@ -954,6 +971,18 @@ class EquationSolver2dNode(object): bfs_nodes = self.hacky_bfs() return Succession(*map(lambda n : n.first_anim, bfs_nodes)) + def play_in_bfs(self, scene, border_anim): + bfs_nodes = self.hacky_bfs() + print "Number of nodes: ", len(bfs_nodes) + + if len(bfs_nodes) < 1: + print "Less than 1 node! Aborting!" + return + + scene.play(bfs_nodes[0].first_anim, border_anim) + for node in bfs_nodes[1:]: + scene.play(node.first_anim) + class EquationSolver2d(ColorMappedObjectsScene): CONFIG = { "camera_config" : {"use_z_coordinate_for_display_order": True}, @@ -961,7 +990,7 @@ class EquationSolver2d(ColorMappedObjectsScene): "initial_upper_x" : 5, "initial_lower_y" : -3, "initial_upper_y" : 3, - "num_iterations" : 1, + "num_iterations" : 0, "num_checkpoints" : 10, # Should really merge this into one enum-style variable @@ -969,6 +998,9 @@ class EquationSolver2d(ColorMappedObjectsScene): "display_in_bfs" : False, "use_fancy_lines" : True, + "line_color" : WHITE, # Only used for non-fancy lines + + # 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 @@ -986,18 +1018,26 @@ class EquationSolver2d(ColorMappedObjectsScene): # Used for UhOhScene; "manual_wind_override" : None, - "show_cursor" : False, + "show_cursor" : True, - "linger_parameter" : 0.2, + "linger_parameter" : 0.5, + + "use_separate_plays" : False, + + "use_cheap_winding_numbers" : False, # To use this, make num_checkpoints large } def construct(self): + if self.num_iterations == 0: + print "You forgot to set num_iterations (maybe you meant to subclass something other than EquationSolver2d directly?)" + return + ColorMappedObjectsScene.construct(self) num_plane = self.num_plane clockwise_val_func = lambda p : -point_to_rev(self.func(p)) - base_line = Line(UP, RIGHT, stroke_width = border_stroke_width, color = WHITE) + base_line = Line(UP, RIGHT, stroke_width = border_stroke_width, color = self.line_color) if self.use_fancy_lines: base_line.color_using_background_image(self.background_image_file) @@ -1037,7 +1077,7 @@ class EquationSolver2d(ColorMappedObjectsScene): 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) + alpha_winder = make_alpha_winder(clockwise_val_func, start, end, self.num_checkpoints, cheap = self.use_cheap_winding_numbers) a0 = alpha_winder(0) rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind colored_line = Line(num_plane.coords_to_point(*start) + IN, num_plane.coords_to_point(*end) + IN) @@ -1092,7 +1132,7 @@ class EquationSolver2d(ColorMappedObjectsScene): width = rect.get_width() height = rect.get_height() - cursor.move_to(num_plane.coords_to_point(center_x, center_y)) + cursor.move_to(num_plane.coords_to_point(center_x, center_y) + 10 * IN) cursor.scale(min(width, height)) # Do a quick FadeIn, wait, and quick FadeOut on the cursor, matching rectangle-drawing time @@ -1104,7 +1144,11 @@ class EquationSolver2d(ColorMappedObjectsScene): anim = AnimationGroup(anim, cursor_anim) - total_wind = head(manual_wind_override) or round(wind_so_far) + override_wind = head(manual_wind_override) + if override_wind != None: + total_wind = override_wind + else: + total_wind = round(wind_so_far) if total_wind == 0: coords = [ @@ -1197,15 +1241,17 @@ class EquationSolver2d(ColorMappedObjectsScene): rate_func = rect_rate ) - self.play(anim, border_anim) + print "About to do the big Play; for reference, the current time is ", time.strftime("%H:%M:%S") + + if self.use_separate_plays: + node.play_in_bfs(self, border_anim) + else: + self.play(anim, border_anim) + + print "All done; for reference, the current time is ", time.strftime("%H:%M:%S") 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? # @@ -1218,7 +1264,7 @@ class LinePulser(ContinualAnimation): self.pulse_time = pulse_time self.bullets = [bullet_template.copy() for i in range(num_bullets)] self.output_func = output_func - ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs) + ContinualAnimation.__init__(self, VGroup(*self.bullets), **kwargs) def update_mobject(self, dt): alpha = self.external_time % self.pulse_time @@ -1953,7 +1999,9 @@ class LoopSplitScene(ColorMappedObjectsScene): def flash_circles(circles): self.play(LaggedStart(FadeIn, VGroup(circles))) - self.play(*map(FadeOut, circles)) + self.wait() + self.play(FadeOut(VGroup(circles))) + self.wait() self.add(left_square_lines_vmobject, right_square_lines_vmobject) self.remove(*midline_lines) @@ -1967,15 +2015,17 @@ class LoopSplitScene(ColorMappedObjectsScene): 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.wait() 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() 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.wait() outside_circlers = [ indicate_circle(left_line), @@ -1987,20 +2037,15 @@ class LoopSplitScene(ColorMappedObjectsScene): inner_circle = indicate_circle(midline_lines[0]) self.play(FadeIn(inner_circle)) + self.wait() self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - - 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): - - def setup(self): - left_camera = Camera(**self.camera_config) - right_camera = MappingCamera( - mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3), - **self.camera_config) - split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) - self.camera = split_screen_camera + self.wait() + + # Repeat for effect, goes well with narration + self.play(FadeIn(inner_circle), line_fade(0, 1, midline_lines_vmobject), *bullet_list_fade(0, 1, midline_bullets)) + self.wait() + self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) + self.wait() # TODO: Perhaps do extra illustration of zooming out and winding around a large circle, # to illustrate relation between degree and large-scale winding number @@ -2008,16 +2053,121 @@ class FundThmAlg(EquationSolver2d): CONFIG = { "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)), "num_iterations" : 2, - "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, + "num_iterations" : 10, + "show_cursor" : True, + "display_in_bfs" : True, } +class PureColorMapOfX5Thing(PureColorMap): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), + } + +class X5ThingWithRightHalfGreyed(SolveX5MinusXMinus1): + CONFIG = { + "num_iterations" : 3, + "manual_wind_override" : (1, None, (1, (0, None, None), (0, None, None))) + } + +class SolveX5MinusXMinus1_5Iterations(EquationSolver2d): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), + "num_iterations" : 5, + "show_cursor" : True, + "display_in_bfs" : True, + "manual_wind_override" : (None, None, (None, (0, None, None), (0, None, None))) + } + +class X5_Monster_Red_Lines(SolveX5MinusXMinus1_5Iterations): + CONFIG = { + "use_separate_plays" : True, + "use_fancy_lines" : False, + "line_color" : RED, + } + +class X5_Monster_Green_Lines(X5_Monster_Red_Lines): + CONFIG = { + "line_color" : GREEN, + } + +class X5_Monster_Red_Lines_Long(X5_Monster_Red_Lines): + CONFIG = { + "num_iterations" : 6 + } + +class X5_Monster_Green_Lines_Long(X5_Monster_Green_Lines): + CONFIG = { + "num_iterations" : 6 + } + +class X5_Monster_Red_Lines_Little_More(X5_Monster_Red_Lines_Long): + CONFIG = { + "num_iterations" : 7 + } + +class X5_Monster_Green_Lines_Little_More(X5_Monster_Green_Lines_Long): + CONFIG = { + "num_iterations" : 7 + } + +class X5_Monster_Red_Lines_No_Numbers(X5_Monster_Red_Lines): + CONFIG = { + "num_iterations" : 3, + "show_winding_numbers" : False, + } + +class X5_Monster_Green_Lines_No_Numbers(X5_Monster_Green_Lines): + CONFIG = { + "num_iterations" : 3, + "show_winding_numbers" : False, + } + +class SolveX5MinusXMinus1_3Iterations(EquationSolver2d): + CONFIG = { + "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), + "num_iterations" : 3, + "show_cursor" : True, + "display_in_bfs" : True, + } + +class Diagnostic(SolveX5MinusXMinus1_3Iterations): + CONFIG = { + # I think the combination of these two makes things slow + "use_separate_plays" : not False, # This one isn't important to set any particular way, so let's leave it like this + "use_fancy_lines" : True, + + # This causes a small slowdown (before rendering, in particular), but not the big one, I think + "show_winding_numbers" : True, + + # This doesn't significantly matter for rendering time, I think + "camera_config" : {"use_z_coordinate_for_display_order" : True} + } + +# All above flags False (meaning not db = False): just under 30 it/s +# not db = True: 30 +# use_fancy_lines = True: 30 at first (if scene.play(bfs_nodes[0].first_anim, border_anim is off), but then drops to 3 (or drops right away if that simultaneous play is on) +# use_z_coordinate = True: 30 +# show_winding_numbers = True: 10 +# winding AND use_fancy_lines: 10 +# not db AND fancy_lines AND z_coords = true, winding = false: 3. Not 30, but 3. Slow. +# db AND use_fancy: 3. Slow. +# fancy AND z_coords: 30. Fast. [Hm, this may have been a mistake; fancy and z_coords is now slow?] +# fancy, winding, AND z_coords, but not (not db): 10 +# not db, winding, AND z_coords, but not fancy: 10 + +# class DiagnosticB(Diagnostic): +# CONFIG = { +# "num_iterations" : 3, +# #"num_checkpoints" : 100, +# #"show_winding_numbers" : False, +# #"use_cheap_winding_numbers" : True, +# } + class SolveX5MinusXMinus1Parallel(SolveX5MinusXMinus1): CONFIG = { "display_in_parallel" : True @@ -2036,6 +2186,37 @@ class PreviewClip(EquationSolver2d): "use_fancy_lines" : True, } +class ParallelClip(EquationSolver2d): + CONFIG = { + "func" : plane_func_by_wind_spec( + (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) + ), + "num_iterations" : 5, + "display_in_parallel" : True, + } + +class EquationSolver2dMatchBreakdown(EquationSolver2d): + CONFIG = { + "func" : plane_func_by_wind_spec( + (-2, 0.3, 2), (2, -0.2, 1) # Not an exact match, because our breakdown function has a zero along midlines... + ), + "num_iterations" : 5, + "display_in_parallel" : True, + "show_cursor" : True + } + +class EquationSolver2dMatchBreakdown_parallel(EquationSolver2dMatchBreakdown): + CONFIG = { + "display_in_parallel" : True, + "display_in_bfs" : False, + } + +class EquationSolver2dMatchBreakdown_bfs(EquationSolver2dMatchBreakdown): + CONFIG = { + "display_in_parallel" : False, + "display_in_bfs" : True, + } + class QuickPreview(PreviewClip): CONFIG = { "num_iterations" : 3, @@ -2053,6 +2234,11 @@ class LongEquationSolver(EquationSolver2d): "show_cursor" : True, } +class QuickPreviewUnfancy(LongEquationSolver): + CONFIG = { + # "use_fancy_lines" : False, + } + # TODO: Borsuk-Ulam visuals # Note: May want to do an ordinary square scene, then MappingCamera it into a circle # class BorsukUlamScene(PiWalker): @@ -2479,18 +2665,6 @@ class ShowBack(PiWalkerRect): "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)) } -class Diagnostic(Scene): - def construct(self): - testList = map(lambda n : (n, rev_to_rgba(n)), [0, 0.1, 0.2, 0.3, 0.4]) - print "Here you go:", testList - self.wait() - -class DiagnosticColorMap(PureColorMap): - CONFIG = { - "func" : lambda (x, y) : (25 * x, 25 * y), - "show_num_plane" : False, - } - class PiWalkerOdometerTest(PiWalkerExamplePlaneFunc): CONFIG = { "display_odometer" : True diff --git a/active_projects/WindingNumber_G.py b/old_projects/WindingNumber_G.py similarity index 97% rename from active_projects/WindingNumber_G.py rename to old_projects/WindingNumber_G.py index fa6a8354..501fc289 100644 --- a/active_projects/WindingNumber_G.py +++ b/old_projects/WindingNumber_G.py @@ -1896,6 +1896,20 @@ class TinyLoopAroundRed(TinyLoop): "target_coords" : (-1, 1), } +class ConfusedPiCreature(PiCreatureScene): + def construct(self): + morty = self.pi_creature + morty.set_color(YELLOW_E) + morty.flip() + morty.center() + + self.play(morty.change, "awe", DOWN+3*RIGHT) + self.wait(2) + self.play(morty.change, "confused") + self.wait(2) + self.play(morty.change, "pondering") + self.wait(2) + class FailureOfComposition(ColorMappedObjectsScene): CONFIG = { "func" : lambda p : ( @@ -2148,7 +2162,11 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): CONFIG = { "func" : plane_func_by_wind_spec( (-2, 0, 2), (2, 0, 1) - ) + ), + "dot_fill_opacity" : 1, + "dot_stroke_width" : 1, + "include_walkers" : True, + "include_question_mark" : True, } def construct(self): ColorMappedObjectsScene.construct(self) @@ -2178,7 +2196,10 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): #Setup region labels - for square, tex in (left_square, "x"), (right_square, "y"), (joint_rect, "x+y \\, ?"): + sum_tex = "x+y" + if self.include_question_mark: + sum_tex += "\\, ?" + for square, tex in (left_square, "x"), (right_square, "y"), (joint_rect, sum_tex): square.label = TextMobject("Winding = ", "$%s$"%tex) square.label.move_to(square) @@ -2232,7 +2253,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): path_arc = TAU/6 ), FadeIn(joint_rect.label[1][1]), - FadeIn(joint_rect.label[1][3]), + FadeIn(joint_rect.label[1][3:]), FadeOut(right_square.label[0]), Transform( right_square.label[1], joint_rect.label[1][2], @@ -2281,9 +2302,12 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): #Setup dot, arrow and label dot = self.dot = Dot(radius = 0.1) - dot.set_stroke(WHITE, 1) + dot.set_stroke(WHITE, self.dot_stroke_width) update_dot_color = ContinualUpdateFromFunc( - dot, lambda d : d.set_fill(get_output_color()) + dot, lambda d : d.set_fill( + get_output_color(), + self.dot_fill_opacity + ) ) label = DecimalNumber(0, num_decimal_points = 1) @@ -2294,7 +2318,7 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): arrow_length = 0.75 arrow = Vector(arrow_length*RIGHT) - arrow.set_stroke(WHITE, 1) + arrow.set_stroke(WHITE, self.dot_stroke_width) def arrow_update_func(arrow): arrow.set_fill(get_output_color(), 1) arrow.rotate(-TAU*get_output_rev() - arrow.get_angle()) @@ -2303,7 +2327,8 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): return arrow update_arrow = ContinualUpdateFromFunc(arrow, arrow_update_func) - self.add(update_arrow, update_dot_color, label_upadte) + if self.include_walkers: + self.add(update_arrow, update_dot_color, label_upadte) return dot def position_dot(self, point): @@ -2311,6 +2336,17 @@ class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): self.start_rev = self.get_output_rev() self.curr_winding = 0 +class TransitionFromPathsToBoundariesArrowless(TransitionFromPathsToBoundaries): + CONFIG = { + "func" : plane_func_by_wind_spec( + (-2, 0, 2), (2, 0, 1) + ), + "dot_fill_opacity" : 0, + "dot_stroke_width" : 0, + "include_walkers" : False, + "include_question_mark" : False, + } + class BreakDownLoopWithNonzeroWinding(TransitionFromPathsToBoundaries): def construct(self): zero_point = 2*LEFT @@ -2589,9 +2625,7 @@ class PolynomialTerms(MonomialTerm): class SearchSpacePerimeterVsArea(EquationSolver2d): CONFIG = { - "func" : plane_func_by_wind_spec( - (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) - ), + "func" : example_plane_func, "num_iterations" : 15, "display_in_parallel" : False, "use_fancy_lines" : True, @@ -2654,7 +2688,6 @@ class SearchSpacePerimeterVsArea(EquationSolver2d): self.play(FadeOut(full_rect)) self.wait() -<<<<<<< HEAD:old_projects/WindingNumber_G.py class ShowPolynomialFinalState(SolveX5MinusXMinus1): CONFIG = { "num_iterations" : 15, @@ -2872,8 +2905,6 @@ class AllOfTheVideos(Scene): self.play(LaggedStart(FadeIn, images, run_time = 4)) self.wait() -======= ->>>>>>> parent of feeb9ec... Merge branch 'master' into eop:active_projects/WindingNumber_G.py class EndingCredits(Scene): def construct(self): text = TextMobject( @@ -3187,8 +3218,33 @@ class EndScreen(PatreonEndScreen, PiCreatureScene): self.play(morty.change, mode) self.wait(2) - - +class Thumbnail(SearchSpacePerimeterVsArea): + CONFIG = { + "num_iterations" : 18, + "func" : plane_func_by_wind_spec( + (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) + ), + } + def construct(self): + self.force_skipping() + EquationSolver2d.construct(self) + self.revert_to_original_skipping_status() + + mobjects = VGroup(*self.get_mobjects()) + lines = VGroup() + rects = VGroup() + get_length = lambda mob : max(mob.get_width(), mob.get_height()) + for mob in mobjects: + if mob.background_image_file is not None: + mob.set_stroke(width = 4*np.sqrt(get_length(mob))) + lines.add(mob) + elif isinstance(mob, Polygon): + rects.add(mob) + else: + self.remove(mob) + + self.clear() + self.add(lines) diff --git a/once_useful_constructs/complex_transformation_scene.py b/once_useful_constructs/complex_transformation_scene.py index 7d008b3a..efbbe21b 100644 --- a/once_useful_constructs/complex_transformation_scene.py +++ b/once_useful_constructs/complex_transformation_scene.py @@ -156,112 +156,6 @@ class ComplexTransformationScene(Scene): *added_anims ) -<<<<<<< HEAD:once_useful_constructs/complex_transformation_scene.py -======= -##### Unsure about what comes under here... - -def complex_string(complex_num): - return filter(lambda c : c not in "()", str(complex_num)) - -class ComplexPlane(NumberPlane): - CONFIG = { - "color" : BLUE, - "unit_size" : 1, - "line_frequency" : 1, - "faded_line_frequency" : 0.5, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - kwargs.update({ - "x_unit_size" : self.unit_size, - "y_unit_size" : self.unit_size, - "x_line_frequency" : self.line_frequency, - "x_faded_line_frequency" : self.faded_line_frequency, - "y_line_frequency" : self.line_frequency, - "y_faded_line_frequency" : self.faded_line_frequency, - }) - NumberPlane.__init__(self, **kwargs) - - def number_to_point(self, number): - number = complex(number) - return self.coords_to_point(number.real, number.imag) - - def point_to_number(self, point): - x, y = self.point_to_coords(point) - return complex(x, y) - - def get_coordinate_labels(self, *numbers): - # TODO: Should merge this with the code from NumberPlane.get_coordinate_labels - - result = VGroup() - nudge = 0.1*(DOWN+RIGHT) - if len(numbers) == 0: - numbers = range(-int(self.x_radius), int(self.x_radius)+1) - numbers += [ - complex(0, y) - for y in range(-int(self.y_radius), int(self.y_radius)+1) - ] - for number in numbers: - if number == complex(0, 0): - continue - point = self.number_to_point(number) - num_str = str(number).replace("j", "i") - if num_str.startswith("0"): - num_str = "0" - elif num_str in ["1i", "-1i"]: - num_str = num_str.replace("1", "") - num_mob = TexMobject(num_str) - num_mob.add_background_rectangle() - num_mob.scale_to_fit_height(self.written_coordinate_height) - num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF) - result.add(num_mob) - return result - - def add_coordinates(self, *numbers): - self.add(*self.get_coordinate_labels(*numbers)) - return self - - def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6): - # This code no longer works because it has this reference to self.fade_factor - # which is never initialized. Shall we delete this little-used function entirely? - self.fade(self.fade_factor) - config = { - "color" : self.color, - "density" : self.density, - } - for radius in np.arange(circle_freq, SPACE_WIDTH, circle_freq): - self.add(Circle(radius = radius, **config)) - for angle in np.arange(0, 2*np.pi, angle_freq): - end_point = np.cos(angle)*RIGHT + np.sin(angle)*UP - end_point *= SPACE_WIDTH - self.add(Line(ORIGIN, end_point, **config)) - return self - -class ComplexFunction(ApplyPointwiseFunction): - def __init__(self, function, mobject = ComplexPlane, **kwargs): - if "path_func" not in kwargs: - self.path_func = path_along_arc( - np.log(function(complex(1))).imag - ) - ApplyPointwiseFunction.__init__( - self, - lambda (x, y, z) : complex_to_R3(function(complex(x, y))), - instantiate(mobject), - **kwargs - ) - -class ComplexHomotopy(Homotopy): - def __init__(self, complex_homotopy, mobject = ComplexPlane, **kwargs): - """ - Complex Hootopy a function Cx[0, 1] to C - """ - def homotopy(event): - x, y, z, t = event - c = complex_homotopy((complex(x, y), t)) - return (c.real, c.imag, z) - Homotopy.__init__(self, homotopy, mobject, *args, **kwargs) - ->>>>>>> parent of feeb9ec... Merge branch 'master' into eop:topics/complex_numbers.py From d0732e1dcb1f7e20a0443753d8b7d4e91b77c6ec Mon Sep 17 00:00:00 2001 From: bhbr Date: Wed, 4 Apr 2018 22:26:33 +0200 Subject: [PATCH 05/25] added aggdraw-1.3 caveat to README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fedcbc1..ec2349b7 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,12 @@ Note: pip will install the python module `aggdraw` from https://github.com/scottopell/aggdraw-64bits/ and it might have additional dependencies. -This doesn't install freetype, but I don't think it's required for this project +This doesn't install freetype, but I don't think it's required for this project. + +The latest version of aggdraw (1.3 as of 2018) does not work with manim. Uninstall it beforehand if necessary: +```sh +pip uninstall aggdraw +``` ## How to Use Try running the following: From 99191646aa3962715983125d835ffe9678fd41dd Mon Sep 17 00:00:00 2001 From: bhbr Date: Wed, 4 Apr 2018 23:48:36 +0200 Subject: [PATCH 06/25] Revert "added aggdraw-1.3 caveat to README" This reverts commit d0732e1dcb1f7e20a0443753d8b7d4e91b77c6ec. --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index ec2349b7..5fedcbc1 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,7 @@ Note: pip will install the python module `aggdraw` from https://github.com/scottopell/aggdraw-64bits/ and it might have additional dependencies. -This doesn't install freetype, but I don't think it's required for this project. - -The latest version of aggdraw (1.3 as of 2018) does not work with manim. Uninstall it beforehand if necessary: -```sh -pip uninstall aggdraw -``` +This doesn't install freetype, but I don't think it's required for this project ## How to Use Try running the following: From 34d4d4316e380c56f22d27cf904c01d7c062dc53 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 18:49:42 +0200 Subject: [PATCH 07/25] fixed and expanded GeneralizedPascalsTriangle --- once_useful_constructs/combinatorics.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/once_useful_constructs/combinatorics.py b/once_useful_constructs/combinatorics.py index b804ad6e..88a300b3 100644 --- a/once_useful_constructs/combinatorics.py +++ b/once_useful_constructs/combinatorics.py @@ -1,7 +1,8 @@ from constants import * from mobject.svg.tex_mobject import TexMobject -from mobject.types.vectorized_mobject import VMobject +from mobject.types.vectorized_mobject import VMobject, VGroup +from mobject.numbers import Integer from scene.scene import Scene from utils.simple_functions import choose @@ -85,9 +86,8 @@ class CountingScene(Scene): self.number = num_mob return self -class CombinationTexMobject(TexMobject): - def __init(self,n,k,**kwargs): - TexMobject.__init__(str(choose(n,k)),**kwargs) +def combinationMobject(n,k): + return Integer(choose(n,k)) class GeneralizedPascalsTriangle(VMobject): @@ -96,7 +96,7 @@ class GeneralizedPascalsTriangle(VMobject): "height" : FRAME_HEIGHT - 1, "width" : 1.5*FRAME_X_RADIUS, "portion_to_fill" : 0.7, - "submob_class" : CombinationTexMobject, + "submob_class" : combinationMobject, } def generate_points(self): self.cell_height = float(self.height) / self.nrows @@ -173,9 +173,18 @@ class GeneralizedPascalsTriangle(VMobject): self.add(mob) return self + def get_lowest_row(self): + n = self.nrows - 1 + lowest_row = VGroup(*[ + self.coords_to_mobs[n][k] + for k in range(n+1) + ]) + return lowest_row + + class PascalsTriangle(GeneralizedPascalsTriangle): CONFIG = { - "submob_class" : CombinationTexMobject, + "submob_class" : combinationMobject, } From 5ab540a04445c3af17bbbfcd1293a45cf5d53079 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 18:50:09 +0200 Subject: [PATCH 08/25] first variants of generalized Pascal's Triangle --- active_projects/eop/pascal.py | 422 +++++++++++++++++++++++----------- 1 file changed, 286 insertions(+), 136 deletions(-) diff --git a/active_projects/eop/pascal.py b/active_projects/eop/pascal.py index 0870bef1..2ea554b6 100644 --- a/active_projects/eop/pascal.py +++ b/active_projects/eop/pascal.py @@ -1,201 +1,351 @@ from big_ol_pile_of_manim_imports import * from once_useful_constructs.combinatorics import * -nb_levels = 50 +nb_levels = 5 dev_x_step = 2 dev_y_step = 5 def rainbow_color(alpha): - nb_colors = 100 - rainbow = color_gradient([RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE], nb_colors) - rainbow = np.append(rainbow,PURPLE) - index = int(alpha * nb_colors) - return rainbow[index] + nb_colors = 100 + rainbow = color_gradient([RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE], nb_colors) + rainbow = np.append(rainbow,PURPLE) + index = int(alpha * nb_colors) + return rainbow[index] + +def graded_color(n,k): + if n != 0: + alpha = float(k)/n + else: + alpha = 0.5 + color = interpolate_color(RED, BLUE, alpha) + return color -class SampleScene(Scene): +def graded_square(n,k): + return Square( + side_length = 1, + fill_color = graded_color(n,k), + fill_opacity = 1, + stroke_width = 1 + ) - def construct(self): +def graded_binomial(n,k): + return Integer( + choose(n,k), + color = graded_color(n,k) + ) - triangle = Polygon() - self.add(triangle) - self.wait() +def split_square(n,k): + width = 1 + height = 1 + + proportion = float(choose(n,k)) / 2**n + + lower_height = proportion * height + upper_height = (1 - proportion) * height + lower_rect = Rectangle( + width = width, + height = lower_height, + fill_color = RED, + fill_opacity = 1.0, + stroke_color = WHITE, + stroke_width = 3 + ) + upper_rect = Rectangle( + width = width, + height = upper_height, + fill_color = BLUE, + fill_opacity = 1.0, + stroke_color = WHITE, + stroke_width = 3 + ) + upper_rect.next_to(lower_rect,UP,buff = 0) + square = VGroup(lower_rect, upper_rect).move_to(ORIGIN) + return square + + +class BuildNewPascalRow(Transform): + + def __init__(self,mobject, duplicate_row = None, **kwargs): + if mobject.__class__ != GeneralizedPascalsTriangle and mobject.__class__ != PascalsTriangle: + raise("Transform BuildNewPascalRow only works on members of (Generalized)PascalsTriangle!") + + n = mobject.nrows - 1 + lowest_row_copy1 = mobject.get_lowest_row() + lowest_row_copy2 = duplicate_row + + start_mob = VGroup(lowest_row_copy1, lowest_row_copy2) + + new_pt = mobject.copy() + new_pt.nrows += 1 + new_pt.generate_points() + # align with original (copy got centered on screen) + c1 = new_pt.coords_to_mobs[0][0].get_center() + c2 = mobject.coords_to_mobs[0][0].get_center() + print c1, c2 + v = c2 - c1 + new_pt.shift(v) + + new_row_left_copy = VGroup(*[ + new_pt.coords_to_mobs[n+1][k] + for k in range(0,n+1) + ]) + + new_row_right_copy = VGroup(*[ + new_pt.coords_to_mobs[n+1][k] + for k in range(1,n+2) + ]).copy() + + target_mob = VGroup(new_row_left_copy, new_row_right_copy) + + Transform.__init__(self, start_mob, target_mob, **kwargs) + + + + + +class SimplePascal(Scene): + + def build_new_pascal_row(self,old_pt): + + lowest_row_copy = old_pt.get_lowest_row().copy() + self.add(lowest_row_copy) + + n = old_pt.nrows - 1 + lowest_row_copy1 = old_pt.get_lowest_row() + lowest_row_copy2 = lowest_row_copy1.copy() + + + start_mob = VGroup(lowest_row_copy1, lowest_row_copy2) + self.add(start_mob) + + new_pt = old_pt.copy() + cell_height = old_pt.height / old_pt.nrows + cell_width = old_pt.width / old_pt.nrows + new_pt.nrows += 1 + new_pt.height = new_pt.nrows * cell_height + new_pt.width = new_pt.nrows * cell_width + + new_pt.generate_points() + # align with original (copy got centered on screen) + c1 = new_pt.coords_to_mobs[0][0].get_center() + c2 = old_pt.coords_to_mobs[0][0].get_center() + v = c2 - c1 + new_pt.shift(v) + + new_row_left_copy = VGroup(*[ + new_pt.coords_to_mobs[n+1][k] + for k in range(0,n+1) + ]) + + new_row_right_copy = VGroup(*[ + new_pt.coords_to_mobs[n+1][k] + for k in range(1,n+2) + ]).copy() + + target_mob = VGroup(new_row_left_copy, new_row_right_copy) + self.play(Transform(start_mob, target_mob)) + + return new_pt + + + + def construct(self): + + cell_height = 1 + cell_width = 1 + nrows = 1 + pt = GeneralizedPascalsTriangle( + nrows = nrows, + height = nrows * cell_height, + width = nrows * cell_width, + submob_class = graded_square, + portion_to_fill = 0.9 + ) + pt.shift(3 * UP) + self.add(pt) + lowest_row_copy = pt.get_lowest_row().copy() + self.add(lowest_row_copy) + #self.play(BuildNewPascalRow(pt, duplicate_row = lowest_row_copy)) + for i in range(7): + pt = self.build_new_pascal_row(pt) + class PascalNetScene(Scene): - def construct(self): + def construct(self): - unit_width = 0.25 - top_height = 4.0 - level_height = 2.0 * top_height / nb_levels + unit_width = 0.25 + top_height = 4.0 + level_height = 2.0 * top_height / nb_levels - start_points = np.array([top_height * UP]) + start_points = np.array([top_height * UP]) - dev_start = start_points[0] + dev_start = start_points[0] - j = 0 + j = 0 - for n in range(nb_levels): + for n in range(nb_levels): - half_width = 0.5 * (n + 0.5) * unit_width + half_width = 0.5 * (n + 0.5) * unit_width - stop_points_left = start_points.copy() - stop_points_left[:,0] -= 0.5 * unit_width - stop_points_left[:,1] -= level_height + stop_points_left = start_points.copy() + stop_points_left[:,0] -= 0.5 * unit_width + stop_points_left[:,1] -= level_height - stop_points_right = start_points.copy() - stop_points_right[:,0] += 0.5 * unit_width - stop_points_right[:,1] -= level_height - - for (p,q) in zip(start_points,stop_points_left): - alpha = np.abs((p[0]+q[0])/2) / half_width - color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) + stop_points_right = start_points.copy() + stop_points_right[:,0] += 0.5 * unit_width + stop_points_right[:,1] -= level_height + + for (p,q) in zip(start_points,stop_points_left): + alpha = np.abs((p[0]+q[0])/2) / half_width + color = rainbow_color(alpha) + line = Line(p,q, stroke_color = color) + self.add(line) - for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): - alpha = np.abs((p[0]+q[0])/2) / half_width - color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) + for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): + alpha = np.abs((p[0]+q[0])/2) / half_width + color = rainbow_color(alpha) + line = Line(p,q, stroke_color = color) + self.add(line) - if (n + 1) % dev_y_step == 0 and n != 1: - j += dev_x_step - dev_stop = stop_points_left[j] - line = Line(dev_start,dev_stop,stroke_color = WHITE) - self.add(line) - dot = Dot(dev_stop, fill_color = WHITE) - self.add_foreground_mobject(dot) - dev_start = dev_stop + if (n + 1) % dev_y_step == 0 and n != 1: + j += dev_x_step + dev_stop = stop_points_left[j] + line = Line(dev_start,dev_stop,stroke_color = WHITE) + self.add(line) + dot = Dot(dev_stop, fill_color = WHITE) + self.add_foreground_mobject(dot) + dev_start = dev_stop - start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) + start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) - self.wait() + self.wait() class RescaledPascalNetScene(Scene): - def construct(self): + def construct(self): - half_width = 3.0 - top_height = 4.0 - level_height = 2.0 * top_height / nb_levels + half_width = 3.0 + top_height = 4.0 + level_height = 2.0 * top_height / nb_levels - start_points = np.array([top_height * UP]) - left_edge = top_height * UP + half_width * LEFT - right_edge = top_height * UP + half_width * RIGHT + start_points = np.array([top_height * UP]) + left_edge = top_height * UP + half_width * LEFT + right_edge = top_height * UP + half_width * RIGHT - dev_start = start_points[0] + dev_start = start_points[0] - j = 0 + j = 0 - for n in range(nb_levels): + for n in range(nb_levels): - if n == 0: - start_points_left_shift = np.array([left_edge]) - else: - start_points_left_shift = start_points[:-1] - start_points_left_shift = np.insert(start_points_left_shift,0,left_edge, axis = 0) - stop_points_left = 0.5 * (start_points + start_points_left_shift) - stop_points_left += level_height * DOWN + if n == 0: + start_points_left_shift = np.array([left_edge]) + else: + start_points_left_shift = start_points[:-1] + start_points_left_shift = np.insert(start_points_left_shift,0,left_edge, axis = 0) + stop_points_left = 0.5 * (start_points + start_points_left_shift) + stop_points_left += level_height * DOWN - - if n == 0: - start_points_right_shift = np.array([right_edge]) - else: - start_points_right_shift = start_points[1:] - start_points_right_shift = np.append(start_points_right_shift,np.array([right_edge]), axis = 0) - stop_points_right = 0.5 * (start_points + start_points_right_shift) - stop_points_right += level_height * DOWN + + if n == 0: + start_points_right_shift = np.array([right_edge]) + else: + start_points_right_shift = start_points[1:] + start_points_right_shift = np.append(start_points_right_shift,np.array([right_edge]), axis = 0) + stop_points_right = 0.5 * (start_points + start_points_right_shift) + stop_points_right += level_height * DOWN - - for (i,(p,q)) in enumerate(zip(start_points,stop_points_left)): - - color = LIGHT_GRAY + + for (i,(p,q)) in enumerate(zip(start_points,stop_points_left)): + + color = LIGHT_GRAY - if n % 2 == 0 and i <= n/2: - m = n/2 + 0.25 - jj = i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + if n % 2 == 0 and i <= n/2: + m = n/2 + 0.25 + jj = i + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 0 and i > n/2: - m = n/2 + 0.25 - jj = n - i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 0 and i > n/2: + m = n/2 + 0.25 + jj = n - i + 0.5 + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 1 and i <= n/2: - m = n/2 + 0.75 - jj = i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 1 and i <= n/2: + m = n/2 + 0.75 + jj = i + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 1 and i > n/2: - m = n/2 + 0.75 - jj = n - i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 1 and i > n/2: + m = n/2 + 0.75 + jj = n - i + 0.5 + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) + line = Line(p,q, stroke_color = color) + self.add(line) - for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): - - color = LIGHT_GRAY + for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): + + color = LIGHT_GRAY - if n % 2 == 0 and i < n/2: - m = n/2 + 0.25 - jj = i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + if n % 2 == 0 and i < n/2: + m = n/2 + 0.25 + jj = i + 0.5 + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 0 and i >= n/2: - m = n/2 + 0.25 - jj = n - i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 0 and i >= n/2: + m = n/2 + 0.25 + jj = n - i + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 1 and i <= n/2: - m = n/2 + 0.75 - jj = i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 1 and i <= n/2: + m = n/2 + 0.75 + jj = i + 0.5 + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - elif n % 2 == 1 and i > n/2: - m = n/2 + 0.75 - jj = n - i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) + elif n % 2 == 1 and i > n/2: + m = n/2 + 0.75 + jj = n - i + alpha = 1 - float(jj)/m + color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) + line = Line(p,q, stroke_color = color) + self.add(line) - if (n + 1) % dev_y_step == 0 and n != 1: - j += dev_x_step - dev_stop = stop_points_left[j] - line = Line(dev_start,dev_stop,stroke_color = WHITE) - self.add(line) - dot = Dot(dev_stop, fill_color = WHITE) - self.add_foreground_mobject(dot) - dev_start = dev_stop + if (n + 1) % dev_y_step == 0 and n != 1: + j += dev_x_step + dev_stop = stop_points_left[j] + line = Line(dev_start,dev_stop,stroke_color = WHITE) + self.add(line) + dot = Dot(dev_stop, fill_color = WHITE) + self.add_foreground_mobject(dot) + dev_start = dev_stop - start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) - - left_edge += level_height * DOWN - right_edge += level_height * DOWN + start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) + + left_edge += level_height * DOWN + right_edge += level_height * DOWN - self.wait() + self.wait() From d31ae17154f129aa0e6d753a39237601dc9c5e12 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 21:23:57 +0200 Subject: [PATCH 09/25] Started on coin toss area model --- active_projects/eop/area_coin_toss.py | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 active_projects/eop/area_coin_toss.py diff --git a/active_projects/eop/area_coin_toss.py b/active_projects/eop/area_coin_toss.py new file mode 100644 index 00000000..35e988bf --- /dev/null +++ b/active_projects/eop/area_coin_toss.py @@ -0,0 +1,65 @@ +from big_ol_pile_of_manim_imports import * +from eop.pascal import * + + +WIDTH = 6 +HEIGHT = 0.25 +COLOR_HEADS = YELLOW +COLOR_TAILS = BLUE +NB_ROWS = 28 + + +class AreaSplittingScene(Scene): + + def create_rect_row(self,n): + rects_group = VGroup() + for k in range(n+1): + proportion = float(choose(n,k)) / 2**n + new_rect = Rectangle( + width = proportion * WIDTH, + height = HEIGHT, + fill_color = graded_color(n,k), + fill_opacity = 1 + ) + new_rect.next_to(rects_group,RIGHT,buff = 0) + rects_group.add(new_rect) + return rects_group + + def split_rect_row(self,rect_row): + + split_row = VGroup() + for rect in rect_row.submobjects: + half = rect.copy().stretch_in_place(0.5,0) + left_half = half.next_to(rect.get_center(),LEFT,buff = 0) + right_half = half.copy().next_to(rect.get_center(),RIGHT,buff = 0) + split_row.add(left_half, right_half) + return split_row + + def construct(self): + + rect_row = self.create_rect_row(0) + rect_row.move_to(3.5*UP + 0*HEIGHT*DOWN) + self.add(rect_row) + for n in range(NB_ROWS): + # copy and shift + new_rect_row = rect_row.copy() + self.add(new_rect_row) + self.play(new_rect_row.shift,HEIGHT * DOWN) + self.wait() + + #split + split_row = self.split_rect_row(new_rect_row) + self.play(FadeIn(split_row)) + self.wait() + + # merge + rect_row = self.create_rect_row(n+1) + rect_row.move_to(3.5*UP + (n+1)*HEIGHT*DOWN) + self.play(FadeIn(rect_row)) + self.wait() + + + + + + From 18bac2e186ecd6e5930ed6e6cdfffb0aeb64d8a5 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 22:53:52 +0200 Subject: [PATCH 10/25] fixed typo --- mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 1e67793b..819fb4f2 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -553,7 +553,7 @@ class Mobject(Container): return self def fade_to(self, color, alpha): - for mob in self.subobject_family(): + for mob in self.submobject_family(): mob.fade_to_no_recurse(self, color, alpha) return self From b7e5363cbbcab0908afdf6f6c16f27baefbab658 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 22:55:07 +0200 Subject: [PATCH 11/25] missing import in picreatures --- for_3b1b_videos/pi_creature.py | 1 + 1 file changed, 1 insertion(+) diff --git a/for_3b1b_videos/pi_creature.py b/for_3b1b_videos/pi_creature.py index ea688c46..e0f53707 100644 --- a/for_3b1b_videos/pi_creature.py +++ b/for_3b1b_videos/pi_creature.py @@ -1,4 +1,5 @@ import numpy as np +import warnings from constants import * From b7e2646b47e71bd05611354f5193a470104c95ed Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 5 Apr 2018 22:55:27 +0200 Subject: [PATCH 12/25] started Chapter 1 with a coin-flipping Randy --- active_projects/eop/chapter1.py | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 active_projects/eop/chapter1.py diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py new file mode 100644 index 00000000..baa5cfee --- /dev/null +++ b/active_projects/eop/chapter1.py @@ -0,0 +1,103 @@ +from big_ol_pile_of_manim_imports import * + +class Coin(VMobject): + CONFIG = { + "diameter": 0.8, + "thickness": 0.2, + "nb_ridges" : 7, + "stroke_color": YELLOW, + "stroke_width": 3, + "fill_color": YELLOW, + "fill_opacity": 0.7, + } + + def generate_points(self): + outer_rect = Rectangle( + width = self.diameter, + height = self.thickness, + fill_color = self.fill_color, + fill_opacity = self.fill_opacity, + stroke_color = self.stroke_color, + stroke_width = 0, #self.stroke_width + ) + self.add(outer_rect) + PI = TAU/2 + ridge_angles = np.arange(PI/self.nb_ridges,PI,PI/self.nb_ridges) + ridge_positions = 0.5 * self.diameter * np.array([ + np.cos(theta) for theta in ridge_angles + ]) + ridge_color = interpolate_color(BLACK,self.stroke_color,0.5) + for x in ridge_positions: + ridge = Line( + x * RIGHT + 0.5 * self.thickness * DOWN, + x * RIGHT + 0.5 * self.thickness * UP, + stroke_color = ridge_color, + stroke_width = self.stroke_width + ) + self.add(ridge) + + + +class CoinFlippingPiCreature(PiCreature): + + def __init__(self, **kwargs): + + coin = Coin() # Line(ORIGIN, 0.4 * RIGHT, stroke_width = 15, color = YELLOW) + PiCreature.__init__(self,**kwargs) + self.coin = coin + self.add(coin) + right_arm = self.get_arm_copies()[1] + coin.next_to(right_arm, RIGHT+UP, buff = 0) + coin.shift(0.15 * self.get_width() * LEFT) + + def flip_coin_up(self): + self.change("raise_right_hand") + + +class FlipUpAndDown(Animation): + CONFIG = { + "vector" : UP, + "nb_turns" : 1 + } + + def update(self,t): + self.mobject.shift(4 * t * (1 - t) * self.vector) + self.mobject.rotate(t * self.nb_turns * TAU) + + +class FlipCoin(AnimationGroup): + CONFIG = { + "rate_func" : there_and_back + } + def __init__(self, pi_creature, **kwargs): + digest_config(self, kwargs) + pi_creature_motion = ApplyMethod( + pi_creature.flip_coin_up, + rate_func = self.rate_func, + **kwargs + ) + coin_motion = FlipUpAndDown( + pi_creature.coin, + vector = UP, + nb_turns = 5, + rate_func = self.rate_func, + **kwargs + ) + AnimationGroup.__init__(self,pi_creature_motion, coin_motion) + + + + +class CoinFlipScene(Scene): + + def construct(self): + + randy = CoinFlippingPiCreature() + self.add(randy) + self.play(FlipCoin(randy, run_time = 3)) + + + + + + From 01737a0c4df2748f2f6218262beb40a3930c8d57 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 6 Apr 2018 13:58:22 +0200 Subject: [PATCH 13/25] workaround for CTEX bug --- template.tex | 2 +- text_template.tex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/template.tex b/template.tex index 97bc0ffa..712c439a 100644 --- a/template.tex +++ b/template.tex @@ -11,7 +11,7 @@ \usepackage{mathrsfs} \usepackage{calligra} \usepackage{wasysym} -\usepackage[UTF8]{ctex} +%\usepackage[UTF8]{ctex} \begin{document} \centering diff --git a/text_template.tex b/text_template.tex index c5c76982..430b1d8e 100644 --- a/text_template.tex +++ b/text_template.tex @@ -15,7 +15,7 @@ \usepackage{ragged2e} \usepackage{physics} \usepackage{xcolor} -\usepackage[UTF8]{ctex} +%\usepackage[UTF8]{ctex} \linespread{1} \begin{document} From c44fa6ccfbad029ec02385fc74c6196d1f5dea9c Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 6 Apr 2018 13:58:49 +0200 Subject: [PATCH 14/25] added coins and tallies --- active_projects/eop/area_coin_toss.py | 149 +++++++++++++++++++++++++- active_projects/eop/chapter1.py | 23 ++++ active_projects/eop/pascal.py | 6 +- 3 files changed, 172 insertions(+), 6 deletions(-) diff --git a/active_projects/eop/area_coin_toss.py b/active_projects/eop/area_coin_toss.py index 35e988bf..004f218d 100644 --- a/active_projects/eop/area_coin_toss.py +++ b/active_projects/eop/area_coin_toss.py @@ -2,11 +2,73 @@ from big_ol_pile_of_manim_imports import * from eop.pascal import * -WIDTH = 6 -HEIGHT = 0.25 -COLOR_HEADS = YELLOW -COLOR_TAILS = BLUE -NB_ROWS = 28 +WIDTH = 12 +HEIGHT = 1 +GRADE_COLOR_1 = COLOR_HEADS = RED +GRADE_COLOR_2 = COLOR_TAILS = BLUE +NB_ROWS = 3 + + +class Coin(Circle): + CONFIG = { + "radius": 0.2, + "stroke_width": 3, + "stroke_color": WHITE, + "fill_opacity": 1, + "symbol": "\euro", + } + + def __init__(self, **kwargs): + Circle.__init__(self,**kwargs) + self.symbol_mob = TextMobject(self.symbol, stroke_color = self.stroke_color) + self.symbol_mob.scale_to_fit_height(0.5*self.get_height()).move_to(self) + self.add(self.symbol_mob) + + +class Heads(Coin): + CONFIG = { + "fill_color": COLOR_HEADS, + "symbol": "H", + } + + +class Tails(Coin): + CONFIG = { + "fill_color": COLOR_TAILS, + "symbol": "T", + } + +class CoinStack(VGroup): + CONFIG = { + "spacing": 0.1, + "size": 5, + "face": Heads, + } + + def generate_points(self): + for n in range(self.size): + coin = self.face() + coin.shift(n * self.spacing * RIGHT) + self.add(coin) + +class HeadsStack(CoinStack): + CONFIG = { "face": Heads } + +class TailsStack(CoinStack): + CONFIG = { "face": Tails } + +class TallyStack(VGroup): + + def __init__(self,h,t,**kwargs): + self.nb_heads = h + self.nb_tails = t + VGroup.__init__(self,**kwargs) + + def generate_points(self): + stack1 = HeadsStack(size = self.nb_heads) + stack2 = TailsStack(size = self.nb_tails) + stack2.next_to(stack1, RIGHT, buff = SMALL_BUFF) + self.add(stack1, stack2) class AreaSplittingScene(Scene): @@ -37,9 +99,15 @@ class AreaSplittingScene(Scene): def construct(self): + # Draw the bricks + + brick_wall = VGroup() rect_row = self.create_rect_row(0) rect_row.move_to(3.5*UP + 0*HEIGHT*DOWN) self.add(rect_row) + brick_wall.add(rect_row) + brick_dict = {"0": {"0": rect_row.submobjects[0]}} + for n in range(NB_ROWS): # copy and shift new_rect_row = rect_row.copy() @@ -50,16 +118,87 @@ class AreaSplittingScene(Scene): #split split_row = self.split_rect_row(new_rect_row) self.play(FadeIn(split_row)) + self.remove(new_rect_row) self.wait() # merge rect_row = self.create_rect_row(n+1) rect_row.move_to(3.5*UP + (n+1)*HEIGHT*DOWN) self.play(FadeIn(rect_row)) + brick_wall.add(rect_row) + self.remove(split_row) self.wait() + # add to brick dict + rect_dict = {} + i = 0 + for rect in rect_row.submobjects: + rect_dict[str(i)] = rect + print "added rect for (", n+1, ",", i, ")" + i += 1 + + brick_dict[str(n+1)] = rect_dict + + + for nrow_str,rect_row_dict in brick_dict.iteritems(): + for i_str, rect in rect_row_dict.iteritems(): + pos = rect.get_center() + nrow = int(nrow_str) + i = int(i_str) + print nrow, i + tally = TallyStack(nrow - i, i) + tally.move_to(pos) + self.add(tally) + + + + self.play( + brick_wall.set_fill, {"opacity" : 0.2} + ) + + + # Draw the branches + +# for n in NB_ROWS: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + self.wait() + + + diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index baa5cfee..4af9c46b 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -101,3 +101,26 @@ class CoinFlipScene(Scene): + + + + + + + + + + + + + + + + + + + + + + + diff --git a/active_projects/eop/pascal.py b/active_projects/eop/pascal.py index 2ea554b6..688d3a5e 100644 --- a/active_projects/eop/pascal.py +++ b/active_projects/eop/pascal.py @@ -6,6 +6,10 @@ nb_levels = 5 dev_x_step = 2 dev_y_step = 5 +GRADE_COLOR_1 = RED +GRADE_COLOR_2 = BLUE + + def rainbow_color(alpha): nb_colors = 100 rainbow = color_gradient([RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE], nb_colors) @@ -18,7 +22,7 @@ def graded_color(n,k): alpha = float(k)/n else: alpha = 0.5 - color = interpolate_color(RED, BLUE, alpha) + color = interpolate_color(GRADE_COLOR_1, GRADE_COLOR_2, alpha) return color From b2b9c065f4a86448caa905a757b93955add9f0a5 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Fri, 6 Apr 2018 21:14:35 +0200 Subject: [PATCH 15/25] working branches in brick wall --- active_projects/eop/area_coin_toss.py | 81 ++++++++++++++++++++------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/active_projects/eop/area_coin_toss.py b/active_projects/eop/area_coin_toss.py index 004f218d..e1e64fc4 100644 --- a/active_projects/eop/area_coin_toss.py +++ b/active_projects/eop/area_coin_toss.py @@ -6,7 +6,7 @@ WIDTH = 12 HEIGHT = 1 GRADE_COLOR_1 = COLOR_HEADS = RED GRADE_COLOR_2 = COLOR_TAILS = BLUE -NB_ROWS = 3 +NB_ROWS = 6 class Coin(Circle): @@ -97,6 +97,22 @@ class AreaSplittingScene(Scene): split_row.add(left_half, right_half) return split_row + + def rect_center(self,n,i,j): + if n < 0: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + if i < 0 or i > n: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + if j > choose(n,i) or j < 0: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + + rect = self.brick_array[n][i] + width = rect.get_width() + left_x = rect.get_center()[0] - width/2 + spacing = width / choose(n,i) + x = left_x + (j+0.5) * spacing + return np.array([x,rect.get_center()[1], rect.get_center()[2]]) + def construct(self): # Draw the bricks @@ -106,7 +122,7 @@ class AreaSplittingScene(Scene): rect_row.move_to(3.5*UP + 0*HEIGHT*DOWN) self.add(rect_row) brick_wall.add(rect_row) - brick_dict = {"0": {"0": rect_row.submobjects[0]}} + self.brick_array = [[rect_row.submobjects[0]]] for n in range(NB_ROWS): # copy and shift @@ -130,26 +146,11 @@ class AreaSplittingScene(Scene): self.wait() # add to brick dict - rect_dict = {} - i = 0 + rect_array = [] for rect in rect_row.submobjects: - rect_dict[str(i)] = rect - print "added rect for (", n+1, ",", i, ")" - i += 1 - - brick_dict[str(n+1)] = rect_dict - - - for nrow_str,rect_row_dict in brick_dict.iteritems(): - for i_str, rect in rect_row_dict.iteritems(): - pos = rect.get_center() - nrow = int(nrow_str) - i = int(i_str) - print nrow, i - tally = TallyStack(nrow - i, i) - tally.move_to(pos) - self.add(tally) + rect_array.append(rect) + self.brick_array.append(rect_array) self.play( @@ -159,7 +160,45 @@ class AreaSplittingScene(Scene): # Draw the branches -# for n in NB_ROWS: + for (n, rect_row_array) in enumerate(self.brick_array): + for (i, rect) in enumerate(rect_row_array): + pos = rect.get_center() + tally = TallyStack(n - i, i) + tally.move_to(pos) + + + # from the left + lines = VGroup() + + if i > 0: + for j in range(choose(n-1,i-1)): + start_pos = self.rect_center(n-1,i-1,j) + end_pos = self.rect_center(n,i,j) + lines.add(Line(start_pos,end_pos, stroke_color = GRADE_COLOR_2)) + self.play( + LaggedStart(ShowCreation, lines)) + + # from the right + lines = VGroup() + + if i < n: + for j in range(choose(n-1,i)): + start_pos = self.rect_center(n-1,i,j) + if i != 0: + end_pos = self.rect_center(n,i,choose(n-1,i-1) + j) + else: + end_pos = self.rect_center(n,i,j) + + lines.add(Line(start_pos,end_pos, stroke_color = GRADE_COLOR_1)) + self.play( + LaggedStart(ShowCreation, lines)) + + + + #self.play(FadeIn(tally)) + + + From 9b35a5daeb7e3bfcb680f6f8c1d194b69d6bb37c Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 10 Apr 2018 13:56:27 +0200 Subject: [PATCH 16/25] pretty little coin stacks and sequences --- active_projects/eop/chapter1.py | 320 +++++++++++++++++++++++++++++- active_projects/eop/histograms.py | 2 +- 2 files changed, 317 insertions(+), 5 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 4af9c46b..fb230a41 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -1,6 +1,19 @@ from big_ol_pile_of_manim_imports import * -class Coin(VMobject): +COIN_RADIUS = 0.3 +COIN_THICKNESS = 0.4 * COIN_RADIUS +COIN_FORESHORTENING = 0.3 +COIN_NB_RIDGES = 20 +COIN_STROKE_WIDTH = 2 + +COIN_SEQUENCE_SPACING = 0.1 + +GRADE_COLOR_1 = COLOR_HEADS = RED +GRADE_COLOR_2 = COLOR_TAILS = BLUE + + + +class PiCreatureCoin(VMobject): CONFIG = { "diameter": 0.8, "thickness": 0.2, @@ -42,7 +55,7 @@ class CoinFlippingPiCreature(PiCreature): def __init__(self, **kwargs): - coin = Coin() # Line(ORIGIN, 0.4 * RIGHT, stroke_width = 15, color = YELLOW) + coin = PiCreatureCoin() # Line(ORIGIN, 0.4 * RIGHT, stroke_width = 15, color = YELLOW) PiCreature.__init__(self,**kwargs) self.coin = coin self.add(coin) @@ -88,7 +101,7 @@ class FlipCoin(AnimationGroup): -class CoinFlipScene(Scene): +class CoinFlippingPiCreatureScene(Scene): def construct(self): @@ -101,7 +114,306 @@ class CoinFlipScene(Scene): - +class UprightCoin(Circle): +# For use in coin sequences + CONFIG = { + "radius": COIN_RADIUS, + "stroke_width": COIN_STROKE_WIDTH, + "stroke_color": WHITE, + "fill_opacity": 1, + "symbol": "\euro" + } + + def __init__(self, **kwargs): + Circle.__init__(self,**kwargs) + self.symbol_mob = TextMobject(self.symbol, stroke_color = self.stroke_color) + self.symbol_mob.scale_to_fit_height(0.5*self.get_height()).move_to(self) + self.add(self.symbol_mob) + +class UprightHeads(UprightCoin): + CONFIG = { + "fill_color": COLOR_HEADS, + "symbol": "H", + } + + +class UprightTails(UprightCoin): + CONFIG = { + "fill_color": COLOR_TAILS, + "symbol": "T", + } + +class CoinSequence(VGroup): + CONFIG = { + "sequence": [], + "spacing": COIN_SEQUENCE_SPACING + } + + def __init__(self, sequence, **kwargs): + VGroup.__init__(self, **kwargs) + self.sequence = sequence + offset = 0 + for symbol in self.sequence: + if symbol == "H": + new_coin = UprightHeads() + elif symbol == "T": + new_coin = UprightTails() + else: + new_coin = UprightCoin(symbol = symbol) + new_coin.shift(offset * RIGHT) + self.add(new_coin) + offset += self.spacing + + +class FlatCoin(UprightCoin): +# For use in coin stacks + CONFIG = { + "thickness": COIN_THICKNESS, + "foreshortening": COIN_FORESHORTENING, + "nb_ridges": COIN_NB_RIDGES + } + + def __init__(self, **kwargs): + UprightCoin.__init__(self, **kwargs) + self.symbol_mob.rotate(TAU/8) + self.stretch_in_place(self.foreshortening, 1) + + # draw the edge + control_points1 = self.points[12:25].tolist() + control_points2 = self.copy().shift(self.thickness * DOWN).points[12:25].tolist() + edge_anchors_and_handles = control_points1 + edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * DOWN) + edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * UP) + edge_anchors_and_handles += control_points2[::-1] # list concatenation + edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * UP) + edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * DOWN) + edge_anchors_and_handles.append(control_points1[0]) + #edge_anchors_and_handles = edge_anchors_and_handles[::-1] + edge = VMobject() + edge.set_points(edge_anchors_and_handles) + edge.set_fill( + color = self.fill_color, + opacity = self.fill_opacity + ) + edge.set_stroke(width = self.stroke_width) + self.add(edge) + + # draw the ridges + PI = TAU/2 + ridge_angles = np.arange(PI/self.nb_ridges,PI,PI/self.nb_ridges) + ridge_positions = 0.5 * 2 * self.radius * np.array([ + np.cos(theta) for theta in ridge_angles + ]) + ridge_color = interpolate_color(self.stroke_color, self.fill_color, 0.7) + for x in ridge_positions: + y = -(1 - (x/self.radius)**2)**0.5 * self.foreshortening * self.radius + ridge = Line( + x * RIGHT + y * UP, + x * RIGHT + y * UP + self.thickness * DOWN, + stroke_color = ridge_color, + stroke_width = self.stroke_width + ) + self.add(ridge) + + # redraw the unfilled edge to cover the ridge ends + empty_edge = edge.copy() + empty_edge.set_fill(opacity = 0) + self.add(empty_edge) + + +class FlatHeads(FlatCoin): + CONFIG = { + "fill_color": COLOR_HEADS, + "symbol": "H", + } + + +class FlatTails(FlatCoin): + CONFIG = { + "fill_color": COLOR_TAILS, + "symbol": "T", + } + + + +class CoinStack(VGroup): + CONFIG = { + "coin_thickness": COIN_THICKNESS, + "size": 5, + "face": FlatCoin, + } + + def generate_points(self): + for n in range(self.size): + coin = self.face(thickness = self.coin_thickness) + coin.shift(n * self.coin_thickness * UP) + self.add(coin) + + + + +class HeadsStack(CoinStack): + CONFIG = { "face": FlatHeads } + +class TailsStack(CoinStack): + CONFIG = { "face": FlatTails } + + + + +class TallyStack(VGroup): + + def __init__(self,h,t,**kwargs): + self.nb_heads = h + self.nb_tails = t + VGroup.__init__(self,**kwargs) + + def generate_points(self): + stack1 = HeadsStack(size = self.nb_heads, coin_thickness = self.coin_thickness) + stack2 = TailsStack(size = self.nb_tails, coin_thickness = self.coin_thickness) + stack2.next_to(stack1, RIGHT, buff = SMALL_BUFF) + self.add(stack1, stack2) + + + +class TestScene(Scene): + + def construct(self): + + #seq = CoinSequence(["H", "T", "T", "H"]) + #self.add(seq) + + stack = TallyStack(4,5, coin_thickness = COIN_THICKNESS) + self.add(stack) + + self.wait() + + +class AreaSplittingScene(Scene): + + def create_rect_row(self,n): + rects_group = VGroup() + for k in range(n+1): + proportion = float(choose(n,k)) / 2**n + new_rect = Rectangle( + width = proportion * WIDTH, + height = HEIGHT, + fill_color = graded_color(n,k), + fill_opacity = 1 + ) + new_rect.next_to(rects_group,RIGHT,buff = 0) + rects_group.add(new_rect) + return rects_group + + def split_rect_row(self,rect_row): + + split_row = VGroup() + for rect in rect_row.submobjects: + half = rect.copy().stretch_in_place(0.5,0) + left_half = half.next_to(rect.get_center(),LEFT,buff = 0) + right_half = half.copy().next_to(rect.get_center(),RIGHT,buff = 0) + split_row.add(left_half, right_half) + return split_row + + + def rect_center(self,n,i,j): + if n < 0: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + if i < 0 or i > n: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + if j > choose(n,i) or j < 0: + raise Exception("wrong indices " + str(n) + ", " + str(i) + ", " + str(j)) + + rect = self.brick_array[n][i] + width = rect.get_width() + left_x = rect.get_center()[0] - width/2 + spacing = width / choose(n,i) + x = left_x + (j+0.5) * spacing + return np.array([x,rect.get_center()[1], rect.get_center()[2]]) + + def construct(self): + + # Draw the bricks + + brick_wall = VGroup() + rect_row = self.create_rect_row(0) + rect_row.move_to(3.5*UP + 0*HEIGHT*DOWN) + self.add(rect_row) + brick_wall.add(rect_row) + self.brick_array = [[rect_row.submobjects[0]]] + + for n in range(NB_ROWS): + # copy and shift + new_rect_row = rect_row.copy() + self.add(new_rect_row) + self.play(new_rect_row.shift,HEIGHT * DOWN) + self.wait() + + #split + split_row = self.split_rect_row(new_rect_row) + self.play(FadeIn(split_row)) + self.remove(new_rect_row) + self.wait() + + # merge + rect_row = self.create_rect_row(n+1) + rect_row.move_to(3.5*UP + (n+1)*HEIGHT*DOWN) + self.play(FadeIn(rect_row)) + brick_wall.add(rect_row) + self.remove(split_row) + self.wait() + + # add to brick dict + rect_array = [] + for rect in rect_row.submobjects: + rect_array.append(rect) + + self.brick_array.append(rect_array) + + + self.play( + brick_wall.set_fill, {"opacity" : 0.2} + ) + + + # Draw the branches + + for (n, rect_row_array) in enumerate(self.brick_array): + for (i, rect) in enumerate(rect_row_array): + pos = rect.get_center() + tally = TallyStack(n - i, i) + tally.move_to(pos) + + + # from the left + lines = VGroup() + + if i > 0: + for j in range(choose(n-1,i-1)): + start_pos = self.rect_center(n-1,i-1,j) + end_pos = self.rect_center(n,i,j) + lines.add(Line(start_pos,end_pos, stroke_color = GRADE_COLOR_2)) + self.play( + LaggedStart(ShowCreation, lines)) + + # from the right + lines = VGroup() + + if i < n: + for j in range(choose(n-1,i)): + start_pos = self.rect_center(n-1,i,j) + if i != 0: + end_pos = self.rect_center(n,i,choose(n-1,i-1) + j) + else: + end_pos = self.rect_center(n,i,j) + + lines.add(Line(start_pos,end_pos, stroke_color = GRADE_COLOR_1)) + self.play( + LaggedStart(ShowCreation, lines)) + + + + #self.play(FadeIn(tally)) diff --git a/active_projects/eop/histograms.py b/active_projects/eop/histograms.py index 7303155d..2de66ea4 100644 --- a/active_projects/eop/histograms.py +++ b/active_projects/eop/histograms.py @@ -1,7 +1,7 @@ from big_ol_pile_of_manim_imports import * from random import * -def text_range(start,stop,step): +def text_range(start,stop,step): # a range as a list of strings numbers = np.arange(start,stop,step) labels = [] for x in numbers: From 7ccabd7472822e91ef74db411ebe9b4d5caaa2a1 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 10 Apr 2018 14:36:21 +0200 Subject: [PATCH 17/25] tweak to make the coin stacks more realistic --- active_projects/eop/chapter1.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index fb230a41..2803bc6e 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -200,7 +200,12 @@ class FlatCoin(UprightCoin): # draw the ridges PI = TAU/2 - ridge_angles = np.arange(PI/self.nb_ridges,PI,PI/self.nb_ridges) + dtheta = PI/self.nb_ridges + ridge_angles = np.arange(dtheta,PI,dtheta) + # add a twist onto each coin + ridge_angles += np.random.rand(1) * dtheta + # crop the angles that overshoot on either side + ridge_angles = ridge_angles[(ridge_angles > 0) * (ridge_angles < PI)] ridge_positions = 0.5 * 2 * self.radius * np.array([ np.cos(theta) for theta in ridge_angles ]) From 52305dc3fe5660ab9c4d29d4ffdcae2b141b80ad Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 10 Apr 2018 21:42:41 +0200 Subject: [PATCH 18/25] Tree can unfold in both default and sorted mode, animating btw them does not yet work as intended --- active_projects/eop/chapter1.py | 151 +++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 2803bc6e..0cf28dc4 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -12,6 +12,24 @@ GRADE_COLOR_1 = COLOR_HEADS = RED GRADE_COLOR_2 = COLOR_TAILS = BLUE +def binary(i): + if i == 0: + return [] + j = i + binary_array = [] + while j > 0: + jj = j/2 + if jj > 0: + binary_array.append(j % 2) + else: + binary_array.append(1) + j = jj + return binary_array[::-1] + + +def nb_of_ones(i): + return binary(i).count(1) + class PiCreatureCoin(VMobject): CONFIG = { @@ -277,21 +295,148 @@ class TallyStack(VGroup): stack1 = HeadsStack(size = self.nb_heads, coin_thickness = self.coin_thickness) stack2 = TailsStack(size = self.nb_tails, coin_thickness = self.coin_thickness) stack2.next_to(stack1, RIGHT, buff = SMALL_BUFF) + stack2.align_to(stack1, DOWN) self.add(stack1, stack2) + + + +class CoinFlipTree(VGroup): + CONFIG = { + "total_width": 12, + "level_height": 0.8, + "nb_levels": 4, + "sort_until_level": 3 + } + + def __init__(self, **kwargs): + + VGroup.__init__(self, **kwargs) + + self.rows = [] + for n in range(self.nb_levels + 1): + if n <= self.sort_until_level: + self.create_row(n, sorted = True) + else: + self.create_row(n, sorted = False) + + + for row in self.rows: + for leaf in row: + dot = Dot() + dot.move_to(leaf[0]) + line = Line(leaf[2], leaf[0]) + if leaf[2][0] > leaf[0][0]: + line_color = COLOR_HEADS + else: + line_color = COLOR_TAILS + line.set_stroke(color = line_color) + group = VGroup() + group.add(dot) + group.add_to_back(line) + self.add(group) + + + + + def create_row(self, level, sorted = True): + + if level == 0: + new_row = [[ORIGIN,0,ORIGIN]] # is its own parent + self.rows.append(new_row) + return + + previous_row = self.rows[level - 1] + new_row = [] + dx = float(self.total_width) / (2 ** level) + x = - 0.5 * self.total_width + 0.5 * dx + y = - self.level_height * level + for root in previous_row: + root_point = root[0] + root_tally = root[1] + for i in range(2): # 0 = heads = left, 1 = tails = right + leaf = x * RIGHT + y * UP + new_row.append([leaf, root_tally + i, root_point]) # leaf and its parent + x += dx + + #print "tallies for row", level, ":", [new_row[i][1] for i in range(2**level)] + + if sorted: + # sort the new_row by its tallies + sorted_row = [] + x = - 0.5 * self.total_width + 0.5 * dx + for i in range(level + 1): + for leaf in new_row: + if leaf[1] == i: + sorted_leaf = leaf + sorted_leaf[0][0] = x + x += dx + sorted_row.append(leaf) + print "sorted roots:", [sorted_row[i][2][0] for i in range(2**level)] + self.rows.append(sorted_row) + else: + self.rows.append(new_row) + + + + class TestScene(Scene): def construct(self): - #seq = CoinSequence(["H", "T", "T", "H"]) + #seq = CoinSequence(["H", "T", "T", "H"]).move_to(2 * LEFT) #self.add(seq) - stack = TallyStack(4,5, coin_thickness = COIN_THICKNESS) - self.add(stack) + #stack = TallyStack(4,7, coin_thickness = COIN_THICKNESS) + #self.add(stack) + + tree = CoinFlipTree(nb_levels = 7, sort_until_level = 0) + tree.move_to(ORIGIN) + self.add(tree) + + for i in range(1, 8): + new_tree = CoinFlipTree(nb_levels = 7, sort_until_level = i) + new_tree.move_to(ORIGIN) + self.play(Transform(tree, new_tree)) + self.wait() self.wait() + + +class CoinFlipBranchToAreaScene(Scene): + + def construct(self): + + pass + + + + + + + + + + + + + + + + + + + + + + + + + + + class AreaSplittingScene(Scene): From d239d96b7d32c5a5778fbe99dfb1800497a44077 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 16:58:49 +0200 Subject: [PATCH 19/25] added options to OpeningQuote scenes --- for_3b1b_videos/common_scenes.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/for_3b1b_videos/common_scenes.py b/for_3b1b_videos/common_scenes.py index e25cd68b..f1731d8c 100644 --- a/for_3b1b_videos/common_scenes.py +++ b/for_3b1b_videos/common_scenes.py @@ -34,6 +34,10 @@ class OpeningQuote(Scene): "lag_factor": 4, "run_time": 5, }, + "text_size" : "\\Large", + "use_quotation_marks": True, + "top_buff" : 1.0, + "author_buff": 1.0, } def construct(self): @@ -42,7 +46,7 @@ class OpeningQuote(Scene): self.play(FadeIn(self.quote, **self.fade_in_kwargs)) self.wait(2) - self.play(Write(self.author, run_time=3)) + self.play(Write(self.author, run_time = 3)) self.wait() def get_quote(self, max_width=FRAME_WIDTH - 1): @@ -51,25 +55,32 @@ class OpeningQuote(Scene): "arg_separator": self.quote_arg_separator, } if isinstance(self.quote, str): - quote = TextMobject("``%s''" % + if self.use_quotation_marks: + quote = TextMobject("``%s''" % + self.quote.strip(), **text_mobject_kwargs) + else: + quote = TextMobject("%s" % self.quote.strip(), **text_mobject_kwargs) else: - words = ["\\Large ``"] + list(self.quote) + ["''"] + if self.use_quotation_marks: + words = [self.text_size + " ``"] + list(self.quote) + ["''"] + else: + words = [self.text_size] + list(self.quote) quote = TextMobject(*words, **text_mobject_kwargs) # TODO, make less hacky if self.quote_arg_separator == " ": quote[0].shift(0.2 * RIGHT) quote[-1].shift(0.2 * LEFT) - for term, color in self.set_colored_quote_terms.items(): + for term, color in self.highlighted_quote_terms: quote.set_color_by_tex(term, color) - quote.to_edge(UP) + quote.to_edge(UP, buff = self.top_buff) if quote.get_width() > max_width: quote.scale_to_fit_width(max_width) return quote def get_author(self, quote): - author = TextMobject("\\Large -" + self.author) - author.next_to(quote, DOWN) + author = TextMobject(self.text_size + " --" + self.author) + author.next_to(quote, DOWN, buff = self.author_buff) author.set_color(YELLOW) return author From 487f6747e1e94d5eb044b0c856de7fecbc134ccf Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 16:59:39 +0200 Subject: [PATCH 20/25] Teacher in TeacherStudentScene can now be colored --- for_3b1b_videos/pi_creature_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/for_3b1b_videos/pi_creature_scene.py b/for_3b1b_videos/pi_creature_scene.py index 220e58a7..3f9da5db 100644 --- a/for_3b1b_videos/pi_creature_scene.py +++ b/for_3b1b_videos/pi_creature_scene.py @@ -266,7 +266,7 @@ class TeacherStudentsScene(PiCreatureScene): UP + LEFT) + MED_LARGE_BUFF * UP def create_pi_creatures(self): - self.teacher = Mortimer() + self.teacher = Mortimer(color = self.default_pi_creature_kwargs["color"]) self.teacher.to_corner(DOWN + RIGHT) self.teacher.look(DOWN + LEFT) self.students = VGroup(*[ From 5f2aa31fc3759668457d0e56203b75e6a3a21cd1 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 16:59:56 +0200 Subject: [PATCH 21/25] import bug in SampleSpaceScene --- scene/sample_space_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scene/sample_space_scene.py b/scene/sample_space_scene.py index b1fa129f..a03e4195 100644 --- a/scene/sample_space_scene.py +++ b/scene/sample_space_scene.py @@ -1,6 +1,6 @@ from constants import * -from scene.scene import Scene +from scene import Scene from animation.animation import Animation from animation.transform import MoveToTarget From e0ff0f2a9f46943b3e154fe9f07d9984f1782ff6 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 17:00:38 +0200 Subject: [PATCH 22/25] rearranged imports bc of GraphScene name conflict --- big_ol_pile_of_manim_imports.py | 20 +++++++++++--------- scene/graph_scene.py | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/big_ol_pile_of_manim_imports.py b/big_ol_pile_of_manim_imports.py index 9e941ec7..628367c3 100644 --- a/big_ol_pile_of_manim_imports.py +++ b/big_ol_pile_of_manim_imports.py @@ -46,6 +46,7 @@ from mobject.numbers import * from mobject.probability import * from mobject.shape_matchers import * from mobject.svg.brace import * +from mobject.svg.drawings import * from mobject.svg.svg_mobject import * from mobject.svg.tex_mobject import * from mobject.three_dimensions import * @@ -59,15 +60,6 @@ from for_3b1b_videos.pi_creature import * from for_3b1b_videos.pi_creature_animations import * from for_3b1b_videos.pi_creature_scene import * -from scene.graph_scene import * -from scene.moving_camera_scene import * -from scene.reconfigurable_scene import * -from scene.scene import * -from scene.scene_from_video import * -from scene.three_d_scene import * -from scene.vector_space_scene import * -from scene.zoomed_scene import * - from once_useful_constructs.arithmetic import * from once_useful_constructs.combinatorics import * from once_useful_constructs.complex_transformation_scene import * @@ -76,6 +68,16 @@ from once_useful_constructs.fractals import * from once_useful_constructs.graph_theory import * from once_useful_constructs.light import * +from scene.graph_scene import * +from scene.moving_camera_scene import * +from scene.reconfigurable_scene import * +from scene.scene import * +from scene.sample_space_scene import * +from scene.graph_scene import * +from scene.scene_from_video import * +from scene.three_d_scene import * +from scene.vector_space_scene import * +from scene.zoomed_scene import * from utils.bezier import * from utils.color import * diff --git a/scene/graph_scene.py b/scene/graph_scene.py index 3d014d62..5caec9e6 100644 --- a/scene/graph_scene.py +++ b/scene/graph_scene.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from constants import * +import itertools as it from scene.scene import Scene from animation.creation import Write From 43faec638e4f7aa944cf8e65521a72671f6f0f82 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 17:01:01 +0200 Subject: [PATCH 23/25] fix for copy bug (again) --- mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index e4c2b051..c42dc1fc 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -115,7 +115,7 @@ class Mobject(Container): def copy(self): # TODO, either justify reason for shallow copy, or # remove this redundancy everywhere - return self.deepcopy() + #return self.deepcopy() copy_mobject = copy.copy(self) copy_mobject.points = np.array(self.points) From 514bb560999e35ed750a1e3cdf478c09d5330cba Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 17:02:13 +0200 Subject: [PATCH 24/25] added OpeningQuote, skeletal overview, 1st illustration of area model --- active_projects/eop/chapter1.py | 422 ++++++++++++++++++++++++++++---- 1 file changed, 370 insertions(+), 52 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 0cf28dc4..87656e85 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -1,4 +1,5 @@ from big_ol_pile_of_manim_imports import * +from old_projects.eoc.chapter8 import * COIN_RADIUS = 0.3 COIN_THICKNESS = 0.4 * COIN_RADIUS @@ -13,6 +14,7 @@ GRADE_COLOR_2 = COLOR_TAILS = BLUE def binary(i): + # returns an array of 0s and 1s if i == 0: return [] j = i @@ -26,11 +28,9 @@ def binary(i): j = jj return binary_array[::-1] - def nb_of_ones(i): return binary(i).count(1) - class PiCreatureCoin(VMobject): CONFIG = { "diameter": 0.8, @@ -67,8 +67,6 @@ class PiCreatureCoin(VMobject): ) self.add(ridge) - - class CoinFlippingPiCreature(PiCreature): def __init__(self, **kwargs): @@ -84,7 +82,6 @@ class CoinFlippingPiCreature(PiCreature): def flip_coin_up(self): self.change("raise_right_hand") - class FlipUpAndDown(Animation): CONFIG = { "vector" : UP, @@ -95,7 +92,6 @@ class FlipUpAndDown(Animation): self.mobject.shift(4 * t * (1 - t) * self.vector) self.mobject.rotate(t * self.nb_turns * TAU) - class FlipCoin(AnimationGroup): CONFIG = { "rate_func" : there_and_back @@ -116,9 +112,6 @@ class FlipCoin(AnimationGroup): ) AnimationGroup.__init__(self,pi_creature_motion, coin_motion) - - - class CoinFlippingPiCreatureScene(Scene): def construct(self): @@ -128,10 +121,6 @@ class CoinFlippingPiCreatureScene(Scene): self.play(FlipCoin(randy, run_time = 3)) - - - - class UprightCoin(Circle): # For use in coin sequences CONFIG = { @@ -154,7 +143,6 @@ class UprightHeads(UprightCoin): "symbol": "H", } - class UprightTails(UprightCoin): CONFIG = { "fill_color": COLOR_TAILS, @@ -182,7 +170,6 @@ class CoinSequence(VGroup): self.add(new_coin) offset += self.spacing - class FlatCoin(UprightCoin): # For use in coin stacks CONFIG = { @@ -243,22 +230,18 @@ class FlatCoin(UprightCoin): empty_edge.set_fill(opacity = 0) self.add(empty_edge) - class FlatHeads(FlatCoin): CONFIG = { "fill_color": COLOR_HEADS, "symbol": "H", } - class FlatTails(FlatCoin): CONFIG = { "fill_color": COLOR_TAILS, "symbol": "T", } - - class CoinStack(VGroup): CONFIG = { "coin_thickness": COIN_THICKNESS, @@ -272,17 +255,15 @@ class CoinStack(VGroup): coin.shift(n * self.coin_thickness * UP) self.add(coin) - - - class HeadsStack(CoinStack): - CONFIG = { "face": FlatHeads } + CONFIG = { + "face": FlatHeads + } class TailsStack(CoinStack): - CONFIG = { "face": FlatTails } - - - + CONFIG = { + "face": FlatTails + } class TallyStack(VGroup): @@ -298,11 +279,6 @@ class TallyStack(VGroup): stack2.align_to(stack1, DOWN) self.add(stack1, stack2) - - - - - class CoinFlipTree(VGroup): CONFIG = { "total_width": 12, @@ -413,6 +389,367 @@ class CoinFlipBranchToAreaScene(Scene): +class Chapter1OpeningQuote(OpeningQuote): + CONFIG = { + "fade_in_kwargs": { + "submobject_mode": "lagged_start", + "rate_func": None, + "lag_factor": 9, + "run_time": 10, + }, + "text_size" : "\\normalsize", + "use_quotation_marks": False, + "quote" : [ + "To see a world in a grain of sand\\\\", + "And a heaven in a wild flower,\\\\", + "Hold infinity in the palm of your hand\\\\", + "\phantom{r}And eternity in an hour.\\\\" + ], + "quote_arg_separator" : " ", + "highlighted_quote_terms" : {}, + "author" : "William Blake: \\\\ \emph{Auguries of Innocence}", + } + +class Introduction(TeacherStudentsScene): + + CONFIG = { + "default_pi_creature_kwargs": { + "color": MAROON_E, + "flip_at_start": True, + }, + } + + def construct(self): + self.show_series() + + def show_series(self): + series = VideoSeries() + series.to_edge(UP) + this_video = series[0] + this_video.set_color(YELLOW) + this_video.save_state() + this_video.set_fill(opacity = 0) + this_video.center() + this_video.scale_to_fit_height(FRAME_HEIGHT) + self.this_video = this_video + + + words = TextMobject( + "Welcome to \\\\", + "Essence of Probability" + ) + words.set_color_by_tex("Essence of Probability", YELLOW) + + self.teacher.change_mode("happy") + self.play( + FadeIn( + series, + submobject_mode = "lagged_start", + run_time = 2 + ), + Blink(self.get_teacher()) + ) + self.teacher_says(words, target_mode = "hooray") + self.change_student_modes( + *["hooray"]*3, + look_at_arg = series[1].get_left(), + added_anims = [ + ApplyMethod(this_video.restore, run_time = 3), + ] + ) + self.play(*[ + ApplyMethod( + video.shift, 0.5*video.get_height()*DOWN, + run_time = 3, + rate_func = squish_rate_func( + there_and_back, alpha, alpha+0.3 + ) + ) + for video, alpha in zip(series, np.linspace(0, 0.7, len(series))) + ]+[ + Animation(self.teacher.bubble), + Animation(self.teacher.bubble.content), + ]) + + essence_words = words.get_part_by_tex("Essence").copy() + self.play( + FadeOut(self.teacher.bubble), + FadeOut(self.teacher.bubble.content), + essence_words.next_to, series, DOWN, + *[ + ApplyMethod(pi.change_mode, "pondering") + for pi in self.get_pi_creatures() + ] + ) + self.wait(3) + + self.series = series + self.essence_words = essence_words + +class IllustrateAreaModel1(Scene): + + def construct(self): + + # show independent events + + sample_space_width = sample_space_height = 3.0 + p_of_A = 0.7 + p_of_not_A = 1 - p_of_A + p_of_B = 0.8 + p_of_not_B = 1 - p_of_B + + + rect_A = Rectangle( + width = p_of_A * sample_space_width, + height = 1 * sample_space_height, + stroke_width = 0, + fill_color = BLUE, + fill_opacity = 1.0 + ) + rect_not_A = Rectangle( + width = p_of_not_A * sample_space_width, + height = 1 * sample_space_height, + stroke_width = 0, + fill_color = BLUE_E, + fill_opacity = 1.0 + ).next_to(rect_A, RIGHT, buff = 0) + + brace_A = Brace(rect_A, DOWN) + label_A = TexMobject("P(A)").next_to(brace_A, DOWN).scale(0.7) + brace_not_A = Brace(rect_not_A, DOWN) + label_not_A = TexMobject("P(\\text{not }A)").next_to(brace_not_A, DOWN).scale(0.7) + + self.play( + LaggedStart(FadeIn, VGroup(rect_A, rect_not_A), lag_factor = 0.5) + ) + self.play( + ShowCreation(brace_A), + Write(label_A), + ) + # self.play( + # ShowCreation(brace_not_A), + # Write(label_not_A), + # ) + + + + + rect_B = Rectangle( + width = 1 * sample_space_width, + height = p_of_B * sample_space_height, + stroke_width = 0, + fill_color = GREEN, + fill_opacity = 0.5 + ) + rect_not_B = Rectangle( + width = 1 * sample_space_width, + height = p_of_not_B * sample_space_height, + stroke_width = 0, + fill_color = GREEN_E, + fill_opacity = 0.5 + ).next_to(rect_B, UP, buff = 0) + + VGroup(rect_B, rect_not_B).move_to(VGroup(rect_A, rect_not_A)) + + brace_B = Brace(rect_B, LEFT) + label_B = TexMobject("P(B)").next_to(brace_B, LEFT).scale(0.7) + brace_not_B = Brace(rect_not_B, LEFT) + label_not_B = TexMobject("P(\\text{not }B)").next_to(brace_not_B, LEFT).scale(0.7) + + self.play( + LaggedStart(FadeIn, VGroup(rect_B, rect_not_B), lag_factor = 0.5) + ) + self.play( + ShowCreation(brace_B), + Write(label_B), + ) + # self.play( + # ShowCreation(brace_not_B), + # Write(label_not_B), + # ) + + rect_A_and_B = Rectangle( + width = p_of_A * sample_space_width, + height = p_of_B * sample_space_height, + stroke_width = 3, + fill_opacity = 0.0 + ).align_to(rect_A, DOWN).align_to(rect_A,LEFT) + label_A_and_B = TexMobject("P(A\\text{ and }B)").scale(0.7) + label_A_and_B.move_to(rect_A_and_B) + + self.play( + ShowCreation(rect_A_and_B) + ) + self.play(FadeIn(label_A_and_B)) + self.add_foreground_mobject(label_A_and_B) + + indep_formula = TexMobject("P(A\\text{ and }B)", "=", "P(A)", "\cdot", "P(B)") + indep_formula = indep_formula.scale(0.7).next_to(rect_not_B, UP, buff = MED_LARGE_BUFF) + + label_A_and_B_copy = label_A_and_B.copy() + label_A_copy = label_A.copy() + label_B_copy = label_B.copy() + self.add(label_A_and_B_copy, label_A_copy, label_B_copy) + + self.play(Transform(label_A_and_B_copy, indep_formula[0])) + self.play(FadeIn(indep_formula[1])) + self.play(Transform(label_A_copy, indep_formula[2])) + self.play(FadeIn(indep_formula[3])) + self.play(Transform(label_B_copy, indep_formula[4])) + + self.wait() + + + # show conditional prob + + rect_A_and_B.set_fill(color = GREEN, opacity = 0.5) + rect_A_and_not_B = Rectangle( + width = p_of_A * sample_space_width, + height = p_of_not_B * sample_space_height, + stroke_width = 0, + fill_color = GREEN_E, + fill_opacity = 0.5 + ).next_to(rect_A_and_B, UP, buff = 0) + + rect_not_A_and_B = Rectangle( + width = p_of_not_A * sample_space_width, + height = p_of_B * sample_space_height, + stroke_width = 0, + fill_color = GREEN, + fill_opacity = 0.5 + ).next_to(rect_A_and_B, RIGHT, buff = 0) + + rect_not_A_and_not_B = Rectangle( + width = p_of_not_A * sample_space_width, + height = p_of_not_B * sample_space_height, + stroke_width = 0, + fill_color = GREEN_E, + fill_opacity = 0.5 + ).next_to(rect_not_A_and_B, UP, buff = 0) + + self.remove(rect_B, rect_not_B) + self.add(rect_A_and_not_B, rect_not_A_and_B, rect_not_A_and_not_B) + + + + + + p_of_B_knowing_A = 0.6 + rect_A_and_B.target = Rectangle( + width = p_of_A * sample_space_width, + height = p_of_B_knowing_A * sample_space_height, + stroke_width = 3, + fill_color = GREEN, + fill_opacity = 0.5 + ).align_to(rect_A_and_B, DOWN).align_to(rect_A_and_B, LEFT) + + rect_A_and_not_B.target = Rectangle( + width = p_of_A * sample_space_width, + height = (1 - p_of_B_knowing_A) * sample_space_height, + stroke_width = 0, + fill_color = GREEN_E, + fill_opacity = 0.5 + ).next_to(rect_A_and_B.target, UP, buff = 0) + + brace_B.target = Brace(rect_A_and_B.target, LEFT) + label_B.target = TexMobject("P(B\mid A)").scale(0.7).next_to(brace_B.target, LEFT) + + + self.play( + MoveToTarget(rect_A_and_B), + MoveToTarget(rect_A_and_not_B), + MoveToTarget(brace_B), + MoveToTarget(label_B), + label_A_and_B.move_to,rect_A_and_B.target + ) + label_B_knowing_A = label_B + + self.play(FadeOut(label_B_copy)) + label_B_knowing_A_copy = label_B_knowing_A.copy() + self.add(label_B_knowing_A_copy) + + self.play( + label_B_knowing_A_copy.next_to, indep_formula[-2], RIGHT + ) + + + + + + self.wait() + + + # def show_independent_events(self): + # sample_space = SampleSpace( + # full_space_config = { + # "height" : 3, + # "width" : 3, + # "fill_opacity" : 0 + # } + # ) + # sample_space.divide_horizontally(0.4) + # sample_space.horizontal_parts.set_fill(opacity = 0) + # h_labels = [ + # TexMobject("P(", "A", ")"), + # TexMobject("P(\\text{not }", "A", ")"), + # ] + # for label in h_labels: + # label.scale(0.7) + # #self.color_label(label) + # sample_space.get_side_braces_and_labels(h_labels) + # sample_space.add_braces_and_labels() + # h_parts = sample_space.horizontal_parts + # for (label, part) in zip(h_labels, h_parts): + # label.next_to(part, 2 * LEFT) + # sample_space.add(label) + + # values = [0.2, 0.2] + # color_pairs = [(GREEN, BLUE), (GREEN_E, BLUE_E)] + # v_parts = VGroup() + # for tup in zip(h_parts, values, color_pairs): + # part, value, colors = tup + # part.divide_vertically(value, colors = colors) + # part.vertical_parts.set_fill(opacity = 0.8) + # #label = TexMobject( + # # "P(", "B", "|", given_str, "A", ")" + # #) + # #label.scale(0.7) + # #self.color_label(label) + # if part == h_parts[0]: + # part.get_subdivision_braces_and_labels( + # part.vertical_parts, [label], DOWN + # ) + # sample_space.add( + # part.vertical_parts.braces, + # # part.vertical_parts.labels, + # ) + # v_parts.add(part.vertical_parts.submobjects) + + + # v_labels = [ + # TexMobject("P(", "B", ")"), + # TexMobject("P(\\text{not }", "B", ")"), + # ] + # for (label, part) in zip(v_labels, v_parts[1::2]): + # label.scale(0.7) + # label.next_to(part, DOWN) + # sample_space.add(label) + + + # sample_space.to_edge(LEFT) + + # self.add(sample_space) + # self.sample_space = sample_space + + # self.wait() + + + + + + def color_label(self, label): + label.set_color_by_tex("B", RED) + label.set_color_by_tex("I", GREEN) @@ -420,26 +757,7 @@ class CoinFlipBranchToAreaScene(Scene): - - - - - - - - - - - - - - - - - - - -class AreaSplittingScene(Scene): +class AreaSplitting(Scene): def create_rect_row(self,n): rects_group = VGroup() From 881b2dd9cb222635b2e8e7f5ec9067ea3ae79b8e Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 17:03:28 +0200 Subject: [PATCH 25/25] got code from EoC working (for the 2nd area model example) --- active_projects/eop/chapter1.py | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 87656e85..7cb6c469 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -755,6 +755,54 @@ class IllustrateAreaModel1(Scene): +class IllustrateAreaModel2(AreaIsDerivative): + + CONFIG = { + "y_max" : 4, + "y_min" : -4, + "num_iterations" : 7, + "y_axis_label" : "", + "num_rects" : 400, + "dT" : 0.25, + "variable_point_label" : "T", + "area_opacity" : 0.8, + } + def construct(self): + + self.setup_axes() + self.introduce_variable_area() + + graph, label = self.get_v_graph_and_label() + + rect_list = self.get_riemann_rectangles_list( + graph, self.num_iterations + ) + VGroup(*rect_list).set_fill(opacity = 0.8) + rects = rect_list[0] + + self.play(ShowCreation(graph)) + self.play(Write(rects)) + for new_rects in rect_list[1:]: + rects.align_submobjects(new_rects) + for every_other_rect in rects[::2]: + every_other_rect.set_fill(opacity = 0) + self.play(Transform( + rects, new_rects, + run_time = 2, + submobject_mode = "lagged_start" + )) + self.wait() + +# self.play(FadeOut(self.x_axis.numbers)) + self.add_T_label(6) + self.change_area_bounds( + new_t_max = 4, + rate_func = there_and_back, + run_time = 2 + ) + + def func(self, x): + return np.exp(-x**2/2) class AreaSplitting(Scene):