From 74972216b50103d1761867857fdddc68e4771f4a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 18 Apr 2018 00:13:21 -0700 Subject: [PATCH] Partial progress through KeeperAndSailor --- active_projects/wallis_g.py | 523 +++++++++++++++++++++++++++++++++--- 1 file changed, 480 insertions(+), 43 deletions(-) diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index 3d139a6f..fb40370c 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -346,11 +346,58 @@ class SourcesOfOriginality(TeacherStudentsScene): self.wait(5) -class SridharWatchingScene(Scene): +class SridharWatchingScene(PiCreatureScene): + CONFIG = { + "default_pi_creature_kwargs": { + "color": YELLOW_E, + "flip_at_start": False, + }, + } + def construct(self): laptop = Laptop() - sridhar = PiCreature(color=YELLOW_E) - sridhar.next_to(laptop, LEFT) + laptop.scale(1.8) + laptop.to_corner(DR) + sridhar = self.pi_creature + sridhar.next_to(laptop, LEFT, SMALL_BUFF, DOWN) + bubble = ThoughtBubble() + bubble.flip() + bubble.pin_to(sridhar) + + basel = TexMobject( + "{1", "\\over", "1^2}", "+" + "{1", "\\over", "2^2}", "+" + "{1", "\\over", "3^2}", "+", "\\cdots", + "= \\frac{\\pi^2}{6}" + ) + wallis = get_wallis_product(n_terms=4) + VGroup(basel, wallis).scale(0.7) + basel.move_to(bubble.get_bubble_center()) + basel.to_edge(UP, buff=MED_SMALL_BUFF) + wallis.next_to(basel, DOWN, buff=0.75) + arrow = TexMobject("\\updownarrow") + arrow.move_to(VGroup(basel, wallis)) + basel.set_color(YELLOW) + wallis.set_color(BLUE) + + self.play(LaggedStart(DrawBorderThenFill, laptop)) + self.play(sridhar.change, "pondering", laptop.screen) + self.wait() + self.play(ShowCreation(bubble)) + self.play(LaggedStart(FadeIn, basel)) + self.play( + ReplacementTransform(basel.copy(), wallis), + GrowFromPoint(arrow, arrow.get_top()) + ) + self.wait(4) + self.play(sridhar.change, "thinking", wallis) + self.wait(4) + self.play(LaggedStart( + ApplyFunction, + VGroup(*list(laptop) + [bubble, basel, arrow, wallis, sridhar]), + lambda mob: (lambda m: m.set_color(BLACK).fade(1).scale(0.8), mob), + run_time=3, + )) class DistanceProductScene(MovingCameraScene): @@ -604,17 +651,14 @@ class DistanceProductScene(MovingCameraScene): class IntroduceDistanceProduct(DistanceProductScene): CONFIG = { - "ambient_light_config": { - # "num_levels": 10, - # "radius": 1, - "color": YELLOW, - }, + "ambient_light_config": {"color": YELLOW}, } def construct(self): self.draw_circle_with_points() self.turn_into_lighthouses_and_observer() self.show_sum_of_inverse_squares() + self.transition_to_lemma_1() def draw_circle_with_points(self): circle = self.circle @@ -833,15 +877,44 @@ class IntroduceDistanceProduct(DistanceProductScene): )) self.wait() + def transition_to_lemma_1(self): + self.lighthouse_height = Lemma1.CONFIG["lighthouse_height"] + self.circle_radius = Lemma1.CONFIG["circle_radius"] + self.observer_fraction = Lemma1.CONFIG["observer_fraction"] + + self.ambient_light_config["color"] = BLUE + + circle = self.circle + lighthouses = self.lighthouses + lights = self.lights + + circle.generate_target() + circle.target.scale_to_fit_width(2 * self.circle_radius) + circle.target.to_corner(DL) + self.circle = circle.target + + new_lighthouses = self.get_lighthouses() + new_lights = self.get_lights() + + self.clear() + self.play( + MoveToTarget(circle), + Transform(lighthouses, new_lighthouses), + Transform(lights, new_lights), + ApplyMethod( + self.observer_dot.move_to, + self.get_circle_point_at_proportion( + self.observer_fraction / self.num_lighthouses + ) + ), + MaintainPositionRelativeTo(self.observer, self.observer_dot), + ) + class Lemma1(DistanceProductScene): CONFIG = { "circle_radius": 2.5, "observer_fraction": 0.5, - # "ambient_light_config": { - # "num_levels": 5, - # "radius": 1, - # }, "lighthouse_height": 0.25, "lemma_text": "distance product = 2", } @@ -2448,8 +2521,7 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): "num_lighthouses": 9, "circle_radius": 2.75, "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - "add_lights_in_foreground": False, - # "add_lights_in_foreground": True, + "add_lights_in_foreground": False, # Keep this way "text_scale_val": 0.7, "observer_fraction": 0.5, } @@ -2464,6 +2536,7 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): self.introduce_observers() self.write_distance_product_fraction() self.break_down_distance_product_by_parts() + self.grow_circle_and_N() self.show_limit_for_each_fraction() def place_lighthouses(self): @@ -2673,7 +2746,7 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): self.play( ReplacementTransform(keeper_lines.copy(), sailor_lines), ReplacementTransform(keeper_line_lengths.copy(), sailor_line_lengths), - FadeOut(keeper_dp_column), + FadeOut(keeper_dp_column[:-1]), FadeOut(new_keeper_dp_decimal), ) self.wait() @@ -2699,7 +2772,7 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): result_fraction = self.result_fraction result_fraction_rect = SurroundingRectangle(result_fraction) - product_parts = TexMobject( + product_parts = self.product_parts = TexMobject( "{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot", "{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot", "{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdots", @@ -2712,22 +2785,12 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): product_parts.next_to(result_fraction, DOWN, LARGE_BUFF, RIGHT) product_parts.shift(MED_SMALL_BUFF * RIGHT) - center = self.circle.get_center() - lighthouse_labels = VGroup() - for count, point in enumerate(self.get_lh_points()): - if count > self.num_lighthouses / 2: - count -= self.num_lighthouses - label = TexMobject("L_{%d}" % count) - label.scale(0.8) - label.move_to(center + 1.15 * (point - center)) - # label.move_to(center + 0.87 * (point - center)) - lighthouse_labels.add(label) + lighthouse_labels = self.get_light_labels() + self.lighthouse_labels = lighthouse_labels - sailor_lines = self.get_distance_lines(self.get_sailor_point()) - sailor_lines.set_stroke(BLUE_C, 3) + sailor_lines = self.get_sailor_lines() sailor_lines.save_state() - keeper_lines = self.get_distance_lines(self.get_keeper_point()) - keeper_lines.set_stroke(YELLOW, 3) + keeper_lines = self.get_keeper_lines() keeper_lines.save_state() sailor_length_braces = VGroup(VMobject()) # Add fluff first object @@ -2742,11 +2805,6 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): brace.match_color(part) brace_group.add(brace) - term_rects = VGroup(*[ - SurroundingRectangle(product_parts[i:i + 3], color=WHITE) - for i in [0, 4, 8] - ]) - # Animations self.remove(self.lights[0], self.lighthouses[0]) if self.add_lights_in_foreground: @@ -2764,8 +2822,8 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): keeper_lines.restore() self.wait() - keeper_line = keeper_lines[1].copy() - sailor_line = sailor_lines[1].copy() + keeper_line = self.keeper_line = keeper_lines[1].copy() + sailor_line = self.sailor_line = sailor_lines[1].copy() keeper_brace = keeper_length_braces[1].copy() sailor_brace = sailor_length_braces[1].copy() self.play( @@ -2787,6 +2845,7 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): Transform(sailor_line, sailor_lines[i]), Transform(sailor_brace, sailor_length_braces[i]), ) + self.wait() for i in range(4, self.num_lighthouses): anims = [ Transform(keeper_line, keeper_lines[i]), @@ -2802,18 +2861,360 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): self.play(ShowPassingFlash(result_fraction_rect)) self.wait(3) - # Analyze first distance + def grow_circle_and_N(self, circle_scale_factor=2, N_multiple=3): + circle = self.circle + lights = self.lights + labels = self.lighthouse_labels + keeper_line = self.keeper_line + sailor_line = self.sailor_line + keeper = self.keeper + sailor = self.sailor + half_N = self.num_lighthouses / 2 + + circle.generate_target() + for pi in keeper, sailor: + for mob in pi, pi.dot, pi.title: + mob.generate_target() + + circle.target.scale(circle_scale_factor) + circle.target.move_to(FRAME_WIDTH * LEFT / 2 + SMALL_BUFF * RIGHT) + self.circle = circle.target + self.num_lighthouses = int(N_multiple * self.num_lighthouses) + new_lights = self.get_lights() + for light in new_lights: + light.scale(1.0 / circle_scale_factor) + new_labels = self.get_light_labels() + self.keeper_lines = self.get_keeper_lines() + self.sailor_lines = self.get_sailor_lines() + + for group in lights, labels, new_lights, new_labels: + group[0].fade(1) + + keeper.dot.target.move_to(self.get_keeper_point()) + sailor.dot.target.move_to(self.get_sailor_point()) + for pi in keeper, sailor: + pi.target.scale(0) + pi.target.move_to(pi.dot.target) + pi.title.target.scale(0.85) + pi.title.target.next_to(pi.dot.target, RIGHT, SMALL_BUFF) + + self.circle = circle + + for mob in lights, labels: + for x in range(len(new_lights) - len(mob)): + mob.submobjects.insert(half_N + 1, VectorizedPoint(circle.get_left())) + + light_anim = ReplacementTransform(lights, new_lights) + self.play( + MoveToTarget(circle), + Transform(keeper_line, self.keeper_lines[-1]), + Transform(sailor_line, self.sailor_lines[-1]), + ReplacementTransform(labels, new_labels), + light_anim, + *[ + MoveToTarget(part) + for pi in self.observers + for part in pi, pi.dot, pi.title + ], + run_time=2 + ) + if self.add_lights_in_foreground: + self.remove_foreground_mobjects(*self.lights) + self.remove_foreground_mobjects(*self.lighthouse_labels) + self.add_foreground_mobjects(new_lights, new_labels) + self.wait() + self.lights = new_lights + self.lighthouse_labels = new_labels + + def show_limit_for_each_fraction(self): + product_parts = self.product_parts + keeper_line = self.keeper_line + keeper_lines = self.keeper_lines + sailor_line = self.sailor_line + sailor_lines = self.sailor_lines + labels = self.lighthouse_labels + + center = self.circle.get_center() + center_dot = Dot(center) + lh_points = self.get_lh_points() + sailor_point = self.get_sailor_point() + keeper_point = self.get_keeper_point() + + def get_angle_mob(p1, p2): + angle1 = angle_of_vector(p1 - center) + angle2 = angle_of_vector(p2 - center) + arc = Arc(start_angle=angle1, angle=(angle2 - angle1), radius=1) + arc.shift(center) + return VGroup( + center_dot, + Line(center, p1), + Line(center, p2), + arc, + ) + + angle_mob = get_angle_mob(lh_points[1], keeper_point) + + ratios = VGroup(*[ + product_parts[i:i + 3] + for i in [0, 4, 8] + ]) + term_rects = self.get_term_rects(ratios) + + limit_fractions = VGroup( + TexMobject("{2", "\\over", "1}"), + TexMobject("{4", "\\over", "3}"), + TexMobject("{6", "\\over", "5}"), + ) + limit_arrows = VGroup() + for rect, fraction in zip(term_rects, limit_fractions): + fraction.next_to(rect, DOWN, LARGE_BUFF) + arrow = Arrow(rect, fraction, color=WHITE) + limit_arrows.add(arrow) + + approx = TexMobject("\\approx") + approx.scale(1.5) + approx.rotate(90 * DEGREES) + approx.move_to(limit_arrows[0]) + + # Show first lighthouse + term_rect = term_rects[0].copy() self.play( Transform(keeper_line, keeper_lines[1]), Transform(sailor_line, sailor_lines[1]), - FadeIn(term_rects[0]), + FadeIn(term_rect), + path_arc=-180 * DEGREES + ) + self.wait(2) + self.play(*map(ShowCreation, angle_mob)) + self.wait() + self.play(Transform(angle_mob, get_angle_mob(lh_points[1], sailor_point))) + self.wait(2) + self.play( + Write(approx), + ReplacementTransform(ratios[0].copy(), limit_fractions[0]), + FadeOut(angle_mob) + ) + self.wait() + self.play(ReplacementTransform(approx, limit_arrows[0])) + self.let_N_approach_infinity() + + # Show second lighthouse + self.play( + Transform(keeper_line, keeper_lines[2]), + Transform(sailor_line, sailor_lines[2]), + Transform(term_rect, term_rects[1]), + ) + angle_mob = get_angle_mob(lh_points[2], keeper_point) + self.play(*map(ShowCreation, angle_mob)) + self.wait() + self.play(Transform( + angle_mob, get_angle_mob(lh_points[2], sailor_point) + )) + self.wait() + self.play( + GrowArrow(limit_arrows[1]), + ReplacementTransform(ratios[1].copy(), limit_fractions[1]) + ) + self.wait() + + # Show third lighthouse + self.play( + Transform(keeper_line, keeper_lines[3]), + Transform(sailor_line, sailor_lines[3]), + Transform(term_rect, term_rects[2]), + FadeOut(angle_mob) + ) + self.play( + GrowArrow(limit_arrows[2]), + ReplacementTransform(ratios[2].copy(), limit_fractions[2]) + ) + self.let_N_approach_infinity() + self.wait() + + # Set up for lighthouse "before" keeper + ccw_product_group = VGroup(product_parts, limit_arrows, limit_fractions) + cw_product_parts = TexMobject( + "\\cdots", "{|L_{-3} - K|", "\\over", "|L_{-3} - S|}", + "\\cdot", "{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", + "\\cdot", "{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", + ) + cw_product_parts.match_height(product_parts) + cw_product_parts.set_color_by_tex_to_color_map({ + "K": YELLOW, + "S": BLUE, + }) + cw_product_parts.move_to(ratios, RIGHT) + cw_ratios = VGroup(*[cw_product_parts[i:i + 3] for i in 9, 5, 1]) + cw_term_rects = self.get_term_rects(cw_ratios) + cw_limit_fractions = VGroup( + TexMobject("{2", "\\over", "1}"), + TexMobject("{4", "\\over", "3}"), + TexMobject("{6", "\\over", "5}"), + ) + cw_limit_arrows = VGroup() + for rect, fraction in zip(cw_term_rects, cw_limit_fractions): + fraction.next_to(rect, DOWN, LARGE_BUFF) + arrow = Arrow(rect, fraction, color=WHITE) + cw_limit_arrows.add(arrow) + + cw_product_parts.save_state() + cw_product_parts.next_to(product_parts, RIGHT, LARGE_BUFF) + + cw_label_rects = self.get_term_rects(labels[-1:-5:-1]) + cw_label_rects.set_color(RED) + + # Animated clockwise-from-keeper terms + self.play( + ccw_product_group.scale, 0.7, {"about_edge": UL}, + ccw_product_group.to_edge, LEFT, + FadeOut(term_rect), + cw_product_parts.restore, + ) + term_rect = cw_term_rects[0].copy() + self.play(LaggedStart(ShowCreationThenDestruction, cw_label_rects)) + self.wait() + self.play( + Transform(keeper_line, keeper_lines[-1]), + Transform(sailor_line, sailor_lines[-1]), + FadeIn(term_rect) ) - def show_limit_for_each_fraction(self): - pass - # + def let_N_approach_infinity(self, factor=3.5, run_time=5, zoom_in_after=False): + lights = self.lights + labels = self.lighthouse_labels + keeper, sailor = self.observers + keeper_line = self.keeper_line + sailor_line = self.sailor_line + circle = self.circle + + start_fraction = 1.0 / self.num_lighthouses + target_fraction = start_fraction / factor + half_N = self.num_lighthouses / 2 + + fraction_tracker = ValueTracker(start_fraction) + + def get_fraction(): + return fraction_tracker.get_value() + + def get_ks_distance(): + return np.linalg.norm(keeper.dot.get_center() - sailor.dot.get_center()) + + def update_title_heights(*titles): + for title in titles: + if not hasattr(title, "original_height"): + title.original_height = title.get_height() + title.scale_to_fit_height(min( + title.original_height, + 0.8 * get_ks_distance(), + )) + if len(titles) > 1: + return titles + else: + return titles[0] + + initial_light_width = lights[0].get_width() + + def update_lights(lights): + for k in range(-half_N, half_N + 1): + if k == 0: + continue + light = lights[k] + light = light.scale_to_fit_width( + (get_fraction() / start_fraction) * initial_light_width + ) + point = self.get_circle_point_at_proportion(k * get_fraction()) + light.move_source_to(point) + return lights + + light_update_anim = UpdateFromFunc(lights, update_lights) + label_update_anim = UpdateFromFunc( + labels, + lambda ls: self.position_labels_outside_lights(update_title_heights(*ls)), + ) + sailor_dot_anim = UpdateFromFunc( + sailor.dot, + lambda d: d.move_to(self.get_circle_point_at_proportion(get_fraction() / 2)) + ) + sailor_title_anim = UpdateFromFunc( + sailor.title, + lambda m: update_title_heights(m).next_to(sailor.dot, RIGHT, SMALL_BUFF) + ) + keeper_title_anim = UpdateFromFunc( + keeper.title, + lambda m: update_title_heights(m).next_to(keeper.dot, RIGHT, SMALL_BUFF) + ) + + center = self.circle.get_center() + keeper_line_end_angle = angle_of_vector(keeper_line.get_end() - center) + keeper_line_end_mult = (keeper_line_end_angle / TAU) / get_fraction() + sailor_line_end_angle = angle_of_vector(sailor_line.get_end() - center) + sailor_line_end_mult = (sailor_line_end_angle / TAU) / get_fraction() + keeper_line_update = UpdateFromFunc( + keeper_line, + lambda l: l.put_start_and_end_on( + keeper.dot.get_center(), + self.get_circle_point_at_proportion( + keeper_line_end_mult * get_fraction() + ) + ) + ) + sailor_line_update = UpdateFromFunc( + sailor_line, + lambda l: l.put_start_and_end_on( + sailor.dot.get_center(), + self.get_circle_point_at_proportion( + sailor_line_end_mult * get_fraction() + ) + ) + ) + + lights[0].fade(1) + labels[0].fade(1) + + movers = VGroup( + circle, lights, labels, + sailor.dot, sailor.title, + keeper.dot, keeper.title, + sailor_line, keeper_line, + ) + movers.save_state() + + all_updates = [ + light_update_anim, + label_update_anim, + sailor_dot_anim, + sailor_title_anim, + keeper_title_anim, + sailor_line_update, + keeper_line_update, + ] + + self.play( + fraction_tracker.set_value, target_fraction, + *all_updates, + run_time=run_time + ) + if zoom_in_after: + self.play( + circle.scale, factor, {"about_point": circle.get_right()}, + *all_updates, + run_time=1 + ) + self.wait() + self.play( + circle.scale, 1.0 / factor, {"about_point": circle.get_right()}, + *all_updates, + run_time=1 + ) + self.wait() + self.play( + fraction_tracker.set_value, start_fraction, + *all_updates, + run_time=run_time / 2 + ) + def get_keeper_point(self): return self.get_circle_point_at_proportion(0) @@ -2829,7 +3230,43 @@ class KeeperAndSailor(DistanceProductScene, PiCreatureScene): sailor.shift(4 * RIGHT + 2 * UP) return VGroup(keeper, sailor) - + def get_light_labels(self): + labels = VGroup() + for count, point in enumerate(self.get_lh_points()): + if count > self.num_lighthouses / 2: + count -= self.num_lighthouses + label = TexMobject("L_{%d}" % count) + label.scale(0.8) + labels.add(label) + self.position_labels_outside_lights(labels) + return labels + + def position_labels_outside_lights(self, labels): + center = self.circle.get_center() + for light, label in zip(self.lights, labels): + point = light[0].get_center() + vect = (point - center) + norm = np.linalg.norm(vect) + buff = label.get_height() + vect *= (norm + buff) / norm + label.move_to(center + vect) + return labels + + def get_keeper_lines(self, line_class=Line): + lines = self.get_distance_lines(self.get_keeper_point()) + lines.set_stroke(YELLOW, 3) + return lines + + def get_sailor_lines(self, line_class=Line): + lines = self.get_distance_lines(self.get_sailor_point()) + lines.set_stroke(BLUE, 3) + return lines + + def get_term_rects(self, terms): + return VGroup(*[ + SurroundingRectangle(term, color=WHITE) + for term in terms + ])