mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 21:12:35 +08:00
1894 lines
54 KiB
Python
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)
|