mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
More animations for the heat equation
This commit is contained in:
parent
c1e2ee7843
commit
a1878308e0
5 changed files with 376 additions and 23 deletions
|
@ -1,6 +1,7 @@
|
|||
from active_projects.ode.part2.staging import *
|
||||
from active_projects.ode.part2.fourier_series import *
|
||||
from active_projects.ode.part2.heat_equation import *
|
||||
from active_projects.ode.part2.pi_scenes import *
|
||||
|
||||
OUTPUT_DIRECTORY = "ode/part2"
|
||||
ALL_SCENE_CLASSES = [
|
||||
|
@ -28,4 +29,8 @@ ALL_SCENE_CLASSES = [
|
|||
TwoDBodyWithManyTemperaturesGraph,
|
||||
TwoDBodyWithManyTemperaturesContour,
|
||||
BringTwoRodsTogether,
|
||||
ShowEvolvingTempGraphWithArrows,
|
||||
WriteHeatEquation,
|
||||
ReactionsToInitialHeatEquation,
|
||||
TalkThrough1DHeatGraph,
|
||||
]
|
||||
|
|
|
@ -387,7 +387,9 @@ class FourierNailAndGear(FourierOfTrebleClef):
|
|||
CONFIG = {
|
||||
"height": 6,
|
||||
"n_circles": 200,
|
||||
"run_time": 10,
|
||||
"run_time": 100,
|
||||
"slow_factor": 0.01,
|
||||
"parametric_function_step_size": 0.0001,
|
||||
"arrow_config": {
|
||||
"tip_length": 0.1,
|
||||
"stroke_width": 2,
|
||||
|
|
|
@ -153,7 +153,7 @@ class TwoDBodyWithManyTemperaturesContour(ExternallyAnimatedScene):
|
|||
|
||||
class BringTwoRodsTogether(Scene):
|
||||
CONFIG = {
|
||||
"step_size": 0.1,
|
||||
"step_size": 0.05,
|
||||
"axes_config": {
|
||||
"x_min": -1,
|
||||
"x_max": 11,
|
||||
|
@ -162,10 +162,12 @@ class BringTwoRodsTogether(Scene):
|
|||
"y_axis_config": {
|
||||
"unit_size": 0.06,
|
||||
"tick_frequency": 10,
|
||||
# "numbers_with_elongated_ticks": range(20, 100, 20)
|
||||
},
|
||||
},
|
||||
"wait_time": 5,
|
||||
"graph_x_min": 0,
|
||||
"graph_x_max": 10,
|
||||
"wait_time": 30,
|
||||
"alpha": 1.0,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
@ -173,9 +175,6 @@ class BringTwoRodsTogether(Scene):
|
|||
self.setup_graph()
|
||||
self.setup_clock()
|
||||
|
||||
self.add(self.axes)
|
||||
self.add(self.graph)
|
||||
|
||||
self.show_rods()
|
||||
self.show_equilibration()
|
||||
|
||||
|
@ -194,9 +193,9 @@ class BringTwoRodsTogether(Scene):
|
|||
|
||||
def setup_graph(self):
|
||||
graph = self.axes.get_graph(
|
||||
lambda x: 90 if x <= 5 else 10,
|
||||
x_min=0,
|
||||
x_max=10,
|
||||
self.initial_function,
|
||||
x_min=self.graph_x_min,
|
||||
x_max=self.graph_x_max,
|
||||
step_size=self.step_size,
|
||||
discontinuities=[5],
|
||||
)
|
||||
|
@ -229,34 +228,115 @@ class BringTwoRodsTogether(Scene):
|
|||
self.clock = clock
|
||||
|
||||
def show_rods(self):
|
||||
pass
|
||||
rod1, rod2 = rods = VGroup(
|
||||
self.get_rod(0, 5),
|
||||
self.get_rod(5, 10),
|
||||
)
|
||||
rod1.set_color(rod1[0].get_color())
|
||||
rod2.set_color(rod2[-1].get_color())
|
||||
|
||||
rods.save_state()
|
||||
rods.space_out_submobjects(1.5)
|
||||
rods.center()
|
||||
|
||||
labels = VGroup(
|
||||
TexMobject("90^\\circ"),
|
||||
TexMobject("10^\\circ"),
|
||||
)
|
||||
for rod, label in zip(rods, labels):
|
||||
label.next_to(rod, DOWN)
|
||||
rod.label = label
|
||||
|
||||
self.play(
|
||||
FadeInFrom(rod1, UP),
|
||||
Write(rod1.label),
|
||||
)
|
||||
self.play(
|
||||
FadeInFrom(rod2, DOWN),
|
||||
Write(rod2.label)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.rods = rods
|
||||
self.rod_labels = labels
|
||||
|
||||
def show_equilibration(self):
|
||||
self.add(self.time_group)
|
||||
self.add(self.clock)
|
||||
rods = self.rods
|
||||
axes = self.axes
|
||||
graph = self.graph
|
||||
labels = self.rod_labels
|
||||
self.play(
|
||||
Write(axes),
|
||||
rods.restore,
|
||||
rods.space_out_submobjects, 1.1,
|
||||
FadeIn(self.time_group),
|
||||
FadeIn(self.clock),
|
||||
*[
|
||||
MaintainPositionRelativeTo(
|
||||
rod.label, rod
|
||||
)
|
||||
for rod in rods
|
||||
],
|
||||
)
|
||||
|
||||
self.graph.add_updater(self.update_graph)
|
||||
br1 = Rectangle(height=0.2, width=1)
|
||||
br1.set_stroke(width=0)
|
||||
br1.set_fill(BLACK, opacity=1)
|
||||
br2 = br1.copy()
|
||||
br1.add_updater(lambda b: b.move_to(axes.c2p(0, 90)))
|
||||
br1.add_updater(
|
||||
lambda b: b.align_to(rods[0].get_right(), LEFT)
|
||||
)
|
||||
br2.add_updater(lambda b: b.move_to(axes.c2p(0, 10)))
|
||||
br2.add_updater(
|
||||
lambda b: b.align_to(rods[1].get_left(), RIGHT)
|
||||
)
|
||||
|
||||
self.add(graph, br1, br2)
|
||||
self.play(
|
||||
ShowCreation(graph),
|
||||
labels[0].align_to, axes.c2p(0, 87), UP,
|
||||
labels[1].align_to, axes.c2p(0, 13), DOWN,
|
||||
)
|
||||
self.play()
|
||||
self.play(
|
||||
rods.restore,
|
||||
rate_func=rush_into,
|
||||
)
|
||||
self.remove(br1, br2)
|
||||
|
||||
graph.add_updater(self.update_graph)
|
||||
self.time_label.add_updater(
|
||||
lambda d, dt: d.increment_value(dt)
|
||||
)
|
||||
rods.add_updater(self.update_rods)
|
||||
|
||||
self.play(
|
||||
ClockPassesTime(
|
||||
self.clock,
|
||||
run_time=self.wait_time,
|
||||
hours_passed=self.wait_time,
|
||||
)
|
||||
),
|
||||
FadeOut(labels)
|
||||
)
|
||||
|
||||
#
|
||||
def update_graph(self, graph, dt, alpha=1.0, n_mini_steps=100):
|
||||
def initial_function(self, x):
|
||||
if x <= 5:
|
||||
return 90
|
||||
else:
|
||||
return 10
|
||||
|
||||
def update_graph(self, graph, dt, alpha=None, n_mini_steps=100):
|
||||
if alpha is None:
|
||||
alpha = self.alpha
|
||||
points = np.append(
|
||||
graph.get_start_anchors(),
|
||||
[graph.get_last_point()],
|
||||
axis=0,
|
||||
)
|
||||
for k in range(n_mini_steps):
|
||||
change = np.zeros(points.shape)
|
||||
y_change = np.zeros(points.shape[0])
|
||||
dx = points[1][0] - points[0][0]
|
||||
for i in range(len(points)):
|
||||
p = points[i]
|
||||
|
@ -270,12 +350,188 @@ class BringTwoRodsTogether(Scene):
|
|||
second_deriv = 0.5 * d2y / dx
|
||||
second_deriv = 0
|
||||
|
||||
change[i][1] = alpha * second_deriv * dt / n_mini_steps
|
||||
y_change[i] = alpha * second_deriv * dt / n_mini_steps
|
||||
|
||||
change[0][1] = change[1][1]
|
||||
change[-1][1] = change[-2][1]
|
||||
# change[0][1] = 0
|
||||
# change[-1][1] = 0
|
||||
points += change
|
||||
# y_change[0] = y_change[1]
|
||||
# y_change[-1] = y_change[-2]
|
||||
y_change[0] = 0
|
||||
y_change[-1] = 0
|
||||
y_change -= np.mean(y_change)
|
||||
points[:, 1] += y_change
|
||||
graph.set_points_smoothly(points)
|
||||
return graph
|
||||
|
||||
def get_second_derivative(self, x, dx=0.001):
|
||||
graph = self.graph
|
||||
x_min = self.graph_x_min
|
||||
x_max = self.graph_x_max
|
||||
|
||||
ly, y, ry = [
|
||||
graph.point_from_proportion(
|
||||
inverse_interpolate(x_min, x_max, alt_x)
|
||||
)[1]
|
||||
for alt_x in (x - dx, x, x + dx)
|
||||
]
|
||||
d2y = ry - 2 * y + ly
|
||||
return d2y / (dx**2)
|
||||
|
||||
def get_rod(self, x_min, x_max, n_pieces=20):
|
||||
axes = self.axes
|
||||
line = Line(axes.c2p(x_min, 0), axes.c2p(x_max, 0))
|
||||
rod = VGroup(*[
|
||||
Square()
|
||||
for n in range(n_pieces)
|
||||
])
|
||||
rod.arrange(RIGHT, buff=0)
|
||||
rod.match_width(line)
|
||||
rod.set_height(0.2, stretch=True)
|
||||
rod.move_to(axes.c2p(x_min, 0), LEFT)
|
||||
rod.set_fill(opacity=1)
|
||||
rod.set_stroke(width=1)
|
||||
rod.set_sheen_direction(RIGHT)
|
||||
self.color_rod_by_graph(rod)
|
||||
return rod
|
||||
|
||||
def update_rods(self, rods):
|
||||
for rod in rods:
|
||||
self.color_rod_by_graph(rod)
|
||||
|
||||
def color_rod_by_graph(self, rod):
|
||||
for piece in rod:
|
||||
piece.set_color(color=[
|
||||
self.rod_point_to_color(piece.get_left()),
|
||||
self.rod_point_to_color(piece.get_right()),
|
||||
])
|
||||
|
||||
def rod_point_to_color(self, point):
|
||||
axes = self.axes
|
||||
x = axes.x_axis.p2n(point)
|
||||
|
||||
graph = self.graph
|
||||
alpha = inverse_interpolate(
|
||||
self.graph_x_min,
|
||||
self.graph_x_max,
|
||||
x,
|
||||
)
|
||||
y = axes.y_axis.p2n(
|
||||
graph.point_from_proportion(alpha)
|
||||
)
|
||||
return temperature_to_color(
|
||||
(y - 45) / 45
|
||||
)
|
||||
|
||||
|
||||
class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether):
|
||||
CONFIG = {
|
||||
"alpha": 0.1,
|
||||
"n_arrows": 20,
|
||||
"wait_time": 30,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.add_graph()
|
||||
self.add_clock()
|
||||
self.add_rod()
|
||||
self.add_arrows()
|
||||
self.let_play()
|
||||
|
||||
def add_axes(self):
|
||||
self.setup_axes()
|
||||
self.add(self.axes)
|
||||
|
||||
def add_graph(self):
|
||||
self.setup_graph()
|
||||
self.add(self.graph)
|
||||
|
||||
def add_clock(self):
|
||||
self.setup_clock()
|
||||
self.add(self.clock)
|
||||
self.add(self.time_label)
|
||||
|
||||
def add_rod(self):
|
||||
rod = self.rod = self.get_rod(0, 10)
|
||||
self.add(rod)
|
||||
|
||||
def add_arrows(self):
|
||||
graph = self.graph
|
||||
x_min = self.graph_x_min
|
||||
x_max = self.graph_x_max
|
||||
|
||||
xs = np.linspace(x_min, x_max, self.n_arrows + 2)[1:-1]
|
||||
arrows = VGroup(*[Vector(DOWN) for x in xs])
|
||||
|
||||
def update_arrows(arrows):
|
||||
for x, arrow in zip(xs, arrows):
|
||||
d2y_dx2 = self.get_second_derivative(x)
|
||||
mag = np.sign(d2y_dx2) * np.sqrt(abs(d2y_dx2))
|
||||
mag = np.clip(mag, -2, 2)
|
||||
arrow.put_start_and_end_on(
|
||||
ORIGIN, mag * UP
|
||||
)
|
||||
point = graph.point_from_proportion(
|
||||
inverse_interpolate(x_min, x_max, x)
|
||||
)
|
||||
arrow.shift(point - arrow.get_start())
|
||||
arrow.set_color(
|
||||
self.rod_point_to_color(point)
|
||||
)
|
||||
|
||||
arrows.add_updater(update_arrows)
|
||||
|
||||
self.add(arrows)
|
||||
self.arrows = arrows
|
||||
|
||||
def let_play(self):
|
||||
graph = self.graph
|
||||
rod = self.rod
|
||||
clock = self.clock
|
||||
time_label = self.time_label
|
||||
|
||||
graph.add_updater(self.update_graph)
|
||||
time_label.add_updater(
|
||||
lambda d, dt: d.increment_value(dt)
|
||||
)
|
||||
rod.add_updater(self.color_rod_by_graph)
|
||||
|
||||
# return
|
||||
self.play(
|
||||
ClockPassesTime(
|
||||
clock,
|
||||
run_time=self.wait_time,
|
||||
hours_passed=self.wait_time,
|
||||
),
|
||||
)
|
||||
|
||||
#
|
||||
def initial_function(self, x):
|
||||
new_x = TAU * x / 10
|
||||
return 50 + 20 * np.sum([
|
||||
np.sin(new_x),
|
||||
np.sin(2 * new_x),
|
||||
0.5 * np.sin(3 * new_x),
|
||||
0.3 * np.sin(4 * new_x),
|
||||
0.3 * np.sin(5 * new_x),
|
||||
0.2 * np.sin(7 * new_x),
|
||||
0.1 * np.sin(21 * new_x),
|
||||
0.05 * np.sin(41 * new_x),
|
||||
])
|
||||
|
||||
|
||||
class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows):
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.add_graph()
|
||||
self.add_rod()
|
||||
|
||||
#
|
||||
def initial_function(self, x):
|
||||
new_x = TAU * x / 10
|
||||
return 50 + 20 * np.sum([
|
||||
np.sin(new_x),
|
||||
np.sin(2 * new_x),
|
||||
0.5 * np.sin(3 * new_x),
|
||||
0.3 * np.sin(4 * new_x),
|
||||
0.3 * np.sin(5 * new_x),
|
||||
0.2 * np.sin(7 * new_x),
|
||||
])
|
||||
|
|
23
active_projects/ode/part2/pi_scenes.py
Normal file
23
active_projects/ode/part2/pi_scenes.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
class ReactionsToInitialHeatEquation(PiCreatureScene):
|
||||
def construct(self):
|
||||
randy = self.pi_creature
|
||||
randy.set_color(BLUE_C)
|
||||
randy.center()
|
||||
|
||||
point = VectorizedPoint().next_to(randy, UL, LARGE_BUFF)
|
||||
randy.add_updater(lambda r: r.look_at(point))
|
||||
|
||||
self.play(randy.change, "horrified")
|
||||
self.wait()
|
||||
self.play(randy.change, "pondering")
|
||||
self.wait()
|
||||
self.play(
|
||||
randy.change, "confused",
|
||||
point.next_to, randy, UR, LARGE_BUFF,
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(point.shift, 2 * DOWN)
|
||||
self.wait(3)
|
|
@ -86,3 +86,70 @@ class PartTwoOfTour(TourOfDifferentialEquations):
|
|||
class CompareODEToPDE(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class WriteHeatEquation(Scene):
|
||||
def construct(self):
|
||||
d1_words = TextMobject("Heat equation\\\\", "(1 dimension)")
|
||||
d3_words = TextMobject("Heat equation\\\\", "(3 dimensions)")
|
||||
|
||||
kwargs = {
|
||||
"tex_to_color_map": {
|
||||
"{T}": YELLOW,
|
||||
"{t}": WHITE,
|
||||
"{x}": GREEN,
|
||||
"{y}": RED,
|
||||
"{z}": BLUE,
|
||||
}
|
||||
}
|
||||
d1_equation = TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}}({x}, {t})="
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})",
|
||||
**kwargs
|
||||
)
|
||||
d3_equation = TexMobject(
|
||||
"{\\partial {T} \\over \\partial {t}} = ",
|
||||
"{\\partial^2 {T} \\over \\partial {x}^2} + ",
|
||||
"{\\partial^2 {T} \\over \\partial {y}^2} + ",
|
||||
"{\\partial^2 {T} \\over \\partial {z}^2}",
|
||||
**kwargs
|
||||
)
|
||||
|
||||
d1_group = VGroup(d1_words, d1_equation)
|
||||
d3_group = VGroup(d3_words, d3_equation)
|
||||
groups = VGroup(d1_group, d3_group)
|
||||
for group in groups:
|
||||
group.arrange(DOWN, buff=MED_LARGE_BUFF)
|
||||
groups.arrange(RIGHT, buff=2)
|
||||
groups.to_edge(UP)
|
||||
|
||||
d3_rhs = d3_equation[6:]
|
||||
d3_brace = Brace(d3_rhs, DOWN)
|
||||
nabla_words = TextMobject("Sometimes written as")
|
||||
nabla_words.match_width(d3_brace)
|
||||
nabla_words.next_to(d3_brace, DOWN)
|
||||
nabla_exp = TexMobject("\\nabla^2 {T}", **kwargs)
|
||||
nabla_exp.next_to(nabla_words, DOWN)
|
||||
# nabla_group = VGroup(nabla_words, nabla_exp)
|
||||
|
||||
d1_group.save_state()
|
||||
d1_group.center().to_edge(UP)
|
||||
|
||||
self.play(
|
||||
Write(d1_words),
|
||||
FadeInFrom(d1_equation, UP),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
Restore(d1_group),
|
||||
FadeInFrom(d3_group, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
GrowFromCenter(d3_brace),
|
||||
Write(nabla_words),
|
||||
TransformFromCopy(d3_rhs, nabla_exp),
|
||||
run_time=1,
|
||||
)
|
||||
self.wait()
|
||||
|
|
Loading…
Add table
Reference in a new issue