From 5ac3004ae288b36d6b455446a51fe679dbdd1508 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 11 Apr 2018 12:10:35 -0700 Subject: [PATCH 1/7] Small additions to Wallis --- active_projects/wallis_g.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index d595a1de..5e928337 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -555,7 +555,7 @@ class Lemma2(Lemma1): def state_lemma_premise(self): premise = self.premise = TextMobject( - "If the observer replaces a lighthouse," + "Lemma 2: If the observer replaces a lighthouse," ) premise.next_to(self.title, DOWN) @@ -921,15 +921,17 @@ class FromGeometryToAlgebra(DistanceProductScene): self.play(Write(func_label, run_time=1)) for i, rotation in enumerate(rotations[:4]): - anims = [rotation] if i == 3: rect = SurroundingRectangle(polynomial) rect.set_color(YELLOW) - anims += [ + self.play( FadeIn(polynomial), ShowCreationThenDestruction(rect) - ] - self.play(*anims, run_time=np.sqrt(i + 1)) + ) + self.play( + rotation, + run_time=np.sqrt(i + 1) + ) self.play(*rotations[4:], run_time=3) self.wait() @@ -1042,12 +1044,6 @@ class FromGeometryToAlgebra(DistanceProductScene): - - - - - - From 1a186dca3b70031bb615214f0f78702a264d1b2c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 11 Apr 2018 17:12:45 -0700 Subject: [PATCH 2/7] Made coordinate_labels an attribute of ComplexPlane --- mobject/coordinate_systems.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobject/coordinate_systems.py b/mobject/coordinate_systems.py index bb6f49ef..d2f01b60 100644 --- a/mobject/coordinate_systems.py +++ b/mobject/coordinate_systems.py @@ -352,5 +352,6 @@ class ComplexPlane(NumberPlane): return result def add_coordinates(self, *numbers): - self.add(*self.get_coordinate_labels(*numbers)) + self.coordinate_labels = self.get_coordinate_labels(*numbers) + self.add(self.coordinate_labels) return self From b1887dc3f92e538faadb06a19f64aaa91d83f15b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 11 Apr 2018 17:13:39 -0700 Subject: [PATCH 3/7] Removed specialized insert_n_anchor_points for Line --- mobject/geometry.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/mobject/geometry.py b/mobject/geometry.py index 79896fa4..46bce6f7 100644 --- a/mobject/geometry.py +++ b/mobject/geometry.py @@ -427,17 +427,6 @@ class Line(VMobject): self.shift(new_start - self.get_start()) return self - def insert_n_anchor_points(self, n): - if not self.path_arc: - n_anchors = self.get_num_anchor_points() - new_num_points = 3 * (n_anchors + n) - 2 - self.points = np.array([ - self.point_from_proportion(alpha) - for alpha in np.linspace(0, 1, new_num_points) - ]) - else: - VMobject.insert_n_anchor_points(self, n) - class DashedLine(Line): CONFIG = { From 3798e4550124b6c2d2444c82fd361e7520895831 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 11 Apr 2018 17:14:02 -0700 Subject: [PATCH 4/7] added TexMobject.add_background_rectangle_to_parts --- mobject/svg/tex_mobject.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index b28e7c9b..2fbe11f3 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -198,7 +198,7 @@ class TexMobject(SVGMobject): split_self = self.split() if part not in split_self: raise Exception("Trying to get index of part not in TexMobject") - return self.split().index(part) + return split_self.index(part) def index_of_part_by_tex(self, tex, **kwargs): part = self.get_part_by_tex(tex, **kwargs) @@ -226,6 +226,11 @@ class TexMobject(SVGMobject): self.submobjects = [self.background_rectangle, letters] return self + def add_background_rectangle_to_parts(self): + for part in self: + part.add_background_rectangle() + return self + class TextMobject(TexMobject): CONFIG = { From 5886f4746e36b2409fb4c0696628831f0f7d582c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 11 Apr 2018 17:14:12 -0700 Subject: [PATCH 5/7] PlugObserverIntoPolynomial --- active_projects/wallis_g.py | 458 +++++++++++++++++++++++++++++++++++- 1 file changed, 447 insertions(+), 11 deletions(-) diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index 5e928337..3f223dd8 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -5,16 +5,18 @@ from once_useful_constructs.light import SwitchOn # from once_useful_constructs.light import LightSource PRODUCT_COLOR = BLUE +DEFAULT_OPACITY_FUNCTION = inverse_power_law(1, 1.5, 1, 4) CHEAP_AMBIENT_LIGHT_CONFIG = { "num_levels": 5, - "radius": 0.5, + "radius": 0.25, + "opacity_function": DEFAULT_OPACITY_FUNCTION, } class DistanceProductScene(MovingCameraScene): CONFIG = { "ambient_light_config": { - "opacity_function": inverse_power_law(1, 1.5, 1, 4), + "opacity_function": DEFAULT_OPACITY_FUNCTION, "num_levels": 100, "light_radius": 5, "max_opacity": 0.8, @@ -1021,15 +1023,449 @@ class FromGeometryToAlgebra(DistanceProductScene): ]) - - - - - - - - - +class PlugObserverIntoPolynomial(DistanceProductScene): + CONFIG = { + # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + "num_lighthouses": 7, + } + + def construct(self): + self.add_plane() + self.add_circle_group() + self.label_roots() + self.add_polynomial() + self.point_out_rhs() + self.introduce_observer() + self.raise_observer_to_the_N() + + def add_plane(self): + plane = self.plane = ComplexPlane( + unit_size=2, + y_radius=5, + ) + plane.shift(DOWN) + plane.add_coordinates() + plane.coordinate_labels.submobjects.pop(-4) + self.origin = plane.number_to_point(0) + + self.add(plane) + + def add_circle_group(self): + self.circle.set_color(RED) + self.circle.scale_to_fit_width( + 2 * np.linalg.norm(self.plane.number_to_point(1) - self.origin) + ) + self.circle.move_to(self.origin) + + lights = self.lights = self.get_lights() + dots = VGroup(*[ + Dot(point) for point in self.get_lh_points() + ]) + for dot, light in zip(dots, lights): + light.add_to_back(dot) + + self.add(self.circle, lights) + + def label_roots(self): + origin = self.origin + labels = VGroup(*[ + TexMobject("L_%d" % d) + for d in range(self.num_lighthouses) + ]) + self.root_labels = labels + points = self.get_lh_points() + for label, point in zip(labels, points): + label.move_to(interpolate(origin, point, 1.2)) + labels[0].align_to(origin, UP) + labels[0].shift(SMALL_BUFF * DOWN) + self.add(labels) + + def add_polynomial(self): + self.polynomial = self.get_polynomial_equation("x") + self.add(self.polynomial) + + def point_out_rhs(self): + rhs = self.get_polynomial_rhs(self.polynomial) + brace = Brace(rhs, DOWN, buff=SMALL_BUFF) + brace_text = brace.get_text("Useful for distance product", buff=SMALL_BUFF) + brace_text.set_color(YELLOW) + brace_text.add_background_rectangle() + + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.wait() + self.play(FadeOut(VGroup(brace, brace_text))) + + def introduce_observer(self): + dot = self.observer_dot = Dot() + dot.move_to(self.plane.coords_to_point(1.6, 0.8)) + observer = PiCreature(**self.observer_config) + observer.move_to(dot) + dot.match_color(observer) + + vect = 2 * DOWN + LEFT + vect /= np.linalg.norm(vect) + arrow = self.arrow = Vector(0.5 * vect) + arrow.next_to(observer, -vect, buff=SMALL_BUFF) + arrow.set_color(WHITE) + + full_name = TextMobject("Observer") + var_name = self.var_name = TexMobject("O") + for mob in full_name, var_name: + mob.match_color(observer) + mob.next_to(arrow.get_start(), UP, SMALL_BUFF) + mob.add_background_rectangle() + + complex_decimal = DecimalNumber(0, include_background_rectangle=True) + equals = TexMobject("=") + complex_decimal_animation = ChangingDecimal( + complex_decimal, + lambda a: self.plane.point_to_number(dot.get_center()), + position_update_func=lambda m: m.next_to(equals, RIGHT, SMALL_BUFF) + ) + complex_decimal_animation.update(0) + equals_decimal = VGroup(equals, complex_decimal) + equals_decimal.next_to(var_name, RIGHT) + + new_polynomial = self.get_polynomial_equation("O") + O_terms = new_polynomial.get_parts_by_tex("O") + + lhs, poly_eq, rhs = self.get_polynomial_split(new_polynomial) + lhs_rect = SurroundingRectangle(lhs, color=YELLOW) + rhs_rect = SurroundingRectangle(rhs, color=YELLOW) + self.lhs, self.rhs = lhs, rhs + self.lhs_rect, self.rhs_rect = lhs_rect, rhs_rect + + lines = self.lines = self.get_lines() + lines_update = self.lines_update = UpdateFromFunc( + lines, lambda l: Transform(l, self.get_lines()).update(1) + ) + + anims_for_dot_movement = self.anims_for_dot_movement = [ + MaintainPositionRelativeTo(arrow, dot), + MaintainPositionRelativeTo(var_name, arrow), + MaintainPositionRelativeTo(equals, var_name), + complex_decimal_animation, + lines_update, + ] + + self.play( + FadeInAndShiftFromDirection(observer, direction=-vect), + GrowArrow(arrow) + ) + self.play(Write(full_name)) + self.wait() + self.play( + ReplacementTransform(full_name[0], var_name[0]), + ReplacementTransform(full_name[1][0], var_name[1][0]), + FadeOut(full_name[1][1:]), + ReplacementTransform(observer, dot), + FadeIn(equals_decimal) + ) + self.add_foreground_mobject(dot) + + # Substitute + self.wait() + self.play( + ReplacementTransform(var_name.copy(), O_terms), + ReplacementTransform(self.polynomial, new_polynomial) + ) + self.polynomial = new_polynomial + self.wait() + + # Show distances + self.play(ShowCreation(rhs_rect)) + self.play( + LaggedStart(ShowCreation, lines), + Animation(dot) + ) + + self.play( + Rotating( + dot, + radians=TAU, + rate_func=smooth, + about_point=dot.get_center() + MED_LARGE_BUFF * LEFT, + run_time=4 + ), + *anims_for_dot_movement + ) + self.wait() + + self.remove(rhs_rect) + self.play(ReplacementTransform(rhs_rect.copy(), lhs_rect)) + self.wait() + + # Move onto circle + angle = self.observer_angle = TAU / self.num_lighthouses / 3.0 + target_point = self.plane.number_to_point( + np.exp(complex(0, angle)) + ) + self.play( + dot.move_to, target_point, + *anims_for_dot_movement + ) + self.play(FadeOut(VGroup( + equals, complex_decimal, + var_name, arrow, + ))) + + def raise_observer_to_the_N(self): + dot = self.observer_dot + origin = self.origin + radius = self.get_radius() + + text_scale_val = 0.8 + + question = TextMobject( + "What fraction \\\\", "between $L_0$ and $L_1$", "?", + arg_separator="" + ) + question.scale(text_scale_val) + question.next_to(dot, RIGHT) + question.add_background_rectangle_to_parts() + + f_words = TextMobject("$f$", "of the way") + third_words = TextMobject("$\\frac{1}{3}$", "of the way") + for words in f_words, third_words: + words.scale(text_scale_val) + words.move_to(question[0]) + words[0].set_color(YELLOW) + words.add_background_rectangle() + + obs_angle = self.observer_angle + full_angle = TAU / self.num_lighthouses + + def get_arc(angle): + result = Arc(angle=angle, radius=radius, color=YELLOW, stroke_width=4) + result.shift(origin) + return result + + arc = get_arc(obs_angle) + O_to_N_arc = get_arc(obs_angle * self.num_lighthouses) + + O_to_N_dot = dot.copy().move_to(O_to_N_arc.point_from_proportion(1)) + O_to_N_arrow = Vector(0.5 * DR).next_to(O_to_N_dot, UL, SMALL_BUFF) + O_to_N_arrow.set_color(WHITE) + O_to_N_label = TexMobject("O", "^N") + O_to_N_label.set_color_by_tex("O", dot.get_color()) + O_to_N_label.next_to(O_to_N_arrow.get_start(), UP, SMALL_BUFF) + O_to_N_label.shift(SMALL_BUFF * RIGHT) + O_to_N_group = VGroup(O_to_N_arc, O_to_N_arrow, O_to_N_label) + + around_circle_words = TextMobject("around the circle") + around_circle_words.scale(text_scale_val) + around_circle_words.add_background_rectangle() + around_circle_words.next_to(self.circle.get_top(), UR) + + chord = Line(O_to_N_dot.get_center(), self.circle.get_right()) + chord.set_stroke(GREEN) + + chord_halves = VGroup( + Line(chord.get_center(), chord.get_start()), + Line(chord.get_center(), chord.get_end()), + ) + chord_halves.set_stroke(WHITE, 5) + + chord_label = TexMobject("|", "O", "^N", "-", "1", "|") + chord_label.set_color_by_tex("O", MAROON_B) + chord_label.add_background_rectangle() + chord_label.next_to(chord.get_center(), DOWN, SMALL_BUFF) + chord_label.rotate( + chord.get_angle(), about_point=chord.get_center() + ) + + numeric_chord_label = DecimalNumber( + np.sqrt(3), + num_decimal_points=3, + # include_background_rectangle=True, + show_ellipsis=True, + ) + numeric_chord_label.set_stroke(BLACK, 1) + numeric_chord_label.rotate(chord.get_angle()) + numeric_chord_label.move_to(chord_label) + + self.play( + FadeIn(question), + ShowCreation(arc), + ) + for angle in [full_angle - obs_angle, -full_angle, obs_angle]: + last_angle = angle_of_vector(dot.get_center() - origin) + self.play( + self.lines_update, + UpdateFromAlphaFunc( + arc, lambda arc, a: Transform( + arc, get_arc(last_angle + a * angle) + ).update(1) + ), + Rotate(dot, angle, about_point=origin), + run_time=2 + ) + self.play( + FadeOut(question[0]), + FadeOut(question[2]), + FadeIn(f_words), + ) + self.wait() + self.play( + FadeOut(self.lines), + FadeOut(self.root_labels), + ) + self.play( + ReplacementTransform(dot.copy(), O_to_N_dot), + ReplacementTransform(arc, O_to_N_arc), + path_arc=O_to_N_arc.angle - arc.angle, + ) + self.add_foreground_mobject(O_to_N_dot) + self.play( + FadeIn(O_to_N_label), + GrowArrow(O_to_N_arrow), + ) + self.wait() + self.play( + FadeOut(question[1]), + f_words.next_to, around_circle_words, UP, SMALL_BUFF, + FadeIn(around_circle_words) + ) + self.wait() + self.play( + FadeIn(chord_label[0]), + ReplacementTransform(self.lhs.copy(), chord_label[1]), + ShowCreation(chord) + ) + self.wait() + + # Talk through current example + light_rings = VGroup(*it.chain(self.lights)) + self.play(LaggedStart( + ApplyMethod, light_rings, + lambda m: (m.shift, MED_SMALL_BUFF * UP), + rate_func=wiggle + )) + self.play( + FadeOut(around_circle_words), + FadeIn(question[1]), + ReplacementTransform(f_words, third_words) + ) + self.play( + Rotate(dot, 0.05 * TAU, about_point=origin, rate_func=wiggle) + ) + self.wait(2) + self.play(ReplacementTransform(dot.copy(), O_to_N_dot, path_arc=TAU / 3)) + self.play( + third_words.next_to, around_circle_words, UP, SMALL_BUFF, + FadeIn(around_circle_words), + FadeOut(question[1]) + ) + self.wait() + self.play(Indicate(self.lhs)) + for x in range(2): + self.play(ShowCreationThenDestruction(chord_halves)) + self.play( + FadeOut(chord_label), + FadeIn(numeric_chord_label) + ) + self.wait() + self.remove(self.lhs_rect) + self.play( + FadeOut(chord), + FadeOut(numeric_chord_label), + FadeOut(O_to_N_group), + FadeIn(self.lines), + ReplacementTransform(self.lhs_rect.copy(), self.rhs_rect) + ) + self.wait() + + # Add new lights + for light in self.lights: + light[1:].fade(0.5) + added_lights = self.lights.copy() + added_lights.rotate(full_angle / 2, about_point=origin) + new_lights = VGroup(*it.chain(*zip(self.lights, added_lights))) + self.num_lighthouses *= 2 + dot.generate_target() + dot.target.move_to(self.get_circle_point_at_proportion( + obs_angle / TAU / 2 + )) + dot.save_state() + dot.move_to(dot.target) + new_lines = self.get_lines() + dot.restore() + + self.play(Transform(self.lights, new_lights)) + self.play( + MoveToTarget(dot), + Transform(self.lines, new_lines) + ) + self.wait() + self.play( + third_words.next_to, question[1], UP, SMALL_BUFF, + FadeOut(around_circle_words), + FadeIn(question[1]), + ) + self.wait() + self.play( + FadeIn(chord), + FadeIn(chord_label), + ) + self.wait() + + # Helpers + + def get_polynomial_equation(self, var="x", color=None): + if color is None: + color = self.observer_config["color"] + equation = TexMobject( + "\\left(", var, "^N", "-", "1", "\\right)", "=", + "\\left(", var, "-", "L_0", "\\right)", + "\\left(", var, "-", "L_1", "\\right)", + "\\cdots", + "\\left(", var, "-", "L_{N-1}", "\\right)", + ) + equation.set_color_by_tex(var, color) + equation.to_edge(UP) + equation.add_background_rectangle() + return equation + + def get_polynomial_rhs(self, polynomial): + return self.get_polynomial_split(polynomial)[2] + + def get_polynomial_lhs(self, polynomial): + return self.get_polynomial_split(polynomial)[0] + + def get_polynomial_split(self, polynomial): + eq = polynomial.get_part_by_tex("=") + i = polynomial[1].submobjects.index(eq) + return polynomial[1][:i], polynomial[1][i], polynomial[1][i + 1:] + + def get_lines(self): + dot = self.observer_dot + lines = VGroup(*[ + DashedLine(dot.get_center(), point) + for point in self.get_lh_points() + ]) + lines.set_stroke(width=2) + return lines + + +class PlugObserverIntoPolynomial5Lighthouses(PlugObserverIntoPolynomial): + CONFIG = { + "num_lighthouses": 5, + } + + +class PlugObserverIntoPolynomial3Lighthouses(PlugObserverIntoPolynomial): + CONFIG = { + "num_lighthouses": 3, + } + + +class PlugObserverIntoPolynomial2Lighthouses(PlugObserverIntoPolynomial): + CONFIG = { + "num_lighthouses": 2, + } From aa5e1df5b274a453cd166a01e1222032599698f2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 12 Apr 2018 15:11:45 -0700 Subject: [PATCH 6/7] Added GrowFromEdge --- animation/creation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/animation/creation.py b/animation/creation.py index a0417b80..5a0a82fd 100644 --- a/animation/creation.py +++ b/animation/creation.py @@ -188,6 +188,13 @@ class GrowFromCenter(GrowFromPoint): GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs) +class GrowFromEdge(GrowFromPoint): + def __init__(self, mobject, edge, **kwargs): + GrowFromPoint.__init__( + self, mobject, mobject.get_critical_point(edge), **kwargs + ) + + class GrowArrow(GrowFromPoint): def __init__(self, arrow, **kwargs): GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs) From 60d7a0ef2fe971f4272e68438d1710fd55a26d91 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 12 Apr 2018 15:12:00 -0700 Subject: [PATCH 7/7] DistanceProductIsChordF --- active_projects/wallis_g.py | 462 ++++++++++++++++++++++++++++++++---- 1 file changed, 416 insertions(+), 46 deletions(-) diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index 3f223dd8..64a67fbf 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -13,6 +13,21 @@ CHEAP_AMBIENT_LIGHT_CONFIG = { } +def get_chord_f_label(chord, arg="f", direction=DOWN): + chord_f = TextMobject("Chord(", "$%s$" % arg, ")", arg_separator="") + chord_f.set_color_by_tex("$%s$" % arg, YELLOW) + chord_f.add_background_rectangle() + chord_f.next_to(chord.get_center(), direction, SMALL_BUFF) + angle = ((chord.get_angle() + TAU / 2) % TAU) - TAU / 2 + if np.abs(angle) > TAU / 4: + angle += TAU / 2 + chord_f.rotate(angle, about_point=chord.get_center()) + chord_f.angle = angle + return chord_f + +# Scenes + + class DistanceProductScene(MovingCameraScene): CONFIG = { "ambient_light_config": { @@ -37,6 +52,7 @@ class DistanceProductScene(MovingCameraScene): "d_label_height": 0.35, "numeric_distance_label_height": 0.25, "default_product_column_top": FRAME_WIDTH * RIGHT / 4 + 1.5 * UP, + "include_lighthouses": True, } def setup(self): @@ -126,6 +142,7 @@ class DistanceProductScene(MovingCameraScene): line.get_length() / radius, num_decimal_points=num_decimal_points, show_ellipsis=show_ellipsis, + include_background_rectangle=True, ) label.scale_to_fit_height(self.numeric_distance_label_height) max_width = 0.5 * line.get_length() @@ -140,6 +157,37 @@ class DistanceProductScene(MovingCameraScene): labels.add(label) return labels + def get_distance_product_column(self, column_top): + if not hasattr(self, "numeric_distance_labels"): + self.get_numeric_distance_labels() + if column_top is None: + column_top = self.default_product_column_top + labels = self.numeric_distance_labels + stacked_labels = labels.copy() + for label in stacked_labels: + label.rotate(-label.angle) + label.scale_to_fit_height(self.numeric_distance_label_height) + stacked_labels.arrange_submobjects(DOWN) + stacked_labels.move_to(column_top, UP) + + h_line = Line(LEFT, RIGHT) + h_line.scale_to_fit_width(1.5 * stacked_labels.get_width()) + h_line.next_to(stacked_labels, DOWN, aligned_edge=RIGHT) + times = TexMobject("\\times") + times.next_to(h_line, UP, SMALL_BUFF, aligned_edge=LEFT) + + product_decimal = DecimalNumber( + self.get_distance_product(), + num_decimal_points=3, + show_ellipsis=True, + include_background_rectangle=True, + ) + product_decimal.scale_to_fit_height(self.numeric_distance_label_height) + product_decimal.next_to(h_line, DOWN) + product_decimal.align_to(stacked_labels, RIGHT) + product_decimal.set_color(BLUE) + return VGroup(stacked_labels, h_line, times, product_decimal) + def get_circle_group(self): group = VGroup(self.circle) if not hasattr(self, "observer_dot"): @@ -150,12 +198,10 @@ class DistanceProductScene(MovingCameraScene): self.get_lighthouses() if not hasattr(self, "lights"): self.get_lights() - group.add( - self.observer_dot, - self.observer, - self.lighthouses, - self.lights, - ) + group.add(self.observer_dot, self.observer) + if self.include_lighthouses: + group.add(self.lighthouses) + group.add(self.lights) return group def setup_lighthouses_and_observer(self): @@ -189,34 +235,9 @@ class DistanceProductScene(MovingCameraScene): self.play(*anims) - def show_distance_product_in_column(self, column_top=None): - if not hasattr(self, "numeric_distance_labels"): - self.get_numeric_distance_labels() - if column_top is None: - column_top = self.default_product_column_top - labels = self.numeric_distance_labels - stacked_labels = labels.copy() - for label in stacked_labels: - label.rotate(-label.angle) - label.scale_to_fit_height(self.numeric_distance_label_height) - stacked_labels.arrange_submobjects(DOWN) - stacked_labels.move_to(column_top, UP) - - h_line = Line(LEFT, RIGHT) - h_line.scale_to_fit_width(1.5 * stacked_labels.get_width()) - h_line.next_to(stacked_labels, DOWN, aligned_edge=RIGHT) - times = TexMobject("\\times") - times.next_to(h_line, UP, SMALL_BUFF, aligned_edge=LEFT) - - product_decimal = DecimalNumber( - self.get_distance_product(), - num_decimal_points=3, - show_ellipsis=True - ) - product_decimal.scale_to_fit_height(self.numeric_distance_label_height) - product_decimal.next_to(h_line, DOWN) - product_decimal.align_to(stacked_labels, RIGHT) - product_decimal.set_color(BLUE) + def show_distance_product_in_column(self, **kwargs): + group = self.get_distance_product_column(**kwargs) + stacked_labels, h_line, times, product_decimal = group self.play(ReplacementTransform(labels.copy(), stacked_labels)) self.play( @@ -831,7 +852,7 @@ class FromGeometryToAlgebra(DistanceProductScene): ) label.add_background_rectangle() power_labels.add(label) - power_labels[-1].next_to(outer_arrows[-1].get_start(), UR, SMALL_BUFF) + power_labels[0].next_to(outer_arrows[-1].get_start(), UR, SMALL_BUFF) power_labels.submobjects[1] = x_term L_labels = self.L_labels = VGroup(*[ @@ -853,7 +874,7 @@ class FromGeometryToAlgebra(DistanceProductScene): ) self.wait() self.add(line_ghost) - for i in range(2, self.num_lighthouses + 1): + for i in range(2, self.num_lighthouses) + [0]: anims = [ Transform(angle_arc, angle_arcs[i]), Transform(angle_label, angle_labels[i]), @@ -872,7 +893,7 @@ class FromGeometryToAlgebra(DistanceProductScene): new_anims.append(FadeOut(self.complex_plane_words)) self.play(*new_anims) self.wait() - self.play(ReplacementTransform(power_labels[1:], L_labels[1:])) + self.play(ReplacementTransform(power_labels, L_labels)) self.wait() self.play( Rotate(self.lights, TAU / self.num_lighthouses / 2), @@ -900,7 +921,7 @@ class FromGeometryToAlgebra(DistanceProductScene): polynomial.to_edge(LEFT) factored_polynomial = TexMobject( - "(x-L_1)(x-L_2)\\cdots(x-L_%d)" % self.num_lighthouses, "=", "0" + "(x-L_0)(x-L_1)\\cdots(x-L_{%d - 1})" % self.num_lighthouses, "=", "0" ) factored_polynomial.scale(polynomial_scale_factor) factored_polynomial.next_to(polynomial, DOWN, aligned_edge=LEFT) @@ -992,7 +1013,7 @@ class FromGeometryToAlgebra(DistanceProductScene): TAU / self.num_lighthouses / 2, about_point=origin ) - sevens = VGroup(polynomial[0][1][1], factored_polynomial[0][1][-2]) + sevens = VGroup(polynomial[0][1][1], factored_polynomial[0][1][-4]) n_terms = VGroup() for seven in sevens: n_term = TexMobject("N") @@ -1002,7 +1023,7 @@ class FromGeometryToAlgebra(DistanceProductScene): n_terms.add(n_term) self.play(LaggedStart(FadeOut, VGroup(*it.chain( - L1_rhs, self.outer_arrows, self.L_labels[1:], self.outer_arrow, + L1_rhs, self.outer_arrows, self.L_labels, self.outer_arrow, self.angle_line, self.ghost_angle_line )))) self.play(LaggedStart(SwitchOn, new_lights), morty.look_at, new_lights) @@ -1027,6 +1048,8 @@ class PlugObserverIntoPolynomial(DistanceProductScene): CONFIG = { # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, "num_lighthouses": 7, + # This makes it look slightly better, but renders much slower + "add_lights_in_foreground": True, } def construct(self): @@ -1065,6 +1088,8 @@ class PlugObserverIntoPolynomial(DistanceProductScene): light.add_to_back(dot) self.add(self.circle, lights) + if self.add_lights_in_foreground: + self.add_foreground_mobject(lights) def label_roots(self): origin = self.origin @@ -1080,8 +1105,8 @@ class PlugObserverIntoPolynomial(DistanceProductScene): labels[0].shift(SMALL_BUFF * DOWN) self.add(labels) - def add_polynomial(self): - self.polynomial = self.get_polynomial_equation("x") + def add_polynomial(self, arg="x"): + self.polynomial = self.get_polynomial_equation(arg) self.add(self.polynomial) def point_out_rhs(self): @@ -1279,11 +1304,10 @@ class PlugObserverIntoPolynomial(DistanceProductScene): numeric_chord_label = DecimalNumber( np.sqrt(3), - num_decimal_points=3, - # include_background_rectangle=True, + num_decimal_points=4, + include_background_rectangle=True, show_ellipsis=True, ) - numeric_chord_label.set_stroke(BLACK, 1) numeric_chord_label.rotate(chord.get_angle()) numeric_chord_label.move_to(chord_label) @@ -1406,9 +1430,13 @@ class PlugObserverIntoPolynomial(DistanceProductScene): FadeIn(question[1]), ) self.wait() + + chord_group = VGroup(chord, numeric_chord_label[1]) + chord_group.set_color(YELLOW) + self.add_foreground_mobjects(*chord_group) self.play( FadeIn(chord), - FadeIn(chord_label), + FadeIn(numeric_chord_label), ) self.wait() @@ -1468,6 +1496,348 @@ class PlugObserverIntoPolynomial2Lighthouses(PlugObserverIntoPolynomial): } +class DefineChordF(Scene): + def construct(self): + radius = 2.5 + + full_chord_f = TextMobject("``", "Chord(", "$f$", ")", "''", arg_separator="") + full_chord_f.set_color_by_tex("$f$", YELLOW) + full_chord_f.to_edge(UP) + chord_f = full_chord_f[1:-1] + chord_f.generate_target() + + circle = Circle(radius=2.5) + circle.set_color(RED) + radius_line = Line(circle.get_center(), circle.get_right()) + one_label = TexMobject("1") + one_label.next_to(radius_line, DOWN, SMALL_BUFF) + + chord = Line(*[circle.point_from_proportion(f) for f in [0, 1. / 3]]) + chord.set_color(YELLOW) + chord_third = TextMobject("Chord(", "$1/3$", ")", arg_separator="") + chord_third.set_color_by_tex("1/3", YELLOW) + for term in chord_third, chord_f.target: + term.next_to(chord.get_center(), UP, SMALL_BUFF) + chord_angle = chord.get_angle() + np.pi + term.rotate(chord_angle, about_point=chord.get_center()) + + brace = Brace(Line(ORIGIN, TAU * UP / 3), RIGHT, buff=0) + brace.generate_target() + brace.target.stretch(0.5, 0) + brace.target.apply_complex_function(np.exp) + VGroup(brace, brace.target).scale(radius) + brace.next_to(circle.get_right(), RIGHT, SMALL_BUFF, DOWN) + brace.scale(0.5, about_edge=DOWN) + brace.target.move_to(brace, DR) + brace.target.shift(2 * SMALL_BUFF * LEFT) + + f_label = TexMobject("f") + f_label.set_color(YELLOW) + point = circle.point_from_proportion(1.0 / 6) + f_label.move_to(point + 0.4 * (point - circle.get_center())) + + third_label = TexMobject("\\frac{1}{3}") + third_label.scale(0.7) + third_label.move_to(f_label) + third_label.match_color(f_label) + + alphas = np.linspace(0, 1, 4) + third_arcs = VGroup(*[ + VMobject().pointwise_become_partial(circle, a1, a2) + for a1, a2 in zip(alphas, alphas[1:]) + ]) + third_arcs.set_color_by_gradient(BLUE, PINK, GREEN) + + # Terms for sine formula + origin = circle.get_center() + height = DashedLine(origin, chord.get_center()) + half_chords = VGroup( + Line(chord.get_start(), chord.get_center()), + Line(chord.get_end(), chord.get_center()), + ) + half_chords.set_color_by_gradient(BLUE, PINK) + alt_radius_line = Line(origin, chord.get_end()) + alt_radius_line.set_color(WHITE) + angle_arc = Arc( + radius=0.3, + angle=TAU / 6, + ) + angle_arc.shift(origin) + angle_label = TexMobject("\\frac{f}{2}", "2\\pi") + angle_label[0][0].set_color(YELLOW) + angle_label.scale(0.6) + angle_label.next_to(angle_arc, RIGHT, SMALL_BUFF, DOWN) + angle_label.shift(SMALL_BUFF * UR) + + circle_group = VGroup( + circle, chord, radius_line, one_label, + brace, f_label, chord_f, + half_chords, height, + angle_arc, angle_label, + ) + + formula = TexMobject( + "= 2 \\cdot \\sin\\left(\\frac{f}{2} 2\\pi \\right)", + "= 2 \\cdot \\sin\\left(f \\pi \\right)", + ) + for part in formula: + part[7].set_color(YELLOW) + + # Draw circle and chord + self.add(radius_line, circle, one_label) + self.play(Write(full_chord_f)) + self.play(ShowCreation(chord)) + self.play( + MoveToTarget(chord_f), + FadeOut(VGroup(full_chord_f[0], full_chord_f[-1])) + ) + self.play(GrowFromEdge(brace, DOWN)) + self.play(MoveToTarget(brace, path_arc=TAU / 3)) + self.play(Write(f_label)) + self.wait(2) + + # Show third + self.remove(chord_f, f_label) + self.play( + ReplacementTransform(chord_f.copy(), chord_third), + ReplacementTransform(f_label.copy(), third_label), + ) + chord_copies = VGroup() + last_chord = chord + for color in PINK, BLUE: + chord_copy = last_chord.copy() + old_color = chord_copy.get_color() + self.play( + Rotate(chord_copy, -TAU / 6, about_point=last_chord.get_end()), + UpdateFromAlphaFunc( + chord_copy, + lambda m, a: m.set_stroke(interpolate_color(old_color, color, a)) + ) + ) + chord_copy.reverse_points() + last_chord = chord_copy + chord_copies.add(chord_copy) + self.wait() + self.play( + FadeOut(chord_copies), + ReplacementTransform(chord_third, chord_f), + ReplacementTransform(third_label, f_label), + ) + + # Show sine formula + top_chord_f = chord_f.copy() + top_chord_f.generate_target() + top_chord_f.target.rotate(-chord_angle) + top_chord_f.target.center().to_edge(UP, buff=LARGE_BUFF) + top_chord_f.target.shift(3 * LEFT) + formula.next_to(top_chord_f.target, RIGHT) + + self.play( + ShowCreation(height), + FadeIn(half_chords), + ShowCreation(angle_arc), + Write(angle_label) + ) + self.wait() + self.play( + MoveToTarget(top_chord_f), + circle_group.shift, 1.5 * DOWN, + ) + self.play(Write(formula[0], run_time=1)) + self.wait() + self.play(ReplacementTransform( + formula[0].copy(), formula[1], + path_arc=45 * DEGREES + )) + self.wait() + + +class DistanceProductIsChordF(PlugObserverIntoPolynomial): + CONFIG = { + "include_lighthouses": False, + "num_lighthouses": 8, + # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + # "add_lights_in_foreground": False, + } + + def construct(self): + self.add_plane() + self.add_circle_group() + self.add_polynomial("O") + self.add_observer_and_lines() + + def add_observer_and_lines(self): + fraction = self.observer_fraction = 0.3 + circle = self.circle + + O_dot = self.observer_dot = Dot() + O_dot.set_color(self.observer_config["color"]) + O_to_N_dot = O_dot.copy() + O_dot.move_to(self.get_circle_point_at_proportion(fraction / self.num_lighthouses)) + O_to_N_dot.move_to(self.get_circle_point_at_proportion(fraction)) + + for dot, vect, tex in [(O_dot, DL, "O"), (O_to_N_dot, DR, "O^N")]: + arrow = Vector(0.5 * vect, color=WHITE) + arrow.next_to(dot.get_center(), -vect, SMALL_BUFF) + label = TexMobject(tex) + O_part = label[0] + O_part.match_color(dot) + label.add_background_rectangle() + label.next_to(arrow.get_start(), -vect, buff=0, submobject_to_align=O_part) + dot.arrow = arrow + dot.label = label + self.add_foreground_mobject(dot) + self.add(arrow, label) + # For the transition to f = 1 / 2 + dot.generate_target() + + fraction_words = VGroup( + TextMobject("$f$", "of the way"), + TextMobject("between lighthouses") + ) + fraction_words.scale(0.8) + fraction_words[0][0].set_color(YELLOW) + fraction_words.arrange_submobjects(DOWN, SMALL_BUFF, aligned_edge=LEFT) + fraction_words.next_to(O_dot.label, RIGHT) + map(TexMobject.add_background_rectangle, fraction_words) + + f_arc, new_arc = [ + Arc( + angle=(TAU * f / self.num_lighthouses), + radius=self.get_radius(), + color=YELLOW, + ).shift(circle.get_center()) + for f in fraction, 0.5 + ] + self.add(f_arc) + + lines = self.lines = self.get_lines() + labels = self.get_numeric_distance_labels() + + black_rect = Rectangle(height=6, width=3.5) + black_rect.set_stroke(width=0) + black_rect.set_fill(BLACK, 1) + black_rect.to_corner(DL, buff=0) + colum_group = self.get_distance_product_column( + column_top=black_rect.get_top() + MED_SMALL_BUFF * DOWN + ) + stacked_labels, h_line, times, product_decimal = colum_group + + chord = Line(*[ + self.get_circle_point_at_proportion(f) + for f in 0, fraction + ]) + chord.set_stroke(YELLOW) + chord_f = get_chord_f_label(chord) + chord_f_as_product = chord_f.copy() + chord_f_as_product.generate_target() + chord_f_as_product.target.rotate(-chord_f_as_product.angle) + chord_f_as_product.target.scale(0.8) + chord_f_as_product.target.move_to(product_decimal, RIGHT) + + # Constructs for the case f = 1 / 2 + new_chord = Line(circle.get_right(), circle.get_left()) + new_chord.match_style(chord) + chord_half = get_chord_f_label(new_chord, "1/2") + + f_terms = VGroup(fraction_words[0][1][0], chord_f_as_product[1][1]) + half_terms = VGroup(*[ + TexMobject("\\frac{1}{2}").scale(0.6).set_color(YELLOW).move_to(f) + for f in f_terms + ]) + half_terms[1].move_to(chord_f_as_product.target[1][1]) + + O_dot.target.move_to(self.get_circle_point_at_proportion(0.5 / self.num_lighthouses)) + O_to_N_dot .target.move_to(circle.get_left()) + self.observer_dot = O_dot.target + new_lines = self.get_lines() + + changing_decimals = [] + radius = self.get_radius() + for decimal, line in zip(stacked_labels, new_lines): + changing_decimals.append( + ChangeDecimalToValue(decimal, line.get_length() / radius) + ) + + equals_two_terms = VGroup(*[ + TexMobject("=2").next_to(mob, DOWN, SMALL_BUFF) + for mob in chord_half, chord_f_as_product.target + ]) + + # Animations + + self.play(Write(fraction_words)) + self.wait() + self.play( + LaggedStart(ShowCreation, lines), + LaggedStart(FadeIn, labels), + ) + self.play( + FadeIn(black_rect), + ReplacementTransform(labels.copy(), stacked_labels), + ShowCreation(h_line), + Write(times), + ) + self.wait(2) + self.add_foreground_mobjects( + chord_f[1], chord, O_dot, O_to_N_dot + ) + self.play( + FadeOut(labels), + ShowCreation(chord), + FadeIn(chord_f), + ) + self.play(MoveToTarget(chord_f_as_product)) + self.wait(2) + + # Transition to f = 1 / 2 + self.play( + Transform(lines, new_lines), + Transform(f_arc, new_arc), + Transform(chord, new_chord), + chord_f.rotate, -chord_f.angle, + chord_f.move_to, chord_half, + MoveToTarget(O_dot), + MoveToTarget(O_to_N_dot), + MaintainPositionRelativeTo(O_dot.arrow, O_dot), + MaintainPositionRelativeTo(O_dot.label, O_dot), + MaintainPositionRelativeTo(O_to_N_dot.arrow, O_to_N_dot), + MaintainPositionRelativeTo(O_to_N_dot.label, O_to_N_dot), + *changing_decimals, + path_arc=(45 * DEGREES), + run_time=2 + ) + self.play( + Transform(chord_f, chord_half), + Transform(f_terms, half_terms), + ) + self.wait() + for term in equals_two_terms: + term.add_background_rectangle() + self.add_foreground_mobject(term[1]) + self.play( + Write(equals_two_terms) + ) + self.wait() + + + + + + + + + + + + + + + + + + +