Files
manim/active_projects/diffyq/part4/fourier_series_scenes.py
2019-07-03 11:08:39 -07:00

1894 lines
54 KiB
Python

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)