mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
475 lines
14 KiB
Python
475 lines
14 KiB
Python
from manimlib.imports import *
|
|
from active_projects.diffyq.part3.temperature_graphs import TemperatureGraphScene
|
|
from active_projects.diffyq.part2.wordy_scenes import WriteHeatEquationTemplate
|
|
|
|
|
|
class ShowLinearity(WriteHeatEquationTemplate, TemperatureGraphScene):
|
|
CONFIG = {
|
|
"temp_text": "Temp",
|
|
"alpha": 0.1,
|
|
"axes_config": {
|
|
"z_max": 2,
|
|
"z_min": -2,
|
|
"z_axis_config": {
|
|
"tick_frequency": 0.5,
|
|
"unit_size": 1.5,
|
|
},
|
|
},
|
|
"default_surface_config": {
|
|
"resolution": (16, 16)
|
|
# "resolution": (4, 4)
|
|
},
|
|
"freqs": [2, 5],
|
|
}
|
|
|
|
def setup(self):
|
|
TemperatureGraphScene.setup(self)
|
|
WriteHeatEquationTemplate.setup(self)
|
|
|
|
def construct(self):
|
|
self.init_camera()
|
|
self.add_three_graphs()
|
|
self.show_words()
|
|
self.add_function_labels()
|
|
self.change_scalars()
|
|
|
|
def init_camera(self):
|
|
self.camera.set_distance(1000)
|
|
|
|
def add_three_graphs(self):
|
|
axes_group = self.get_axes_group()
|
|
axes0, axes1, axes2 = axes_group
|
|
freqs = self.freqs
|
|
scalar_trackers = Group(
|
|
ValueTracker(1),
|
|
ValueTracker(1),
|
|
)
|
|
graphs = VGroup(
|
|
self.get_graph(axes0, [freqs[0]], [scalar_trackers[0]]),
|
|
self.get_graph(axes1, [freqs[1]], [scalar_trackers[1]]),
|
|
self.get_graph(axes2, freqs, scalar_trackers),
|
|
)
|
|
|
|
plus = TexMobject("+").scale(2)
|
|
equals = TexMobject("=").scale(2)
|
|
plus.move_to(midpoint(
|
|
axes0.get_right(),
|
|
axes1.get_left(),
|
|
))
|
|
equals.move_to(midpoint(
|
|
axes1.get_right(),
|
|
axes2.get_left(),
|
|
))
|
|
|
|
self.add(axes_group)
|
|
self.add(graphs)
|
|
self.add(plus)
|
|
self.add(equals)
|
|
|
|
self.axes_group = axes_group
|
|
self.graphs = graphs
|
|
self.scalar_trackers = scalar_trackers
|
|
self.plus = plus
|
|
self.equals = equals
|
|
|
|
def show_words(self):
|
|
equation = self.get_d1_equation()
|
|
name = TextMobject("Heat equation")
|
|
name.next_to(equation, DOWN)
|
|
name.set_color_by_gradient(RED, YELLOW)
|
|
group = VGroup(equation, name)
|
|
group.to_edge(UP)
|
|
|
|
shift_val = 0.5 * RIGHT
|
|
|
|
arrow = Vector(1.5 * RIGHT)
|
|
arrow.move_to(group)
|
|
arrow.shift(shift_val)
|
|
linear_word = TextMobject("``Linear''")
|
|
linear_word.scale(2)
|
|
linear_word.next_to(arrow, RIGHT)
|
|
|
|
self.add(group)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
group.next_to, arrow, LEFT
|
|
)
|
|
self.play(FadeInFrom(linear_word, LEFT))
|
|
self.wait()
|
|
|
|
def add_function_labels(self):
|
|
axes_group = self.axes_group
|
|
graphs = self.graphs
|
|
|
|
solution_labels = VGroup()
|
|
for axes in axes_group:
|
|
label = TextMobject("Solution", "$\\checkmark$")
|
|
label.set_color_by_tex("checkmark", GREEN)
|
|
label.next_to(axes, DOWN)
|
|
solution_labels.add(label)
|
|
|
|
kw = {
|
|
"tex_to_color_map": {
|
|
"T_1": BLUE,
|
|
"T_2": GREEN,
|
|
}
|
|
}
|
|
T1 = TexMobject("a", "T_1", **kw)
|
|
T2 = TexMobject("b", "T_2", **kw)
|
|
T_sum = TexMobject("T_1", "+", "T_2", **kw)
|
|
T_sum_with_scalars = TexMobject(
|
|
"a", "T_1", "+", "b", "T_2", **kw
|
|
)
|
|
|
|
T1.next_to(graphs[0], UP)
|
|
T2.next_to(graphs[1], UP)
|
|
T_sum.next_to(graphs[2], UP)
|
|
T_sum.shift(SMALL_BUFF * DOWN)
|
|
T_sum_with_scalars.move_to(T_sum)
|
|
|
|
a_brace = Brace(T1[0], UP, buff=SMALL_BUFF)
|
|
b_brace = Brace(T2[0], UP, buff=SMALL_BUFF)
|
|
s1_decimal = DecimalNumber()
|
|
s1_decimal.match_color(T1[1])
|
|
s1_decimal.next_to(a_brace, UP, SMALL_BUFF)
|
|
s1_decimal.add_updater(lambda m: m.set_value(
|
|
self.scalar_trackers[0].get_value()
|
|
))
|
|
s2_decimal = DecimalNumber()
|
|
s2_decimal.match_color(T2[1])
|
|
s2_decimal.next_to(b_brace, UP, SMALL_BUFF)
|
|
s2_decimal.add_updater(lambda m: m.set_value(
|
|
self.scalar_trackers[1].get_value()
|
|
))
|
|
|
|
self.play(
|
|
FadeInFrom(T1[1], DOWN),
|
|
FadeInFrom(solution_labels[0], UP),
|
|
)
|
|
self.play(
|
|
FadeInFrom(T2[1], DOWN),
|
|
FadeInFrom(solution_labels[1], UP),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(T1[1], T_sum[0]),
|
|
TransformFromCopy(T2[1], T_sum[2]),
|
|
TransformFromCopy(self.plus, T_sum[1]),
|
|
*[
|
|
Transform(
|
|
graph.copy().set_fill(opacity=0),
|
|
graphs[2].copy().set_fill(opacity=0),
|
|
remover=True
|
|
)
|
|
for graph in graphs[:2]
|
|
]
|
|
)
|
|
self.wait()
|
|
self.play(FadeInFrom(solution_labels[2], UP))
|
|
self.wait()
|
|
|
|
# Show constants
|
|
self.play(
|
|
FadeIn(T1[0]),
|
|
FadeIn(T2[0]),
|
|
FadeIn(a_brace),
|
|
FadeIn(b_brace),
|
|
FadeIn(s1_decimal),
|
|
FadeIn(s2_decimal),
|
|
FadeOut(T_sum),
|
|
FadeIn(T_sum_with_scalars),
|
|
)
|
|
|
|
def change_scalars(self):
|
|
s1, s2 = self.scalar_trackers
|
|
|
|
kw = {
|
|
"run_time": 2,
|
|
}
|
|
for graph in self.graphs:
|
|
graph.resume_updating()
|
|
self.play(s2.set_value, -0.5, **kw)
|
|
self.play(s1.set_value, -0.2, **kw)
|
|
self.play(s2.set_value, 1.5, **kw)
|
|
self.play(s1.set_value, 1.2, **kw)
|
|
self.play(s2.set_value, 0.3, **kw)
|
|
self.wait()
|
|
|
|
#
|
|
def get_axes_group(self):
|
|
axes_group = VGroup(*[
|
|
self.get_axes()
|
|
for x in range(3)
|
|
])
|
|
axes_group.arrange(RIGHT, buff=2)
|
|
axes_group.set_width(FRAME_WIDTH - 1)
|
|
axes_group.to_edge(DOWN, buff=1)
|
|
return axes_group
|
|
|
|
def get_axes(self, **kwargs):
|
|
axes = self.get_three_d_axes(**kwargs)
|
|
# axes.input_plane.set_fill(opacity=0)
|
|
# axes.input_plane.set_stroke(width=0.5)
|
|
# axes.add(axes.input_plane)
|
|
self.orient_three_d_mobject(axes)
|
|
axes.rotate(-5 * DEGREES, UP)
|
|
axes.set_width(4)
|
|
axes.x_axis.label.next_to(
|
|
axes.x_axis.get_end(), DOWN,
|
|
buff=2 * SMALL_BUFF
|
|
)
|
|
return axes
|
|
|
|
def get_graph(self, axes, freqs, scalar_trackers):
|
|
L = axes.x_max
|
|
a = self.alpha
|
|
|
|
def func(x, t):
|
|
scalars = [st.get_value() for st in scalar_trackers]
|
|
return np.sum([
|
|
s * np.cos(k * x) * np.exp(-a * (k**2) * t)
|
|
for freq, s in zip(freqs, scalars)
|
|
for k in [freq * PI / L]
|
|
])
|
|
|
|
def get_surface_graph_group():
|
|
return VGroup(
|
|
self.get_surface(axes, func),
|
|
self.get_time_slice_graph(axes, func, t=0),
|
|
)
|
|
|
|
result = always_redraw(get_surface_graph_group)
|
|
result.func = func
|
|
result.suspend_updating()
|
|
return result
|
|
|
|
|
|
class CombineSeveralSolutions(ShowLinearity):
|
|
CONFIG = {
|
|
"default_surface_config": {
|
|
"resolution": (16, 16),
|
|
# "resolution": (4, 4),
|
|
},
|
|
"n_top_graphs": 5,
|
|
"axes_config": {
|
|
"y_max": 15,
|
|
},
|
|
"target_scalars": [
|
|
0.81, -0.53, 0.41, 0.62, -0.95
|
|
],
|
|
"final_run_time": 14,
|
|
}
|
|
|
|
def construct(self):
|
|
self.init_camera()
|
|
self.add_all_axes()
|
|
self.setup_all_graphs()
|
|
self.show_infinite_family()
|
|
self.show_sum()
|
|
self.show_time_passing()
|
|
|
|
def add_all_axes(self):
|
|
top_axes_group = VGroup(*[
|
|
self.get_axes(
|
|
z_min=-1.25,
|
|
z_max=1.25,
|
|
z_axis_config={
|
|
"unit_size": 2,
|
|
"tick_frequency": 0.5,
|
|
},
|
|
)
|
|
for x in range(self.n_top_graphs)
|
|
])
|
|
top_axes_group.arrange(RIGHT, buff=2)
|
|
top_axes_group.set_width(FRAME_WIDTH - 1.5)
|
|
top_axes_group.to_corner(UL)
|
|
dots = TexMobject("\\dots")
|
|
dots.next_to(top_axes_group, RIGHT)
|
|
|
|
low_axes = self.get_axes()
|
|
low_axes.center()
|
|
low_axes.scale(1.2)
|
|
low_axes.to_edge(DOWN, buff=SMALL_BUFF)
|
|
|
|
self.add(top_axes_group)
|
|
self.add(dots)
|
|
self.add(low_axes)
|
|
|
|
self.top_axes_group = top_axes_group
|
|
self.low_axes = low_axes
|
|
|
|
def setup_all_graphs(self):
|
|
scalar_trackers = Group(*[
|
|
ValueTracker(1)
|
|
for x in range(self.n_top_graphs)
|
|
])
|
|
freqs = np.arange(self.n_top_graphs)
|
|
freqs += 1
|
|
self.top_graphs = VGroup(*[
|
|
self.get_graph(axes, [n], [st])
|
|
for axes, n, st in zip(
|
|
self.top_axes_group,
|
|
freqs,
|
|
scalar_trackers,
|
|
)
|
|
])
|
|
self.low_graph = self.get_graph(
|
|
self.low_axes, freqs, scalar_trackers
|
|
)
|
|
|
|
self.scalar_trackers = scalar_trackers
|
|
|
|
def show_infinite_family(self):
|
|
top_axes_group = self.top_axes_group
|
|
top_graphs = self.top_graphs
|
|
scalar_trackers = self.scalar_trackers
|
|
|
|
decimals = self.get_decimals(
|
|
top_axes_group, scalar_trackers
|
|
)
|
|
|
|
self.play(LaggedStart(*[
|
|
AnimationGroup(
|
|
Write(graph[0]),
|
|
FadeIn(graph[1]),
|
|
)
|
|
for graph in top_graphs
|
|
]))
|
|
self.wait()
|
|
self.play(FadeIn(decimals))
|
|
for graph in top_graphs:
|
|
graph.resume_updating()
|
|
|
|
self.play(LaggedStart(*[
|
|
ApplyMethod(st.set_value, value)
|
|
for st, value in zip(
|
|
scalar_trackers,
|
|
self.target_scalars,
|
|
)
|
|
]), run_time=3)
|
|
self.wait()
|
|
|
|
def show_sum(self):
|
|
top_graphs = self.top_graphs
|
|
low_graph = self.low_graph
|
|
low_graph.resume_updating()
|
|
low_graph.update()
|
|
|
|
self.play(
|
|
LaggedStart(*[
|
|
Transform(
|
|
top_graph.copy().set_fill(opacity=0),
|
|
low_graph.copy().set_fill(opacity=0),
|
|
remover=True,
|
|
)
|
|
for top_graph in top_graphs
|
|
]),
|
|
FadeIn(
|
|
low_graph,
|
|
rate_func=squish_rate_func(smooth, 0.7, 1)
|
|
),
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
|
|
def show_time_passing(self):
|
|
all_graphs = [*self.top_graphs, self.low_graph]
|
|
all_axes = [*self.top_axes_group, self.low_axes]
|
|
|
|
time_tracker = ValueTracker(0)
|
|
get_t = time_tracker.get_value
|
|
|
|
anims = [
|
|
ApplyMethod(
|
|
time_tracker.set_value, 1,
|
|
run_time=1,
|
|
rate_func=linear
|
|
)
|
|
]
|
|
|
|
for axes, graph_group in zip(all_axes, all_graphs):
|
|
graph_group.clear_updaters()
|
|
surface, gslice = graph_group
|
|
plane = self.get_const_time_plane(axes)
|
|
plane.t_tracker.add_updater(
|
|
lambda m: m.set_value(get_t())
|
|
)
|
|
gslice.axes = axes
|
|
gslice.func = graph_group.func
|
|
gslice.add_updater(lambda m: m.become(
|
|
self.get_time_slice_graph(
|
|
m.axes, m.func, t=get_t()
|
|
)
|
|
))
|
|
self.add(gslice)
|
|
self.add(plane.t_tracker)
|
|
anims.append(FadeIn(plane))
|
|
|
|
self.play(*anims)
|
|
run_time = self.final_run_time
|
|
self.play(
|
|
time_tracker.increment_value, run_time,
|
|
run_time=run_time,
|
|
rate_func=linear,
|
|
)
|
|
|
|
#
|
|
def get_decimals(self, axes_group, scalar_trackers):
|
|
result = VGroup()
|
|
for axes, st in zip(axes_group, scalar_trackers):
|
|
decimal = DecimalNumber()
|
|
decimal.move_to(axes.get_bottom(), UP)
|
|
decimal.shift(SMALL_BUFF * RIGHT)
|
|
decimal.set_color(YELLOW)
|
|
decimal.scalar_tracker = st
|
|
times = TexMobject("\\times")
|
|
times.next_to(decimal, LEFT, SMALL_BUFF)
|
|
decimal.add_updater(lambda d: d.set_value(
|
|
d.scalar_tracker.get_value()
|
|
))
|
|
group = VGroup(times, decimal)
|
|
group.scale(0.7)
|
|
result.add(group)
|
|
return result
|
|
|
|
|
|
class CycleThroughManyLinearCombinations(CombineSeveralSolutions):
|
|
CONFIG = {
|
|
"default_surface_config": {
|
|
"resolution": (16, 16),
|
|
# "resolution": (4, 4),
|
|
},
|
|
"n_cycles": 10,
|
|
}
|
|
|
|
def construct(self):
|
|
self.init_camera()
|
|
self.add_all_axes()
|
|
self.setup_all_graphs()
|
|
#
|
|
self.cycle_through_superpositions()
|
|
|
|
def cycle_through_superpositions(self):
|
|
top_graphs = self.top_graphs
|
|
low_graph = self.low_graph
|
|
scalar_trackers = self.scalar_trackers
|
|
self.add(self.get_decimals(
|
|
self.top_axes_group, scalar_trackers
|
|
))
|
|
|
|
for graph in [low_graph, *top_graphs]:
|
|
graph.resume_updating()
|
|
self.add(graph)
|
|
|
|
nst = len(scalar_trackers)
|
|
for x in range(self.n_cycles):
|
|
self.play(LaggedStart(*[
|
|
ApplyMethod(st.set_value, value)
|
|
for st, value in zip(
|
|
scalar_trackers,
|
|
3 * np.random.random(nst) - 1.5
|
|
)
|
|
]), run_time=3)
|
|
self.wait()
|