mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
Latest scenes for diffyq part 3
This commit is contained in:
parent
503b2bc896
commit
caa4efdfa5
6 changed files with 1369 additions and 74 deletions
|
@ -6,19 +6,38 @@ from active_projects.ode.part3.wordy_scenes import *
|
|||
|
||||
OUTPUT_DIRECTORY = "ode/part3"
|
||||
SCENES_IN_ORDER = [
|
||||
LastChapterWrapper,
|
||||
ThreeConstraints,
|
||||
OceanOfPossibilities,
|
||||
# TODO
|
||||
ThreeMainObservations,
|
||||
BreakDownAFunction,
|
||||
SineCurveIsUnrealistic,
|
||||
AnalyzeSineCurve,
|
||||
EquationAboveSineAnalysis,
|
||||
ExponentialDecay,
|
||||
InvestmentGrowth,
|
||||
GrowingPileOfMoney,
|
||||
CarbonDecayCurve,
|
||||
CarbonDecayingInMammoth,
|
||||
SineWaveScaledByExp,
|
||||
ShowSinExpDerivatives,
|
||||
IfOnly,
|
||||
BoundaryConditionInterlude,
|
||||
BoundaryConditionReference,
|
||||
GiantCross,
|
||||
SimulateRealSineCurve,
|
||||
SimulateLinearGraph,
|
||||
|
||||
# SimpleCosExpGraph,
|
||||
# AddMultipleSolutions,
|
||||
# IveHeardOfThis,
|
||||
# FourierSeriesOfLineIllustration,
|
||||
# InFouriersShoes,
|
||||
]
|
||||
|
||||
PART_4_SCENES = [
|
||||
FourierSeriesIllustraiton,
|
||||
FourierNameIntro,
|
||||
CircleAnimationOfF,
|
||||
LastChapterWrapper,
|
||||
ThreeMainObservations,
|
||||
SimpleCosExpGraph,
|
||||
AddMultipleSolutions,
|
||||
IveHeardOfThis,
|
||||
FourierSeriesOfLineIllustration,
|
||||
BreakDownAFunction,
|
||||
ThreeConstraints,
|
||||
OceanOfPossibilities,
|
||||
InFouriersShoes,
|
||||
AnalyzeSineCurve,
|
||||
SineCurveIsUnrealistic,
|
||||
]
|
||||
|
|
|
@ -349,16 +349,16 @@ class BringTwoRodsTogether(Scene):
|
|||
if (0 < i < len(points) - 1):
|
||||
second_deriv = d2y / (dx**2)
|
||||
else:
|
||||
second_deriv = 0.5 * d2y / dx
|
||||
second_deriv = 0
|
||||
second_deriv = d2y / dx
|
||||
# second_deriv = 0
|
||||
|
||||
y_change[i] = alpha * second_deriv * dt / n_mini_steps
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
|
@ -374,8 +374,17 @@ class BringTwoRodsTogether(Scene):
|
|||
)[1]
|
||||
for alt_x in (x - dx, x, x + dx)
|
||||
]
|
||||
d2y = ry - 2 * y + ly
|
||||
return d2y / (dx**2)
|
||||
|
||||
# At the boundary, don't return the second deriv,
|
||||
# but instead something matching the Neumann
|
||||
# boundary condition.
|
||||
if x == x_max:
|
||||
return (ly - y) / dx
|
||||
elif x == x_min:
|
||||
return (ry - y) / dx
|
||||
else:
|
||||
d2y = ry - 2 * y + ly
|
||||
return d2y / (dx**2)
|
||||
|
||||
def get_rod(self, x_min, x_max, n_pieces=None):
|
||||
if n_pieces is None:
|
||||
|
@ -407,7 +416,7 @@ class BringTwoRodsTogether(Scene):
|
|||
self.rod_point_to_color(piece.get_right()),
|
||||
])
|
||||
|
||||
def rod_point_to_color(self, point):
|
||||
def rod_point_to_graph_y(self, point):
|
||||
axes = self.axes
|
||||
x = axes.x_axis.p2n(point)
|
||||
|
||||
|
@ -417,11 +426,16 @@ class BringTwoRodsTogether(Scene):
|
|||
self.graph_x_max,
|
||||
x,
|
||||
)
|
||||
y = axes.y_axis.p2n(
|
||||
return axes.y_axis.p2n(
|
||||
graph.point_from_proportion(alpha)
|
||||
)
|
||||
return temperature_to_color(
|
||||
(y - 45) / 45
|
||||
|
||||
def y_to_color(self, y):
|
||||
return temperature_to_color((y - 45) / 45)
|
||||
|
||||
def rod_point_to_color(self, point):
|
||||
return self.y_to_color(
|
||||
self.rod_point_to_graph_y(point)
|
||||
)
|
||||
|
||||
|
||||
|
@ -467,7 +481,10 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether):
|
|||
self.time_label.next_to(self.clock, DOWN)
|
||||
|
||||
def add_rod(self):
|
||||
rod = self.rod = self.get_rod(0, 10)
|
||||
rod = self.rod = self.get_rod(
|
||||
self.graph_x_min,
|
||||
self.graph_x_max,
|
||||
)
|
||||
self.add(rod)
|
||||
|
||||
def add_arrows(self):
|
||||
|
|
|
@ -113,4 +113,28 @@ class SineCurveIsUnrealistic(TeacherStudentsScene):
|
|||
self.teacher.change, "tease"
|
||||
]
|
||||
)
|
||||
self.wait(2)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
RemovePiCreatureBubble(self.students[1]),
|
||||
self.teacher.change, "raise_right_hand"
|
||||
)
|
||||
self.change_all_student_modes(
|
||||
"pondering",
|
||||
look_at_arg=3 * UP,
|
||||
)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
|
||||
class IfOnly(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.teacher_says(
|
||||
"If only!",
|
||||
target_mode="angry"
|
||||
)
|
||||
self.change_all_student_modes(
|
||||
"confused",
|
||||
look_at_arg=self.screen
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
|
|
@ -200,6 +200,36 @@ class FourierNameIntro(Scene):
|
|||
self.wait(3)
|
||||
|
||||
|
||||
class ManyCousinsOfFourierThings(Scene):
|
||||
def construct(self):
|
||||
series_variants = VGroup(
|
||||
TextMobject("Complex", "Fourier Series"),
|
||||
TextMobject("Discrete", "Fourier Series"),
|
||||
)
|
||||
transform_variants = VGroup(
|
||||
TextMobject("Discrete", "Fourier Transform"),
|
||||
TextMobject("Fast", "Fourier Transform"),
|
||||
TextMobject("Quantum", "Fourier Transform"),
|
||||
)
|
||||
groups = VGroup(series_variants, transform_variants)
|
||||
for group, vect in zip(groups, [LEFT, RIGHT]):
|
||||
group.scale(0.7)
|
||||
group.arrange(DOWN, aligned_edge=LEFT)
|
||||
group.move_to(
|
||||
vect * FRAME_WIDTH / 4
|
||||
)
|
||||
group.set_color(YELLOW)
|
||||
|
||||
self.play(*[
|
||||
LaggedStartMap(FadeIn, group)
|
||||
for group in groups
|
||||
])
|
||||
self.play(*[
|
||||
LaggedStartMap(FadeOut, group)
|
||||
for group in groups
|
||||
])
|
||||
|
||||
|
||||
class FourierSeriesIllustraiton(Scene):
|
||||
CONFIG = {
|
||||
"n_range": range(1, 31, 2),
|
||||
|
@ -417,12 +447,568 @@ class CircleAnimationOfF(FourierOfTrebleClef):
|
|||
|
||||
def get_shape(self):
|
||||
path = VMobject()
|
||||
shape = TexMobject("F")
|
||||
shape = TextMobject("F")
|
||||
for sp in shape.family_members_with_points():
|
||||
path.append_points(sp.points)
|
||||
return path
|
||||
|
||||
|
||||
class NewSceneName(Scene):
|
||||
class ExponentialDecay(PiCreatureScene):
|
||||
def construct(self):
|
||||
pass
|
||||
k = 0.2
|
||||
mk_tex = "-0.2"
|
||||
mk_tex_color = GREEN
|
||||
t2c = {mk_tex: mk_tex_color}
|
||||
|
||||
# Pi creature
|
||||
randy = self.pi_creature
|
||||
randy.flip()
|
||||
randy.set_height(2.5)
|
||||
randy.move_to(3 * RIGHT)
|
||||
randy.to_edge(DOWN)
|
||||
bubble = ThoughtBubble(
|
||||
direction=LEFT,
|
||||
height=3.5,
|
||||
width=3,
|
||||
)
|
||||
bubble.pin_to(randy)
|
||||
bubble.set_fill(DARKER_GREY)
|
||||
exp = TexMobject(
|
||||
"Ce^{", mk_tex, "t}",
|
||||
tex_to_color_map=t2c,
|
||||
)
|
||||
exp.move_to(bubble.get_bubble_center())
|
||||
|
||||
# Setup axes
|
||||
axes = Axes(
|
||||
x_min=0,
|
||||
x_max=13,
|
||||
y_min=-4,
|
||||
y_max=4,
|
||||
)
|
||||
axes.set_stroke(width=2)
|
||||
axes.set_color(LIGHT_GREY)
|
||||
axes.scale(0.9)
|
||||
axes.to_edge(LEFT, buff=LARGE_BUFF)
|
||||
axes.x_axis.add_numbers()
|
||||
axes.y_axis.add_numbers()
|
||||
axes.y_axis.add_numbers(0)
|
||||
axes.x_axis.add(
|
||||
TextMobject("Time").next_to(
|
||||
axes.x_axis.get_end(), DR,
|
||||
)
|
||||
)
|
||||
axes.y_axis.add(
|
||||
TexMobject("f").next_to(
|
||||
axes.y_axis.get_corner(UR), RIGHT,
|
||||
).set_color(YELLOW)
|
||||
)
|
||||
axes.x_axis.set_opacity(0)
|
||||
|
||||
# Value trackers
|
||||
y_tracker = ValueTracker(3)
|
||||
x_tracker = ValueTracker(0)
|
||||
dydt_tracker = ValueTracker()
|
||||
dxdt_tracker = ValueTracker(0)
|
||||
self.add(
|
||||
y_tracker, x_tracker,
|
||||
dydt_tracker, dxdt_tracker,
|
||||
)
|
||||
|
||||
get_y = y_tracker.get_value
|
||||
get_x = x_tracker.get_value
|
||||
get_dydt = dydt_tracker.get_value
|
||||
get_dxdt = dxdt_tracker.get_value
|
||||
|
||||
dydt_tracker.add_updater(lambda m: m.set_value(
|
||||
- k * get_y()
|
||||
))
|
||||
y_tracker.add_updater(lambda m, dt: m.increment_value(
|
||||
dt * get_dydt()
|
||||
))
|
||||
x_tracker.add_updater(lambda m, dt: m.increment_value(
|
||||
dt * get_dxdt()
|
||||
))
|
||||
|
||||
# Tip/decimal
|
||||
tip = ArrowTip(color=YELLOW)
|
||||
tip.set_width(0.25)
|
||||
tip.add_updater(lambda m: m.move_to(
|
||||
axes.c2p(get_x(), get_y()), LEFT
|
||||
))
|
||||
decimal = DecimalNumber()
|
||||
decimal.add_updater(lambda d: d.set_value(get_y()))
|
||||
decimal.add_updater(lambda d: d.next_to(
|
||||
tip, RIGHT,
|
||||
SMALL_BUFF,
|
||||
))
|
||||
|
||||
# Rate of change arrow
|
||||
arrow = Vector(
|
||||
DOWN, color=RED,
|
||||
max_stroke_width_to_length_ratio=50,
|
||||
max_tip_length_to_length_ratio=0.2,
|
||||
)
|
||||
arrow.set_stroke(width=4)
|
||||
arrow.add_updater(lambda m: m.scale(
|
||||
2.5 * abs(get_dydt()) / m.get_length()
|
||||
))
|
||||
arrow.add_updater(lambda m: m.move_to(
|
||||
tip.get_left(), UP
|
||||
))
|
||||
|
||||
# Graph
|
||||
graph = TracedPath(tip.get_left)
|
||||
|
||||
# Equation
|
||||
ode = TexMobject(
|
||||
"{d{f} \\over dt}(t)",
|
||||
"=", mk_tex, "\\cdot {f}(t)",
|
||||
tex_to_color_map={
|
||||
"{f}": YELLOW,
|
||||
"=": WHITE,
|
||||
mk_tex: mk_tex_color
|
||||
}
|
||||
)
|
||||
ode.to_edge(UP)
|
||||
dfdt = ode[:3]
|
||||
ft = ode[-2:]
|
||||
|
||||
self.add(axes)
|
||||
self.add(tip)
|
||||
self.add(decimal)
|
||||
self.add(arrow)
|
||||
self.add(randy)
|
||||
self.add(ode)
|
||||
|
||||
# Show rate of change dependent on itself
|
||||
rect = SurroundingRectangle(dfdt)
|
||||
self.play(ShowCreation(rect))
|
||||
self.wait()
|
||||
self.play(
|
||||
Transform(
|
||||
rect,
|
||||
SurroundingRectangle(ft)
|
||||
)
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
# Show graph over time
|
||||
self.play(
|
||||
DrawBorderThenFill(bubble),
|
||||
Write(exp),
|
||||
FadeOut(rect),
|
||||
randy.change, "thinking",
|
||||
)
|
||||
axes.x_axis.set_opacity(1)
|
||||
self.play(
|
||||
y_tracker.set_value, 3,
|
||||
ShowCreation(axes.x_axis),
|
||||
)
|
||||
dxdt_tracker.set_value(1)
|
||||
self.add(graph)
|
||||
randy.add_updater(lambda r: r.look_at(tip))
|
||||
self.wait(4)
|
||||
|
||||
# Show derivative of exponential
|
||||
eq = TexMobject("=")
|
||||
eq.next_to(ode.get_part_by_tex("="), DOWN, LARGE_BUFF)
|
||||
exp.generate_target()
|
||||
exp.target.next_to(eq, LEFT)
|
||||
d_dt = TexMobject("{d \\over dt}")
|
||||
d_dt.next_to(exp.target, LEFT)
|
||||
const = TexMobject(mk_tex)
|
||||
const.set_color(mk_tex_color)
|
||||
dot = TexMobject("\\cdot")
|
||||
const.next_to(eq, RIGHT)
|
||||
dot.next_to(const, RIGHT, 2 * SMALL_BUFF)
|
||||
exp_copy = exp.copy()
|
||||
exp_copy.next_to(dot, RIGHT, 2 * SMALL_BUFF)
|
||||
VGroup(const, dot, eq).align_to(exp_copy, DOWN)
|
||||
|
||||
self.play(
|
||||
MoveToTarget(exp),
|
||||
FadeOut(bubble),
|
||||
FadeIn(d_dt),
|
||||
FadeIn(eq),
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
ApplyMethod(
|
||||
exp[1].copy().replace,
|
||||
const[0],
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
rect = SurroundingRectangle(exp)
|
||||
rect.set_stroke(BLUE, 2)
|
||||
self.play(FadeIn(rect))
|
||||
self.play(
|
||||
Write(dot),
|
||||
TransformFromCopy(exp, exp_copy),
|
||||
rect.move_to, exp_copy
|
||||
)
|
||||
self.play(FadeOut(rect))
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class InvestmentGrowth(Scene):
|
||||
CONFIG = {
|
||||
"output_tex": "{M}",
|
||||
"output_color": GREEN,
|
||||
"initial_value": 1,
|
||||
"initial_value_tex": "{M_0}",
|
||||
"k": 0.05,
|
||||
"k_tex": "0.05",
|
||||
"total_time": 43,
|
||||
"time_rate": 4,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
# Axes
|
||||
axes = Axes(
|
||||
x_min=0,
|
||||
x_max=self.total_time,
|
||||
y_min=0,
|
||||
y_max=6,
|
||||
x_axis_config={
|
||||
"unit_size": 0.3,
|
||||
"tick_size": 0.05,
|
||||
"numbers_with_elongated_ticks": range(
|
||||
0, self.total_time, 5
|
||||
)
|
||||
}
|
||||
)
|
||||
axes.to_corner(DL, buff=LARGE_BUFF)
|
||||
|
||||
time_label = TextMobject("Time")
|
||||
time_label.next_to(
|
||||
axes.x_axis.get_right(),
|
||||
UP, MED_LARGE_BUFF
|
||||
)
|
||||
time_label.shift_onto_screen()
|
||||
axes.x_axis.add(time_label)
|
||||
money_label = TexMobject(self.output_tex)
|
||||
money_label.set_color(self.output_color)
|
||||
money_label.next_to(
|
||||
axes.y_axis.get_top(),
|
||||
UP,
|
||||
)
|
||||
axes.y_axis.add(money_label)
|
||||
|
||||
# Graph
|
||||
graph = axes.get_graph(
|
||||
lambda x: self.initial_value * np.exp(self.k * x)
|
||||
)
|
||||
graph.set_color(self.output_color)
|
||||
full_graph = graph.copy()
|
||||
time_tracker = self.get_time_tracker()
|
||||
graph.add_updater(lambda m: m.pointwise_become_partial(
|
||||
full_graph, 0,
|
||||
np.clip(
|
||||
time_tracker.get_value() / self.total_time,
|
||||
0, 1,
|
||||
)
|
||||
))
|
||||
|
||||
# Equation
|
||||
tex_kwargs = {
|
||||
"tex_to_color_map": {
|
||||
self.output_tex: self.output_color,
|
||||
self.initial_value_tex: BLUE,
|
||||
}
|
||||
}
|
||||
ode = TexMobject(
|
||||
"{d",
|
||||
"\\over dt}",
|
||||
self.output_tex,
|
||||
"(t)",
|
||||
"=", self.k_tex,
|
||||
"\\cdot", self.output_tex, "(t)",
|
||||
**tex_kwargs
|
||||
)
|
||||
ode.to_edge(UP)
|
||||
exp = TexMobject(
|
||||
self.output_tex,
|
||||
"(t) =", self.initial_value_tex,
|
||||
"e^{", self.k_tex, "t}",
|
||||
**tex_kwargs
|
||||
)
|
||||
exp.next_to(ode, DOWN, LARGE_BUFF)
|
||||
|
||||
M0_part = exp.get_part_by_tex(self.initial_value_tex)
|
||||
exp_part = exp[-3:]
|
||||
M0_label = M0_part.copy()
|
||||
M0_label.next_to(
|
||||
axes.c2p(0, self.initial_value),
|
||||
LEFT
|
||||
)
|
||||
M0_part.set_opacity(0)
|
||||
exp_part.save_state()
|
||||
exp_part.align_to(M0_part, LEFT)
|
||||
|
||||
self.add(axes)
|
||||
self.add(graph)
|
||||
self.add(time_tracker)
|
||||
|
||||
self.play(FadeInFromDown(ode))
|
||||
self.wait(2)
|
||||
self.play(FadeInFrom(exp, UP))
|
||||
self.wait(2)
|
||||
self.play(
|
||||
Restore(exp_part),
|
||||
M0_part.set_opacity, 1,
|
||||
)
|
||||
self.play(TransformFromCopy(
|
||||
M0_part, M0_label
|
||||
))
|
||||
self.wait(5)
|
||||
|
||||
def get_time_tracker(self):
|
||||
time_tracker = ValueTracker(0)
|
||||
time_tracker.add_updater(
|
||||
lambda m, dt: m.increment_value(
|
||||
self.time_rate * dt
|
||||
)
|
||||
)
|
||||
return time_tracker
|
||||
|
||||
|
||||
class GrowingPileOfMoney(InvestmentGrowth):
|
||||
CONFIG = {
|
||||
"total_time": 60
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
initial_count = 5
|
||||
k = self.k
|
||||
total_time = self.total_time
|
||||
|
||||
time_tracker = self.get_time_tracker()
|
||||
|
||||
final_count = initial_count * np.exp(k * total_time)
|
||||
dollar_signs = VGroup(*[
|
||||
TexMobject("\\$")
|
||||
for x in range(int(final_count))
|
||||
])
|
||||
dollar_signs.set_color(GREEN)
|
||||
for ds in dollar_signs:
|
||||
ds.shift(
|
||||
3 * np.random.random(3)
|
||||
)
|
||||
dollar_signs.center()
|
||||
dollar_signs.sort(get_norm)
|
||||
dollar_signs.set_stroke(BLACK, 3, background=True)
|
||||
|
||||
def update_dollar_signs(group):
|
||||
t = time_tracker.get_value()
|
||||
count = initial_count * np.exp(k * t)
|
||||
alpha = count / final_count
|
||||
n, sa = integer_interpolate(0, len(dollar_signs), alpha)
|
||||
group.set_opacity(1)
|
||||
group[n:].set_opacity(0)
|
||||
group[n].set_opacity(sa)
|
||||
|
||||
dollar_signs.add_updater(update_dollar_signs)
|
||||
|
||||
self.add(time_tracker)
|
||||
self.add(dollar_signs)
|
||||
self.wait(20)
|
||||
|
||||
|
||||
class CarbonDecayCurve(InvestmentGrowth):
|
||||
CONFIG = {
|
||||
"output_tex": "{^{14}C}",
|
||||
"output_color": GOLD,
|
||||
"initial_value": 4,
|
||||
"initial_value_tex": "{^{14}C_0}",
|
||||
"k": -0.1,
|
||||
"k_tex": "-k",
|
||||
"time_rate": 6,
|
||||
}
|
||||
|
||||
|
||||
class CarbonDecayingInMammoth(Scene):
|
||||
def construct(self):
|
||||
mammoth = SVGMobject("Mammoth")
|
||||
mammoth.set_color(
|
||||
interpolate_color(GREY_BROWN, WHITE, 0.25)
|
||||
)
|
||||
mammoth.set_height(4)
|
||||
body = mammoth[9]
|
||||
|
||||
atoms = VGroup(*[
|
||||
self.get_atom(body)
|
||||
for n in range(600)
|
||||
])
|
||||
|
||||
p_decay = 0.2
|
||||
|
||||
def update_atoms(group, dt):
|
||||
for atom in group:
|
||||
if np.random.random() < dt * p_decay:
|
||||
group.remove(atom)
|
||||
return group
|
||||
atoms.add_updater(update_atoms)
|
||||
|
||||
self.add(mammoth)
|
||||
self.add(atoms)
|
||||
self.wait(20)
|
||||
|
||||
def get_atom(self, body):
|
||||
atom = Dot(color=GOLD)
|
||||
atom.set_height(0.05)
|
||||
|
||||
dl = body.get_corner(DL)
|
||||
ur = body.get_corner(UR)
|
||||
|
||||
wn = 0
|
||||
while wn == 0:
|
||||
point = np.array([
|
||||
interpolate(dl[0], ur[0], np.random.random()),
|
||||
interpolate(dl[1], ur[1], np.random.random()),
|
||||
0
|
||||
])
|
||||
wn = get_winding_number([
|
||||
body.point_from_proportion(a) - point
|
||||
for a in np.linspace(0, 1, 300)
|
||||
])
|
||||
wn = int(np.round(wn))
|
||||
|
||||
atom.move_to(point)
|
||||
return atom
|
||||
|
||||
|
||||
class BoundaryConditionInterlude(Scene):
|
||||
def construct(self):
|
||||
background = FullScreenFadeRectangle(
|
||||
fill_color=DARK_GREY
|
||||
)
|
||||
storyline = self.get_main_storyline()
|
||||
storyline.generate_target()
|
||||
v_shift = 2 * DOWN
|
||||
storyline.target.shift(v_shift)
|
||||
im_to_im = storyline[1].get_center() - storyline[0].get_center()
|
||||
|
||||
bc_image = self.get_labeled_image(
|
||||
"Boundary\\\\conditions",
|
||||
"boundary_condition_thumbnail"
|
||||
)
|
||||
bc_image.next_to(storyline[1], UP)
|
||||
new_arrow0 = Arrow(
|
||||
storyline.arrows[0].get_start() + v_shift,
|
||||
bc_image.get_left() + SMALL_BUFF * LEFT,
|
||||
path_arc=-90 * DEGREES,
|
||||
buff=0,
|
||||
)
|
||||
new_mid_arrow = Arrow(
|
||||
bc_image.get_bottom(),
|
||||
storyline[1].get_top() + v_shift,
|
||||
buff=SMALL_BUFF,
|
||||
path_arc=60 * DEGREES,
|
||||
)
|
||||
|
||||
self.add(background)
|
||||
self.add(storyline[0])
|
||||
for im1, im2, arrow in zip(storyline, storyline[1:], storyline.arrows):
|
||||
self.add(im2, im1)
|
||||
self.play(
|
||||
FadeInFrom(im2, -im_to_im),
|
||||
ShowCreation(arrow),
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
GrowFromCenter(bc_image),
|
||||
MoveToTarget(storyline),
|
||||
Transform(
|
||||
storyline.arrows[0],
|
||||
new_arrow0,
|
||||
),
|
||||
MaintainPositionRelativeTo(
|
||||
storyline.arrows[1],
|
||||
storyline,
|
||||
),
|
||||
)
|
||||
self.play(ShowCreation(new_mid_arrow))
|
||||
self.wait()
|
||||
|
||||
def get_main_storyline(self):
|
||||
images = Group(
|
||||
self.get_sine_curve_image(),
|
||||
self.get_linearity_image(),
|
||||
self.get_fourier_series_image(),
|
||||
)
|
||||
for image in images:
|
||||
image.set_height(3)
|
||||
images.arrange(RIGHT, buff=1)
|
||||
images.set_width(FRAME_WIDTH - 1)
|
||||
|
||||
arrows = VGroup()
|
||||
for im1, im2 in zip(images, images[1:]):
|
||||
arrow = Arrow(
|
||||
im1.get_top(),
|
||||
im2.get_top(),
|
||||
color=WHITE,
|
||||
buff=MED_SMALL_BUFF,
|
||||
path_arc=-120 * DEGREES,
|
||||
rectangular_stem_width=0.025,
|
||||
)
|
||||
arrow.scale(0.7, about_edge=DOWN)
|
||||
arrows.add(arrow)
|
||||
images.arrows = arrows
|
||||
|
||||
return images
|
||||
|
||||
def get_sine_curve_image(self):
|
||||
return self.get_labeled_image(
|
||||
"Sine curves",
|
||||
"sine_curve_temp_graph",
|
||||
)
|
||||
|
||||
def get_linearity_image(self):
|
||||
return self.get_labeled_image(
|
||||
"Linearity",
|
||||
"linearity_thumbnail",
|
||||
)
|
||||
|
||||
def get_fourier_series_image(self):
|
||||
return self.get_labeled_image(
|
||||
"Fourier series",
|
||||
"fourier_series_thumbnail",
|
||||
)
|
||||
|
||||
def get_labeled_image(self, text, image_file):
|
||||
rect = ScreenRectangle(height=2)
|
||||
border = rect.copy()
|
||||
rect.set_fill(BLACK, 1)
|
||||
rect.set_stroke(WHITE, 0)
|
||||
border.set_stroke(WHITE, 2)
|
||||
|
||||
text_mob = TextMobject(text)
|
||||
text_mob.set_stroke(BLACK, 5, background=True)
|
||||
text_mob.next_to(rect.get_top(), DOWN, SMALL_BUFF)
|
||||
|
||||
image = ImageMobject(image_file)
|
||||
image.replace(rect, dim_to_match=1)
|
||||
image.scale(0.8, about_edge=DOWN)
|
||||
|
||||
return Group(rect, image, border, text_mob)
|
||||
|
||||
|
||||
class GiantCross(Scene):
|
||||
def construct(self):
|
||||
rect = FullScreenFadeRectangle()
|
||||
cross = Cross(rect)
|
||||
cross.set_stroke(RED, 25)
|
||||
|
||||
words = TextMobject("This wouldn't\\\\happen!")
|
||||
words.scale(2)
|
||||
words.set_color(RED)
|
||||
words.to_edge(UP)
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(words),
|
||||
ShowCreation(cross),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from scipy import integrate
|
||||
|
||||
from manimlib.imports import *
|
||||
from active_projects.ode.part2.heat_equation import *
|
||||
|
||||
|
||||
class TemperatureGraphScene(SpecialThreeDScene):
|
||||
|
@ -24,11 +25,6 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
|||
"background_image_file": "VerticalTempGradient",
|
||||
},
|
||||
"default_surface_config": {
|
||||
"u_min": 0,
|
||||
"u_max": TAU,
|
||||
"v_min": 0,
|
||||
"v_max": 10,
|
||||
"resolution": (16, 10),
|
||||
"fill_opacity": 0.1,
|
||||
"checkerboard_colors": [LIGHT_GREY],
|
||||
"stroke_width": 0.5,
|
||||
|
@ -90,6 +86,7 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
|||
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:
|
||||
|
@ -117,26 +114,39 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
|||
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)
|
||||
),
|
||||
t_min=axes.x_min,
|
||||
t_max=axes.x_max,
|
||||
**config,
|
||||
)
|
||||
|
||||
def get_initial_state_graph(self, axes, func, **kwargs):
|
||||
return self.get_time_slice_graph(
|
||||
axes, func, t=0, **kwargs
|
||||
axes,
|
||||
lambda x, t: func(x),
|
||||
t=0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_surface(self, axes, func, **kwargs):
|
||||
config = merge_dicts_recursively(
|
||||
self.default_surface_config,
|
||||
kwargs
|
||||
)
|
||||
config = {
|
||||
"u_min": axes.x_min,
|
||||
"u_max": axes.x_max,
|
||||
"v_min": axes.y_min,
|
||||
"v_max": axes.y_max,
|
||||
"resolution": (
|
||||
(axes.x_max - axes.x_min) // axes.x_axis.tick_frequency,
|
||||
(axes.y_max - axes.y_min) // axes.y_axis.tick_frequency,
|
||||
),
|
||||
}
|
||||
config.update(self.default_surface_config)
|
||||
config.update(kwargs)
|
||||
return ParametricSurface(
|
||||
lambda x, t: axes.c2p(
|
||||
x, t, func(x, t)
|
||||
|
@ -151,6 +161,9 @@ class TemperatureGraphScene(SpecialThreeDScene):
|
|||
mobject.rotate(phi, LEFT)
|
||||
return mobject
|
||||
|
||||
def get_rod_length(self):
|
||||
return self.axes_config["x_max"]
|
||||
|
||||
|
||||
class SimpleCosExpGraph(TemperatureGraphScene):
|
||||
def construct(self):
|
||||
|
@ -336,9 +349,11 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
"unit_size": 0.75,
|
||||
"include_tip": False,
|
||||
},
|
||||
"z_min": 0,
|
||||
"z_min": -2,
|
||||
"y_max": 20,
|
||||
},
|
||||
"n_low_axes": 4,
|
||||
"k": 0.2,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
@ -407,12 +422,12 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
lambda x: A * np.cos(n * x / 2)
|
||||
)
|
||||
for n, axes, A in zip(
|
||||
it.count(0, 2),
|
||||
it.count(),
|
||||
low_axes_group,
|
||||
fourier_terms[::2],
|
||||
fourier_terms
|
||||
)
|
||||
])
|
||||
k = 0.1
|
||||
k = self.k
|
||||
low_surfaces = VGroup(*[
|
||||
self.get_surface(
|
||||
axes,
|
||||
|
@ -423,9 +438,9 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
])
|
||||
)
|
||||
for n, axes, A in zip(
|
||||
it.count(0, 2),
|
||||
it.count(),
|
||||
low_axes_group,
|
||||
fourier_terms[::2],
|
||||
fourier_terms
|
||||
)
|
||||
])
|
||||
top_surface = self.get_surface(
|
||||
|
@ -437,8 +452,8 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
np.exp(-k * (n / 2)**2 * t)
|
||||
])
|
||||
for n, A in zip(
|
||||
it.count(0, 2),
|
||||
fourier_terms[::2]
|
||||
it.count(),
|
||||
fourier_terms
|
||||
)
|
||||
])
|
||||
)
|
||||
|
@ -463,10 +478,11 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
])
|
||||
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_graph.get_end() + 1.4 * DOWN + 1.7 * RIGHT,
|
||||
path_arc=90 * DEGREES,
|
||||
)
|
||||
|
||||
top_words = TextMobject("Arbitrary\\\\function")
|
||||
|
@ -474,7 +490,7 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
top_words.set_color(YELLOW)
|
||||
top_arrow = Arrow(
|
||||
top_words.get_right(),
|
||||
top_graph.get_center() + LEFT,
|
||||
top_graph.point_from_proportion(0.3)
|
||||
)
|
||||
|
||||
low_words = TextMobject("Sine curves")
|
||||
|
@ -491,14 +507,11 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
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.wait()
|
||||
self.play(
|
||||
LaggedStartMap(FadeInFromDown, [*plusses, dots]),
|
||||
],
|
||||
)
|
||||
self.play(ShowCreation(arrow))
|
||||
self.wait()
|
||||
|
@ -515,7 +528,11 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
surface.sort(lambda p: -p[2])
|
||||
|
||||
anims1 = []
|
||||
anims2 = []
|
||||
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)
|
||||
|
@ -558,38 +575,35 @@ class BreakDownAFunction(SimpleCosExpGraph):
|
|||
|
||||
#
|
||||
def initial_func(self, x):
|
||||
return 3 * np.exp(-(x - PI)**2)
|
||||
# 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
|
||||
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:
|
||||
return interpolate(
|
||||
T0, T1,
|
||||
inverse_interpolate(x1, x2, x)
|
||||
)
|
||||
alpha = inverse_interpolate(x1, x2, x)
|
||||
return bezier([T0, T0, T1, T1])(alpha)
|
||||
elif x < x3:
|
||||
return T1
|
||||
elif x < x4:
|
||||
return interpolate(
|
||||
T1, T0,
|
||||
inverse_interpolate(x3, x4, x)
|
||||
)
|
||||
alpha = inverse_interpolate(x3, x4, x)
|
||||
return bezier([T1, T1, T2, T2])(alpha)
|
||||
else:
|
||||
return T0
|
||||
return T2
|
||||
|
||||
def get_initial_func_discontinuities(self):
|
||||
# return [TAU / 4, 3 * TAU / 4]
|
||||
return []
|
||||
|
||||
def get_fourier_cosine_terms(self, func, n_terms=20):
|
||||
def get_fourier_cosine_terms(self, func, n_terms=40):
|
||||
result = [
|
||||
integrate.quad(
|
||||
lambda x: (1 / PI) * func(x) * np.cos(n * x / 2),
|
||||
|
@ -914,7 +928,7 @@ class AnalyzeSineCurve(TemperatureGraphScene):
|
|||
def show_sine_wave_on_axes(self):
|
||||
axes = self.axes
|
||||
graph = self.get_initial_state_graph(
|
||||
axes, lambda x, t: np.sin(x)
|
||||
axes, lambda x: np.sin(x)
|
||||
)
|
||||
graph.set_stroke(width=4)
|
||||
graph_label = TexMobject(
|
||||
|
@ -1020,7 +1034,7 @@ class AnalyzeSineCurve(TemperatureGraphScene):
|
|||
curve_x_tracker = self.curve_x_tracker
|
||||
|
||||
d2_graph = self.get_initial_state_graph(
|
||||
axes, lambda x, t: -np.sin(x),
|
||||
axes, lambda x: -np.sin(x),
|
||||
)
|
||||
dashed_d2_graph = DashedVMobject(d2_graph, num_dashes=50)
|
||||
dashed_d2_graph.color_using_background_image(None)
|
||||
|
@ -1266,3 +1280,326 @@ class AnalyzeSineCurve(TemperatureGraphScene):
|
|||
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),
|
||||
"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.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(0.5)
|
||||
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 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
|
||||
|
|
|
@ -151,3 +151,315 @@ class ThreeConstraints(WriteHeatEquationTemplate):
|
|||
self.wait(2)
|
||||
self.play(Write(items[2][1]))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class EquationAboveSineAnalysis(WriteHeatEquationTemplate):
|
||||
def construct(self):
|
||||
equation = self.get_d1_equation()
|
||||
equation.to_edge(UP)
|
||||
equation.shift(2 * LEFT)
|
||||
eq_index = equation.index_of_part_by_tex("=")
|
||||
lhs = equation[:eq_index]
|
||||
eq = equation[eq_index]
|
||||
rhs = equation[eq_index + 1:]
|
||||
t_terms = equation.get_parts_by_tex("{t}")[1:]
|
||||
t_terms.save_state()
|
||||
zeros = VGroup(*[
|
||||
TexMobject("0").replace(t, dim_to_match=1)
|
||||
for t in t_terms
|
||||
])
|
||||
zeros.align_to(t_terms, DOWN)
|
||||
new_rhs = TexMobject(
|
||||
"=", "-\\alpha \\cdot {T}", "({x}, 0)",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
# new_rhs.move_to(equation.get_right())
|
||||
# new_rhs.next_to(equation, DOWN, MED_LARGE_BUFF)
|
||||
# new_rhs.align_to(eq, LEFT)
|
||||
new_rhs.next_to(equation, RIGHT)
|
||||
new_rhs.shift(SMALL_BUFF * DOWN)
|
||||
|
||||
self.add(equation)
|
||||
self.play(ShowCreationThenFadeAround(rhs))
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeOutAndShift(t_terms, UP),
|
||||
FadeInFrom(zeros, DOWN),
|
||||
)
|
||||
t_terms.fade(1)
|
||||
self.wait()
|
||||
self.play(
|
||||
# VGroup(equation, zeros).next_to,
|
||||
# new_rhs, LEFT,
|
||||
FadeIn(new_rhs),
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
VGroup(
|
||||
lhs[6:],
|
||||
eq,
|
||||
rhs,
|
||||
new_rhs[0],
|
||||
new_rhs[-3:],
|
||||
zeros,
|
||||
).fade, 0.5,
|
||||
)
|
||||
self.play(ShowCreationThenFadeAround(lhs[:6]))
|
||||
self.play(ShowCreationThenFadeAround(new_rhs[1:-3]))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ShowSinExpDerivatives(WriteHeatEquationTemplate):
|
||||
CONFIG = {
|
||||
"tex_mobject_config": {
|
||||
"tex_to_color_map": {
|
||||
"{0}": WHITE,
|
||||
"\\partial": WHITE,
|
||||
"=": WHITE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
pde = self.get_d1_equation_without_inputs()
|
||||
pde.to_edge(UP)
|
||||
pde.generate_target()
|
||||
|
||||
new_rhs = TexMobject(
|
||||
"=- \\alpha \\cdot T",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
new_rhs.next_to(pde, RIGHT)
|
||||
new_rhs.align_to(pde.get_part_by_tex("alpha"), DOWN)
|
||||
|
||||
equation1 = TexMobject(
|
||||
"T({x}, {0}) = \\sin\\left({x}\\right)",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
equation2 = TexMobject(
|
||||
"T({x}, {t}) = \\sin\\left({x}\\right)",
|
||||
"e^{-\\alpha{t}}",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
for eq in equation1, equation2:
|
||||
eq.next_to(pde, DOWN, MED_LARGE_BUFF)
|
||||
|
||||
eq2_part1 = equation2[:len(equation1)]
|
||||
eq2_part2 = equation2[len(equation1):]
|
||||
|
||||
# Rectangles
|
||||
exp_rect = SurroundingRectangle(eq2_part2)
|
||||
exp_rect.set_stroke(RED, 3)
|
||||
sin_rect = SurroundingRectangle(
|
||||
eq2_part1[-3:]
|
||||
)
|
||||
sin_rect.set_color(BLUE)
|
||||
|
||||
VGroup(pde.target, new_rhs).center().to_edge(UP)
|
||||
|
||||
# Show proposed solution
|
||||
self.add(pde)
|
||||
self.add(equation1)
|
||||
self.wait()
|
||||
self.play(
|
||||
MoveToTarget(pde),
|
||||
FadeInFrom(new_rhs, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
ReplacementTransform(equation1, eq2_part1),
|
||||
FadeIn(eq2_part2),
|
||||
)
|
||||
self.play(ShowCreation(exp_rect))
|
||||
self.wait()
|
||||
self.play(FadeOut(exp_rect))
|
||||
|
||||
# Take partial derivatives wrt x
|
||||
q_mark = TexMobject("?")
|
||||
q_mark.next_to(pde.get_part_by_tex("="), UP)
|
||||
q_mark.set_color(RED)
|
||||
|
||||
arrow1 = Vector(3 * DOWN + 1 * RIGHT, color=WHITE)
|
||||
arrow1.scale(1.2 / arrow1.get_length())
|
||||
arrow1.next_to(
|
||||
eq2_part2.get_corner(DL),
|
||||
DOWN, MED_LARGE_BUFF
|
||||
)
|
||||
ddx_label1 = TexMobject(
|
||||
"\\partial \\over \\partial {x}",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
ddx_label1.scale(0.7)
|
||||
ddx_label1.next_to(
|
||||
arrow1.point_from_proportion(0.8),
|
||||
UR, SMALL_BUFF
|
||||
)
|
||||
|
||||
pde_ddx = VGroup(
|
||||
*pde.get_parts_by_tex("\\partial")[2:],
|
||||
pde.get_parts_by_tex("\\over")[1],
|
||||
pde.get_part_by_tex("{x}"),
|
||||
)
|
||||
pde_ddx_rect = SurroundingRectangle(pde_ddx)
|
||||
pde_ddx_rect.set_color(GREEN)
|
||||
|
||||
eq2_part2_rect = SurroundingRectangle(eq2_part2)
|
||||
|
||||
dx = TexMobject(
|
||||
"\\cos\\left({x}\\right)", "e^{-\\alpha {t}}",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
ddx = TexMobject(
|
||||
"-\\sin\\left({x}\\right)", "e^{-\\alpha {t}}",
|
||||
**self.tex_mobject_config
|
||||
)
|
||||
dx.next_to(arrow1, DOWN)
|
||||
dx.align_to(eq2_part2, RIGHT)
|
||||
x_shift = arrow1.get_end()[0] - arrow1.get_start()[0]
|
||||
x_shift *= 2
|
||||
dx.shift(x_shift * RIGHT)
|
||||
arrow2 = arrow1.copy()
|
||||
arrow2.next_to(dx, DOWN)
|
||||
arrow2.shift(MED_SMALL_BUFF * RIGHT)
|
||||
dx_arrows = VGroup(arrow1, arrow2)
|
||||
|
||||
ddx_label2 = ddx_label1.copy()
|
||||
ddx_label2.shift(
|
||||
arrow2.get_center() - arrow1.get_center()
|
||||
)
|
||||
ddx.next_to(arrow2, DOWN)
|
||||
ddx.align_to(eq2_part2, RIGHT)
|
||||
ddx.shift(2 * x_shift * RIGHT)
|
||||
|
||||
rhs = equation2[-6:]
|
||||
|
||||
self.play(
|
||||
FadeInFromDown(q_mark)
|
||||
)
|
||||
self.play(
|
||||
ShowCreation(pde_ddx_rect)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
LaggedStart(
|
||||
GrowArrow(arrow1),
|
||||
GrowArrow(arrow2),
|
||||
),
|
||||
TransformFromCopy(
|
||||
pde_ddx[0], ddx_label1
|
||||
),
|
||||
TransformFromCopy(
|
||||
pde_ddx[0], ddx_label2
|
||||
),
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
TransformFromCopy(rhs, dx)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeIn(eq2_part2_rect)
|
||||
)
|
||||
self.play(
|
||||
Transform(
|
||||
eq2_part2_rect,
|
||||
SurroundingRectangle(dx[-3:])
|
||||
)
|
||||
)
|
||||
self.play(
|
||||
FadeOut(eq2_part2_rect)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
TransformFromCopy(dx, ddx)
|
||||
)
|
||||
self.play(
|
||||
FadeIn(
|
||||
SurroundingRectangle(ddx).match_style(
|
||||
pde_ddx_rect
|
||||
)
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Take partial derivative wrt t
|
||||
pde_ddt = pde[:pde.index_of_part_by_tex("=") - 1]
|
||||
pde_ddt_rect = SurroundingRectangle(pde_ddt)
|
||||
|
||||
dt_arrow = Arrow(
|
||||
arrow1.get_start(),
|
||||
arrow2.get_end() + RIGHT,
|
||||
buff=0
|
||||
)
|
||||
dt_arrow.flip(UP)
|
||||
dt_arrow.next_to(dx_arrows, LEFT, MED_LARGE_BUFF)
|
||||
|
||||
dt_label = TexMobject(
|
||||
"\\partial \\over \\partial {t}",
|
||||
**self.tex_mobject_config,
|
||||
)
|
||||
dt_label.scale(1)
|
||||
dt_label.next_to(
|
||||
dt_arrow.get_center(), UL,
|
||||
SMALL_BUFF,
|
||||
)
|
||||
|
||||
rhs_copy = rhs.copy()
|
||||
rhs_copy.next_to(dt_arrow.get_end(), DOWN)
|
||||
rhs_copy.shift(MED_LARGE_BUFF * LEFT)
|
||||
rhs_copy.match_y(ddx)
|
||||
|
||||
minus_alpha_in_exp = rhs_copy[-3][1:].copy()
|
||||
minus_alpha_in_exp.set_color(RED)
|
||||
minus_alpha = TexMobject("-\\alpha")
|
||||
minus_alpha.next_to(rhs_copy, LEFT)
|
||||
minus_alpha.align_to(rhs_copy[0][0], DOWN)
|
||||
dot = TexMobject("\\cdot")
|
||||
dot.move_to(midpoint(
|
||||
minus_alpha.get_right(),
|
||||
rhs_copy.get_left(),
|
||||
))
|
||||
|
||||
self.play(
|
||||
TransformFromCopy(
|
||||
pde_ddx_rect,
|
||||
pde_ddt_rect,
|
||||
)
|
||||
)
|
||||
self.play(
|
||||
GrowArrow(dt_arrow),
|
||||
TransformFromCopy(
|
||||
pde_ddt,
|
||||
dt_label,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
self.play(TransformFromCopy(rhs, rhs_copy))
|
||||
self.play(FadeIn(minus_alpha_in_exp))
|
||||
self.play(
|
||||
ApplyMethod(
|
||||
minus_alpha_in_exp.replace, minus_alpha,
|
||||
path_arc=TAU / 4
|
||||
),
|
||||
FadeIn(dot),
|
||||
)
|
||||
self.play(
|
||||
FadeIn(minus_alpha),
|
||||
FadeOut(minus_alpha_in_exp),
|
||||
)
|
||||
self.wait()
|
||||
rhs_copy.add(minus_alpha, dot)
|
||||
self.play(
|
||||
FadeIn(SurroundingRectangle(rhs_copy))
|
||||
)
|
||||
self.wait()
|
||||
|
||||
#
|
||||
checkmark = TexMobject("\\checkmark")
|
||||
checkmark.set_color(GREEN)
|
||||
checkmark.move_to(q_mark, DOWN)
|
||||
self.play(
|
||||
FadeInFromDown(checkmark),
|
||||
FadeOutAndShift(q_mark, UP)
|
||||
)
|
||||
self.wait()
|
||||
|
|
Loading…
Add table
Reference in a new issue