mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Animations up to the preview for the breakdown into sine curves for diffyq chapter 3
This commit is contained in:
parent
4096fc28b8
commit
ddd7ce2f12
4 changed files with 515 additions and 70 deletions
|
@ -1,5 +1,6 @@
|
||||||
from active_projects.ode.part3.staging import *
|
from active_projects.ode.part3.staging import *
|
||||||
from active_projects.ode.part3.temperature_graphs import *
|
from active_projects.ode.part3.temperature_graphs import *
|
||||||
|
from active_projects.ode.part3.pi_creature_scenes import *
|
||||||
|
|
||||||
|
|
||||||
OUTPUT_DIRECTORY = "ode/part3"
|
OUTPUT_DIRECTORY = "ode/part3"
|
||||||
|
@ -9,5 +10,6 @@ SCENES_IN_ORDER = [
|
||||||
CircleAnimationOfF,
|
CircleAnimationOfF,
|
||||||
LastChapterWrapper,
|
LastChapterWrapper,
|
||||||
ThreeMainObservations,
|
ThreeMainObservations,
|
||||||
SimpleSinExpGraph,
|
SimpleCosExpGraph,
|
||||||
|
AddMultipleSolutions,
|
||||||
]
|
]
|
||||||
|
|
41
active_projects/ode/part3/pi_creature_scenes.py
Normal file
41
active_projects/ode/part3/pi_creature_scenes.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from manimlib.imports import *
|
||||||
|
|
||||||
|
|
||||||
|
class IveHeardOfThis(TeacherStudentsScene):
|
||||||
|
def construct(self):
|
||||||
|
point = VectorizedPoint()
|
||||||
|
point.move_to(3 * RIGHT + 2 * UP)
|
||||||
|
self.student_says(
|
||||||
|
"I've heard\\\\", "of this!",
|
||||||
|
student_index=1,
|
||||||
|
target_mode="hooray",
|
||||||
|
bubble_kwargs={
|
||||||
|
"height": 3,
|
||||||
|
"width": 3,
|
||||||
|
"direction": RIGHT,
|
||||||
|
},
|
||||||
|
run_time=1,
|
||||||
|
)
|
||||||
|
self.change_student_modes(
|
||||||
|
"thinking", "hooray", "thinking",
|
||||||
|
look_at_arg=point,
|
||||||
|
added_anims=[self.teacher.change, "happy"]
|
||||||
|
)
|
||||||
|
self.wait(3)
|
||||||
|
self.student_says(
|
||||||
|
"But who\\\\", "cares?",
|
||||||
|
student_index=1,
|
||||||
|
target_mode="maybe",
|
||||||
|
bubble_kwargs={
|
||||||
|
"direction": RIGHT,
|
||||||
|
"width": 3,
|
||||||
|
"height": 3,
|
||||||
|
},
|
||||||
|
run_time=1,
|
||||||
|
)
|
||||||
|
self.change_student_modes(
|
||||||
|
"pondering", "maybe", "pondering",
|
||||||
|
look_at_arg=point,
|
||||||
|
added_anims=[self.teacher.change, "guilty"]
|
||||||
|
)
|
||||||
|
self.wait(5)
|
|
@ -221,32 +221,21 @@ class FourierSeriesIllustraiton(Scene):
|
||||||
y_min=-1,
|
y_min=-1,
|
||||||
y_max=1,
|
y_max=1,
|
||||||
)
|
)
|
||||||
axes2 = axes1.copy()
|
axes1.x_axis.add_numbers(
|
||||||
step_func = axes2.get_graph(
|
0.5, 1,
|
||||||
lambda x: (1 if x < 0.5 else -1),
|
number_config={"num_decimal_places": 1}
|
||||||
discontinuities=[0.5],
|
|
||||||
color=YELLOW,
|
|
||||||
stroke_width=3,
|
|
||||||
)
|
)
|
||||||
dot = Dot(axes2.c2p(0.5, 0), color=step_func.get_color())
|
axes2 = axes1.copy()
|
||||||
dot.scale(0.5)
|
target_func_graph = self.get_target_func_graph(axes2)
|
||||||
step_func.add(dot)
|
axes2.add(target_func_graph)
|
||||||
axes2.add(step_func)
|
|
||||||
|
|
||||||
arrow = Arrow(LEFT, RIGHT, color=WHITE)
|
arrow = Arrow(LEFT, RIGHT, color=WHITE)
|
||||||
VGroup(axes1, arrow, axes2).arrange(RIGHT).shift(UP)
|
group = VGroup(axes1, arrow, axes2)
|
||||||
|
group.arrange(RIGHT, buff=LARGE_BUFF)
|
||||||
def generate_nth_func(n):
|
group.shift(2 * UP)
|
||||||
return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
|
|
||||||
|
|
||||||
def generate_kth_partial_sum_func(k):
|
|
||||||
return lambda x: np.sum([
|
|
||||||
generate_nth_func(n)(x)
|
|
||||||
for n in n_range[:k]
|
|
||||||
])
|
|
||||||
|
|
||||||
sine_graphs = VGroup(*[
|
sine_graphs = VGroup(*[
|
||||||
axes1.get_graph(generate_nth_func(n))
|
axes1.get_graph(self.generate_nth_func(n))
|
||||||
for n in n_range
|
for n in n_range
|
||||||
])
|
])
|
||||||
sine_graphs.set_stroke(width=3)
|
sine_graphs.set_stroke(width=3)
|
||||||
|
@ -256,44 +245,43 @@ class FourierSeriesIllustraiton(Scene):
|
||||||
)
|
)
|
||||||
|
|
||||||
partial_sums = VGroup(*[
|
partial_sums = VGroup(*[
|
||||||
axes1.get_graph(generate_kth_partial_sum_func(k + 1))
|
axes1.get_graph(self.generate_kth_partial_sum_func(k + 1))
|
||||||
for k in range(len(n_range))
|
for k in range(len(n_range))
|
||||||
])
|
])
|
||||||
partial_sums.match_style(sine_graphs)
|
partial_sums.match_style(sine_graphs)
|
||||||
|
|
||||||
sum_tex = TexMobject(
|
sum_tex = self.get_sum_tex()
|
||||||
"\\frac{4}{\\pi}"
|
sum_tex.next_to(axes1, DOWN, LARGE_BUFF)
|
||||||
"\\sum_{1, 3, 5, \\dots}"
|
sum_tex.shift(RIGHT)
|
||||||
"\\frac{1}{n} \\sin(2\\pi \\cdot n \\cdot x)"
|
|
||||||
)
|
|
||||||
sum_tex.next_to(partial_sums, DOWN, buff=0.7)
|
|
||||||
eq = TexMobject("=")
|
eq = TexMobject("=")
|
||||||
step_tex = TexMobject(
|
target_func_tex = self.get_target_func_tex()
|
||||||
"""
|
target_func_tex.next_to(axes2, DOWN)
|
||||||
1 \\quad \\text{if $x < 0.5$} \\\\
|
target_func_tex.match_y(sum_tex)
|
||||||
0 \\quad \\text{if $x = 0.5$} \\\\
|
|
||||||
-1 \\quad \\text{if $x > 0.5$} \\\\
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
lb = Brace(step_tex, LEFT, buff=SMALL_BUFF)
|
|
||||||
step_tex.add(lb)
|
|
||||||
step_tex.next_to(axes2, DOWN, buff=MED_LARGE_BUFF)
|
|
||||||
eq.move_to(midpoint(
|
eq.move_to(midpoint(
|
||||||
step_tex.get_left(),
|
target_func_tex.get_left(),
|
||||||
sum_tex.get_right()
|
sum_tex.get_right()
|
||||||
))
|
))
|
||||||
|
|
||||||
|
range_words = TextMobject(
|
||||||
|
"For $0 \\le x \\le 1$"
|
||||||
|
)
|
||||||
|
range_words.next_to(
|
||||||
|
VGroup(sum_tex, target_func_tex),
|
||||||
|
DOWN,
|
||||||
|
)
|
||||||
|
|
||||||
rects = it.chain(
|
rects = it.chain(
|
||||||
[
|
[
|
||||||
SurroundingRectangle(sum_tex[0][i])
|
SurroundingRectangle(piece)
|
||||||
for i in [4, 6, 8]
|
for piece in self.get_sum_tex_pieces(sum_tex)
|
||||||
],
|
],
|
||||||
it.cycle([None])
|
it.cycle([None])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add(axes1, arrow, axes2)
|
self.add(axes1, arrow, axes2)
|
||||||
self.add(step_func)
|
self.add(target_func_graph)
|
||||||
self.add(sum_tex, eq, step_tex)
|
self.add(sum_tex, eq, target_func_tex)
|
||||||
|
self.add(range_words)
|
||||||
|
|
||||||
curr_partial_sum = axes1.get_graph(lambda x: 0)
|
curr_partial_sum = axes1.get_graph(lambda x: 0)
|
||||||
curr_partial_sum.set_stroke(width=1)
|
curr_partial_sum.set_stroke(width=1)
|
||||||
|
@ -320,6 +308,101 @@ class FourierSeriesIllustraiton(Scene):
|
||||||
self.play(*anims2)
|
self.play(*anims2)
|
||||||
curr_partial_sum = partial_sum
|
curr_partial_sum = partial_sum
|
||||||
|
|
||||||
|
def get_sum_tex(self):
|
||||||
|
return TexMobject(
|
||||||
|
"\\frac{4}{\\pi} \\left(",
|
||||||
|
"\\frac{\\cos(\\pi x)}{1}",
|
||||||
|
"-\\frac{\\cos(3\\pi x)}{3}",
|
||||||
|
"+\\frac{\\cos(5\\pi x)}{5}",
|
||||||
|
"- \\cdots \\right)"
|
||||||
|
).scale(0.75)
|
||||||
|
|
||||||
|
def get_sum_tex_pieces(self, sum_tex):
|
||||||
|
return sum_tex[1:4]
|
||||||
|
|
||||||
|
def get_target_func_tex(self):
|
||||||
|
step_tex = TexMobject(
|
||||||
|
"""
|
||||||
|
1 \\quad \\text{if $x < 0.5$} \\\\
|
||||||
|
0 \\quad \\text{if $x = 0.5$} \\\\
|
||||||
|
-1 \\quad \\text{if $x > 0.5$} \\\\
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
lb = Brace(step_tex, LEFT, buff=SMALL_BUFF)
|
||||||
|
step_tex.add(lb)
|
||||||
|
return step_tex
|
||||||
|
|
||||||
|
def get_target_func_graph(self, axes):
|
||||||
|
step_func = axes.get_graph(
|
||||||
|
lambda x: (1 if x < 0.5 else -1),
|
||||||
|
discontinuities=[0.5],
|
||||||
|
color=YELLOW,
|
||||||
|
stroke_width=3,
|
||||||
|
)
|
||||||
|
dot = Dot(axes.c2p(0.5, 0), color=step_func.get_color())
|
||||||
|
dot.scale(0.5)
|
||||||
|
step_func.add(dot)
|
||||||
|
return step_func
|
||||||
|
|
||||||
|
# def generate_nth_func(self, n):
|
||||||
|
# return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
|
||||||
|
|
||||||
|
def generate_nth_func(self, n):
|
||||||
|
return lambda x: np.prod([
|
||||||
|
(4 / PI),
|
||||||
|
(1 / n) * (-1)**((n - 1) / 2),
|
||||||
|
np.cos(PI * n * x)
|
||||||
|
])
|
||||||
|
|
||||||
|
def generate_kth_partial_sum_func(self, k):
|
||||||
|
return lambda x: np.sum([
|
||||||
|
self.generate_nth_func(n)(x)
|
||||||
|
for n in self.n_range[:k]
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class FourierSeriesOfLineIllustration(FourierSeriesIllustraiton):
|
||||||
|
CONFIG = {
|
||||||
|
"n_range": range(1, 31, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_sum_tex(self):
|
||||||
|
return TexMobject(
|
||||||
|
"\\frac{8}{\\pi^2} \\left(",
|
||||||
|
"\\frac{\\cos(\\pi x)}{1^2}",
|
||||||
|
"+\\frac{\\cos(3\\pi x)}{3^2}",
|
||||||
|
"+\\frac{\\cos(5\\pi x)}{5^2}",
|
||||||
|
"+ \\cdots \\right)"
|
||||||
|
).scale(0.75)
|
||||||
|
|
||||||
|
# def get_sum_tex_pieces(self, sum_tex):
|
||||||
|
# return sum_tex[1:4]
|
||||||
|
|
||||||
|
def get_target_func_tex(self):
|
||||||
|
result = TexMobject("1 - 2x")
|
||||||
|
result.scale(1.5)
|
||||||
|
point = VectorizedPoint()
|
||||||
|
point.next_to(result, RIGHT, 1.5 * LARGE_BUFF)
|
||||||
|
# result.add(point)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_target_func_graph(self, axes):
|
||||||
|
return axes.get_graph(
|
||||||
|
lambda x: 1 - 2 * x,
|
||||||
|
color=YELLOW,
|
||||||
|
stroke_width=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# def generate_nth_func(self, n):
|
||||||
|
# return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
|
||||||
|
|
||||||
|
def generate_nth_func(self, n):
|
||||||
|
return lambda x: np.prod([
|
||||||
|
(8 / PI**2),
|
||||||
|
(1 / n**2),
|
||||||
|
np.cos(PI * n * x)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
class CircleAnimationOfF(FourierOfTrebleClef):
|
class CircleAnimationOfF(FourierOfTrebleClef):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from scipy import integrate
|
||||||
|
|
||||||
from manimlib.imports import *
|
from manimlib.imports import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,8 +32,10 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_three_d_axes(self, include_labels=True):
|
def get_three_d_axes(self, include_labels=True, **kwargs):
|
||||||
axes = ThreeDAxes(**self.axes_config)
|
config = dict(self.axes_config)
|
||||||
|
config.update(kwargs)
|
||||||
|
axes = ThreeDAxes(**config)
|
||||||
axes.set_stroke(width=2)
|
axes.set_stroke(width=2)
|
||||||
|
|
||||||
# Add number labels
|
# Add number labels
|
||||||
|
@ -41,17 +45,19 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
||||||
if include_labels:
|
if include_labels:
|
||||||
x_label = TexMobject("x")
|
x_label = TexMobject("x")
|
||||||
x_label.next_to(axes.x_axis.get_right(), DOWN)
|
x_label.next_to(axes.x_axis.get_right(), DOWN)
|
||||||
axes.x_axis.add(x_label)
|
axes.x_axis.label = x_label
|
||||||
|
|
||||||
t_label = TextMobject("Time")
|
t_label = TextMobject("Time")
|
||||||
t_label.rotate(90 * DEGREES, OUT)
|
t_label.rotate(90 * DEGREES, OUT)
|
||||||
t_label.next_to(axes.y_axis.get_top(), DL)
|
t_label.next_to(axes.y_axis.get_top(), DL)
|
||||||
axes.y_axis.add(t_label)
|
axes.y_axis.label = t_label
|
||||||
|
|
||||||
temp_label = TextMobject("Temperature")
|
temp_label = TextMobject("Temperature")
|
||||||
temp_label.rotate(90 * DEGREES, RIGHT)
|
temp_label.rotate(90 * DEGREES, RIGHT)
|
||||||
temp_label.next_to(axes.z_axis.get_zenith(), RIGHT)
|
temp_label.next_to(axes.z_axis.get_zenith(), RIGHT)
|
||||||
axes.z_axis.add(temp_label)
|
axes.z_axis.label = temp_label
|
||||||
|
for axis in axes:
|
||||||
|
axis.add(axis.label)
|
||||||
|
|
||||||
# Adjust axis orinetations
|
# Adjust axis orinetations
|
||||||
axes.x_axis.rotate(
|
axes.x_axis.rotate(
|
||||||
|
@ -88,19 +94,24 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
||||||
|
|
||||||
return axes
|
return axes
|
||||||
|
|
||||||
def get_initial_state_graph(self, axes, func, **kwargs):
|
def get_time_slice_graph(self, axes, func, t, **kwargs):
|
||||||
config = dict()
|
config = dict()
|
||||||
config.update(self.default_graph_style)
|
config.update(self.default_graph_style)
|
||||||
config.update(kwargs)
|
config.update(kwargs)
|
||||||
return ParametricFunction(
|
return ParametricFunction(
|
||||||
lambda x: axes.c2p(
|
lambda x: axes.c2p(
|
||||||
x, 0, func(x)
|
x, t, func(x)
|
||||||
),
|
),
|
||||||
t_min=axes.x_min,
|
t_min=axes.x_min,
|
||||||
t_max=axes.x_max,
|
t_max=axes.x_max,
|
||||||
**config,
|
**config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_initial_state_graph(self, axes, func, **kwargs):
|
||||||
|
return self.get_time_slice_graph(
|
||||||
|
axes, func, t=0, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def get_surface(self, axes, func, **kwargs):
|
def get_surface(self, axes, func, **kwargs):
|
||||||
config = dict()
|
config = dict()
|
||||||
config.update(axes.surface_config)
|
config.update(axes.surface_config)
|
||||||
|
@ -121,11 +132,11 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
||||||
return mobject
|
return mobject
|
||||||
|
|
||||||
|
|
||||||
class SimpleSinExpGraph(TemperatureGraphScene):
|
class SimpleCosExpGraph(TemperatureGraphScene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
axes = self.get_three_d_axes()
|
axes = self.get_three_d_axes()
|
||||||
sine_graph = self.get_sine_graph(axes)
|
cos_graph = self.get_cos_graph(axes)
|
||||||
sine_exp_surface = self.get_sine_exp_surface(axes)
|
cos_exp_surface = self.get_cos_exp_surface(axes)
|
||||||
|
|
||||||
self.set_camera_orientation(
|
self.set_camera_orientation(
|
||||||
phi=80 * DEGREES,
|
phi=80 * DEGREES,
|
||||||
|
@ -135,36 +146,73 @@ class SimpleSinExpGraph(TemperatureGraphScene):
|
||||||
self.begin_ambient_camera_rotation(rate=0.01)
|
self.begin_ambient_camera_rotation(rate=0.01)
|
||||||
|
|
||||||
self.add(axes)
|
self.add(axes)
|
||||||
self.play(ShowCreation(sine_graph))
|
self.play(ShowCreation(cos_graph))
|
||||||
self.play(UpdateFromAlphaFunc(
|
self.play(UpdateFromAlphaFunc(
|
||||||
sine_exp_surface,
|
cos_exp_surface,
|
||||||
lambda m, a: m.become(
|
lambda m, a: m.become(
|
||||||
self.get_sine_exp_surface(axes, v_max=a * 10)
|
self.get_cos_exp_surface(axes, v_max=a * 10)
|
||||||
),
|
),
|
||||||
run_time=3
|
run_time=3
|
||||||
))
|
))
|
||||||
self.wait(20)
|
|
||||||
|
self.add(cos_graph.copy())
|
||||||
|
|
||||||
|
t_tracker = ValueTracker(0)
|
||||||
|
get_t = t_tracker.get_value
|
||||||
|
cos_graph.add_updater(
|
||||||
|
lambda m: m.become(self.get_time_slice_graph(
|
||||||
|
axes,
|
||||||
|
lambda x: self.cos_exp(x, get_t()),
|
||||||
|
t=get_t()
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
plane = Rectangle(
|
||||||
|
stroke_width=0,
|
||||||
|
fill_color=WHITE,
|
||||||
|
fill_opacity=0.1,
|
||||||
|
)
|
||||||
|
plane.rotate(90 * DEGREES, RIGHT)
|
||||||
|
plane.match_width(axes.x_axis)
|
||||||
|
plane.match_depth(axes.z_axis, stretch=True)
|
||||||
|
plane.move_to(axes.c2p(0, 0, 0), LEFT)
|
||||||
|
|
||||||
|
self.add(plane, cos_graph)
|
||||||
|
self.play(
|
||||||
|
ApplyMethod(
|
||||||
|
t_tracker.set_value, 10,
|
||||||
|
run_time=10,
|
||||||
|
rate_func=linear,
|
||||||
|
),
|
||||||
|
ApplyMethod(
|
||||||
|
plane.shift, 10 * UP,
|
||||||
|
run_time=10,
|
||||||
|
rate_func=linear,
|
||||||
|
),
|
||||||
|
VFadeIn(plane),
|
||||||
|
)
|
||||||
|
self.wait(10)
|
||||||
|
|
||||||
#
|
#
|
||||||
def sin_exp(self, x, t, A=2, omega=1, k=0.25):
|
def cos_exp(self, x, t, A=2, omega=1.5, k=0.1):
|
||||||
return A * np.sin(omega * x) * np.exp(-k * (omega**2) * t)
|
return A * np.cos(omega * x) * np.exp(-k * (omega**2) * t)
|
||||||
|
|
||||||
def get_sine_graph(self, axes, **config):
|
def get_cos_graph(self, axes, **config):
|
||||||
return self.get_initial_state_graph(
|
return self.get_initial_state_graph(
|
||||||
axes,
|
axes,
|
||||||
lambda x: self.sin_exp(x, 0),
|
lambda x: self.cos_exp(x, 0),
|
||||||
**config
|
**config
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_sine_exp_surface(self, axes, **config):
|
def get_cos_exp_surface(self, axes, **config):
|
||||||
return self.get_surface(
|
return self.get_surface(
|
||||||
axes,
|
axes,
|
||||||
lambda x, t: self.sin_exp(x, t),
|
lambda x, t: self.cos_exp(x, t),
|
||||||
**config
|
**config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AddMultipleSolutions(SimpleSinExpGraph):
|
class AddMultipleSolutions(SimpleCosExpGraph):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"axes_config": {
|
"axes_config": {
|
||||||
"x_axis_config": {
|
"x_axis_config": {
|
||||||
|
@ -184,8 +232,8 @@ class AddMultipleSolutions(SimpleSinExpGraph):
|
||||||
self.orient_three_d_mobject(all_axes)
|
self.orient_three_d_mobject(all_axes)
|
||||||
|
|
||||||
As = [1.5, 1.5]
|
As = [1.5, 1.5]
|
||||||
omegas = [1, 2]
|
omegas = [1.5, 2.5]
|
||||||
ks = [0.25, 0.01]
|
ks = [0.1, 0.1]
|
||||||
quads = [
|
quads = [
|
||||||
(axes1, [As[0]], [omegas[0]], [ks[0]]),
|
(axes1, [As[0]], [omegas[0]], [ks[0]]),
|
||||||
(axes2, [As[1]], [omegas[1]], [ks[1]]),
|
(axes2, [As[1]], [omegas[1]], [ks[1]]),
|
||||||
|
@ -196,15 +244,15 @@ class AddMultipleSolutions(SimpleSinExpGraph):
|
||||||
graph = self.get_initial_state_graph(
|
graph = self.get_initial_state_graph(
|
||||||
axes,
|
axes,
|
||||||
lambda x: np.sum([
|
lambda x: np.sum([
|
||||||
self.sin_exp(x, 0, A, omega, k)
|
self.cos_exp(x, 0, A, omega, k)
|
||||||
for A, omega, k in zip(As, omegas, ks)
|
for A, omega, k in zip(As, omegas, ks)
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
surface = self.get_surface(
|
surface = self.get_surface(
|
||||||
axes,
|
axes,
|
||||||
lambda x, t: np.sum([
|
lambda x, t: np.sum([
|
||||||
self.sin_exp(x, t, A, omega)
|
self.cos_exp(x, t, A, omega, k)
|
||||||
for A, omega in zip(As, omegas)
|
for A, omega, k in zip(As, omegas, ks)
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
surface.sort(lambda p: -p[2])
|
surface.sort(lambda p: -p[2])
|
||||||
|
@ -259,3 +307,274 @@ class AddMultipleSolutions(SimpleSinExpGraph):
|
||||||
FadeInFrom(axes3.checkmark, DOWN),
|
FadeInFrom(axes3.checkmark, DOWN),
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class BreakDownAFunction(SimpleCosExpGraph):
|
||||||
|
CONFIG = {
|
||||||
|
"axes_config": {
|
||||||
|
"z_axis_config": {
|
||||||
|
"unit_size": 0.75,
|
||||||
|
"include_tip": False,
|
||||||
|
},
|
||||||
|
"z_min": 0,
|
||||||
|
},
|
||||||
|
"n_low_axes": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
def construct(self):
|
||||||
|
self.set_camera_orientation(distance=100)
|
||||||
|
self.set_axes()
|
||||||
|
self.setup_graphs()
|
||||||
|
self.show_break_down()
|
||||||
|
self.show_solutions_for_waves()
|
||||||
|
|
||||||
|
def set_axes(self):
|
||||||
|
top_axes = self.get_three_d_axes()
|
||||||
|
top_axes.z_axis.label.next_to(
|
||||||
|
top_axes.z_axis.get_end(), OUT, SMALL_BUFF
|
||||||
|
)
|
||||||
|
top_axes.y_axis.set_opacity(0)
|
||||||
|
self.orient_three_d_mobject(top_axes)
|
||||||
|
top_axes.y_axis.label.rotate(-10 * DEGREES, UP)
|
||||||
|
top_axes.scale(0.75)
|
||||||
|
top_axes.center()
|
||||||
|
top_axes.to_edge(UP)
|
||||||
|
|
||||||
|
low_axes = self.get_three_d_axes(
|
||||||
|
z_min=-3,
|
||||||
|
z_axis_config={"unit_size": 1}
|
||||||
|
)
|
||||||
|
low_axes.y_axis.set_opacity(0)
|
||||||
|
for axis in low_axes:
|
||||||
|
axis.label.fade(1)
|
||||||
|
# low_axes.add(low_axes.input_plane)
|
||||||
|
# low_axes.input_plane.set_opacity(0)
|
||||||
|
|
||||||
|
self.orient_three_d_mobject(low_axes)
|
||||||
|
low_axes_group = VGroup(*[
|
||||||
|
low_axes.deepcopy()
|
||||||
|
for x in range(self.n_low_axes)
|
||||||
|
])
|
||||||
|
low_axes_group.arrange(
|
||||||
|
RIGHT, buff=low_axes.get_width() / 3
|
||||||
|
)
|
||||||
|
low_axes_group.set_width(FRAME_WIDTH - 2.5)
|
||||||
|
low_axes_group.next_to(top_axes, DOWN, LARGE_BUFF)
|
||||||
|
low_axes_group.to_edge(LEFT)
|
||||||
|
|
||||||
|
self.top_axes = top_axes
|
||||||
|
self.low_axes_group = low_axes_group
|
||||||
|
|
||||||
|
def setup_graphs(self):
|
||||||
|
top_axes = self.top_axes
|
||||||
|
low_axes_group = self.low_axes_group
|
||||||
|
|
||||||
|
top_graph = self.get_initial_state_graph(
|
||||||
|
top_axes,
|
||||||
|
self.initial_func,
|
||||||
|
discontinuities=self.get_initial_func_discontinuities(),
|
||||||
|
color=YELLOW,
|
||||||
|
)
|
||||||
|
|
||||||
|
fourier_terms = self.get_fourier_cosine_terms(
|
||||||
|
self.initial_func
|
||||||
|
)
|
||||||
|
|
||||||
|
low_graphs = VGroup(*[
|
||||||
|
self.get_initial_state_graph(
|
||||||
|
axes,
|
||||||
|
lambda x: A * np.cos(n * x / 2)
|
||||||
|
)
|
||||||
|
for n, axes, A in zip(
|
||||||
|
it.count(0, 2),
|
||||||
|
low_axes_group,
|
||||||
|
fourier_terms[::2],
|
||||||
|
)
|
||||||
|
])
|
||||||
|
k = 0.1
|
||||||
|
low_surfaces = VGroup(*[
|
||||||
|
self.get_surface(
|
||||||
|
axes,
|
||||||
|
lambda x, t: np.prod([
|
||||||
|
A,
|
||||||
|
np.cos(n * x / 2),
|
||||||
|
np.exp(-k * (n / 2)**2 * t)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
for n, axes, A in zip(
|
||||||
|
it.count(0, 2),
|
||||||
|
low_axes_group,
|
||||||
|
fourier_terms[::2],
|
||||||
|
)
|
||||||
|
])
|
||||||
|
top_surface = self.get_surface(
|
||||||
|
top_axes,
|
||||||
|
lambda x, t: np.sum([
|
||||||
|
np.prod([
|
||||||
|
A,
|
||||||
|
np.cos(n * x / 2),
|
||||||
|
np.exp(-k * (n / 2)**2 * t)
|
||||||
|
])
|
||||||
|
for n, A in zip(
|
||||||
|
it.count(0, 2),
|
||||||
|
fourier_terms[::2]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.top_graph = top_graph
|
||||||
|
self.low_graphs = low_graphs
|
||||||
|
self.low_surfaces = low_surfaces
|
||||||
|
self.top_surface = top_surface
|
||||||
|
|
||||||
|
def show_break_down(self):
|
||||||
|
top_axes = self.top_axes
|
||||||
|
low_axes_group = self.low_axes_group
|
||||||
|
top_graph = self.top_graph
|
||||||
|
low_graphs = self.low_graphs
|
||||||
|
|
||||||
|
plusses = VGroup(*[
|
||||||
|
TexMobject("+").next_to(
|
||||||
|
axes.x_axis.get_end(),
|
||||||
|
RIGHT, MED_LARGE_BUFF
|
||||||
|
)
|
||||||
|
for axes in low_axes_group
|
||||||
|
])
|
||||||
|
dots = TexMobject("\\cdots")
|
||||||
|
dots.next_to(plusses, RIGHT, MED_SMALL_BUFF)
|
||||||
|
arrow = Arrow(
|
||||||
|
dots.get_right(),
|
||||||
|
top_axes.get_right(),
|
||||||
|
path_arc=110 * DEGREES,
|
||||||
|
)
|
||||||
|
|
||||||
|
top_words = TextMobject("Arbitrary\\\\function")
|
||||||
|
top_words.next_to(top_axes, LEFT, MED_LARGE_BUFF)
|
||||||
|
top_words.set_color(YELLOW)
|
||||||
|
top_arrow = Arrow(
|
||||||
|
top_words.get_right(),
|
||||||
|
top_graph.get_center() + LEFT,
|
||||||
|
)
|
||||||
|
|
||||||
|
low_words = TextMobject("Sine curves")
|
||||||
|
low_words.set_color(BLUE)
|
||||||
|
low_words.next_to(low_axes_group, DOWN, MED_LARGE_BUFF)
|
||||||
|
|
||||||
|
self.add(top_axes)
|
||||||
|
self.play(ShowCreation(top_graph))
|
||||||
|
self.play(
|
||||||
|
FadeInFrom(top_words, RIGHT),
|
||||||
|
ShowCreation(top_arrow)
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
LaggedStartMap(FadeIn, low_axes_group),
|
||||||
|
*[
|
||||||
|
TransformFromCopy(top_graph, low_graph)
|
||||||
|
for low_graph in low_graphs
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.play(FadeInFrom(low_words, UP))
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
LaggedStartMap(FadeInFromDown, plusses),
|
||||||
|
Write(dots)
|
||||||
|
)
|
||||||
|
self.play(ShowCreation(arrow))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
def show_solutions_for_waves(self):
|
||||||
|
low_axes_group = self.low_axes_group
|
||||||
|
top_axes = self.top_axes
|
||||||
|
low_graphs = self.low_graphs
|
||||||
|
low_surfaces = self.low_surfaces
|
||||||
|
top_surface = self.top_surface
|
||||||
|
top_graph = self.top_graph
|
||||||
|
|
||||||
|
for surface in [top_surface, *low_surfaces]:
|
||||||
|
surface.sort(lambda p: -p[2])
|
||||||
|
|
||||||
|
anims1 = []
|
||||||
|
anims2 = []
|
||||||
|
for axes, surface, graph in zip(low_axes_group, low_surfaces, low_graphs):
|
||||||
|
axes.y_axis.set_opacity(1)
|
||||||
|
axes.y_axis.label.fade(1)
|
||||||
|
anims1 += [
|
||||||
|
ShowCreation(axes.y_axis),
|
||||||
|
Write(surface, run_time=2),
|
||||||
|
]
|
||||||
|
anims2.append(AnimationGroup(
|
||||||
|
TransformFromCopy(graph, top_graph.copy()),
|
||||||
|
Transform(
|
||||||
|
surface.copy().fade(1),
|
||||||
|
top_surface,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
self.play(*anims1)
|
||||||
|
self.wait()
|
||||||
|
self.play(LaggedStart(*anims2, run_time=2))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
checkmark = TexMobject("\\checkmark")
|
||||||
|
checkmark.set_color(GREEN)
|
||||||
|
low_checkmarks = VGroup(*[
|
||||||
|
checkmark.copy().next_to(
|
||||||
|
surface.get_top(), UP, SMALL_BUFF
|
||||||
|
)
|
||||||
|
for surface in low_surfaces
|
||||||
|
])
|
||||||
|
top_checkmark = checkmark.copy()
|
||||||
|
top_checkmark.scale(1.5)
|
||||||
|
top_checkmark.move_to(top_axes.get_corner(UR))
|
||||||
|
|
||||||
|
self.play(LaggedStartMap(FadeInFromDown, low_checkmarks))
|
||||||
|
self.wait()
|
||||||
|
self.play(TransformFromCopy(
|
||||||
|
low_checkmarks, VGroup(top_checkmark)
|
||||||
|
))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
#
|
||||||
|
def initial_func(self, x):
|
||||||
|
return 3 * np.exp(-(x - PI)**2)
|
||||||
|
|
||||||
|
x1 = TAU / 4 - 0.1
|
||||||
|
x2 = TAU / 4 + 0.1
|
||||||
|
x3 = 3 * TAU / 4 - 0.1
|
||||||
|
x4 = 3 * TAU / 4 + 0.1
|
||||||
|
|
||||||
|
T0 = -2
|
||||||
|
T1 = 2
|
||||||
|
|
||||||
|
if x < x1:
|
||||||
|
return T0
|
||||||
|
elif x < x2:
|
||||||
|
return interpolate(
|
||||||
|
T0, T1,
|
||||||
|
inverse_interpolate(x1, x2, x)
|
||||||
|
)
|
||||||
|
elif x < x3:
|
||||||
|
return T1
|
||||||
|
elif x < x4:
|
||||||
|
return interpolate(
|
||||||
|
T1, T0,
|
||||||
|
inverse_interpolate(x3, x4, x)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return T0
|
||||||
|
|
||||||
|
def get_initial_func_discontinuities(self):
|
||||||
|
# return [TAU / 4, 3 * TAU / 4]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_fourier_cosine_terms(self, func, n_terms=20):
|
||||||
|
result = [
|
||||||
|
integrate.quad(
|
||||||
|
lambda x: (1 / PI) * func(x) * np.cos(n * x / 2),
|
||||||
|
0, TAU
|
||||||
|
)[0]
|
||||||
|
for n in range(n_terms)
|
||||||
|
]
|
||||||
|
result[0] = result[0] / 2
|
||||||
|
return result
|
||||||
|
|
Loading…
Add table
Reference in a new issue