diff --git a/active_projects/wallis_g.py b/active_projects/wallis_g.py index cf90cefa..d595a1de 100644 --- a/active_projects/wallis_g.py +++ b/active_projects/wallis_g.py @@ -4,23 +4,238 @@ from once_useful_constructs.light import Lighthouse from once_useful_constructs.light import SwitchOn # from once_useful_constructs.light import LightSource +PRODUCT_COLOR = BLUE +CHEAP_AMBIENT_LIGHT_CONFIG = { + "num_levels": 5, + "radius": 0.5, +} -class IntroduceDistanceProduct(MovingCameraScene): + +class DistanceProductScene(MovingCameraScene): CONFIG = { "ambient_light_config": { - "opacity_function": inverse_quadratic(1, 1.1, 1), + "opacity_function": inverse_power_law(1, 1.5, 1, 4), "num_levels": 100, + "light_radius": 5, + "max_opacity": 0.8, + "color": PRODUCT_COLOR, + }, + "circle_color": BLUE, + "circle_radius": 3, + "num_lighthouses": 6, + "lighthouse_height": 0.5, + "ignored_lighthouse_indices": [], + "observer_config": { + "color": MAROON_B, + "mode": "pondering", + "height": 0.25, + "flip_at_start": True, + }, + "observer_fraction": 1.0 / 3, + "d_label_height": 0.35, + "numeric_distance_label_height": 0.25, + "default_product_column_top": FRAME_WIDTH * RIGHT / 4 + 1.5 * UP, + } + + def setup(self): + super(DistanceProductScene, self).setup() + self.circle = Circle( + color=self.circle_color, + radius=self.circle_radius, + ) + + def get_circle_point_at_proportion(self, alpha): + radius = self.get_radius() + center = self.circle.get_center() + angle = alpha * TAU + unit_circle_point = np.cos(angle) * RIGHT + np.sin(angle) * UP + return radius * unit_circle_point + center + + def get_lh_points(self): + return np.array([ + self.get_circle_point_at_proportion(fdiv(i, self.num_lighthouses)) + for i in range(self.num_lighthouses) + if i not in self.ignored_lighthouse_indices + ]) + + def get_observer_point(self, fraction=None): + if fraction is None: + fraction = self.observer_fraction + return self.get_circle_point_at_proportion(fraction / self.num_lighthouses) + + def get_observer(self): + observer = self.observer = PiCreature(**self.observer_config) + observer.next_to(self.get_observer_point(), RIGHT, buff=SMALL_BUFF) + return observer + + def get_observer_dot(self): + self.observer_dot = Dot( + self.get_observer_point(), + color=self.observer_config["color"] + ) + return self.observer_dot + + def get_lighthouses(self): + self.lighthouses = VGroup() + for point in self.get_lh_points(): + lighthouse = Lighthouse() + lighthouse.scale_to_fit_height(self.lighthouse_height) + lighthouse.move_to(point) + self.lighthouses.add(lighthouse) + return self.lighthouses + + def get_lights(self): + self.lights = VGroup() + for point in self.get_lh_points(): + light = AmbientLight( + source_point=VectorizedPoint(point), + **self.ambient_light_config + ) + self.lights.add(light) + return self.lights + + def get_distance_lines(self): + self.distance_lines = VGroup(*[ + Line(self.get_observer_point(), point) + for point in self.get_lh_points() + ]) + return self.distance_lines + + def get_symbolic_distance_labels(self): + if not hasattr(self, "distance_lines"): + self.get_distance_lines() + self.d_labels = VGroup() + for i, line in enumerate(self.distance_lines): + d_label = TexMobject("d_%d" % i) + d_label.scale_to_fit_height(self.d_label_height) + vect = rotate_vector(line.get_vector(), 90 * DEGREES) + vect *= 2.5 * SMALL_BUFF / np.linalg.norm(vect) + d_label.move_to(line.get_center() + vect) + self.d_labels.add(d_label) + return self.d_labels + + def get_numeric_distance_labels(self, num_decimal_points=3, show_ellipsis=True): + radius = self.circle.get_width() / 2 + if not hasattr(self, "distance_lines"): + self.get_distance_lines() + labels = self.numeric_distance_labels = VGroup() + for line in self.distance_lines: + label = DecimalNumber( + line.get_length() / radius, + num_decimal_points=num_decimal_points, + show_ellipsis=show_ellipsis, + ) + label.scale_to_fit_height(self.numeric_distance_label_height) + max_width = 0.5 * line.get_length() + if label.get_width() > max_width: + label.scale_to_fit_width(max_width) + angle = (line.get_angle() % TAU) - TAU / 2 + if np.abs(angle) > TAU / 4: + angle += np.sign(angle) * np.pi + label.angle = angle + label.next_to(line.get_center(), UP, SMALL_BUFF) + label.rotate(angle, about_point=line.get_center()) + labels.add(label) + return labels + + def get_circle_group(self): + group = VGroup(self.circle) + if not hasattr(self, "observer_dot"): + self.get_observer_dot() + if not hasattr(self, "observer"): + self.get_observer() + if not hasattr(self, "lighthouses"): + self.get_lighthouses() + if not hasattr(self, "lights"): + self.get_lights() + group.add( + self.observer_dot, + self.observer, + self.lighthouses, + self.lights, + ) + return group + + def setup_lighthouses_and_observer(self): + self.add(*self.get_circle_group()) + + # Numerical results + + def get_radius(self): + return self.circle.get_width() / 2.0 + + def get_distance_product(self, fraction=None): + radius = self.get_radius() + observer_point = self.get_observer_point(fraction) + distances = [ + np.linalg.norm(point - observer_point) / radius + for point in self.get_lh_points() + ] + return reduce(op.mul, distances, 1.0) + + # Animating methods + + def add_numeric_distance_labels(self, show_line_creation=True): + anims = [] + if not hasattr(self, "distance_lines"): + self.get_distance_lines() + if not hasattr(self, "numeric_distance_labels"): + self.get_numeric_distance_labels() + if show_line_creation: + anims.append(LaggedStart(ShowCreation, self.distance_lines)) + anims.append(LaggedStart(FadeIn, self.numeric_distance_labels)) + + 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) + + self.play(ReplacementTransform(labels.copy(), stacked_labels)) + self.play( + ShowCreation(h_line), + Write(times) + ) + self.play( + ReplacementTransform( + stacked_labels.copy(), + VGroup(product_decimal) + ) + ) + + +class IntroduceDistanceProduct(DistanceProductScene): + CONFIG = { + "ambient_light_config": { # "num_levels": 10, - "light_radius": 10, # "radius": 1, - "max_opacity": 0.4, "color": YELLOW, }, - "num_lighthouses": 6, - "circle_radius": 3, - "observer_color": MAROON_B, - "lighthouse_height": 0.5, - "camera_class": MovingCamera, } def construct(self): @@ -29,12 +244,10 @@ class IntroduceDistanceProduct(MovingCameraScene): self.show_sum_of_inverse_squares() def draw_circle_with_points(self): - circle = Circle(color=BLUE) - circle.scale(self.circle_radius) + circle = self.circle lh_dots = self.lh_dots = VGroup(*[ - Dot().move_to(circle.point_from_proportion(alpha)) - for alpha in np.arange(0, 1, 1.0 / self.num_lighthouses) + Dot(point) for point in self.get_lh_points() ]) lh_dot_arrows = VGroup(*[ Arrow(*[ @@ -47,8 +260,7 @@ class IntroduceDistanceProduct(MovingCameraScene): evenly_space_dots_label.scale_to_fit_width(0.5 * circle.get_width()) evenly_space_dots_label.move_to(circle) - special_dot = self.special_dot = Dot(color=self.observer_color) - special_dot.move_to(circle.point_from_proportion(0.04)) + special_dot = self.special_dot = self.get_observer_dot() special_dot_arrow = Vector(DL) special_dot_arrow.next_to(special_dot, UR, SMALL_BUFF) special_dot_arrow.match_color(special_dot) @@ -77,24 +289,13 @@ class IntroduceDistanceProduct(MovingCameraScene): self.play(FadeOut(VGroup(special_dot_arrow, special_dot_label))) def turn_into_lighthouses_and_observer(self): - lighthouses = self.lighthouses = VGroup() - lights = self.lights = VGroup() - for dot in self.lh_dots: - point = dot.get_center() - lighthouse = Lighthouse() - lighthouse.scale_to_fit_height(self.lighthouse_height) - lighthouse.move_to(point) - lighthouses.add(lighthouse) + lighthouses = self.get_lighthouses() + lights = self.get_lights() - light = AmbientLight( - source_point=VectorizedPoint(point), - **self.ambient_light_config - ) - lights.add(light) - - observer = self.observer = PiCreature(color=self.observer_color) - observer.flip() - observer.move_to(self.special_dot) + observer = self.get_observer() + observer.save_state() + observer.scale_to_fit_height(2) + observer.change_mode("happy") observer.to_edge(RIGHT) self.play( @@ -104,23 +305,12 @@ class IntroduceDistanceProduct(MovingCameraScene): ) self.wait() self.play(FadeIn(observer)) - self.play( - observer.scale_to_fit_height, 0.25, - observer.next_to, self.special_dot, RIGHT, 0.5 * SMALL_BUFF, - ) + self.play(observer.restore) self.wait() def show_sum_of_inverse_squares(self): - lines = VGroup(*[ - Line(self.special_dot.get_center(), dot.get_center()) - for dot in self.lh_dots - ]) - labels = VGroup(*[TexMobject("d_%d" % i) for i in range(len(lines))]) - for label, line in zip(labels, lines): - label.scale(0.75) - vect = rotate_vector(line.get_vector(), TAU / 4) - vect *= 2 * SMALL_BUFF / np.linalg.norm(vect) - label.move_to(line.get_center() + vect) + lines = self.get_distance_lines() + labels = self.get_symbolic_distance_labels() sum_of_inverse_squares = TexMobject(*it.chain(*[ ["{1", "\\over", "(", "d_%d" % i, ")", "^2}", "+"] @@ -182,7 +372,7 @@ class IntroduceDistanceProduct(MovingCameraScene): labels[:-1].copy(), d_terms[:-1], ), - circle_group.scale, 0.8, {"about_edge": DOWN} + circle_group.scale, 0.8, {"about_point": FRAME_Y_RADIUS * DOWN} ) self.wait() self.play(LaggedStart( @@ -195,9 +385,10 @@ class IntroduceDistanceProduct(MovingCameraScene): # Mention useful just to basel problem circle_group.save_state() + v_point = VectorizedPoint(FRAME_X_RADIUS * LEFT + FRAME_Y_RADIUS * DOWN) self.play( - circle_group.scale, 0.5, - circle_group.to_corner, DL, + circle_group.next_to, v_point, UR, {"submobject_to_align": self.circle}, + circle_group.scale, 0.5, {"about_point": v_point.get_center()}, ) self.play( GrowFromCenter(brace), @@ -230,7 +421,9 @@ class IntroduceDistanceProduct(MovingCameraScene): wallis_product.next_to, basel_sum, UP, {"aligned_edge": RIGHT}, ) self.play( - d_terms.shift, d_terms.get_height() * UP / 2, + d_terms.shift, 0.75 * d_terms.get_height() * UP, + d_terms.set_color, PRODUCT_COLOR, + light_rings.set_fill, PRODUCT_COLOR, *[ FadeOut(mob) for mob in sum_of_inverse_squares @@ -240,7 +433,7 @@ class IntroduceDistanceProduct(MovingCameraScene): self.wait() self.play( FadeOut(plusses), - d_terms.arrange_submobjects, RIGHT, 0.5 * SMALL_BUFF, + d_terms.arrange_submobjects, RIGHT, 0.25 * SMALL_BUFF, d_terms.move_to, sum_of_inverse_squares, DOWN, ) self.wait() @@ -268,6 +461,567 @@ class IntroduceDistanceProduct(MovingCameraScene): self.wait() +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", + } + + def construct(self): + self.add_title() + self.add_circle_group() + self.state_lemma_premise() + self.show_product() + + def add_title(self): + title = self.title = TextMobject("Two lemmas:") + title.set_color(YELLOW) + title.to_edge(UP, buff=MED_SMALL_BUFF) + self.add(title) + + def add_circle_group(self): + self.circle.to_corner(DL) + circle_group = self.get_circle_group() + self.play(LaggedStart(FadeIn, VGroup(*circle_group.family_members_with_points()))) + + def state_lemma_premise(self): + premise = TextMobject("Lemma 1: If observer is halfway between lighthouses,") + self.premise = premise + premise.next_to(self.title, DOWN) + + frac = 1.0 / self.num_lighthouses + arc1, arc2 = arcs = VGroup(VMobject(), VMobject()) + arc1.pointwise_become_partial(self.circle, 0, frac / 2) + arc2.pointwise_become_partial(self.circle, frac / 2, frac) + arc1.reverse_points() + arcs.set_stroke(YELLOW, 5) + show_arcs = ShowCreationThenDestruction( + arcs, + submobject_mode="all_at_once", + run_time=2, + ) + + self.play(Write(premise), show_arcs, run_time=2) + self.wait() + self.play(show_arcs) + self.wait() + + def show_product(self): + lemma = TextMobject(self.lemma_text) + lemma.set_color(BLUE) + lemma.next_to(self.premise, DOWN) + self.add_numeric_distance_labels() + self.play(Write(lemma, run_time=1)) + self.show_distance_product_in_column() + self.wait() + + +class Lemma1With7Lighthouses(Lemma1): + CONFIG = { + "num_lighthouses": 7, + } + + +class Lemma1With8Lighthouses(Lemma1): + CONFIG = { + "num_lighthouses": 8, + } + + +class Lemma1With9Lighthouses(Lemma1): + CONFIG = { + "num_lighthouses": 9, + } + + +class Lemma2(Lemma1): + CONFIG = { + # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + "lemma_text": "distance product = \\# Initial lighthouses" + } + + def construct(self): + self.add_title() + self.add_circle_group() + self.state_lemma_premise() + self.replace_first_lighthouse() + self.show_product() + + def state_lemma_premise(self): + premise = self.premise = TextMobject( + "If the observer replaces a lighthouse," + ) + premise.next_to(self.title, DOWN) + + self.play(Write(premise, run_time=1)) + + def replace_first_lighthouse(self): + dot = self.observer_dot + observer_anim = MaintainPositionRelativeTo(self.observer, dot) + lighthouse_group = VGroup(self.lighthouses[0], self.lights[0]) + point = self.get_lh_points()[0] + + self.play( + lighthouse_group.shift, 5 * RIGHT, + lighthouse_group.fade, 1, + run_time=1.5, + rate_func=running_start, + remover=True, + ) + self.play( + dot.move_to, point, + observer_anim, + path_arc=(-120 * DEGREES), + ) + self.wait() + + self.ignored_lighthouse_indices = [0] + self.observer_fraction = 0 + for group in self.lighthouses, self.lights: + self.lighthouses.submobjects.pop(0) + + +class Lemma2With7Lighthouses(Lemma2): + CONFIG = { + "num_lighthouses": 7, + } + + +class Lemma2With8Lighthouses(Lemma2): + CONFIG = { + "num_lighthouses": 8, + } + + +class Lemma2With9Lighthouses(Lemma2): + CONFIG = { + "num_lighthouses": 9, + } + + +class FromGeometryToAlgebra(DistanceProductScene): + CONFIG = { + "num_lighthouses": 7, + # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, + } + + def construct(self): + self.setup_lights() + self.point_out_evenly_spaced() + self.transition_to_complex_plane() + self.discuss_powers() + self.raise_everything_to_the_nth() + + def setup_lights(self): + circle = self.circle + circle.scale_to_fit_height(5, about_edge=DOWN) + 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(circle, lights) + + def point_out_evenly_spaced(self): + circle = self.circle + step = 1.0 / self.num_lighthouses / 2 + alpha_range = np.arange(0, 1 + step, step) + arcs = VGroup(*[ + VMobject().pointwise_become_partial(circle, a1, a2) + for a1, a2 in zip(alpha_range, alpha_range[1:]) + ]) + arcs.set_stroke(YELLOW, 5) + + for arc in arcs[::2]: + arc.reverse_points() + + arcs_anim = ShowCreationThenDestruction( + arcs, submobject_mode="all_at_once", run_time=2 + ) + + spacing_words = self.spacing_words = TextMobject("Evenly-spaced") + spacing_words.scale_to_fit_width(self.get_radius()) + spacing_words.move_to(circle) + + arrows = self.get_arrows() + + geometric_words = self.geometric_words = TextMobject("Geometric property") + geometric_words.to_edge(UP) + geometric_words.add_background_rectangle() + + self.add(geometric_words) + self.play( + FadeIn(spacing_words), + arcs_anim, + *map(GrowArrow, arrows) + ) + self.play(FadeOut(arrows), arcs_anim) + self.wait() + + def transition_to_complex_plane(self): + plane = self.complex_plane = ComplexPlane( + unit_size=2, y_radius=6, x_radius=9, + ) + plane.shift(1.5 * RIGHT) + plane.add_coordinates() + origin = plane.number_to_point(0) + h_line = Line(plane.number_to_point(-1), plane.number_to_point(1)) + + circle = self.circle + circle_group = VGroup(circle, self.lights) + circle_group.generate_target() + circle_group.target.scale(h_line.get_width() / circle.get_width()) + circle_group.target.shift( + origin - circle_group.target[0].get_center() + ) + circle_group.target[0].set_stroke(RED) + + geometric_words = self.geometric_words + geometric_words.generate_target() + arrow = TexMobject("\\rightarrow") + arrow.add_background_rectangle() + algebraic_words = TextMobject("Algebraic property") + algebraic_words.add_background_rectangle() + word_group = VGroup(geometric_words.target, arrow, algebraic_words) + word_group.arrange_submobjects(RIGHT) + word_group.move_to(origin) + word_group.to_edge(UP) + + unit_circle_words = TextMobject("Unit circle", "") + unit_circle_words.match_color(circle_group.target[0]) + for part in unit_circle_words: + part.add_background_rectangle() + unit_circle_words.next_to(origin, UP) + + complex_plane_words = TextMobject("Complex Plane") + self.complex_plane_words = complex_plane_words + complex_plane_words.move_to(word_group) + complex_plane_words.add_background_rectangle() + + roots_of_unity_words = TextMobject("Roots of\\\\", "unity") + roots_of_unity_words.move_to(origin) + roots_of_unity_words.set_color(YELLOW) + for part in roots_of_unity_words: + part.add_background_rectangle() + + self.play( + Write(plane), + MoveToTarget(circle_group), + FadeOut(self.spacing_words), + MoveToTarget(geometric_words), + FadeIn(arrow), + FadeIn(algebraic_words), + ) + word_group.submobjects[0] = geometric_words + self.play(Write(unit_circle_words, run_time=1)) + + # Show complex values + outer_arrows = self.outer_arrows = self.get_arrows() + for arrow, point in zip(outer_arrows, self.get_lh_points()): + arrow.rotate(np.pi, about_point=point) + outer_arrow = self.outer_arrow = outer_arrows[3].copy() + + values = map(plane.point_to_number, self.get_lh_points()) + complex_decimal = self.complex_decimal = DecimalNumber( + values[3], + num_decimal_points=3, + include_background_rectangle=True + ) + complex_decimal.next_to(outer_arrow.get_start(), LEFT, SMALL_BUFF) + complex_decimal_rect = SurroundingRectangle(complex_decimal) + complex_decimal_rect.fade(1) + + self.play( + FadeIn(complex_plane_words), + FadeOut(word_group), + FadeIn(complex_decimal), + FadeIn(outer_arrow) + ) + self.wait(2) + self.play( + ChangeDecimalToValue( + complex_decimal, values[1], + tracked_mobject=complex_decimal_rect + ), + complex_decimal_rect.next_to, outer_arrows[1].get_start(), UP, SMALL_BUFF, + Transform(outer_arrow, outer_arrows[1]), + run_time=1.5 + ) + self.wait() + + arrows = self.get_arrows() + arrows.set_color(YELLOW) + self.play( + ReplacementTransform(unit_circle_words, roots_of_unity_words), + LaggedStart(GrowArrow, arrows) + ) + self.wait() + self.play( + complex_plane_words.move_to, word_group, + LaggedStart(FadeOut, VGroup(*it.chain( + arrows, roots_of_unity_words + ))) + ) + + # Turn decimal into z + x_term = self.x_term = TexMobject("x") + x_term.add_background_rectangle() + x_term.move_to(complex_decimal, DOWN) + x_term.shift(0.5 * SMALL_BUFF * (DR)) + self.play(ReplacementTransform(complex_decimal, x_term)) + + def discuss_powers(self): + x_term = self.x_term + outer_arrows = self.outer_arrows + outer_arrows.add(outer_arrows[0].copy()) + plane = self.complex_plane + origin = plane.number_to_point(0) + + question = TextMobject("What is $x^2$") + question.next_to(x_term, RIGHT, LARGE_BUFF) + question.set_color(YELLOW) + + lh_points = list(self.get_lh_points()) + lh_points.append(lh_points[0]) + lines = VGroup(*[ + Line(origin, point) + for point in lh_points + ]) + lines.set_color(GREEN) + step = 1.0 / self.num_lighthouses + angle_arcs = VGroup(*[ + Arc(angle=alpha * TAU, radius=0.35).shift(origin) + for alpha in np.arange(0, 1 + step, step) + ]) + angle_labels = VGroup() + for i, arc in enumerate(angle_arcs): + label = TexMobject("(%d / %d)\\tau" % (i, self.num_lighthouses)) + label.scale(0.5) + label.add_background_rectangle() + point = arc.point_from_proportion(0.5) + point += 1.2 * (point - origin) + label.move_to(point) + angle_labels.add(label) + + line = self.angle_line = lines[1].copy() + line_ghost = DashedLine(line.get_start(), line.get_end()) + self.ghost_angle_line = line_ghost + line_ghost.set_stroke(line.get_color(), 2) + angle_arc = angle_arcs[1].copy() + angle_label = angle_labels[1].copy() + angle_label.shift(0.25 * SMALL_BUFF * DR) + + magnitude_label = TexMobject("1") + magnitude_label.next_to(line.get_center(), UL, buff=SMALL_BUFF) + + power_labels = VGroup() + for i, arrow in enumerate(outer_arrows): + label = TexMobject("x^%d" % i) + label.next_to( + arrow.get_start(), -arrow.get_vector(), + submobject_to_align=label[0] + ) + label.add_background_rectangle() + power_labels.add(label) + power_labels[-1].next_to(outer_arrows[-1].get_start(), UR, SMALL_BUFF) + power_labels.submobjects[1] = x_term + + L_labels = self.L_labels = VGroup(*[ + TexMobject("L_%d" % i).move_to(power_label, DOWN).add_background_rectangle() + for i, power_label in enumerate(power_labels) + ]) + + # Ask about squaring + self.play(Write(question)) + self.wait() + self.play( + ShowCreation(line), + Write(magnitude_label) + ) + self.wait() + self.play( + ShowCreation(angle_arc), + Write(angle_label) + ) + self.wait() + self.add(line_ghost) + for i in range(2, self.num_lighthouses + 1): + anims = [ + Transform(angle_arc, angle_arcs[i]), + Transform(angle_label, angle_labels[i]), + Transform(line, lines[i], path_arc=TAU / self.num_lighthouses), + ] + if i == 2: + anims.append(FadeOut(magnitude_label)) + if i == 3: + anims.append(FadeOut(question)) + self.play(*anims) + new_anims = [ + GrowArrow(outer_arrows[i]), + Write(power_labels[i]), + ] + if i == 2: + new_anims.append(FadeOut(self.complex_plane_words)) + self.play(*new_anims) + self.wait() + self.play(ReplacementTransform(power_labels[1:], L_labels[1:])) + self.wait() + self.play( + Rotate(self.lights, TAU / self.num_lighthouses / 2), + rate_func=wiggle + ) + self.wait() + self.play( + FadeOut(angle_arc), + FadeOut(angle_label), + *map(ShowCreationThenDestruction, lines) + ) + self.wait() + + def raise_everything_to_the_nth(self): + func_label = TexMobject("L \\rightarrow L^7") + func_label.set_color(YELLOW) + func_label.to_corner(UL, buff=LARGE_BUFF) + func_label.add_background_rectangle() + + polynomial_scale_factor = 0.8 + + polynomial = TexMobject("x^%d - 1" % self.num_lighthouses, "=", "0") + polynomial.scale(polynomial_scale_factor) + polynomial.next_to(func_label, UP) + polynomial.to_edge(LEFT) + + factored_polynomial = TexMobject( + "(x-L_1)(x-L_2)\\cdots(x-L_%d)" % self.num_lighthouses, "=", "0" + ) + factored_polynomial.scale(polynomial_scale_factor) + factored_polynomial.next_to(polynomial, DOWN, aligned_edge=LEFT) + for group in polynomial, factored_polynomial: + for part in group: + part.add_background_rectangle() + + origin = self.complex_plane.number_to_point(0) + + lights = self.lights + lights.save_state() + rotations = [] + for i, light in enumerate(lights): + rotations.append(Rotating( + light, + radians=(i * TAU - i * TAU / self.num_lighthouses), + about_point=origin, + rate_func=bezier([0, 0, 1, 1]), + )) + + 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 += [ + FadeIn(polynomial), + ShowCreationThenDestruction(rect) + ] + self.play(*anims, run_time=np.sqrt(i + 1)) + self.play(*rotations[4:], run_time=3) + self.wait() + + self.play(lights.restore) + self.play( + FadeOut(func_label), + FadeIn(factored_polynomial) + ) + self.wait(3) + self.play( + factored_polynomial[0].next_to, polynomial[1], RIGHT, 1.5 * SMALL_BUFF, + FadeOut(polynomial[2]), + FadeOut(factored_polynomial[1:]), + ) + + # Comment on formula + formula = VGroup(polynomial[0], polynomial[1], factored_polynomial[0]) + rect = SurroundingRectangle(formula) + + brace = Brace(factored_polynomial[0], DOWN) + brace2 = Brace(polynomial[0], DOWN) + + morty = PiCreature(color=GREY_BROWN) + morty.scale(0.5) + morty.next_to(brace.get_center(), DL, buff=LARGE_BUFF) + + L1_rhs = TexMobject("= \\cos(\\tau / 7) + \\\\", "\\sin(\\tau / 7)i") + L1_rhs.next_to(self.L_labels[1], RIGHT, aligned_edge=UP) + for part in L1_rhs: + part.add_background_rectangle() + + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + self.wait() + self.play(GrowFromCenter(brace)) + self.play(FadeIn(morty)) + self.play(morty.change, "horrified", brace) + self.play(Blink(morty)) + self.wait() + self.play( + Write(L1_rhs), + morty.change, "confused", L1_rhs + ) + self.play(Blink(morty)) + self.wait() + self.play( + Transform(brace, brace2), + morty.change, "hooray", brace2 + ) + self.play(Blink(morty)) + self.wait() + + # Nothing special about 7 + new_lights = self.lights.copy() + new_lights.rotate( + TAU / self.num_lighthouses / 2, + about_point=origin + ) + sevens = VGroup(polynomial[0][1][1], factored_polynomial[0][1][-2]) + n_terms = VGroup() + for seven in sevens: + n_term = TexMobject("N") + n_term.replace(seven, dim_to_match=1) + n_term.scale(0.9) + n_term.shift(0.25 * SMALL_BUFF * DR) + n_terms.add(n_term) + + self.play(LaggedStart(FadeOut, VGroup(*it.chain( + L1_rhs, self.outer_arrows, self.L_labels[1:], self.outer_arrow, + self.angle_line, self.ghost_angle_line + )))) + self.play(LaggedStart(SwitchOn, new_lights), morty.look_at, new_lights) + self.play(Transform(sevens, n_terms)) + self.wait() + self.play(Blink(morty)) + self.wait() + # + + def get_arrows(self): + return VGroup(*[ + Arrow( + interpolate(self.circle.get_center(), point, 0.6), + interpolate(self.circle.get_center(), point, 0.9), + buff=0 + ) + for point in self.get_lh_points() + ]) + + + + + diff --git a/animation/rotation.py b/animation/rotation.py index 6b92e5b6..044cba64 100644 --- a/animation/rotation.py +++ b/animation/rotation.py @@ -12,7 +12,7 @@ from utils.config_ops import digest_config class Rotating(Animation): CONFIG = { "axis": OUT, - "radians": 2 * np.pi, + "radians": TAU, "run_time": 5, "rate_func": None, "in_place": True, @@ -25,10 +25,7 @@ class Rotating(Animation): def update_mobject(self, alpha): Animation.update_mobject(self, alpha) - about_point = None - if self.about_point is not None: - self.about_point = about_point - elif self.in_place: # This is superseeded + if self.in_place and self.about_point is None: self.about_point = self.mobject.get_center() self.mobject.rotate( alpha * self.radians, diff --git a/mobject/mobject.py b/mobject/mobject.py index c42dc1fc..00bc8502 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -255,6 +255,7 @@ class Mobject(Container): def apply_points_function_about_point(self, func, about_point=None, about_edge=ORIGIN): if about_point is None: + assert(about_edge is not None) about_point = self.get_critical_point(about_edge) for mob in self.family_members_with_points(): mob.points -= about_point diff --git a/scene/reconfigurable_scene.py b/scene/reconfigurable_scene.py index f2a0c323..789f9156 100644 --- a/scene/reconfigurable_scene.py +++ b/scene/reconfigurable_scene.py @@ -8,6 +8,9 @@ from constants import * class ReconfigurableScene(Scene): + """ + Note, this seems to no longer work as intented. + """ CONFIG = { "allow_recursion": True, }