mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
2831 lines
76 KiB
Python
2831 lines
76 KiB
Python
from scipy import integrate
|
|
|
|
from manimlib.imports import *
|
|
from active_projects.diffyq.part2.heat_equation import *
|
|
|
|
|
|
class TemperatureGraphScene(SpecialThreeDScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"x_min": 0,
|
|
"x_max": TAU,
|
|
"y_min": 0,
|
|
"y_max": 10,
|
|
"z_min": -3,
|
|
"z_max": 3,
|
|
"x_axis_config": {
|
|
"tick_frequency": TAU / 8,
|
|
"include_tip": False,
|
|
},
|
|
"num_axis_pieces": 1,
|
|
},
|
|
"default_graph_style": {
|
|
"stroke_width": 2,
|
|
"stroke_color": WHITE,
|
|
"background_image_file": "VerticalTempGradient",
|
|
},
|
|
"default_surface_config": {
|
|
"fill_opacity": 0.1,
|
|
"checkerboard_colors": [LIGHT_GREY],
|
|
"stroke_width": 0.5,
|
|
"stroke_color": WHITE,
|
|
"stroke_opacity": 0.5,
|
|
},
|
|
"temp_text": "Temperature",
|
|
}
|
|
|
|
def get_three_d_axes(self, include_labels=True, include_numbers=False, **kwargs):
|
|
config = dict(self.axes_config)
|
|
config.update(kwargs)
|
|
axes = ThreeDAxes(**config)
|
|
axes.set_stroke(width=2)
|
|
|
|
if include_numbers:
|
|
self.add_axes_numbers(axes)
|
|
|
|
if include_labels:
|
|
self.add_axes_labels(axes)
|
|
|
|
# Adjust axis orientation
|
|
axes.x_axis.rotate(
|
|
90 * DEGREES, RIGHT,
|
|
about_point=axes.c2p(0, 0, 0),
|
|
)
|
|
axes.y_axis.rotate(
|
|
90 * DEGREES, UP,
|
|
about_point=axes.c2p(0, 0, 0),
|
|
)
|
|
|
|
# Add xy-plane
|
|
input_plane = self.get_surface(
|
|
axes, lambda x, t: 0
|
|
)
|
|
input_plane.set_style(
|
|
fill_opacity=0.5,
|
|
fill_color=BLUE_B,
|
|
stroke_width=0.5,
|
|
stroke_color=WHITE,
|
|
)
|
|
|
|
axes.input_plane = input_plane
|
|
|
|
return axes
|
|
|
|
def add_axes_numbers(self, axes):
|
|
x_axis = axes.x_axis
|
|
y_axis = axes.y_axis
|
|
tex_vals = [
|
|
("\\pi \\over 2", TAU / 4),
|
|
("\\pi", TAU / 2),
|
|
("3\\pi \\over 2", 3 * TAU / 4),
|
|
("2\\pi", TAU)
|
|
]
|
|
x_labels = VGroup()
|
|
for tex, val in tex_vals:
|
|
label = TexMobject(tex)
|
|
label.scale(0.5)
|
|
label.next_to(x_axis.n2p(val), DOWN)
|
|
x_labels.add(label)
|
|
x_axis.add(x_labels)
|
|
x_axis.numbers = x_labels
|
|
|
|
y_axis.add_numbers()
|
|
for number in y_axis.numbers:
|
|
number.rotate(90 * DEGREES)
|
|
return axes
|
|
|
|
def add_axes_labels(self, axes):
|
|
x_label = TexMobject("x")
|
|
x_label.next_to(axes.x_axis.get_end(), RIGHT)
|
|
axes.x_axis.label = x_label
|
|
|
|
t_label = TextMobject("Time")
|
|
t_label.rotate(90 * DEGREES, OUT)
|
|
t_label.next_to(axes.y_axis.get_end(), UP)
|
|
axes.y_axis.label = t_label
|
|
|
|
temp_label = TextMobject(self.temp_text)
|
|
temp_label.rotate(90 * DEGREES, RIGHT)
|
|
temp_label.next_to(axes.z_axis.get_zenith(), RIGHT)
|
|
axes.z_axis.label = temp_label
|
|
for axis in axes:
|
|
axis.add(axis.label)
|
|
return axes
|
|
|
|
def get_time_slice_graph(self, axes, func, t, **kwargs):
|
|
config = dict()
|
|
config.update(self.default_graph_style)
|
|
config.update({
|
|
"t_min": axes.x_min,
|
|
"t_max": axes.x_max,
|
|
})
|
|
config.update(kwargs)
|
|
return ParametricFunction(
|
|
lambda x: axes.c2p(
|
|
x, t, func(x, t)
|
|
),
|
|
**config,
|
|
)
|
|
|
|
def get_initial_state_graph(self, axes, func, **kwargs):
|
|
return self.get_time_slice_graph(
|
|
axes,
|
|
lambda x, t: func(x),
|
|
t=0,
|
|
**kwargs
|
|
)
|
|
|
|
def get_surface(self, axes, func, **kwargs):
|
|
config = {
|
|
"u_min": axes.y_min,
|
|
"u_max": axes.y_max,
|
|
"v_min": axes.x_min,
|
|
"v_max": axes.x_max,
|
|
"resolution": (
|
|
(axes.y_max - axes.y_min) // axes.y_axis.tick_frequency,
|
|
(axes.x_max - axes.x_min) // axes.x_axis.tick_frequency,
|
|
),
|
|
}
|
|
config.update(self.default_surface_config)
|
|
config.update(kwargs)
|
|
return ParametricSurface(
|
|
lambda t, x: axes.c2p(
|
|
x, t, func(x, t)
|
|
),
|
|
**config
|
|
)
|
|
|
|
def orient_three_d_mobject(self, mobject,
|
|
phi=85 * DEGREES,
|
|
theta=-80 * DEGREES):
|
|
mobject.rotate(-90 * DEGREES - theta, OUT)
|
|
mobject.rotate(phi, LEFT)
|
|
return mobject
|
|
|
|
def get_rod_length(self):
|
|
return self.axes_config["x_max"]
|
|
|
|
def get_const_time_plane(self, axes):
|
|
t_tracker = ValueTracker(0)
|
|
plane = Polygon(
|
|
*[
|
|
axes.c2p(x, 0, z)
|
|
for x, z in [
|
|
(axes.x_min, axes.z_min),
|
|
(axes.x_max, axes.z_min),
|
|
(axes.x_max, axes.z_max),
|
|
(axes.x_min, axes.z_max),
|
|
]
|
|
],
|
|
stroke_width=0,
|
|
fill_color=WHITE,
|
|
fill_opacity=0.2
|
|
)
|
|
plane.add_updater(lambda m: m.shift(
|
|
axes.c2p(
|
|
axes.x_min,
|
|
t_tracker.get_value(),
|
|
axes.z_min,
|
|
) - plane.points[0]
|
|
))
|
|
plane.t_tracker = t_tracker
|
|
return plane
|
|
|
|
|
|
class SimpleCosExpGraph(TemperatureGraphScene):
|
|
def construct(self):
|
|
axes = self.get_three_d_axes()
|
|
cos_graph = self.get_cos_graph(axes)
|
|
cos_exp_surface = self.get_cos_exp_surface(axes)
|
|
|
|
self.set_camera_orientation(
|
|
phi=80 * DEGREES,
|
|
theta=-80 * DEGREES,
|
|
)
|
|
self.camera.frame_center.shift(3 * RIGHT)
|
|
self.begin_ambient_camera_rotation(rate=0.01)
|
|
|
|
self.add(axes)
|
|
self.play(ShowCreation(cos_graph))
|
|
self.play(UpdateFromAlphaFunc(
|
|
cos_exp_surface,
|
|
lambda m, a: m.become(
|
|
self.get_cos_exp_surface(axes, v_max=a * 10)
|
|
),
|
|
run_time=3
|
|
))
|
|
|
|
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 cos_exp(self, x, t, A=2, omega=1.5, k=0.1):
|
|
return A * np.cos(omega * x) * np.exp(-k * (omega**2) * t)
|
|
|
|
def get_cos_graph(self, axes, **config):
|
|
return self.get_initial_state_graph(
|
|
axes,
|
|
lambda x: self.cos_exp(x, 0),
|
|
**config
|
|
)
|
|
|
|
def get_cos_exp_surface(self, axes, **config):
|
|
return self.get_surface(
|
|
axes,
|
|
lambda x, t: self.cos_exp(x, t),
|
|
**config
|
|
)
|
|
|
|
|
|
class AddMultipleSolutions(SimpleCosExpGraph):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"x_axis_config": {
|
|
"unit_size": 0.7,
|
|
},
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
axes1, axes2, axes3 = all_axes = VGroup(*[
|
|
self.get_three_d_axes(
|
|
include_labels=False,
|
|
)
|
|
for x in range(3)
|
|
])
|
|
all_axes.scale(0.5)
|
|
self.orient_three_d_mobject(all_axes)
|
|
|
|
As = [1.5, 1.5]
|
|
omegas = [1.5, 2.5]
|
|
ks = [0.1, 0.1]
|
|
quads = [
|
|
(axes1, [As[0]], [omegas[0]], [ks[0]]),
|
|
(axes2, [As[1]], [omegas[1]], [ks[1]]),
|
|
(axes3, As, omegas, ks),
|
|
]
|
|
|
|
for axes, As, omegas, ks in quads:
|
|
graph = self.get_initial_state_graph(
|
|
axes,
|
|
lambda x: np.sum([
|
|
self.cos_exp(x, 0, A, omega, k)
|
|
for A, omega, k in zip(As, omegas, ks)
|
|
])
|
|
)
|
|
surface = self.get_surface(
|
|
axes,
|
|
lambda x, t: np.sum([
|
|
self.cos_exp(x, t, A, omega, k)
|
|
for A, omega, k in zip(As, omegas, ks)
|
|
])
|
|
)
|
|
surface.sort(lambda p: -p[2])
|
|
|
|
axes.add(surface, graph)
|
|
axes.graph = graph
|
|
axes.surface = surface
|
|
|
|
self.set_camera_orientation(distance=100)
|
|
plus = TexMobject("+").scale(2)
|
|
equals = TexMobject("=").scale(2)
|
|
group = VGroup(
|
|
axes1, plus, axes2, equals, axes3,
|
|
)
|
|
group.arrange(RIGHT, buff=SMALL_BUFF)
|
|
|
|
for axes in all_axes:
|
|
checkmark = TexMobject("\\checkmark")
|
|
checkmark.set_color(GREEN)
|
|
checkmark.scale(2)
|
|
checkmark.next_to(axes, UP)
|
|
checkmark.shift(0.7 * DOWN)
|
|
axes.checkmark = checkmark
|
|
|
|
self.add(axes1, axes2)
|
|
self.play(
|
|
LaggedStart(
|
|
Write(axes1.surface),
|
|
Write(axes2.surface),
|
|
),
|
|
LaggedStart(
|
|
FadeInFrom(axes1.checkmark, DOWN),
|
|
FadeInFrom(axes2.checkmark, DOWN),
|
|
),
|
|
lag_ratio=0.2,
|
|
run_time=1,
|
|
)
|
|
self.wait()
|
|
self.play(Write(plus))
|
|
self.play(
|
|
Transform(
|
|
axes1.copy().set_fill(opacity=0),
|
|
axes3
|
|
),
|
|
Transform(
|
|
axes2.copy().set_fill(opacity=0),
|
|
axes3
|
|
),
|
|
FadeInFrom(equals, LEFT)
|
|
)
|
|
self.play(
|
|
FadeInFrom(axes3.checkmark, DOWN),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class BreakDownAFunction(SimpleCosExpGraph):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"z_axis_config": {
|
|
"unit_size": 0.75,
|
|
"include_tip": False,
|
|
},
|
|
"z_min": -2,
|
|
"y_max": 20,
|
|
},
|
|
"low_axes_config": {
|
|
"z_min": -3,
|
|
"z_axis_config": {"unit_size": 1}
|
|
},
|
|
"n_low_axes": 4,
|
|
"k": 0.2,
|
|
}
|
|
|
|
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(**self.low_axes_config)
|
|
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,
|
|
)
|
|
top_graph.set_stroke(width=4)
|
|
|
|
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(),
|
|
low_axes_group,
|
|
fourier_terms
|
|
)
|
|
])
|
|
k = self.k
|
|
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(),
|
|
low_axes_group,
|
|
fourier_terms
|
|
)
|
|
])
|
|
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(),
|
|
fourier_terms
|
|
)
|
|
])
|
|
)
|
|
|
|
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_SMALL_BUFF
|
|
)
|
|
for axes in low_axes_group
|
|
])
|
|
dots = TexMobject("\\cdots")
|
|
dots.next_to(plusses, RIGHT, MED_SMALL_BUFF)
|
|
|
|
arrow = Arrow(
|
|
dots.get_right(),
|
|
top_graph.get_end() + 1.4 * DOWN + 1.7 * RIGHT,
|
|
path_arc=90 * 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.point_from_proportion(0.3)
|
|
)
|
|
|
|
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),
|
|
FadeInFrom(low_words, UP),
|
|
LaggedStartMap(FadeInFromDown, [*plusses, dots]),
|
|
*[
|
|
TransformFromCopy(top_graph, low_graph)
|
|
for low_graph in low_graphs
|
|
],
|
|
)
|
|
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 = [
|
|
ApplyMethod(
|
|
top_axes.y_axis.set_opacity, 1,
|
|
),
|
|
]
|
|
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().set_fill(opacity=0),
|
|
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_checkmark, top_checkmark.copy())
|
|
for low_checkmark in low_checkmarks
|
|
])
|
|
self.wait()
|
|
|
|
#
|
|
def initial_func(self, x):
|
|
# return 3 * np.exp(-(x - PI)**2)
|
|
|
|
x1 = TAU / 4 - 1
|
|
x2 = TAU / 4 + 1
|
|
x3 = 3 * TAU / 4 - 1.6
|
|
x4 = 3 * TAU / 4 + 0.3
|
|
|
|
T0 = -2
|
|
T1 = 2
|
|
T2 = 1
|
|
|
|
if x < x1:
|
|
return T0
|
|
elif x < x2:
|
|
alpha = inverse_interpolate(x1, x2, x)
|
|
return bezier([T0, T0, T1, T1])(alpha)
|
|
elif x < x3:
|
|
return T1
|
|
elif x < x4:
|
|
alpha = inverse_interpolate(x3, x4, x)
|
|
return bezier([T1, T1, T2, T2])(alpha)
|
|
else:
|
|
return T2
|
|
|
|
def get_initial_func_discontinuities(self):
|
|
# return [TAU / 4, 3 * TAU / 4]
|
|
return []
|
|
|
|
def get_fourier_cosine_terms(self, func, n_terms=40):
|
|
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
|
|
|
|
|
|
class OceanOfPossibilities(TemperatureGraphScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"z_min": 0,
|
|
"z_max": 4,
|
|
},
|
|
"k": 0.2,
|
|
"default_surface_config": {
|
|
# "resolution": (32, 20),
|
|
# "resolution": (8, 5),
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_camera()
|
|
self.setup_axes()
|
|
self.setup_surface()
|
|
self.show_solution()
|
|
self.reference_boundary_conditions()
|
|
self.reference_initial_condition()
|
|
self.ambiently_change_solution()
|
|
|
|
def setup_camera(self):
|
|
self.set_camera_orientation(
|
|
phi=80 * DEGREES,
|
|
theta=-80 * DEGREES,
|
|
)
|
|
self.begin_ambient_camera_rotation(rate=0.01)
|
|
|
|
def setup_axes(self):
|
|
axes = self.get_three_d_axes(include_numbers=True)
|
|
axes.add(axes.input_plane)
|
|
axes.scale(0.8)
|
|
axes.center()
|
|
axes.shift(OUT + RIGHT)
|
|
|
|
self.add(axes)
|
|
self.axes = axes
|
|
|
|
def setup_surface(self):
|
|
axes = self.axes
|
|
k = self.k
|
|
|
|
# Parameters for surface function
|
|
initial_As = [2] + [
|
|
0.8 * random.choice([-1, 1]) / n
|
|
for n in range(1, 20)
|
|
]
|
|
A_trackers = Group(*[
|
|
ValueTracker(A)
|
|
for A in initial_As
|
|
])
|
|
|
|
def get_As():
|
|
return [At.get_value() for At in A_trackers]
|
|
|
|
omegas = [n / 2 for n in range(0, 10)]
|
|
|
|
def func(x, t):
|
|
return np.sum([
|
|
np.prod([
|
|
A * np.cos(omega * x),
|
|
np.exp(-k * omega**2 * t)
|
|
])
|
|
for A, omega in zip(get_As(), omegas)
|
|
])
|
|
|
|
# Surface and graph
|
|
surface = always_redraw(
|
|
lambda: self.get_surface(axes, func)
|
|
)
|
|
t_tracker = ValueTracker(0)
|
|
graph = always_redraw(
|
|
lambda: self.get_time_slice_graph(
|
|
axes, func, t_tracker.get_value(),
|
|
)
|
|
)
|
|
|
|
surface.suspend_updating()
|
|
graph.suspend_updating()
|
|
|
|
self.surface_func = func
|
|
self.surface = surface
|
|
self.graph = graph
|
|
self.t_tracker = t_tracker
|
|
self.A_trackers = A_trackers
|
|
self.omegas = omegas
|
|
|
|
def show_solution(self):
|
|
axes = self.axes
|
|
surface = self.surface
|
|
graph = self.graph
|
|
t_tracker = self.t_tracker
|
|
get_t = t_tracker.get_value
|
|
|
|
opacity_tracker = ValueTracker(0)
|
|
plane = always_redraw(lambda: Polygon(
|
|
*[
|
|
axes.c2p(x, get_t(), T)
|
|
for x, T in [
|
|
(0, 0), (TAU, 0), (TAU, 4), (0, 4)
|
|
]
|
|
],
|
|
stroke_width=0,
|
|
fill_color=WHITE,
|
|
fill_opacity=opacity_tracker.get_value(),
|
|
))
|
|
|
|
self.add(surface, plane, graph)
|
|
graph.resume_updating()
|
|
self.play(
|
|
opacity_tracker.set_value, 0.2,
|
|
ApplyMethod(
|
|
t_tracker.set_value, 1,
|
|
rate_func=linear
|
|
),
|
|
run_time=1
|
|
)
|
|
self.play(
|
|
ApplyMethod(
|
|
t_tracker.set_value, 10,
|
|
rate_func=linear,
|
|
run_time=9
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
self.plane = plane
|
|
|
|
def reference_boundary_conditions(self):
|
|
axes = self.axes
|
|
t_numbers = axes.y_axis.numbers
|
|
|
|
lines = VGroup(*[
|
|
Line(
|
|
axes.c2p(x, 0, 0),
|
|
axes.c2p(x, axes.y_max, 0),
|
|
stroke_width=3,
|
|
stroke_color=MAROON_B,
|
|
)
|
|
for x in [0, axes.x_max]
|
|
])
|
|
surface_boundary_lines = always_redraw(lambda: VGroup(*[
|
|
ParametricFunction(
|
|
lambda t: axes.c2p(
|
|
x, t,
|
|
self.surface_func(x, t)
|
|
),
|
|
t_max=axes.y_max
|
|
).match_style(self.graph)
|
|
for x in [0, axes.x_max]
|
|
]))
|
|
# surface_boundary_lines.suspend_updating()
|
|
words = VGroup()
|
|
for line in lines:
|
|
word = TextMobject("Boundary")
|
|
word.set_stroke(BLACK, 3, background=True)
|
|
word.scale(1.5)
|
|
word.match_color(line)
|
|
word.rotate(90 * DEGREES, RIGHT)
|
|
word.rotate(90 * DEGREES, OUT)
|
|
word.next_to(line, OUT, SMALL_BUFF)
|
|
words.add(word)
|
|
|
|
self.stop_ambient_camera_rotation()
|
|
self.move_camera(
|
|
theta=-45 * DEGREES,
|
|
added_anims=[
|
|
LaggedStartMap(ShowCreation, lines),
|
|
LaggedStartMap(
|
|
FadeInFrom, words,
|
|
lambda m: (m, IN)
|
|
),
|
|
FadeOut(t_numbers),
|
|
]
|
|
)
|
|
self.play(
|
|
LaggedStart(*[
|
|
TransformFromCopy(l1, l2)
|
|
for l1, l2 in zip(lines, surface_boundary_lines)
|
|
])
|
|
)
|
|
self.add(surface_boundary_lines)
|
|
self.wait()
|
|
self.move_camera(
|
|
theta=-70 * DEGREES,
|
|
)
|
|
|
|
self.surface_boundary_lines = surface_boundary_lines
|
|
|
|
def reference_initial_condition(self):
|
|
plane = self.plane
|
|
t_tracker = self.t_tracker
|
|
|
|
self.play(
|
|
t_tracker.set_value, 0,
|
|
run_time=2
|
|
)
|
|
plane.clear_updaters()
|
|
self.play(FadeOut(plane))
|
|
|
|
def ambiently_change_solution(self):
|
|
A_trackers = self.A_trackers
|
|
|
|
def generate_A_updater(A, rate):
|
|
def update(m, dt):
|
|
m.total_time += dt
|
|
m.set_value(
|
|
2 * A * np.sin(rate * m.total_time + PI / 6)
|
|
)
|
|
return update
|
|
|
|
rates = [0, 0.2] + [
|
|
0.5 + 0.5 * np.random.random()
|
|
for x in range(len(A_trackers) - 2)
|
|
]
|
|
|
|
for tracker, rate in zip(A_trackers, rates):
|
|
tracker.total_time = 0
|
|
tracker.add_updater(generate_A_updater(
|
|
tracker.get_value(),
|
|
rate
|
|
))
|
|
|
|
self.add(*A_trackers)
|
|
self.surface_boundary_lines.resume_updating()
|
|
self.surface.resume_updating()
|
|
self.graph.resume_updating()
|
|
self.begin_ambient_camera_rotation(rate=0.01)
|
|
self.wait(30)
|
|
|
|
|
|
class AnalyzeSineCurve(TemperatureGraphScene):
|
|
CONFIG = {
|
|
"origin_point": 3 * LEFT,
|
|
"axes_config": {
|
|
"z_min": -1.5,
|
|
"z_max": 1.5,
|
|
"z_axis_config": {
|
|
"unit_size": 2,
|
|
"tick_frequency": 0.5,
|
|
}
|
|
},
|
|
"tex_to_color_map": {
|
|
"{x}": GREEN,
|
|
"T": YELLOW,
|
|
"=": WHITE,
|
|
"0": WHITE,
|
|
"\\Delta t": WHITE,
|
|
"\\sin": WHITE,
|
|
"{t}": PINK,
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.ask_about_sine_curve()
|
|
self.show_sine_wave_on_axes()
|
|
self.reference_curvature()
|
|
self.show_derivatives()
|
|
self.show_curvature_matching_height()
|
|
self.show_time_step_scalings()
|
|
self.smooth_evolution()
|
|
|
|
def setup_axes(self):
|
|
axes = self.get_three_d_axes()
|
|
axes.rotate(90 * DEGREES, LEFT)
|
|
axes.shift(self.origin_point - axes.c2p(0, 0, 0))
|
|
y_axis = axes.y_axis
|
|
y_axis.fade(1)
|
|
z_axis = axes.z_axis
|
|
z_axis.label.next_to(z_axis.get_end(), UP, SMALL_BUFF)
|
|
|
|
self.add_axes_numbers(axes)
|
|
y_axis.remove(y_axis.numbers)
|
|
axes.z_axis.add_numbers(
|
|
*range(-1, 2),
|
|
direction=LEFT,
|
|
)
|
|
|
|
self.axes = axes
|
|
|
|
def ask_about_sine_curve(self):
|
|
curve = FunctionGraph(
|
|
lambda t: np.sin(t),
|
|
x_min=0,
|
|
x_max=TAU,
|
|
)
|
|
curve.move_to(DR)
|
|
curve.set_width(5)
|
|
curve.set_color(YELLOW)
|
|
question = TextMobject("What's so special?")
|
|
question.scale(1.5)
|
|
question.to_edge(UP)
|
|
question.shift(2 * LEFT)
|
|
arrow = Arrow(
|
|
question.get_bottom(),
|
|
curve.point_from_proportion(0.25)
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(curve),
|
|
Write(question, run_time=1),
|
|
GrowArrow(arrow),
|
|
)
|
|
self.wait()
|
|
|
|
self.quick_sine_curve = curve
|
|
self.question_group = VGroup(question, arrow)
|
|
|
|
def show_sine_wave_on_axes(self):
|
|
axes = self.axes
|
|
graph = self.get_initial_state_graph(
|
|
axes, lambda x: np.sin(x)
|
|
)
|
|
graph.set_stroke(width=4)
|
|
graph_label = TexMobject(
|
|
"T({x}, 0) = \\sin\\left({x}\\right)",
|
|
tex_to_color_map=self.tex_to_color_map,
|
|
)
|
|
graph_label.next_to(
|
|
graph.point_from_proportion(0.25), UR,
|
|
buff=SMALL_BUFF,
|
|
)
|
|
|
|
v_line, x_tracker = self.get_v_line_with_x_tracker(graph)
|
|
|
|
xs = VGroup(
|
|
*graph_label.get_parts_by_tex("x"),
|
|
axes.x_axis.label,
|
|
)
|
|
|
|
self.play(
|
|
Write(axes),
|
|
self.quick_sine_curve.become, graph,
|
|
FadeOutAndShift(self.question_group, UP),
|
|
)
|
|
self.play(
|
|
FadeInFromDown(graph_label),
|
|
FadeIn(graph),
|
|
)
|
|
self.remove(self.quick_sine_curve)
|
|
self.add(v_line)
|
|
self.play(
|
|
ApplyMethod(
|
|
x_tracker.set_value, TAU,
|
|
rate_func=lambda t: smooth(t, 3),
|
|
run_time=5,
|
|
),
|
|
LaggedStartMap(
|
|
ShowCreationThenFadeAround, xs,
|
|
run_time=3,
|
|
lag_ratio=0.2,
|
|
)
|
|
)
|
|
self.remove(v_line, x_tracker)
|
|
self.wait()
|
|
|
|
self.graph = graph
|
|
self.graph_label = graph_label
|
|
self.v_line = v_line
|
|
self.x_tracker = x_tracker
|
|
|
|
def reference_curvature(self):
|
|
curve_segment, curve_x_tracker = \
|
|
self.get_curve_segment_with_x_tracker(self.graph)
|
|
|
|
self.add(curve_segment)
|
|
self.play(
|
|
curve_x_tracker.set_value, TAU,
|
|
run_time=5,
|
|
rate_func=lambda t: smooth(t, 3),
|
|
)
|
|
self.play(FadeOut(curve_segment))
|
|
|
|
self.curve_segment = curve_segment
|
|
self.curve_x_tracker = curve_x_tracker
|
|
|
|
def show_derivatives(self):
|
|
deriv1 = TexMobject(
|
|
"{\\partial T \\over \\partial {x}}({x}, 0)",
|
|
"= \\cos\\left({x}\\right)",
|
|
tex_to_color_map=self.tex_to_color_map,
|
|
)
|
|
deriv2 = TexMobject(
|
|
"{\\partial^2 T \\over \\partial {x}^2}({x}, 0)",
|
|
"= -\\sin\\left({x}\\right)",
|
|
tex_to_color_map=self.tex_to_color_map,
|
|
)
|
|
|
|
deriv1.to_corner(UR)
|
|
deriv2.next_to(
|
|
deriv1, DOWN,
|
|
buff=0.75,
|
|
aligned_edge=LEFT,
|
|
)
|
|
VGroup(deriv1, deriv2).shift(1.4 * RIGHT)
|
|
|
|
self.play(
|
|
Animation(Group(*self.get_mobjects())),
|
|
FadeInFrom(deriv1, LEFT),
|
|
self.camera.frame_center.shift, 2 * RIGHT,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFrom(deriv2, UP)
|
|
)
|
|
self.wait()
|
|
|
|
self.deriv1 = deriv1
|
|
self.deriv2 = deriv2
|
|
|
|
def show_curvature_matching_height(self):
|
|
axes = self.axes
|
|
graph = self.graph
|
|
curve_segment = self.curve_segment
|
|
curve_x_tracker = self.curve_x_tracker
|
|
|
|
d2_graph = self.get_initial_state_graph(
|
|
axes, lambda x: -np.sin(x),
|
|
)
|
|
dashed_d2_graph = DashedVMobject(d2_graph, num_dashes=50)
|
|
dashed_d2_graph.color_using_background_image(None)
|
|
dashed_d2_graph.set_stroke(RED, 2)
|
|
|
|
vector, x_tracker = self.get_v_line_with_x_tracker(
|
|
d2_graph,
|
|
line_creator=lambda p1, p2: Arrow(
|
|
p1, p2, color=RED, buff=0
|
|
)
|
|
)
|
|
|
|
lil_vectors = self.get_many_lil_vectors(graph)
|
|
lil_vector = always_redraw(
|
|
lambda: self.get_lil_vector(
|
|
graph, x_tracker.get_value()
|
|
)
|
|
)
|
|
|
|
d2_rect = SurroundingRectangle(
|
|
self.deriv2[-5:],
|
|
color=RED,
|
|
)
|
|
self.play(ShowCreation(d2_rect))
|
|
self.add(vector)
|
|
self.add(lil_vector)
|
|
self.add(curve_segment)
|
|
curve_x_tracker.set_value(0)
|
|
self.play(
|
|
ShowCreation(dashed_d2_graph),
|
|
x_tracker.set_value, TAU,
|
|
curve_x_tracker.set_value, TAU,
|
|
ShowIncreasingSubsets(lil_vectors[1:]),
|
|
run_time=8,
|
|
rate_func=linear,
|
|
)
|
|
self.remove(vector)
|
|
self.remove(lil_vector)
|
|
self.add(lil_vectors)
|
|
self.play(
|
|
FadeOut(curve_segment),
|
|
FadeOut(d2_rect),
|
|
)
|
|
|
|
self.lil_vectors = lil_vectors
|
|
self.dashed_d2_graph = dashed_d2_graph
|
|
|
|
def show_time_step_scalings(self):
|
|
axes = self.axes
|
|
graph_label = self.graph_label
|
|
dashed_d2_graph = self.dashed_d2_graph
|
|
lil_vectors = self.lil_vectors
|
|
graph = self.graph
|
|
|
|
factor = 0.9
|
|
|
|
new_label = TexMobject(
|
|
"T({x}, \\Delta t) = c \\cdot \\sin\\left({x}\\right)",
|
|
tex_to_color_map=self.tex_to_color_map,
|
|
)
|
|
final_label = TexMobject(
|
|
"T({x}, {t}) = (\\text{something}) \\cdot \\sin\\left({x}\\right)",
|
|
tex_to_color_map=self.tex_to_color_map,
|
|
)
|
|
for label in (new_label, final_label):
|
|
label.shift(
|
|
graph_label.get_part_by_tex("=").get_center() -
|
|
label.get_part_by_tex("=").get_center()
|
|
)
|
|
final_label.shift(1.5 * LEFT)
|
|
|
|
h_lines = VGroup(
|
|
DashedLine(axes.c2p(0, 0, 1), axes.c2p(TAU, 0, 1)),
|
|
DashedLine(axes.c2p(0, 0, -1), axes.c2p(TAU, 0, -1)),
|
|
)
|
|
|
|
lil_vectors.add_updater(lambda m: m.become(
|
|
self.get_many_lil_vectors(graph)
|
|
))
|
|
|
|
i = 4
|
|
self.play(
|
|
ReplacementTransform(
|
|
graph_label[:i], new_label[:i],
|
|
),
|
|
ReplacementTransform(
|
|
graph_label[i + 1:i + 3],
|
|
new_label[i + 1:i + 3],
|
|
),
|
|
FadeOutAndShift(graph_label[i], UP),
|
|
FadeInFrom(new_label[i], DOWN),
|
|
)
|
|
self.play(
|
|
ReplacementTransform(
|
|
graph_label[i + 3:],
|
|
new_label[i + 4:]
|
|
),
|
|
FadeInFromDown(new_label[i + 3])
|
|
)
|
|
self.play(
|
|
FadeOut(dashed_d2_graph),
|
|
FadeIn(h_lines),
|
|
)
|
|
self.play(
|
|
graph.stretch, factor, 1,
|
|
h_lines.stretch, factor, 1,
|
|
)
|
|
self.wait()
|
|
|
|
# Repeat
|
|
last_coef = None
|
|
last_exp = None
|
|
delta_T = new_label.get_part_by_tex("\\Delta t")
|
|
c = new_label.get_part_by_tex("c")[0]
|
|
prefix = new_label[:4]
|
|
prefix.generate_target()
|
|
for x in range(5):
|
|
coef = Integer(x + 2)
|
|
exp = coef.copy().scale(0.7)
|
|
coef.next_to(
|
|
delta_T, LEFT, SMALL_BUFF,
|
|
aligned_edge=DOWN,
|
|
)
|
|
exp.move_to(c.get_corner(UR), DL)
|
|
anims1 = [FadeInFrom(coef, 0.25 * DOWN)]
|
|
anims2 = [FadeInFrom(exp, 0.25 * DOWN)]
|
|
if last_coef:
|
|
anims1.append(
|
|
FadeOutAndShift(last_coef, 0.25 * UP)
|
|
)
|
|
anims2.append(
|
|
FadeOutAndShift(last_exp, 0.25 * UP)
|
|
)
|
|
last_coef = coef
|
|
last_exp = exp
|
|
prefix.target.next_to(coef, LEFT, SMALL_BUFF)
|
|
prefix.target.match_y(prefix)
|
|
anims1.append(MoveToTarget(prefix))
|
|
|
|
self.play(*anims1)
|
|
self.play(
|
|
graph.stretch, factor, 1,
|
|
h_lines.stretch, factor, 1,
|
|
*anims2,
|
|
)
|
|
self.play(
|
|
ReplacementTransform(
|
|
new_label[:4],
|
|
final_label[:4],
|
|
),
|
|
ReplacementTransform(
|
|
VGroup(last_coef, delta_T),
|
|
final_label.get_part_by_tex("{t}"),
|
|
),
|
|
ReplacementTransform(
|
|
last_exp,
|
|
final_label.get_part_by_tex("something"),
|
|
),
|
|
FadeOut(new_label.get_part_by_tex("\\cdot"), UP),
|
|
ReplacementTransform(
|
|
new_label[-4:],
|
|
final_label[-4:],
|
|
),
|
|
ReplacementTransform(
|
|
new_label.get_part_by_tex("="),
|
|
final_label.get_part_by_tex("="),
|
|
),
|
|
ReplacementTransform(
|
|
new_label.get_part_by_tex(")"),
|
|
final_label.get_part_by_tex(")"),
|
|
),
|
|
)
|
|
final_label.add_background_rectangle(opacity=1)
|
|
self.add(final_label)
|
|
self.wait()
|
|
|
|
group = VGroup(graph, h_lines)
|
|
group.add_updater(lambda m, dt: m.stretch(
|
|
(1 - 0.1 * dt), 1
|
|
))
|
|
self.add(group)
|
|
self.wait(10)
|
|
|
|
def smooth_evolution(self):
|
|
pass
|
|
|
|
#
|
|
def get_rod(self, temp_func):
|
|
pass
|
|
|
|
def get_v_line_with_x_tracker(self, graph, line_creator=DashedLine):
|
|
axes = self.axes
|
|
x_min = axes.x_axis.p2n(graph.get_start())
|
|
x_max = axes.x_axis.p2n(graph.get_end())
|
|
x_tracker = ValueTracker(x_min)
|
|
get_x = x_tracker.get_value
|
|
v_line = always_redraw(lambda: line_creator(
|
|
axes.c2p(get_x(), 0, 0),
|
|
graph.point_from_proportion(
|
|
inverse_interpolate(
|
|
x_min, x_max, get_x()
|
|
)
|
|
),
|
|
))
|
|
return v_line, x_tracker
|
|
|
|
def get_curve_segment_with_x_tracker(self, graph, delta_x=0.5):
|
|
axes = self.axes
|
|
x_min = axes.x_axis.p2n(graph.get_start())
|
|
x_max = axes.x_axis.p2n(graph.get_end())
|
|
x_tracker = ValueTracker(x_min)
|
|
get_x = x_tracker.get_value
|
|
|
|
def x2a(x):
|
|
return inverse_interpolate(x_min, x_max, x)
|
|
|
|
curve = VMobject(
|
|
stroke_color=WHITE,
|
|
stroke_width=5
|
|
)
|
|
curve.add_updater(lambda m: m.pointwise_become_partial(
|
|
graph,
|
|
max(x2a(get_x() - delta_x), 0),
|
|
min(x2a(get_x() + delta_x), 1),
|
|
))
|
|
return curve, x_tracker
|
|
|
|
def get_lil_vector(self, graph, x):
|
|
x_axis = self.axes.x_axis
|
|
point = graph.point_from_proportion(x / TAU)
|
|
x_axis_point = x_axis.n2p(x_axis.p2n(point))
|
|
return Arrow(
|
|
point,
|
|
interpolate(
|
|
point, x_axis_point, 0.5,
|
|
),
|
|
buff=0,
|
|
color=RED
|
|
)
|
|
|
|
def get_many_lil_vectors(self, graph, n=13):
|
|
return VGroup(*[
|
|
self.get_lil_vector(graph, x)
|
|
for x in np.linspace(0, TAU, n)
|
|
])
|
|
|
|
|
|
class SineWaveScaledByExp(TemperatureGraphScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"z_min": -1.5,
|
|
"z_max": 1.5,
|
|
"z_axis_config": {
|
|
"unit_size": 2,
|
|
"tick_frequency": 0.5,
|
|
"label_direction": LEFT,
|
|
},
|
|
"y_axis_config": {
|
|
"label_direction": RIGHT,
|
|
},
|
|
},
|
|
"k": 0.3,
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.setup_camera()
|
|
self.show_sine_wave()
|
|
self.show_decay_surface()
|
|
self.linger_at_end()
|
|
|
|
def setup_axes(self):
|
|
axes = self.get_three_d_axes()
|
|
|
|
# Add number labels
|
|
self.add_axes_numbers(axes)
|
|
for axis in [axes.x_axis, axes.y_axis]:
|
|
axis.numbers.rotate(
|
|
90 * DEGREES,
|
|
axis=axis.get_vector(),
|
|
about_point=axis.point_from_proportion(0.5)
|
|
)
|
|
axis.numbers.set_shade_in_3d(True)
|
|
axes.z_axis.add_numbers(*range(-1, 2))
|
|
for number in axes.z_axis.numbers:
|
|
number.rotate(90 * DEGREES, RIGHT)
|
|
|
|
axes.z_axis.label.next_to(
|
|
axes.z_axis.get_end(), OUT,
|
|
)
|
|
|
|
# Input plane
|
|
axes.input_plane.set_opacity(0.25)
|
|
self.add(axes.input_plane)
|
|
|
|
# Shift into place
|
|
# axes.shift(5 * LEFT)
|
|
self.axes = axes
|
|
self.add(axes)
|
|
|
|
def setup_camera(self):
|
|
self.set_camera_orientation(
|
|
phi=80 * DEGREES,
|
|
theta=-80 * DEGREES,
|
|
distance=50,
|
|
)
|
|
self.camera.set_frame_center(
|
|
2 * RIGHT,
|
|
)
|
|
|
|
def show_sine_wave(self):
|
|
time_tracker = ValueTracker(0)
|
|
graph = always_redraw(
|
|
lambda: self.get_time_slice_graph(
|
|
self.axes,
|
|
self.sin_exp,
|
|
t=time_tracker.get_value(),
|
|
)
|
|
)
|
|
graph.suspend_updating()
|
|
|
|
graph_label = TexMobject("\\sin(x)")
|
|
graph_label.set_color(BLUE)
|
|
graph_label.rotate(90 * DEGREES, RIGHT)
|
|
graph_label.next_to(
|
|
graph.point_from_proportion(0.25),
|
|
OUT,
|
|
SMALL_BUFF,
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(graph),
|
|
FadeInFrom(graph_label, IN)
|
|
)
|
|
self.wait()
|
|
graph.resume_updating()
|
|
|
|
self.time_tracker = time_tracker
|
|
self.graph = graph
|
|
|
|
def show_decay_surface(self):
|
|
time_tracker = self.time_tracker
|
|
axes = self.axes
|
|
|
|
plane = Rectangle()
|
|
plane.rotate(90 * DEGREES, RIGHT)
|
|
plane.set_stroke(width=0)
|
|
plane.set_fill(WHITE, 0.2)
|
|
plane.match_depth(axes.z_axis)
|
|
plane.match_width(axes.x_axis, stretch=True)
|
|
plane.add_updater(
|
|
lambda p: p.move_to(axes.c2p(
|
|
0,
|
|
time_tracker.get_value(),
|
|
0,
|
|
), LEFT)
|
|
)
|
|
|
|
time_slices = VGroup(*[
|
|
self.get_time_slice_graph(
|
|
self.axes,
|
|
self.sin_exp,
|
|
t=t,
|
|
)
|
|
for t in range(0, 10)
|
|
])
|
|
surface_t_tracker = ValueTracker(0)
|
|
surface = always_redraw(
|
|
lambda: self.get_surface(
|
|
self.axes,
|
|
self.sin_exp,
|
|
v_max=surface_t_tracker.get_value(),
|
|
).set_stroke(opacity=0)
|
|
)
|
|
|
|
exp_graph = ParametricFunction(
|
|
lambda t: axes.c2p(
|
|
PI / 2,
|
|
t,
|
|
self.sin_exp(PI / 2, t)
|
|
),
|
|
t_min=axes.y_min,
|
|
t_max=axes.y_max,
|
|
)
|
|
exp_graph.set_stroke(RED, 3)
|
|
exp_graph.set_shade_in_3d(True)
|
|
|
|
exp_label = TexMobject("e^{-\\alpha t}")
|
|
exp_label.scale(1.5)
|
|
exp_label.set_color(RED)
|
|
exp_label.rotate(90 * DEGREES, RIGHT)
|
|
exp_label.rotate(90 * DEGREES, OUT)
|
|
exp_label.next_to(
|
|
exp_graph.point_from_proportion(0.3),
|
|
OUT + UP,
|
|
)
|
|
|
|
self.move_camera(
|
|
theta=-25 * DEGREES,
|
|
)
|
|
self.add(surface)
|
|
self.add(plane)
|
|
self.play(
|
|
surface_t_tracker.set_value, axes.y_max,
|
|
time_tracker.set_value, axes.y_max,
|
|
ShowIncreasingSubsets(
|
|
time_slices,
|
|
int_func=np.ceil,
|
|
),
|
|
run_time=5,
|
|
rate_func=linear,
|
|
)
|
|
surface.clear_updaters()
|
|
|
|
self.play(
|
|
ShowCreation(exp_graph),
|
|
FadeOut(plane),
|
|
FadeInFrom(exp_label, IN),
|
|
time_slices.set_stroke, {"width": 1},
|
|
)
|
|
|
|
def linger_at_end(self):
|
|
self.wait()
|
|
self.begin_ambient_camera_rotation(rate=-0.02)
|
|
self.wait(20)
|
|
|
|
#
|
|
def sin_exp(self, x, t):
|
|
return np.sin(x) * np.exp(-self.k * t)
|
|
|
|
|
|
class BoundaryConditionReference(ShowEvolvingTempGraphWithArrows):
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.setup_graph()
|
|
|
|
rod = self.get_rod(0, 10)
|
|
self.color_rod_by_graph(rod)
|
|
|
|
boundary_points = [
|
|
rod.get_right(),
|
|
rod.get_left(),
|
|
]
|
|
boundary_dots = VGroup(*[
|
|
Dot(point, radius=0.2)
|
|
for point in boundary_points
|
|
])
|
|
boundary_arrows = VGroup(*[
|
|
Vector(2 * DOWN).next_to(dot, UP)
|
|
for dot in boundary_dots
|
|
])
|
|
boundary_arrows.set_stroke(YELLOW, 10)
|
|
|
|
words = TextMobject(
|
|
"Different rules\\\\",
|
|
"at the boundary",
|
|
)
|
|
words.scale(1.5)
|
|
words.to_edge(UP)
|
|
|
|
# self.add(self.axes)
|
|
# self.add(self.graph)
|
|
self.add(rod)
|
|
self.play(FadeInFromDown(words))
|
|
self.play(
|
|
LaggedStartMap(GrowArrow, boundary_arrows),
|
|
LaggedStartMap(GrowFromCenter, boundary_dots),
|
|
lag_ratio=0.3,
|
|
run_time=1,
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class SimulateRealSineCurve(ShowEvolvingTempGraphWithArrows):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"x_min": 0,
|
|
"x_max": TAU,
|
|
"x_axis_config": {
|
|
"unit_size": 1.5,
|
|
"include_tip": False,
|
|
"tick_frequency": PI / 4,
|
|
},
|
|
"y_min": -1.5,
|
|
"y_max": 1.5,
|
|
"y_axis_config": {
|
|
"tick_frequency": 0.5,
|
|
"unit_size": 2,
|
|
},
|
|
},
|
|
"graph_x_min": 0,
|
|
"graph_x_max": TAU,
|
|
"arrow_xs": np.linspace(0, TAU, 13),
|
|
"rod_opacity": 0.5,
|
|
"wait_time": 30,
|
|
"alpha": 0.5,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_axes()
|
|
self.add_graph()
|
|
self.add_clock()
|
|
self.add_rod()
|
|
self.add_arrows()
|
|
self.initialize_updaters()
|
|
self.let_play()
|
|
|
|
def add_labels_to_axes(self):
|
|
x_axis = self.axes.x_axis
|
|
x_axis.add(*[
|
|
TexMobject(tex).scale(0.5).next_to(
|
|
x_axis.n2p(n),
|
|
DOWN,
|
|
buff=MED_SMALL_BUFF
|
|
)
|
|
for tex, n in [
|
|
("\\tau \\over 4", TAU / 4),
|
|
("\\tau \\over 2", TAU / 2),
|
|
("3 \\tau \\over 4", 3 * TAU / 4),
|
|
("\\tau", TAU),
|
|
]
|
|
])
|
|
|
|
def add_axes(self):
|
|
super().add_axes()
|
|
self.add_labels_to_axes()
|
|
|
|
def add_rod(self):
|
|
super().add_rod()
|
|
self.rod.set_opacity(self.rod_opacity)
|
|
self.rod.set_stroke(width=0)
|
|
|
|
def initial_function(self, x):
|
|
return np.sin(x)
|
|
|
|
def y_to_color(self, y):
|
|
return temperature_to_color(0.8 * y)
|
|
|
|
|
|
class StraightLine3DGraph(TemperatureGraphScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"z_min": 0,
|
|
"z_max": 10,
|
|
"z_axis_config": {
|
|
'unit_size': 0.5,
|
|
}
|
|
},
|
|
"c": 1.2,
|
|
}
|
|
|
|
def construct(self):
|
|
axes = self.get_three_d_axes()
|
|
axes.add(axes.input_plane)
|
|
axes.move_to(2 * IN + UP, IN)
|
|
surface = self.get_surface(
|
|
axes, self.func,
|
|
)
|
|
initial_graph = self.get_initial_state_graph(
|
|
axes, lambda x: self.func(x, 0)
|
|
)
|
|
|
|
plane = self.get_const_time_plane(axes)
|
|
initial_graph.add_updater(
|
|
lambda m: m.move_to(plane, IN)
|
|
)
|
|
|
|
self.set_camera_orientation(
|
|
phi=80 * DEGREES,
|
|
theta=-100 * DEGREES,
|
|
)
|
|
self.begin_ambient_camera_rotation()
|
|
|
|
self.add(axes)
|
|
self.add(initial_graph)
|
|
self.play(
|
|
TransformFromCopy(initial_graph, surface)
|
|
)
|
|
self.add(surface, initial_graph)
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeIn(plane),
|
|
ApplyMethod(
|
|
plane.t_tracker.set_value, 10,
|
|
rate_func=linear,
|
|
run_time=10,
|
|
)
|
|
)
|
|
self.play(FadeOut(plane))
|
|
self.wait(15)
|
|
|
|
def func(self, x, t):
|
|
return self.c * x
|
|
|
|
|
|
class SimulateLinearGraph(SimulateRealSineCurve):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"y_min": 0,
|
|
"y_max": 3,
|
|
"y_axis_config": {
|
|
"tick_frequency": 0.5,
|
|
"unit_size": 2,
|
|
},
|
|
},
|
|
"arrow_scale_factor": 2,
|
|
"alpha": 1,
|
|
"wait_time": 40,
|
|
"step_size": 0.02,
|
|
}
|
|
|
|
# def let_play(self):
|
|
# pass
|
|
|
|
def add_labels_to_axes(self):
|
|
pass
|
|
|
|
def y_to_color(self, y):
|
|
return temperature_to_color(0.8 * (y - 1.5))
|
|
|
|
def initial_function(self, x):
|
|
axes = self.axes
|
|
y_max = axes.y_max
|
|
x_max = axes.x_max
|
|
slope = y_max / x_max
|
|
return slope * x
|
|
|
|
|
|
class EmphasizeBoundaryPoints(SimulateLinearGraph):
|
|
CONFIG = {
|
|
"wait_time": 30,
|
|
}
|
|
|
|
def let_play(self):
|
|
rod = self.rod
|
|
self.graph.update(0.01)
|
|
self.arrows.update()
|
|
|
|
to_update = VGroup(
|
|
self.graph,
|
|
self.arrows,
|
|
self.time_label,
|
|
)
|
|
for mob in to_update:
|
|
mob.suspend_updating()
|
|
|
|
dots = VGroup(
|
|
Dot(rod.get_left()),
|
|
Dot(rod.get_right()),
|
|
)
|
|
for dot in dots:
|
|
dot.set_height(0.2)
|
|
dot.set_color(YELLOW)
|
|
|
|
words = TextMobject(
|
|
"Different rules\\\\"
|
|
"at the boundary"
|
|
)
|
|
words.next_to(rod, UP)
|
|
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
words.get_corner(corner),
|
|
dot.get_top(),
|
|
path_arc=u * 60 * DEGREES,
|
|
)
|
|
for corner, dot, u in zip(
|
|
[LEFT, RIGHT], dots, [1, -1]
|
|
)
|
|
])
|
|
|
|
self.add(words)
|
|
self.play(
|
|
LaggedStartMap(
|
|
FadeInFromLarge, dots,
|
|
scale_factor=5,
|
|
),
|
|
LaggedStartMap(
|
|
ShowCreation, arrows,
|
|
),
|
|
lag_ratio=0.4
|
|
)
|
|
self.wait()
|
|
|
|
for mob in to_update:
|
|
mob.resume_updating()
|
|
|
|
super().let_play()
|
|
|
|
|
|
class FlatEdgesContinuousEvolution(ShowEvolvingTempGraphWithArrows):
|
|
CONFIG = {
|
|
"wait_time": 30,
|
|
"freq_amplitude_pairs": [
|
|
(1, 0.5),
|
|
(2, 1),
|
|
(3, 0.5),
|
|
(4, 0.3),
|
|
],
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_axes()
|
|
self.add_graph()
|
|
self.add_clock()
|
|
self.add_rod()
|
|
self.initialize_updaters()
|
|
self.add_boundary_lines()
|
|
self.let_play()
|
|
|
|
def add_boundary_lines(self):
|
|
|
|
lines = VGroup(*[
|
|
Line(LEFT, RIGHT)
|
|
for x in range(2)
|
|
])
|
|
lines.set_width(1.5)
|
|
lines.set_stroke(WHITE, 5, opacity=0.5)
|
|
lines.add_updater(self.update_lines)
|
|
|
|
turn_animation_into_updater(
|
|
ShowCreation(lines, run_time=2)
|
|
)
|
|
self.add(lines)
|
|
|
|
def update_lines(self, lines):
|
|
graph = self.graph
|
|
lines[0].move_to(graph.get_start())
|
|
lines[1].move_to(graph.get_end())
|
|
|
|
def initial_function(self, x):
|
|
return ShowEvolvingTempGraphWithArrows.initial_function(self, x)
|
|
|
|
|
|
class MoreAccurateTempFlowWithArrows(ShowEvolvingTempGraphWithArrows):
|
|
CONFIG = {
|
|
"arrow_scale_factor": 1,
|
|
"max_magnitude": 1,
|
|
"freq_amplitude_pairs": [
|
|
(1, 0.5),
|
|
(2, 1),
|
|
(3, 0.5),
|
|
(4, 0.3),
|
|
],
|
|
}
|
|
|
|
def setup_axes(self):
|
|
super().setup_axes()
|
|
y_axis = self.axes.y_axis
|
|
y_axis.remove(y_axis.label)
|
|
|
|
|
|
class SlopeToHeatFlow(FlatEdgesContinuousEvolution):
|
|
CONFIG = {
|
|
"wait_time": 20,
|
|
"xs": [1.75, 5.25, 7.8],
|
|
"axes_config": {
|
|
"x_min": 0,
|
|
"x_max": 10,
|
|
"x_axis_config": {
|
|
"include_tip": False,
|
|
}
|
|
},
|
|
"n_arrows": 10,
|
|
"random_seed": 1,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_axes()
|
|
self.add_graph()
|
|
self.add_rod()
|
|
#
|
|
self.show_slope_labels()
|
|
self.show_heat_flow()
|
|
|
|
def show_slope_labels(self):
|
|
axes = self.axes
|
|
# axes.x_axis.add_numbers()
|
|
graph = self.graph
|
|
xs = self.xs
|
|
|
|
lines = VGroup(*[
|
|
TangentLine(
|
|
graph,
|
|
inverse_interpolate(
|
|
axes.x_min,
|
|
axes.x_max,
|
|
x
|
|
),
|
|
length=2,
|
|
stroke_opacity=0.75,
|
|
)
|
|
for x in xs
|
|
])
|
|
|
|
slope_words = VGroup()
|
|
for line in lines:
|
|
slope = line.get_slope()
|
|
word = TextMobject(
|
|
"Slope =",
|
|
"${:.2}$".format(slope)
|
|
)
|
|
if slope > 0:
|
|
word[1].set_color(GREEN)
|
|
else:
|
|
word[1].set_color(RED)
|
|
|
|
word.set_width(line.get_length())
|
|
word.next_to(ORIGIN, UP, SMALL_BUFF)
|
|
word.rotate(
|
|
line.get_angle(),
|
|
about_point=ORIGIN,
|
|
)
|
|
word.shift(line.get_center())
|
|
slope_words.add(word)
|
|
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, lines),
|
|
LaggedStartMap(Write, slope_words),
|
|
lag_ratio=0.5,
|
|
run_time=1,
|
|
)
|
|
self.wait()
|
|
|
|
self.lines = lines
|
|
self.slope_words = slope_words
|
|
|
|
def show_heat_flow(self):
|
|
axes = self.axes
|
|
xs = self.xs
|
|
slope_words = self.slope_words
|
|
tan_lines = self.lines
|
|
|
|
slopes = map(Line.get_slope, self.lines)
|
|
flow_rates = [-s for s in slopes]
|
|
|
|
points = list(map(axes.x_axis.n2p, xs))
|
|
v_lines = VGroup(*[
|
|
Line(
|
|
line.get_center(), point,
|
|
stroke_width=1,
|
|
)
|
|
for point, line in zip(points, tan_lines)
|
|
])
|
|
|
|
flow_words = VGroup(*[
|
|
TextMobject("Heat flow").next_to(
|
|
point, DOWN
|
|
).scale(0.8)
|
|
for point in points
|
|
])
|
|
flow_mobjects = VGroup(*[
|
|
self.get_flow(x, flow_rate)
|
|
for x, flow_rate in zip(xs, flow_rates)
|
|
])
|
|
|
|
self.add(flow_mobjects)
|
|
self.wait()
|
|
self.play(
|
|
LaggedStart(*[
|
|
TransformFromCopy(
|
|
sw[0], fw[0]
|
|
)
|
|
for sw, fw in zip(slope_words, flow_words)
|
|
]),
|
|
LaggedStartMap(ShowCreation, v_lines),
|
|
lag_ratio=0.4,
|
|
run_time=2,
|
|
)
|
|
self.wait(self.wait_time)
|
|
|
|
def get_flow(self, x, flow_rate):
|
|
return VGroup(*[
|
|
self.get_single_flow_arrow(
|
|
x, flow_rate,
|
|
t_offset=to
|
|
)
|
|
for to in np.linspace(0, 5, self.n_arrows)
|
|
])
|
|
|
|
def get_single_flow_arrow(self, x, flow_rate, t_offset):
|
|
axes = self.axes
|
|
point = axes.x_axis.n2p(x)
|
|
|
|
h_shift = 0.5
|
|
v_shift = 0.4 * np.random.random() + 0.1
|
|
|
|
arrow = Vector(0.5 * RIGHT * np.sign(flow_rate))
|
|
arrow.move_to(point)
|
|
arrow.shift(v_shift * UP)
|
|
lp = arrow.get_center() + h_shift * LEFT
|
|
rp = arrow.get_center() + h_shift * RIGHT
|
|
lc = self.rod_point_to_color(lp)
|
|
rc = self.rod_point_to_color(rp)
|
|
|
|
run_time = 3 / flow_rate
|
|
animation = UpdateFromAlphaFunc(
|
|
arrow,
|
|
lambda m, a: m.move_to(
|
|
interpolate(lp, rp, a)
|
|
).set_color(
|
|
interpolate_color(lc, rc, a)
|
|
).set_opacity(there_and_back(a)),
|
|
run_time=run_time,
|
|
)
|
|
result = cycle_animation(animation)
|
|
animation.total_time += t_offset
|
|
return result
|
|
|
|
|
|
class CloserLookAtStraightLine(SimulateLinearGraph):
|
|
def construct(self):
|
|
self.add_axes()
|
|
self.add_graph()
|
|
self.add_clock()
|
|
self.add_rod()
|
|
# self.initialize_updaters()
|
|
#
|
|
self.show_t_eq_0_state()
|
|
self.show_t_gt_0_state()
|
|
|
|
def show_t_eq_0_state(self):
|
|
t_eq = TexMobject("t", "=", "0")
|
|
t_eq.next_to(self.time_label, DOWN)
|
|
|
|
circles = VGroup(*[
|
|
Circle(
|
|
radius=0.25,
|
|
stroke_color=YELLOW,
|
|
)
|
|
for x in range(2)
|
|
])
|
|
circles.add_updater(self.attach_to_endpoints)
|
|
|
|
self.play(Write(t_eq))
|
|
self.play(ShowCreation(circles))
|
|
self.wait()
|
|
self.play(FadeOut(circles))
|
|
|
|
self.circles = circles
|
|
self.t_eq = t_eq
|
|
|
|
def show_t_gt_0_state(self):
|
|
# circles = self.circles
|
|
t_eq = self.t_eq
|
|
|
|
t_ineq = TexMobject("t", ">", "0")
|
|
t_ineq.move_to(t_eq)
|
|
|
|
slope_lines = VGroup(*[
|
|
Line(LEFT, RIGHT)
|
|
for x in range(2)
|
|
])
|
|
slope_lines.set_opacity(0.5)
|
|
slope_lines.add_updater(self.attach_to_endpoints)
|
|
|
|
self.remove(t_eq)
|
|
self.add(t_ineq)
|
|
|
|
self.initialize_updaters()
|
|
self.run_clock(0.1)
|
|
for mob in self.mobjects:
|
|
mob.suspend_updating()
|
|
self.wait()
|
|
|
|
self.add(slope_lines)
|
|
self.add(self.clock, self.time_label, t_ineq)
|
|
self.play(ShowCreation(slope_lines))
|
|
for mob in self.mobjects:
|
|
mob.resume_updating()
|
|
|
|
self.run_clock(self.wait_time)
|
|
|
|
#
|
|
def attach_to_endpoints(self, mobs):
|
|
points = [
|
|
self.graph.get_start(),
|
|
self.graph.get_end(),
|
|
]
|
|
for mob, point in zip(mobs, points):
|
|
mob.move_to(point)
|
|
return mobs
|
|
|
|
|
|
class ManipulateSinExpSurface(TemperatureGraphScene):
|
|
CONFIG = {
|
|
"axes_config": {
|
|
"z_max": 1.25,
|
|
"z_min": -1.25,
|
|
"z_axis_config": {
|
|
"unit_size": 2.5,
|
|
"tick_frequency": 0.5,
|
|
},
|
|
"x_axis_config": {
|
|
# "unit_size": 1.5,
|
|
"unit_size": 1.0,
|
|
"tick_frequency": 1,
|
|
},
|
|
"x_max": 10,
|
|
"y_max": 15,
|
|
},
|
|
"alpha": 0.2,
|
|
"tex_mobject_config": {
|
|
"tex_to_color_map": {
|
|
"{x}": GREEN,
|
|
"{t}": YELLOW,
|
|
"\\omega": MAROON_B,
|
|
"^2": WHITE,
|
|
},
|
|
},
|
|
"graph_config": {},
|
|
"initial_phi": -90 * DEGREES,
|
|
"initial_omega": 1,
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_sine_wave()
|
|
self.shift_sine_to_cosine()
|
|
self.show_derivatives_of_cos()
|
|
self.show_cos_exp_surface()
|
|
self.change_frequency()
|
|
self.talk_through_omega()
|
|
self.show_cos_omega_derivatives()
|
|
self.show_rebalanced_exp()
|
|
|
|
def setup_axes(self):
|
|
axes = self.get_three_d_axes(include_numbers=True)
|
|
axes.x_axis.remove(axes.x_axis.numbers)
|
|
L = TexMobject("L")
|
|
L.rotate(90 * DEGREES, RIGHT)
|
|
L.next_to(axes.x_axis.get_end(), IN)
|
|
axes.x_axis.label = L
|
|
axes.x_axis.add(L)
|
|
|
|
axes.shift(5 * LEFT + 0.5 * IN)
|
|
axes.z_axis.label[0].remove(
|
|
*axes.z_axis.label[0][1:]
|
|
)
|
|
axes.z_axis.label.next_to(
|
|
axes.z_axis.get_end(), OUT
|
|
)
|
|
axes.z_axis.add_numbers(
|
|
*np.arange(-1, 1.5, 0.5),
|
|
direction=LEFT,
|
|
number_config={
|
|
"num_decimal_places": 1,
|
|
}
|
|
)
|
|
for number in axes.z_axis.numbers:
|
|
number.rotate(90 * DEGREES, RIGHT)
|
|
|
|
self.set_camera_orientation(
|
|
phi=90 * DEGREES,
|
|
theta=-90 * DEGREES,
|
|
distance=100,
|
|
)
|
|
|
|
self.add(axes)
|
|
self.remove(axes.y_axis)
|
|
self.axes = axes
|
|
|
|
def add_sine_wave(self):
|
|
self.initialize_parameter_trackers()
|
|
graph = self.get_graph()
|
|
sin_label = TexMobject(
|
|
"\\sin\\left({x}\\right)",
|
|
**self.tex_mobject_config,
|
|
)
|
|
sin_label.shift(2 * LEFT + 2.75 * UP)
|
|
|
|
self.add_fixed_in_frame_mobjects(sin_label)
|
|
graph.suspend_updating()
|
|
self.play(
|
|
ShowCreation(graph),
|
|
Write(sin_label),
|
|
)
|
|
graph.resume_updating()
|
|
self.wait()
|
|
|
|
self.sin_label = sin_label
|
|
self.graph = graph
|
|
|
|
def shift_sine_to_cosine(self):
|
|
graph = self.graph
|
|
sin_label = self.sin_label
|
|
|
|
sin_cross = Cross(sin_label)
|
|
sin_cross.add_updater(
|
|
lambda m: m.move_to(sin_label)
|
|
)
|
|
cos_label = TexMobject(
|
|
"\\cos\\left({x}\\right)",
|
|
**self.tex_mobject_config,
|
|
)
|
|
cos_label.move_to(sin_label, LEFT)
|
|
cos_label.shift(LEFT)
|
|
# cos_label.shift(
|
|
# axes.c2p(0, 0) - axes.c2p(PI / 2, 0),
|
|
# )
|
|
|
|
left_tangent = Line(ORIGIN, RIGHT)
|
|
left_tangent.set_stroke(WHITE, 5)
|
|
|
|
self.add_fixed_in_frame_mobjects(cos_label)
|
|
self.play(
|
|
ApplyMethod(
|
|
self.phi_tracker.set_value, 0,
|
|
),
|
|
FadeOutAndShift(sin_label, LEFT),
|
|
FadeInFrom(cos_label, RIGHT),
|
|
run_time=2,
|
|
)
|
|
left_tangent.move_to(graph.get_start(), LEFT)
|
|
self.play(ShowCreation(left_tangent))
|
|
self.play(FadeOut(left_tangent))
|
|
|
|
self.cos_label = cos_label
|
|
|
|
def show_derivatives_of_cos(self):
|
|
cos_label = self.cos_label
|
|
cos_exp_label = TexMobject(
|
|
"\\cos\\left({x}\\right)",
|
|
"e^{-\\alpha {t}}",
|
|
**self.tex_mobject_config
|
|
)
|
|
cos_exp_label.move_to(cos_label, LEFT)
|
|
|
|
ddx_group, ddx_exp_group = [
|
|
self.get_ddx_computation_group(
|
|
label,
|
|
*[
|
|
TexMobject(
|
|
s + "\\left({x}\\right)" + exp,
|
|
**self.tex_mobject_config,
|
|
)
|
|
for s in ["-\\sin", "-\\cos"]
|
|
]
|
|
)
|
|
for label, exp in [
|
|
(cos_label, ""),
|
|
(cos_exp_label, "e^{-\\alpha {t}}"),
|
|
]
|
|
]
|
|
|
|
self.add_fixed_in_frame_mobjects(ddx_group)
|
|
self.play(FadeIn(ddx_group))
|
|
self.wait()
|
|
|
|
# Cos exp
|
|
transforms = [
|
|
ReplacementTransform(
|
|
cos_label, cos_exp_label,
|
|
),
|
|
ReplacementTransform(
|
|
ddx_group, ddx_exp_group,
|
|
),
|
|
]
|
|
for trans in transforms:
|
|
trans.begin()
|
|
self.add_fixed_in_frame_mobjects(trans.mobject)
|
|
self.play(*transforms)
|
|
self.add_fixed_in_frame_mobjects(
|
|
cos_exp_label, ddx_exp_group
|
|
)
|
|
self.remove_fixed_in_frame_mobjects(
|
|
cos_label,
|
|
*[
|
|
trans.mobject
|
|
for trans in transforms
|
|
]
|
|
)
|
|
|
|
self.cos_exp_label = cos_exp_label
|
|
self.ddx_exp_group = ddx_exp_group
|
|
|
|
def show_cos_exp_surface(self):
|
|
axes = self.axes
|
|
|
|
surface = self.get_cos_exp_surface()
|
|
self.add(surface)
|
|
surface.max_t_tracker.set_value(0)
|
|
self.move_camera(
|
|
phi=85 * DEGREES,
|
|
theta=-80 * DEGREES,
|
|
added_anims=[
|
|
ApplyMethod(
|
|
surface.max_t_tracker.set_value,
|
|
axes.y_max,
|
|
run_time=4,
|
|
),
|
|
Write(axes.y_axis),
|
|
]
|
|
)
|
|
self.wait()
|
|
|
|
self.surface = surface
|
|
|
|
def change_frequency(self):
|
|
cos_exp_label = self.cos_exp_label
|
|
ddx_exp_group = self.ddx_exp_group
|
|
omega_tracker = self.omega_tracker
|
|
|
|
cos_omega = TexMobject(
|
|
"\\cos\\left(",
|
|
"\\omega", "\\cdot", "{x}",
|
|
"\\right)",
|
|
**self.tex_mobject_config
|
|
)
|
|
cos_omega.move_to(cos_exp_label, LEFT)
|
|
|
|
omega = cos_omega.get_part_by_tex("\\omega")
|
|
brace = Brace(omega, UP, buff=SMALL_BUFF)
|
|
omega_decimal = always_redraw(
|
|
lambda: DecimalNumber(
|
|
omega_tracker.get_value(),
|
|
color=omega.get_color(),
|
|
).next_to(brace, UP, SMALL_BUFF)
|
|
)
|
|
|
|
self.add_fixed_in_frame_mobjects(
|
|
cos_omega,
|
|
brace,
|
|
omega_decimal,
|
|
)
|
|
self.play(
|
|
self.camera.phi_tracker.set_value, 90 * DEGREES,
|
|
self.camera.theta_tracker.set_value, -90 * DEGREES,
|
|
FadeOut(self.surface),
|
|
FadeOut(self.axes.y_axis),
|
|
FadeOut(cos_exp_label),
|
|
FadeOut(ddx_exp_group),
|
|
FadeIn(cos_omega),
|
|
GrowFromCenter(brace),
|
|
FadeInFromDown(omega_decimal)
|
|
)
|
|
for n in [2, 6, 1, 4]:
|
|
freq = n * PI / self.axes.x_max
|
|
self.play(
|
|
omega_tracker.set_value, freq,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
self.wait()
|
|
|
|
self.cos_omega = cos_omega
|
|
self.omega_brace = brace
|
|
|
|
def talk_through_omega(self):
|
|
axes = self.axes
|
|
|
|
x_tracker = ValueTracker(0)
|
|
get_x = x_tracker.get_value
|
|
v_line = always_redraw(lambda: DashedLine(
|
|
axes.c2p(get_x(), 0, 0),
|
|
axes.c2p(get_x(), 0, self.func(get_x(), 0)),
|
|
))
|
|
|
|
x = self.cos_omega.get_part_by_tex("{x}")
|
|
brace = Brace(x, DOWN)
|
|
x_decimal = always_redraw(
|
|
lambda: DecimalNumber(
|
|
get_x(),
|
|
color=x.get_color()
|
|
).next_to(brace, DOWN, SMALL_BUFF)
|
|
)
|
|
|
|
self.add(v_line)
|
|
self.add_fixed_in_frame_mobjects(brace, x_decimal)
|
|
self.play(
|
|
x_tracker.set_value, 5,
|
|
run_time=5,
|
|
rate_func=linear,
|
|
)
|
|
self.play(
|
|
FadeOut(v_line),
|
|
FadeOut(brace),
|
|
FadeOut(x_decimal),
|
|
)
|
|
self.remove_fixed_in_frame_mobjects(
|
|
brace, x_decimal
|
|
)
|
|
|
|
def show_cos_omega_derivatives(self):
|
|
cos_omega = self.cos_omega
|
|
ddx_omega_group = self.get_ddx_computation_group(
|
|
cos_omega,
|
|
*[
|
|
TexMobject(
|
|
s + "\\left(\\omega \\cdot {x}\\right)",
|
|
**self.tex_mobject_config,
|
|
)
|
|
for s in ["-\\omega \\sin", "-\\omega^2 \\cos"]
|
|
]
|
|
)
|
|
|
|
omega_squared = ddx_omega_group[-1][1:3]
|
|
rect = SurroundingRectangle(omega_squared)
|
|
|
|
self.add_fixed_in_frame_mobjects(ddx_omega_group)
|
|
self.play(FadeIn(ddx_omega_group))
|
|
self.wait()
|
|
self.add_fixed_in_frame_mobjects(rect)
|
|
self.play(ShowCreationThenFadeOut(rect))
|
|
self.wait()
|
|
|
|
self.ddx_omega_group = ddx_omega_group
|
|
|
|
def show_rebalanced_exp(self):
|
|
cos_omega = self.cos_omega
|
|
ddx_omega_group = self.ddx_omega_group
|
|
|
|
cos_exp = TexMobject(
|
|
"\\cos\\left(",
|
|
"\\omega", "\\cdot", "{x}",
|
|
"\\right)",
|
|
"e^{-\\alpha \\omega^2 {t}}",
|
|
**self.tex_mobject_config
|
|
)
|
|
cos_exp.move_to(cos_omega, DL)
|
|
|
|
self.add_fixed_in_frame_mobjects(cos_exp)
|
|
self.play(
|
|
FadeOut(cos_omega),
|
|
FadeOut(ddx_omega_group),
|
|
FadeIn(cos_exp),
|
|
)
|
|
self.remove_fixed_in_frame_mobjects(
|
|
cos_omega,
|
|
ddx_omega_group,
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(self.surface),
|
|
FadeIn(self.axes.y_axis),
|
|
VGroup(
|
|
cos_exp,
|
|
self.omega_brace,
|
|
).shift, 4 * RIGHT,
|
|
self.camera.phi_tracker.set_value, 80 * DEGREES,
|
|
self.camera.theta_tracker.set_value, -80 * DEGREES,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
self.omega_tracker.set_value, TAU / 10,
|
|
run_time=6,
|
|
)
|
|
|
|
#
|
|
def initialize_parameter_trackers(self):
|
|
self.phi_tracker = ValueTracker(
|
|
self.initial_phi
|
|
)
|
|
self.omega_tracker = ValueTracker(
|
|
self.initial_omega
|
|
)
|
|
self.t_tracker = ValueTracker(0)
|
|
|
|
def get_graph(self):
|
|
return always_redraw(
|
|
lambda: self.get_time_slice_graph(
|
|
self.axes, self.func,
|
|
t=self.t_tracker.get_value(),
|
|
**self.graph_config
|
|
)
|
|
)
|
|
|
|
def get_cos_exp_surface(self):
|
|
max_t_tracker = ValueTracker(self.axes.y_max)
|
|
surface = always_redraw(
|
|
lambda: self.get_surface(
|
|
self.axes,
|
|
self.func,
|
|
v_max=max_t_tracker.get_value(),
|
|
)
|
|
)
|
|
surface.max_t_tracker = max_t_tracker
|
|
return surface
|
|
|
|
def func(self, x, t):
|
|
phi = self.phi_tracker.get_value()
|
|
omega = self.omega_tracker.get_value()
|
|
alpha = self.alpha
|
|
return op.mul(
|
|
np.cos(omega * (x + phi)),
|
|
np.exp(-alpha * (omega**2) * t)
|
|
)
|
|
|
|
def get_ddx_computation_group(self, f, df, ddf):
|
|
arrows = VGroup(*[
|
|
Vector(0.5 * RIGHT) for x in range(2)
|
|
])
|
|
group = VGroup(
|
|
arrows[0], df, arrows[1], ddf
|
|
)
|
|
group.arrange(RIGHT)
|
|
group.next_to(f, RIGHT)
|
|
|
|
for arrow in arrows:
|
|
label = TexMobject(
|
|
"\\partial \\over \\partial {x}",
|
|
**self.tex_mobject_config,
|
|
)
|
|
label.scale(0.5)
|
|
label.next_to(arrow, UP, SMALL_BUFF)
|
|
arrow.add(label)
|
|
|
|
group.arrows = arrows
|
|
group.funcs = VGroup(df, ddf)
|
|
|
|
return group
|
|
|
|
|
|
class ShowFreq1CosExpDecay(ManipulateSinExpSurface):
|
|
CONFIG = {
|
|
"freq": 1,
|
|
"alpha": 0.2,
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_back_y_axis()
|
|
self.initialize_parameter_trackers()
|
|
self.phi_tracker.set_value(0)
|
|
self.omega_tracker.set_value(
|
|
self.freq * TAU / 10,
|
|
)
|
|
#
|
|
self.show_decay()
|
|
|
|
def add_back_y_axis(self):
|
|
axes = self.axes
|
|
self.add(axes.y_axis)
|
|
self.remove(axes.y_axis.numbers)
|
|
self.remove(axes.y_axis.label)
|
|
|
|
def show_decay(self):
|
|
axes = self.axes
|
|
t_tracker = self.t_tracker
|
|
t_max = self.axes.y_max
|
|
|
|
graph = always_redraw(
|
|
lambda: self.get_time_slice_graph(
|
|
axes,
|
|
self.func,
|
|
t=t_tracker.get_value(),
|
|
stroke_width=5,
|
|
)
|
|
)
|
|
|
|
surface = self.get_surface(
|
|
self.axes, self.func,
|
|
)
|
|
plane = self.get_const_time_plane(axes)
|
|
|
|
self.add(surface, plane, graph)
|
|
|
|
self.set_camera_orientation(
|
|
phi=80 * DEGREES,
|
|
theta=-85 * DEGREES,
|
|
)
|
|
self.begin_ambient_camera_rotation(rate=0.01)
|
|
self.play(
|
|
t_tracker.set_value, t_max,
|
|
plane.t_tracker.set_value, t_max,
|
|
run_time=t_max,
|
|
rate_func=linear,
|
|
)
|
|
|
|
|
|
class ShowFreq2CosExpDecay(ShowFreq1CosExpDecay):
|
|
CONFIG = {
|
|
"freq": 2,
|
|
}
|
|
|
|
|
|
class ShowFreq4CosExpDecay(ShowFreq1CosExpDecay):
|
|
CONFIG = {
|
|
"freq": 4,
|
|
}
|
|
|
|
|
|
class ShowHarmonics(SimulateRealSineCurve):
|
|
CONFIG = {
|
|
"rod_opacity": 0.75,
|
|
"initial_omega": 1.27,
|
|
"default_n_rod_pieces": 32,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_axes()
|
|
self.add_graph()
|
|
self.add_rod()
|
|
self.rod.add_updater(self.color_rod_by_graph)
|
|
#
|
|
self.show_formula()
|
|
|
|
def add_graph(self):
|
|
omega_tracker = ValueTracker(self.initial_omega)
|
|
get_omega = omega_tracker.get_value
|
|
graph = always_redraw(
|
|
lambda: self.axes.get_graph(
|
|
lambda x: np.cos(get_omega() * x),
|
|
x_min=self.graph_x_min,
|
|
x_max=self.graph_x_max,
|
|
step_size=self.step_size,
|
|
discontinuities=[5],
|
|
).color_using_background_image("VerticalTempGradient")
|
|
)
|
|
|
|
self.add(graph)
|
|
self.graph = graph
|
|
self.omega_tracker = omega_tracker
|
|
|
|
def show_formula(self):
|
|
rod = self.rod
|
|
graph = self.graph
|
|
axes = self.axes
|
|
omega_tracker = self.omega_tracker
|
|
L = TAU
|
|
|
|
formula = TexMobject(
|
|
"=", "\\cos\\left(",
|
|
"2(\\pi / L)", "\\cdot", "{x}",
|
|
"\\right)",
|
|
tex_to_color_map={
|
|
"{x}": GREEN,
|
|
}
|
|
)
|
|
formula.next_to(
|
|
self.axes.y_axis.label, RIGHT, SMALL_BUFF
|
|
)
|
|
omega_part = formula.get_part_by_tex("\\pi")
|
|
omega_brace = Brace(omega_part, DOWN)
|
|
omega = TexMobject("\\omega")
|
|
omega.set_color(MAROON_B)
|
|
omega.next_to(omega_brace, DOWN, SMALL_BUFF)
|
|
formula.remove(omega_part)
|
|
|
|
pi_over_L = TexMobject("(\\pi / L)")
|
|
pi_over_L.move_to(omega_part)
|
|
pi_over_L.match_color(omega)
|
|
|
|
self.add(formula)
|
|
self.add(omega_brace)
|
|
self.add(omega)
|
|
|
|
self.remove(graph)
|
|
self.play(GrowFromEdge(rod, LEFT))
|
|
self.play(
|
|
ShowCreationThenFadeAround(axes.x_axis.label)
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(graph))
|
|
self.play(FadeInFromDown(pi_over_L))
|
|
self.play(
|
|
omega_tracker.set_value, PI / L,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
|
|
# Show x
|
|
x_tracker = ValueTracker(0)
|
|
tip = ArrowTip(
|
|
start_angle=-90 * DEGREES,
|
|
color=WHITE,
|
|
)
|
|
tip.add_updater(lambda m: m.move_to(
|
|
axes.x_axis.n2p(x_tracker.get_value()),
|
|
DOWN,
|
|
))
|
|
x_sym = TexMobject("x")
|
|
x_sym.set_color(GREEN)
|
|
x_sym.add_background_rectangle(buff=SMALL_BUFF)
|
|
x_sym.add_updater(lambda m: m.next_to(tip, UP, SMALL_BUFF))
|
|
|
|
self.play(
|
|
Write(tip),
|
|
Write(x_sym),
|
|
)
|
|
self.play(
|
|
x_tracker.set_value, L,
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
self.play(TransformFromCopy(
|
|
x_sym, formula.get_part_by_tex("{x}")
|
|
))
|
|
self.play(
|
|
FadeOut(tip),
|
|
FadeOut(x_sym),
|
|
)
|
|
|
|
# Harmonics
|
|
pi_over_L.generate_target()
|
|
n_sym = Integer(2)
|
|
n_sym.match_color(pi_over_L)
|
|
group = VGroup(n_sym, pi_over_L.target)
|
|
group.arrange(RIGHT, buff=SMALL_BUFF)
|
|
group.move_to(pi_over_L)
|
|
|
|
self.play(
|
|
MoveToTarget(pi_over_L),
|
|
FadeInFromDown(n_sym),
|
|
ApplyMethod(
|
|
omega_tracker.set_value, 2 * PI / L,
|
|
run_time=2,
|
|
)
|
|
)
|
|
self.wait()
|
|
for n in [*range(3, 9), 0]:
|
|
new_n_sym = Integer(n)
|
|
new_n_sym.move_to(n_sym, DR)
|
|
new_n_sym.match_style(n_sym)
|
|
self.play(
|
|
FadeOutAndShift(n_sym, UP),
|
|
FadeInFrom(new_n_sym, DOWN),
|
|
omega_tracker.set_value, n * PI / L,
|
|
)
|
|
self.wait()
|
|
n_sym = new_n_sym
|
|
|
|
#
|
|
def add_labels_to_axes(self):
|
|
x_axis = self.axes.x_axis
|
|
L = TexMobject("L")
|
|
L.next_to(x_axis.get_end(), DOWN)
|
|
x_axis.add(L)
|
|
x_axis.label = L
|
|
|
|
|
|
class ShowHarmonicSurfaces(ManipulateSinExpSurface):
|
|
CONFIG = {
|
|
"alpha": 0.2,
|
|
"initial_phi": 0,
|
|
"initial_omega": PI / 10,
|
|
"n_iterations": 8,
|
|
"default_surface_config": {
|
|
"resolution": (40, 30),
|
|
"surface_piece_config": {
|
|
"stroke_width": 0.5,
|
|
}
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.initialize_parameter_trackers()
|
|
self.add_surface()
|
|
self.add_graph()
|
|
self.show_all_harmonic_surfaces()
|
|
|
|
def setup_axes(self):
|
|
super().setup_axes()
|
|
self.add(self.axes.y_axis)
|
|
self.set_camera_orientation(
|
|
phi=82 * DEGREES,
|
|
theta=-80 * DEGREES,
|
|
)
|
|
|
|
def add_surface(self):
|
|
self.surface = self.get_cos_exp_surface()
|
|
self.add(self.surface)
|
|
|
|
def add_graph(self):
|
|
self.graph = self.get_graph()
|
|
self.add(self.graph)
|
|
|
|
def show_all_harmonic_surfaces(self):
|
|
omega_tracker = self.omega_tracker
|
|
formula = self.get_formula(str(1))
|
|
L = self.axes.x_max
|
|
|
|
self.begin_ambient_camera_rotation(rate=0.01)
|
|
self.add_fixed_in_frame_mobjects(formula)
|
|
self.wait(2)
|
|
for n in range(2, self.n_iterations):
|
|
if n > 5:
|
|
n_str = "n"
|
|
else:
|
|
n_str = str(n)
|
|
new_formula = self.get_formula(n_str)
|
|
self.play(
|
|
Transform(formula, new_formula),
|
|
ApplyMethod(
|
|
omega_tracker.set_value,
|
|
n * PI / L
|
|
),
|
|
)
|
|
self.wait(3)
|
|
|
|
#
|
|
def get_formula(self, n_str):
|
|
n_str = "{" + n_str + "}"
|
|
result = TexMobject(
|
|
"\\cos\\left(",
|
|
n_str, "(", "\\pi / L", ")", "{x}"
|
|
"\\right)"
|
|
"e^{-\\alpha (", n_str, "\\pi / L", ")^2",
|
|
"{t}}",
|
|
tex_to_color_map={
|
|
"{x}": GREEN,
|
|
"{t}": YELLOW,
|
|
"\\pi / L": MAROON_B,
|
|
n_str: MAROON_B,
|
|
}
|
|
)
|
|
result.to_edge(UP)
|
|
return result
|
|
|
|
|
|
class Thumbnail(ShowHarmonicSurfaces):
|
|
CONFIG = {
|
|
"default_surface_config": {
|
|
"resolution": (40, 30),
|
|
# "resolution": (10, 10),
|
|
},
|
|
"graph_config": {
|
|
"stroke_width": 8,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.initialize_parameter_trackers()
|
|
self.add_surface()
|
|
self.add_graph()
|
|
#
|
|
self.omega_tracker.set_value(3 * PI / 10)
|
|
self.set_camera_orientation(
|
|
theta=-70 * DEGREES,
|
|
)
|
|
|
|
axes = self.axes
|
|
for axis in [axes.y_axis, axes.z_axis]:
|
|
axis.numbers.set_opacity(0)
|
|
axis.remove(*axis.numbers)
|
|
axes.x_axis.label.set_opacity(0)
|
|
axes.z_axis.label.set_opacity(0)
|
|
|
|
for n in range(2, 16, 2):
|
|
new_graph = self.get_time_slice_graph(
|
|
axes, self.func, t=n,
|
|
**self.graph_config
|
|
)
|
|
new_graph.set_shade_in_3d(True)
|
|
new_graph.set_stroke(
|
|
width=8 / np.sqrt(n),
|
|
# opacity=1 / n**(1 / 4),
|
|
)
|
|
self.add(new_graph)
|
|
|
|
words = TextMobject(
|
|
"Sine waves + Linearity + Fourier = Solution"
|
|
)
|
|
words.set_width(FRAME_WIDTH - 1)
|
|
words.to_edge(DOWN)
|
|
words.shift(2 * DOWN)
|
|
self.add_fixed_in_frame_mobjects(words)
|
|
|
|
self.camera.frame_center.shift(DOWN)
|
|
self.update_mobjects(0)
|
|
self.surface.set_stroke(width=0.1)
|
|
self.surface.set_fill(opacity=0.2)
|