from manimlib.imports import * from active_projects.diffyq.part2.fourier_series import FourierOfTrebleClef from active_projects.diffyq.part4.complex_functions import TRangingFrom0To1 from active_projects.diffyq.part4.complex_functions import SimpleComplexExponentExample class ComplexFourierSeriesExample(FourierOfTrebleClef): CONFIG = { "file_name": "EighthNote", "run_time": 10, "n_vectors": 200, "n_cycles": 2, "max_circle_stroke_width": 0.75, "drawing_height": 5, "center_point": DOWN, "top_row_center": 3 * UP, "top_row_label_y": 2, "top_row_x_spacing": 1.75, "top_row_copy_scale_factor": 0.9, "start_drawn": False, "plane_config": { "axis_config": {"unit_size": 2}, "y_min": -1.25, "y_max": 1.25, "x_min": -2.5, "x_max": 2.5, "background_line_style": { "stroke_width": 1, "stroke_color": LIGHT_GREY, }, }, "top_rect_height": 2.5, } def construct(self): self.add_vectors_circles_path() self.add_top_row(self.vectors, self.circles) self.write_title() self.highlight_vectors_one_by_one() self.change_shape() def write_title(self): title = TextMobject("Complex\\\\Fourier series") title.scale(1.5) title.to_edge(LEFT) title.match_y(self.path) self.wait(11) self.play(FadeInFromDown(title)) self.wait(2) self.title = title def highlight_vectors_one_by_one(self): # Don't know why these vectors can't get copied. # That seems like a problem that will come up again. labels = self.top_row[-1] next_anims = [] for vector, circle, label in zip(self.vectors, self.circles, labels): # v_color = vector.get_color() c_color = circle.get_color() c_stroke_width = circle.get_stroke_width() rect = SurroundingRectangle(label, color=PINK) self.play( # vector.set_color, PINK, circle.set_stroke, RED, 3, FadeIn(rect), *next_anims ) self.wait() next_anims = [ # vector.set_color, v_color, circle.set_stroke, c_color, c_stroke_width, FadeOut(rect), ] self.play(*next_anims) def change_shape(self): # path_mob = TexMobject("\\pi") path_mob = SVGMobject("Nail_And_Gear") new_path = path_mob.family_members_with_points()[0] new_path.set_height(4) new_path.move_to(self.path, DOWN) new_path.shift(0.5 * UP) self.transition_to_alt_path(new_path) for n in range(self.n_cycles): self.run_one_cycle() def transition_to_alt_path(self, new_path, morph_path=False): new_coefs = self.get_coefficients_of_path(new_path) new_vectors = self.get_rotating_vectors( coefficients=new_coefs ) new_drawn_path = self.get_drawn_path(new_vectors) self.vector_clock.suspend_updating() vectors = self.vectors anims = [] for vect, new_vect in zip(vectors, new_vectors): new_vect.update() new_vect.clear_updaters() line = Line(stroke_width=0) line.put_start_and_end_on(*vect.get_start_and_end()) anims.append(ApplyMethod( line.put_start_and_end_on, *new_vect.get_start_and_end() )) vect.freq = new_vect.freq vect.coefficient = new_vect.coefficient vect.line = line vect.add_updater( lambda v: v.put_start_and_end_on( *v.line.get_start_and_end() ) ) if morph_path: anims.append( ReplacementTransform( self.drawn_path, new_drawn_path ) ) else: anims.append( FadeOut(self.drawn_path) ) self.play(*anims, run_time=3) for vect in self.vectors: vect.remove_updater(vect.updaters[-1]) if not morph_path: self.add(new_drawn_path) self.vector_clock.set_value(0) self.vector_clock.resume_updating() self.drawn_path = new_drawn_path # def get_path(self): path = super().get_path() path.set_height(self.drawing_height) path.to_edge(DOWN) return path def add_top_row(self, vectors, circles, max_freq=3): self.top_row = self.get_top_row( vectors, circles, max_freq ) self.add(self.top_row) def get_top_row(self, vectors, circles, max_freq=3): vector_copies = VGroup() circle_copies = VGroup() for vector, circle in zip(vectors, circles): if vector.freq > max_freq: break vcopy = vector.copy() vcopy.clear_updaters() ccopy = circle.copy() ccopy.clear_updaters() ccopy.original = circle vcopy.original = vector vcopy.center_point = op.add( self.top_row_center, vector.freq * self.top_row_x_spacing * RIGHT, ) ccopy.center_point = vcopy.center_point vcopy.add_updater(self.update_top_row_vector_copy) ccopy.add_updater(self.update_top_row_circle_copy) vector_copies.add(vcopy) circle_copies.add(ccopy) dots = VGroup(*[ TexMobject("\\dots").next_to( circle_copies, direction, MED_LARGE_BUFF, ) for direction in [LEFT, RIGHT] ]) labels = self.get_top_row_labels(vector_copies) return VGroup( vector_copies, circle_copies, dots, labels, ) def update_top_row_vector_copy(self, vcopy): vcopy.become(vcopy.original) vcopy.scale(self.top_row_copy_scale_factor) vcopy.shift(vcopy.center_point - vcopy.get_start()) return vcopy def update_top_row_circle_copy(self, ccopy): ccopy.become(ccopy.original) ccopy.scale(self.top_row_copy_scale_factor) ccopy.move_to(ccopy.center_point) return ccopy def get_top_row_labels(self, vector_copies): labels = VGroup() for vector_copy in vector_copies: freq = vector_copy.freq label = Integer(freq) label.move_to(np.array([ freq * self.top_row_x_spacing, self.top_row_label_y, 0 ])) labels.add(label) return labels def setup_plane(self): plane = ComplexPlane(**self.plane_config) plane.shift(self.center_point) plane.add_coordinates() top_rect = Rectangle( width=FRAME_WIDTH, fill_color=BLACK, fill_opacity=1, stroke_width=0, height=self.top_rect_height, ) top_rect.to_edge(UP, buff=0) self.plane = plane self.add(plane) self.add(top_rect) def get_path_end(self, vectors, stroke_width=None, **kwargs): if stroke_width is None: stroke_width = self.drawn_path_st full_path = self.get_vector_sum_path(vectors, **kwargs) path = VMobject() path.set_stroke( self.drawn_path_color, stroke_width ) def update_path(p): alpha = self.get_vector_time() % 1 p.pointwise_become_partial( full_path, np.clip(alpha - 0.01, 0, 1), np.clip(alpha, 0, 1), ) p.points[-1] = vectors[-1].get_end() path.add_updater(update_path) return path def get_drawn_path_alpha(self): return super().get_drawn_path_alpha() - 0.002 def get_drawn_path(self, vectors, stroke_width=2, **kwargs): odp = super().get_drawn_path(vectors, stroke_width, **kwargs) return VGroup( odp, self.get_path_end(vectors, stroke_width, **kwargs), ) def get_vertically_falling_tracing(self, vector, color, stroke_width=3, rate=0.25): path = VMobject() path.set_stroke(color, stroke_width) path.start_new_path(vector.get_end()) path.vector = vector def update_path(p, dt): p.shift(rate * dt * DOWN) p.add_smooth_curve_to(p.vector.get_end()) path.add_updater(update_path) return path class PiFourierSeries(ComplexFourierSeriesExample): CONFIG = { "tex": "\\pi", "n_vectors": 101, "path_height": 3.5, "max_circle_stroke_width": 1, "top_row_copy_scale_factor": 0.6, } def construct(self): self.setup_plane() self.add_vectors_circles_path() self.add_top_row(self.vectors, self.circles) for n in range(self.n_cycles): self.run_one_cycle() def get_path(self): pi = TexMobject(self.tex) path = pi.family_members_with_points()[0] path.set_height(self.path_height) path.move_to(3 * DOWN, DOWN) path.set_stroke(YELLOW, 0) path.set_fill(opacity=0) return path class RealValuedFunctionFourierSeries(PiFourierSeries): CONFIG = { "n_vectors": 101, "start_drawn": True, } def construct(self): self.setup_plane() self.add_vectors_circles_path() self.add_top_row(self.vectors, self.circles) self.flatten_path() self.focus_on_vector_pair() def flatten_path(self): new_path = self.path.copy() new_path.stretch(0, 1) new_path.set_y(self.plane.n2p(0)[1]) self.vector_clock.set_value(10) self.transition_to_alt_path(new_path, morph_path=True) self.run_one_cycle() def focus_on_vector_pair(self): vectors = self.vectors circles = self.circles top_row = self.top_row top_vectors, top_circles, dots, labels = top_row rects1, rects2, rects3 = [ VGroup(*[ SurroundingRectangle(VGroup( top_circles[i], labels[i], )) for i in pair ]).set_stroke(LIGHT_GREY, 2) for pair in [(1, 2), (3, 4), (5, 6)] ] def get_opacity_animation(i1, i2, alpha_func): v_group = vectors[i1:i2] c_group = circles[i1:i2] return AnimationGroup( UpdateFromAlphaFunc( VectorizedPoint(), lambda m, a: v_group.set_opacity( alpha_func(a) ) ), UpdateFromAlphaFunc( VectorizedPoint(), lambda m, a: c_group.set_stroke( opacity=alpha_func(a) ) ), ) self.remove(self.path, self.drawn_path) self.play( get_opacity_animation( 3, len(vectors), lambda a: smooth(1 - a), ), ShowCreation(rects1, lag_ratio=0.3), ) traced_path2 = self.get_vertically_falling_tracing(vectors[2], GREEN) self.add(traced_path2) for n in range(3): self.run_one_cycle() self.play( get_opacity_animation(3, 5, smooth), get_opacity_animation( 0, 3, lambda a: 1 - 0.75 * smooth(a) ), ReplacementTransform(rects1, rects2), ) traced_path2.set_stroke(width=1) traced_path4 = self.get_vertically_falling_tracing(vectors[4], YELLOW) self.add(traced_path4) self.run_one_cycle() self.play( get_opacity_animation(5, 7, smooth), get_opacity_animation( 3, 5, lambda a: 1 - 0.75 * smooth(a) ), ReplacementTransform(rects2, rects3), ) traced_path2.set_stroke(width=1) traced_path4.set_stroke(width=1) traced_path6 = self.get_vertically_falling_tracing(vectors[6], TEAL) self.add(traced_path6) for n in range(2): self.run_one_cycle() class DemonstrateAddingArrows(PiFourierSeries): CONFIG = { "tex": "\\leftarrow", "n_arrows": 21, "parametric_function_step_size": 0.1, } def construct(self): self.setup_plane() self.add_vectors_circles_path() self.add_top_row(self.vectors, self.circles) circles = self.circles original_vectors = self.vectors vectors = VGroup(*[ Vector( **self.vector_config ).put_start_and_end_on(*v.get_start_and_end()) for v in original_vectors ]) original_top_vectors = self.top_row[0] top_vectors = VGroup(*[ Vector( **self.vector_config ).put_start_and_end_on(*v.get_start_and_end()) for v in original_top_vectors ]) self.plane.axes.set_stroke(LIGHT_GREY, 1) self.vector_clock.suspend_updating() self.remove(circles, original_vectors) self.remove(self.path, self.drawn_path) anims1 = [ TransformFromCopy(tv, v) for tv, v in zip(top_vectors, vectors) ] anims2 = [ ShowCreation(v) for v in vectors[len(top_vectors):25] ] self.play( LaggedStart(*anims1), run_time=3, lag_ratio=0.2, ) self.play( LaggedStart(*anims2), lag_ratio=0.1, run_time=5, ) class LabelRotatingVectors(PiFourierSeries): CONFIG = { "n_vectors": 6, "center_point": 1.5 * DOWN, "top_rect_height": 3, "plane_config": { "axis_config": { "unit_size": 1.75, "stroke_color": LIGHT_GREY, }, }, "top_row_x_spacing": 1.9, "top_row_center": 3 * UP + 0.2 * LEFT, } def construct(self): self.setup_plane() self.setup_top_row() self.ask_about_labels() self.initialize_at_one() self.show_complex_exponents() # self.show_complex_exponents_temp() self.tweak_initial_states() self.constant_examples() def setup_top_row(self): vectors = self.get_rotating_vectors( coefficients=0.5 * np.ones(self.n_vectors) ) circles = self.get_circles(vectors) top_row = self.get_top_row(vectors, circles) top_row.shift(0.5 * DOWN + 0.25 * RIGHT) v_copies, c_copies, dots, labels = top_row labels.to_edge(UP, MED_SMALL_BUFF) freq_label = TextMobject("Frequencies:") freq_label.to_edge(LEFT, MED_SMALL_BUFF) freq_label.match_y(labels) VGroup(freq_label, labels).set_color(YELLOW) def get_constant_func(const): return lambda: const for vector, v_copy in zip(vectors, v_copies): vector.center_func = get_constant_func( v_copy.get_start() ) vectors.update(0) circles.update(0) self.add(vectors) self.add(circles) self.add(dots) self.add(freq_label) self.add(labels) self.vectors = vectors self.circles = circles self.labels = labels self.freq_label = freq_label def ask_about_labels(self): circles = self.circles formulas = TextMobject("Formulas:") formulas.next_to(circles, DOWN) formulas.to_edge(LEFT, MED_SMALL_BUFF) q_marks = VGroup(*[ TexMobject("??").scale(1.0).next_to(circle, DOWN) for circle in circles ]) self.play(FadeInFrom(formulas, DOWN)) self.play(LaggedStartMap( FadeInFrom, q_marks, lambda m: (m, UP), lag_ratio=0.2, run_time=3, )) self.wait(3) self.q_marks = q_marks self.formulas_word = formulas def initialize_at_one(self): vectors = self.vectors circles = self.circles vector_clock = self.vector_clock plane = self.plane q_marks = self.q_marks # Why so nuclear? vc_updater = vector_clock.updaters.pop() self.play( vector_clock.set_value, 0, run_time=2, ) zero_vect = Vector() zero_vect.replace(vectors[0]) zero_circle = self.get_circle(zero_vect) zero_circle.match_style(circles[0]) self.add(zero_circle) one_label = TexMobject("1") one_label.move_to(q_marks[0]) self.play( zero_vect.put_start_and_end_on, plane.n2p(0), plane.n2p(1), ) vector_clock.add_updater(vc_updater) self.wait() self.play( FadeOutAndShift(q_marks[0], UP), FadeInFrom(one_label, DOWN), ) self.wait(4) self.one_label = one_label self.zero_vect = zero_vect self.zero_circle = zero_circle def show_complex_exponents(self): vectors = self.vectors circles = self.circles q_marks = self.q_marks labels = self.labels one_label = self.one_label v_lines = self.get_v_lines(circles) # Vector 1 v1_rect = SurroundingRectangle( VGroup(circles[1], q_marks[1], labels[1]), stroke_color=GREY, stroke_width=2, ) f1_exp = self.get_exp_tex() f1_exp.move_to(q_marks[1], DOWN) self.play( FadeOut(self.zero_vect), FadeOut(self.zero_circle), FadeIn(v1_rect) ) vg1 = self.get_vector_in_plane_group( vectors[1], circles[1], ) vg1_copy = vg1.copy() vg1_copy.clear_updaters() vg1_copy.replace(circles[1]) cps_1 = self.get_cps_label(1) circle_copy = vg1[1].copy().clear_updaters() circle_copy.set_stroke(YELLOW, 3) arclen_decimal = DecimalNumber( num_decimal_places=3, show_ellipsis=True, ) arclen_tracker = ValueTracker(0) arclen_decimal.add_updater(lambda m: m.next_to( circle_copy.get_end(), UR, SMALL_BUFF, )) arclen_decimal.add_updater(lambda m: m.set_value( arclen_tracker.get_value() )) self.play( ReplacementTransform(vg1_copy, vg1), ) self.play(FadeInFrom(cps_1, DOWN)) self.wait(2) self.play( FadeOutAndShift(q_marks[1], UP), FadeInFrom(f1_exp, DOWN), ) self.wait(2) self.play(ShowCreationThenFadeAround( f1_exp.get_part_by_tex("2\\pi") )) self.add(arclen_decimal), self.play( ShowCreation(circle_copy), arclen_tracker.set_value, TAU, run_time=3, ) self.wait() self.play( FadeOut(circle_copy), FadeOut(arclen_decimal), ) self.wait(8) self.play( v1_rect.move_to, circles[2], v1_rect.match_y, v1_rect, FadeOut(vg1), FadeOut(cps_1), ) # Vector -1 vgm1 = self.get_vector_in_plane_group( vectors[2], circles[2], ) vgm1_copy = vgm1.copy() vgm1_copy.clear_updaters() vgm1_copy.replace(circles[2]) cps_m1 = self.get_cps_label(-1) fm1_exp = self.get_exp_tex(-1) fm1_exp.move_to(q_marks[2], DOWN) self.play( ReplacementTransform(vgm1_copy, vgm1), FadeInFromDown(cps_m1) ) self.wait(2) self.play( FadeOutAndShift(q_marks[2], UP), FadeInFromDown(fm1_exp), v1_rect.stretch, 1.4, 0, ) self.wait(5) self.play( v1_rect.move_to, circles[3], v1_rect.match_y, v1_rect, FadeOut(vgm1), FadeOut(cps_m1), ) # Vector 2 # (Lots of copy-pasting here) vg2 = self.get_vector_in_plane_group( vectors[3], circles[3], ) vg2_copy = vg2.copy() vg2_copy.clear_updaters() vg2_copy.replace(circles[3]) cps_2 = self.get_cps_label(2) f2_exp = self.get_exp_tex(2) f2_exp.move_to(q_marks[3], DOWN) circle_copy.append_vectorized_mobject(circle_copy) self.play( ReplacementTransform(vg2_copy, vg2), FadeInFromDown(cps_2) ) self.wait() self.play( FadeOutAndShift(q_marks[3], UP), FadeInFromDown(f2_exp), ) self.wait(3) self.play(ShowCreationThenFadeAround( f2_exp.get_parts_by_tex("2"), )) self.add(arclen_decimal) arclen_tracker.set_value(0) self.play( ShowCreation(circle_copy), arclen_tracker.set_value, 2 * TAU, run_time=5 ) self.wait(3) self.play( FadeOut(circle_copy), FadeOut(arclen_decimal), ) self.play( FadeOut(vg2), FadeOut(cps_2), FadeOut(v1_rect), ) # Show all formulas fm2_exp = self.get_exp_tex(-2) fm2_exp.move_to(q_marks[4], DOWN) f3_exp = self.get_exp_tex(3) f3_exp.move_to(q_marks[5], DOWN) f1_exp_new = self.get_exp_tex(1) f1_exp_new.move_to(q_marks[1], DOWN) f0_exp = self.get_exp_tex(0) f0_exp.move_to(q_marks[0], DOWN) f_exp_general = self.get_exp_tex("n") f_exp_general.next_to(self.formulas_word, DOWN) self.play( FadeOut(q_marks[4:]), FadeOut(f1_exp), FadeIn(f1_exp_new), FadeInFromDown(fm2_exp), FadeInFromDown(f3_exp), FadeIn(v_lines, lag_ratio=0.2) ) self.play( FadeInFrom(f_exp_general, UP) ) self.play(ShowCreationThenFadeAround(f_exp_general)) self.wait(3) self.play( FadeOut(one_label, UP), TransformFromCopy(f_exp_general, f0_exp), ) self.wait(5) self.f_exp_labels = VGroup( f0_exp, f1_exp_new, fm1_exp, f2_exp, fm2_exp, f3_exp, ) self.f_exp_general = f_exp_general def show_complex_exponents_temp(self): self.f_exp_labels = VGroup(*[ self.get_exp_tex(n).move_to(qm, DOWN) for n, qm in zip( [0, 1, -1, 2, -2, 3], self.q_marks, ) ]) self.f_exp_general = self.get_exp_tex("n") self.f_exp_general.next_to(self.formulas_word, DOWN) self.remove(*self.q_marks, self.one_label) self.remove(self.zero_vect, self.zero_circle) self.add(self.f_exp_labels, self.f_exp_general) def tweak_initial_states(self): vector_clock = self.vector_clock f_exp_labels = self.f_exp_labels f_exp_general = self.f_exp_general vectors = self.vectors cn_terms = VGroup() for i, f_exp in enumerate(f_exp_labels): n = (i + 1) // 2 if i % 2 == 0 and i > 0: n *= -1 cn_terms.add(self.get_cn_label(n, f_exp)) cn_general = self.get_cn_label("n", f_exp_general) new_coefs = [ 0.5, np.exp(complex(0, TAU / 8)), 0.7 * np.exp(-complex(0, TAU / 8)), 0.6 * np.exp(complex(0, TAU / 3)), 1.1 * np.exp(-complex(0, TAU / 12)), 0.3 * np.exp(complex(0, TAU / 12)), ] def update_vectors(alpha): for vect, new_coef in zip(vectors, new_coefs): vect.coefficient = 0.5 * interpolate( 1, new_coef, alpha ) vector_clock.incrementer = vector_clock.updaters.pop() self.play( vector_clock.set_value, int(vector_clock.get_value()) ) self.play( LaggedStartMap( MoveToTarget, VGroup(f_exp_general, *f_exp_labels), ), LaggedStartMap( FadeInFromDown, VGroup(cn_general, *cn_terms), ), UpdateFromAlphaFunc( VectorizedPoint(), lambda m, a: update_vectors(a) ), run_time=2 ) self.wait() self.play( LaggedStart(*[ ShowCreationThenFadeAround( cn_term, surrounding_rectangle_config={ "buff": 0.05, "stroke_width": 2, }, ) for cn_term in cn_terms ]) ) self.cn_terms = cn_terms self.cn_general = cn_general def constant_examples(self): cn_terms = self.cn_terms vectors = self.vectors circles = self.circles # c0 term c0_brace = Brace(cn_terms[0], DOWN, buff=SMALL_BUFF) c0_label = TexMobject("0.5") c0_label.next_to(c0_brace, DOWN, SMALL_BUFF) c0_label.add_background_rectangle() vip_group0 = self.get_vector_in_plane_group( vectors[0], circles[0] ) vip_group0_copy = vip_group0.copy() vip_group0_copy.clear_updaters() vip_group0_copy.replace(circles[0]) self.play( Transform(vip_group0_copy, vip_group0) ) self.wait() self.play(vip_group0_copy.scale, 2) self.play( vip_group0_copy.scale, 0.5, GrowFromCenter(c0_brace), GrowFromCenter(c0_label), ) self.wait(2) self.play( FadeOut(c0_brace), FadeOut(c0_label), FadeOut(vip_group0_copy), ) # c1 term c1_brace = Brace(cn_terms[1], DOWN, buff=SMALL_BUFF) c1_label = TexMobject("e^{(\\pi / 4)i}") c1_label.next_to(c1_brace, DOWN, SMALL_BUFF) c1_decimal = DecimalNumber( np.exp(np.complex(0, PI / 4)), num_decimal_places=3, ) approx = TexMobject("\\approx") approx.next_to(c1_label, RIGHT, MED_SMALL_BUFF) c1_decimal.next_to(approx, RIGHT, MED_SMALL_BUFF) scalar = DecimalNumber(0.3) scalar.next_to( c1_label, LEFT, SMALL_BUFF, aligned_edge=DOWN, ) vip_group1 = self.get_vector_in_plane_group( vectors[1], circles[1] ) vip_group1_copy = vip_group1.copy() vip_group1_copy[0].stroke_width = 3 vip_group1_copy.clear_updaters() vip_group1_copy.save_state() vip_group1_copy.replace(circles[1]) self.play( Restore(vip_group1_copy) ) self.play(Rotate(vip_group1_copy, -PI / 4)) self.play(Rotate(vip_group1_copy, PI / 4)) self.play( GrowFromCenter(c1_brace), FadeIn(c1_label), ) self.play( Write(approx), Write(c1_decimal), run_time=1, ) self.wait(2) def update_v1(alpha): vectors[1].coefficient = 0.5 * interpolate( np.exp(complex(0, PI / 4)), 0.3 * np.exp(complex(0, PI / 4)), alpha ) self.play( FadeIn(scalar), c1_decimal.set_value, scalar.get_value() * c1_decimal.get_value(), vip_group1_copy.scale, scalar.get_value(), UpdateFromAlphaFunc( VMobject(), lambda m, a: update_v1(a) ) ) self.wait() self.play( FadeOut(c1_brace), FadeOut(c1_label), FadeOut(approx), FadeOut(c1_decimal), FadeOut(scalar), FadeOut(vip_group1_copy), ) fade_anims = [] for cn_term, vect in zip(cn_terms[2:], vectors[2:]): rect = SurroundingRectangle(cn_term, buff=0.025) rect.set_stroke(width=2) decimal = DecimalNumber(vect.coefficient) decimal.next_to(rect, DOWN) decimal.add_background_rectangle() if cn_term is cn_terms[4]: decimal.shift(0.7 * RIGHT) self.play( ShowCreation(rect), FadeIn(decimal), *fade_anims ) self.wait() fade_anims = [FadeOut(rect), FadeOut(decimal)] self.play(*fade_anims) # def get_vector_in_plane_group(self, top_vector, top_circle): plane = self.plane origin = plane.n2p(0) vector = Vector() vector.add_updater( lambda v: v.put_start_and_end_on( origin, plane.n2p(2 * top_vector.coefficient) ).set_angle(top_vector.get_angle()) ) circle = Circle() circle.match_style(top_circle) circle.set_width(2 * vector.get_length()) circle.move_to(origin) return VGroup(vector, circle) def get_exp_tex(self, freq=None): if freq is None: freq_str = "{}" else: freq_str = "{" + str(freq) + "}" + "\\cdot" result = TexMobject( "e^{", freq_str, "2\\pi i {t}}", tex_to_color_map={ "2\\pi": WHITE, "{t}": PINK, freq_str: YELLOW, } ) result.scale(0.9) return result def get_cn_label(self, n, exp_label): exp_label.generate_target() exp_label.target.scale(0.9) n_str = "{" + str(n) + "}" term = TexMobject("c_", n_str) term.set_color(GREEN) term[1].set_color(YELLOW) term[1].set_width(0.12) term[1].move_to(term[0].get_corner(DR), LEFT) if isinstance(n, str): term[1].scale(1.4, about_edge=LEFT) term[1].shift(0.03 * RIGHT) elif n < 0: term[1].scale(1.4, about_edge=LEFT) term[1].set_stroke(width=0.5) else: term[1].shift(0.05 * RIGHT) term.scale(0.9) term.shift( exp_label.target[0].get_corner(LEFT) - term[0].get_corner(RIGHT) + 0.2 * LEFT ) VGroup(exp_label.target, term).move_to( exp_label, DOWN ) if isinstance(n, str): VGroup(term, exp_label.target).scale( 1.3, about_edge=UP ) return term def get_cps_label(self, n): n_str = str(n) if n == 1: frac_tex = "\\frac{\\text{cycle}}{\\text{second}}" else: frac_tex = "\\frac{\\text{cycles}}{\\text{second}}" result = TexMobject( n_str, frac_tex, tex_to_color_map={ n_str: YELLOW }, ) result[1].scale(0.7, about_edge=LEFT) result[0].scale(1.2, about_edge=RIGHT) result.next_to(self.plane.n2p(2), UR) return result def get_v_lines(self, circles): lines = VGroup() o_circles = VGroup(*circles) o_circles.sort(lambda p: p[0]) for c1, c2 in zip(o_circles, o_circles[1:]): line = DashedLine(3 * UP, ORIGIN) line.set_stroke(WHITE, 1) line.move_to(midpoint( c1.get_center(), c2.get_center(), )) lines.add(line) return lines class IntegralTrick(LabelRotatingVectors, TRangingFrom0To1): CONFIG = { "file_name": "EighthNote", "n_vectors": 101, "path_height": 3.5, "plane_config": { "x_min": -1.75, "x_max": 1.75, "axis_config": { "unit_size": 1.75, "stroke_color": LIGHT_GREY, }, }, "center_point": 1.5 * DOWN + 3 * RIGHT, "input_space_rect_config": { "width": 6, "height": 1.5, }, "start_drawn": True, "parametric_function_step_size": 0.01, "top_row_center": 2 * UP + RIGHT, "top_row_x_spacing": 2.25, } def construct(self): self.setup_plane() self.add_vectors_circles_path() self.setup_input_space() self.setup_input_trackers() self.setup_top_row() self.setup_sum() self.introduce_sum() self.issolate_c0() self.show_center_of_mass() self.write_integral() def setup_input_space(self): super().setup_input_space() self.input_line.next_to( self.input_rect.get_bottom(), UP, ) group = VGroup( self.input_rect, self.input_line, ) group.move_to(self.plane.n2p(0)) group.to_edge(LEFT) def setup_top_row(self): top_row = self.get_top_row( self.vectors, self.circles, max_freq=2, ) self.top_vectors, self.top_circles, dots, labels = top_row self.add(*top_row) self.remove(labels) def setup_sum(self): top_vectors = self.top_vectors terms = VGroup() for vect in top_vectors: freq = vect.freq exp = self.get_exp_tex(freq) cn = self.get_cn_label(freq, exp) exp.become(exp.target) term = VGroup(cn, exp) term.move_to(vect.get_start()) term.shift(UP) terms.add(term) for vect in [LEFT, RIGHT]: dots = TexMobject("\\cdots") dots.next_to(terms, vect, MED_LARGE_BUFF) terms.add(dots) plusses = VGroup() o_terms = VGroup(*terms) o_terms.sort(lambda p: p[0]) for t1, t2 in zip(o_terms, o_terms[1:]): plus = TexMobject("+") plus.scale(0.7) plus.move_to(midpoint( t1.get_right(), t2.get_left(), )) plusses.add(plus) terms[:-2].shift(0.05 * UP) ft_eq = TexMobject("f(t)", "= ") ft_eq.next_to(terms, LEFT) self.add(terms) self.add(plusses) self.add(ft_eq) self.terms = terms self.plusses = plusses self.ft_eq = ft_eq def introduce_sum(self): self.remove( self.vector_clock, self.vectors, self.circles, self.drawn_path, ) ft = self.ft_eq[0] terms = self.terms path = self.path input_tracker = self.input_tracker rect = SurroundingRectangle(ft) coefs = VGroup(*[term[0] for term in terms[:-2]]) terms_rect = SurroundingRectangle(terms) terms_rect.set_stroke(YELLOW, 1.5) dot = Dot() dot.add_updater(lambda d: d.move_to(path.get_end())) self.play(ShowCreation(rect)) self.wait() self.play( ReplacementTransform(rect, dot) ) path.set_stroke(YELLOW, 2) self.play( ShowCreation(path), input_tracker.set_value, 1, run_time=3, rate_func=lambda t: smooth(t, 1), ) self.wait() input_tracker.add_updater( lambda m: m.set_value( self.vector_clock.get_value() % 1 ) ) self.add( self.vector_clock, self.vectors, self.circles, ) self.play( FadeOut(path), FadeOut(dot), FadeIn(self.drawn_path), ) self.play(FadeIn(terms_rect)) self.wait() self.play(FadeOut(terms_rect)) fade_outs = [] for coef in coefs: rect = SurroundingRectangle(coef) self.play(FadeIn(rect), *fade_outs) fade_outs = [FadeOut(rect)] self.play(*fade_outs) self.wait(2) self.vector_clock.clear_updaters() def issolate_c0(self): vectors = self.vectors circles = self.circles terms = self.terms top_circles = self.top_circles path = self.path path.set_stroke(YELLOW, 1) c0_rect = SurroundingRectangle( VGroup(top_circles[0], terms[0]) ) c0_rect.set_stroke(WHITE, 1) opacity_tracker = ValueTracker(1) for vect in vectors[1:]: vect.add_updater( lambda v: v.set_opacity( opacity_tracker.get_value() ) ) for circle in circles[0:]: circle.add_updater( lambda c: c.set_stroke( opacity=opacity_tracker.get_value() ) ) self.play(ShowCreation(c0_rect)) self.play( opacity_tracker.set_value, 0.2, FadeOut(self.drawn_path), FadeIn(path) ) v0 = vectors[0] v0_point = VectorizedPoint(v0.get_end()) origin = self.plane.n2p(0) v0.add_updater(lambda v: v.put_start_and_end_on( origin, v0_point.get_location(), )) self.play( MaintainPositionRelativeTo(path, v0_point), ApplyMethod( v0_point.shift, 1.5 * LEFT, run_time=4, rate_func=there_and_back, path_arc=60 * DEGREES, ) ) v0.updaters.pop() self.opacity_tracker = opacity_tracker def show_center_of_mass(self): dot_sets = VGroup(*[ self.get_sample_dots(dt=dt, radius=radius) for dt, radius in [ (0.05, 0.04), (0.01, 0.03), (0.0025, 0.02), ] ]) input_dots, output_dots = dot_sets[0] v0_dot = input_dots[0].deepcopy() v0_dot.move_to(center_of_mass([ od.get_center() for od in output_dots ])) v0_dot.set_color(RED) self.play(LaggedStartMap( FadeInFromLarge, input_dots, lambda m: (m, 5), run_time=2, lag_ratio=0.5, )) self.wait() self.play( TransformFromCopy( input_dots, output_dots, run_time=3 ) ) self.wait() self.play(*[ Transform( od.copy(), v0_dot.copy(), remover=True ) for od in output_dots ]) self.add(v0_dot) self.wait() for ds1, ds2 in zip(dot_sets, dot_sets[1:]): ind1, outd1 = ds1 ind2, outd2 = ds2 new_v0_dot = v0_dot.copy() new_v0_dot.move_to(center_of_mass([ od.get_center() for od in outd2 ])) self.play( FadeOut(ind1), LaggedStartMap( FadeInFrom, ind2, lambda m: (m, UP), lag_ratio=4 / len(ind2), run_time=2, ) ) self.play( TransformFromCopy(ind2, outd2), FadeOut(outd1), run_time=2, ) self.play( FadeOut(v0_dot), *[ Transform( od.copy(), v0_dot.copy(), remover=True ) for od in outd2 ] ) v0_dot = new_v0_dot self.add(v0_dot) self.wait() self.input_dots, self.output_dots = dot_sets[-1] self.v0_dot = v0_dot def write_integral(self): t_tracker = self.vector_clock path = self.path expression = TexMobject( "c_{0}", "=" "\\int_0^1 f({t}) d{t}", tex_to_color_map={ "{t}": PINK, "{0}": YELLOW, }, ) expression.next_to(self.input_rect, UP) brace = Brace(expression[2:], UP, buff=SMALL_BUFF) average = brace.get_text("Average", buff=SMALL_BUFF) self.play( FadeInFromDown(expression), GrowFromCenter(brace), FadeIn(average), ) t_tracker.clear_updaters() t_tracker.set_value(0) self.add(path) self.play( t_tracker.set_value, 0.999, ShowCreation(path), run_time=8, rate_func=lambda t: smooth(t, 1), ) self.wait() # def get_path(self): mob = SVGMobject(self.file_name) path = mob.family_members_with_points()[0] path.set_height(self.path_height) path.move_to(self.center_point) path.shift(0.5 * UR) path.set_stroke(YELLOW, 0) path.set_fill(opacity=0) return path def get_sample_dots(self, dt, radius): input_line = self.input_line path = self.path t_values = np.arange(0, 1 + dt, dt) dot = Dot(color=PINK, radius=radius) dot.set_stroke( RED, 1, opacity=0.8, background=True, ) input_dots = VGroup() output_dots = VGroup() for t in t_values: in_dot = dot.copy() out_dot = dot.copy() in_dot.move_to(input_line.n2p(t)) out_dot.move_to(path.point_from_proportion(t)) input_dots.add(in_dot) output_dots.add(out_dot) return VGroup(input_dots, output_dots) class IncreaseOrderOfApproximation(ComplexFourierSeriesExample): CONFIG = { "file_name": "FourierOneLine", "drawing_height": 6, "n_vectors": 250, "parametric_function_step_size": 0.001, "run_time": 10, # "n_vectors": 25, # "parametric_function_step_size": 0.01, # "run_time": 5, "slow_factor": 0.05, } def construct(self): path = self.get_path() path.to_edge(DOWN) path.set_stroke(YELLOW, 2) freqs = self.get_freqs() coefs = self.get_coefficients_of_path( path, freqs=freqs, ) vectors = self.get_rotating_vectors(freqs, coefs) circles = self.get_circles(vectors) n_tracker = ValueTracker(2) n_label = VGroup( TextMobject("Approximation using"), Integer(100).set_color(YELLOW), TextMobject("vectors") ) n_label.arrange(RIGHT) n_label.to_corner(UL) n_label.add_updater( lambda n: n[1].set_value( n_tracker.get_value() ).align_to(n[2], DOWN) ) changing_path = VMobject() vector_copies = VGroup() circle_copies = VGroup() def update_changing_path(cp): n = n_label[1].get_value() cp.become(self.get_vector_sum_path(vectors[:n])) cp.set_stroke(YELLOW, 2) # While we're at it... vector_copies.submobjects = list(vectors[:n]) circle_copies.submobjects = list(circles[:n]) changing_path.add_updater(update_changing_path) self.add(n_label, n_tracker, changing_path) self.add(vector_copies, circle_copies) self.play( n_tracker.set_value, self.n_vectors, rate_func=smooth, run_time=self.run_time, ) self.wait(5) class ShowStepFunctionIn2dView(SimpleComplexExponentExample, ComplexFourierSeriesExample): CONFIG = { "input_space_rect_config": { "width": 5, "height": 2, }, "input_line_config": { "unit_size": 3, "x_min": 0, "x_max": 1, "tick_frequency": 0.1, "stroke_width": 2, "decimal_number_config": { "num_decimal_places": 1, } }, "input_numbers": [0, 0.5, 1], "input_tex_args": [], # "n_vectors": 300, "n_vectors": 2, } def construct(self): self.setup_plane() self.setup_input_space() self.setup_input_trackers() self.clear() self.transition_from_step_function() self.show_output() self.show_fourier_series() def setup_input_space(self): super().setup_input_space() rect = self.input_rect line = self.input_line # rect.stretch(1.2, 1, about_edge=UP) line.shift(MED_SMALL_BUFF * UP) sf = 1.2 line.stretch(sf, 0) for n in line.numbers: n.stretch(1 / sf, 0) label = TextMobject("Input space") label.next_to(rect.get_bottom(), UP, SMALL_BUFF) self.add(label) self.input_space_label = label def transition_from_step_function(self): x_axis = self.input_line input_tip = self.input_tip input_label = self.input_label input_rect = self.input_rect input_space_label = self.input_space_label plane = self.plane plane.set_opacity(0) x_axis.save_state() # x_axis.center() x_axis.move_to(ORIGIN, LEFT) sf = 1.5 x_axis.stretch(sf, 0) for number in x_axis.numbers: number.stretch(1 / sf, 0) x_axis.numbers[0].set_opacity(0) y_axis = NumberLine( unit_size=2, x_min=-1.5, x_max=1.5, tick_frequency=0.5, stroke_color=LIGHT_GREY, stroke_width=2, ) # y_axis.match_style(x_axis) y_axis.rotate(90 * DEGREES) y_axis.shift(x_axis.n2p(0) - y_axis.n2p(0)) y_axis.add_numbers( -1, 0, 1, direction=LEFT, ) axes = Axes() axes.x_axis = x_axis axes.y_axis = y_axis axes.axes = VGroup(x_axis, y_axis) graph = VGroup( Line( axes.c2p(0, 1), axes.c2p(0.5, 1), color=RED, ), Line( axes.c2p(0.5, -1), axes.c2p(1, -1), color=BLUE, ), ) dot1 = Dot(color=RED) dot2 = Dot(color=BLUE) dot1.add_updater(lambda d: d.move_to(y_axis.n2p(1))) dot2.add_updater(lambda d: d.move_to(y_axis.n2p(-1))) squish_graph = VGroup(dot1, dot2) self.add(x_axis) self.add(y_axis) self.add(input_tip) self.add(input_label) self.play( self.input_tracker.set_value, 1, ShowCreation(graph), run_time=3, rate_func=lambda t: smooth(t, 1) ) self.wait() self.add( plane, input_rect, input_space_label, x_axis, input_tip, input_label, ) self.play( FadeIn(input_rect), FadeIn(input_space_label), Restore(x_axis), ) self.play(ReplacementTransform(graph, squish_graph)) # Rotate y-axis, fade in plane y_axis.generate_target(use_deepcopy=True) y_axis.target.rotate(-TAU / 4) y_axis.target.shift( plane.n2p(0) - y_axis.target.n2p(0) ) y_axis.target.numbers.set_opacity(0) plane.set_opacity(1) self.play( MoveToTarget(y_axis), ShowCreation(plane), ) self.play(FadeOut(y_axis)) self.wait() self.play(self.input_tracker.set_value, 0) self.output_dots = squish_graph def show_output(self): input_tracker = self.input_tracker def get_output_point(): return self.get_output_point(input_tracker.get_value()) tip = ArrowTip(start_angle=-TAU / 4) tip.set_fill(YELLOW) tip.match_height(self.input_tip) tip.add_updater(lambda m: m.move_to( get_output_point(), DOWN, )) output_label = TextMobject("Output") output_label.add_background_rectangle() output_label.add_updater(lambda m: m.next_to( tip, UP, SMALL_BUFF, )) self.play( FadeIn(tip), FadeIn(output_label), ) self.play( input_tracker.set_value, 1, run_time=8, rate_func=linear ) self.wait() self.play(input_tracker.set_value, 0) self.output_tip = tip def show_fourier_series(self): plane = self.plane input_tracker = self.input_tracker output_tip = self.output_tip self.play( plane.axes.set_stroke, WHITE, 1, plane.background_lines.set_stroke, LIGHT_GREY, 0.5, plane.faded_lines.set_stroke, LIGHT_GREY, 0.25, 0.5, ) self.vector_clock.set_value(0) self.add(self.vector_clock) input_tracker.add_updater(lambda m: m.set_value( self.vector_clock.get_value() % 1 )) self.add_vectors_circles_path() self.remove(self.drawn_path) self.add(self.vectors) output_tip.clear_updaters() output_tip.add_updater(lambda m: m.move_to( self.vectors[-1].get_end(), DOWN )) self.run_one_cycle() path = self.get_vertically_falling_tracing( self.vectors[1], GREEN, rate=0.5, ) self.add(path) for x in range(3): self.run_one_cycle() # def get_freqs(self): n = self.n_vectors all_freqs = [ *range(1, n + 1 // 2, 2), *range(-1, -n + 1 // 2, -2), ] all_freqs.sort(key=abs) return all_freqs def get_path(self): path = VMobject() p0, p1 = [ self.get_output_point(x) for x in [0, 1] ] for p in p0, p1: path.start_new_path(p) path.add_line_to(p) return path def get_output_point(self, x): return self.plane.n2p(self.step(x)) def step(self, x): if x < 0.5: return 1 elif x == 0.5: return 0 else: return -1 class AddVectorsOneByOne(IntegralTrick): CONFIG = { "file_name": "TrebleClef", # "start_drawn": True, "n_vectors": 101, "path_height": 5, } def construct(self): self.setup_plane() self.add_vectors_circles_path() self.setup_input_space() self.setup_input_trackers() self.setup_top_row() self.setup_sum() self.show_sum() def show_sum(self): vectors = self.vectors vector_clock = self.vector_clock terms = self.terms vector_clock.suspend_updating() coef_tracker = ValueTracker(0) def update_vector(vector): vector.coefficient = interpolate( 1, vector.original_coefficient, coef_tracker.get_value() ) for vector in vectors: vector.original_coefficient = vector.coefficient vector.add_updater(update_vector) rects = VGroup(*[ SurroundingRectangle(t[0]) for t in terms[:5] ]) self.remove(self.drawn_path) self.play(LaggedStartMap( VFadeInThenOut, rects )) self.play( coef_tracker.set_value, 1, run_time=3 ) self.wait() vector_clock.resume_updating() self.input_tracker.add_updater( lambda m: m.set_value(vector_clock.get_value() % 1) ) self.add(self.drawn_path, self.input_tracker) self.wait(10) def get_path(self): mob = SVGMobject(self.file_name) path = mob.family_members_with_points()[0] path.set_height(self.path_height) path.move_to(self.plane.n2p(0)) path.set_stroke(YELLOW, 0) path.set_fill(opacity=0) return path class DE4Thumbnail(ComplexFourierSeriesExample): CONFIG = { "file_name": "FourierOneLine", "start_drawn": True, "n_vectors": 300, "parametric_function_step_size": 0.0025, "drawn_path_stroke_width": 7, "drawing_height": 6, } def construct(self): name = TextMobject("Fourier series") name.set_width(FRAME_WIDTH - 2) name.to_edge(UP) name.set_color(YELLOW) subname = TextMobject("a.k.a ``everything is rotations''") subname.match_width(name) subname.next_to(name, DOWN) VGroup(name, subname).to_edge(DOWN) self.add(name) self.add(subname) path = self.get_path() path.to_edge(DOWN) path.set_stroke(YELLOW, 2) freqs = self.get_freqs() coefs = self.get_coefficients_of_path(path, freqs=freqs) vectors = self.get_rotating_vectors(freqs, coefs) # circles = self.get_circles(vectors) ns = [10, 50, 250] approxs = VGroup(*[ self.get_vector_sum_path(vectors[:n]) for n in ns ]) approxs.arrange(RIGHT, buff=2.5) approxs.set_height(3.75) approxs.to_edge(UP, buff=1.25) for a, c, w in zip(approxs, [BLUE, GREEN, YELLOW], [4, 3, 2]): a.set_stroke(c, w) a.set_stroke(WHITE, w + w / 2, background=True) labels = VGroup() for n, approx in zip(ns, approxs): label = TexMobject("n = ", str(n)) label[1].match_color(approx) label.scale(2) label.next_to(approx, UP) label.to_edge(UP, buff=MED_SMALL_BUFF) labels.add(label) self.add(approxs) self.add(labels) return self.add_vectors_circles_path() n = 6 self.circles[n:].set_opacity(0) self.circles[:n].set_stroke(width=3) path = self.drawn_path # path.set_stroke(BLACK, 8, background=True) # path = self.path # path.set_stroke(YELLOW, 5) # path.set_stroke(BLACK, 8, background=True) self.add(path, self.circles, self.vectors) self.update_mobjects(0)