Merge pull request #588 from 3b1b/diffyq

Diffyq
This commit is contained in:
Grant Sanderson 2019-06-13 09:34:25 -07:00 committed by GitHub
commit 34d1d27c56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 2808 additions and 189 deletions

View file

@ -1,13 +1,43 @@
from active_projects.ode.part3.staging import *
from active_projects.ode.part3.temperature_graphs import *
from active_projects.ode.part3.pi_creature_scenes import *
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,
SimpleSinExpGraph,
]

View file

@ -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,6 +374,15 @@ class BringTwoRodsTogether(Scene):
)[1]
for alt_x in (x - dx, x, x + dx)
]
# 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)
@ -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):

View file

@ -0,0 +1,140 @@
from manimlib.imports import *
from active_projects.ode.part2.wordy_scenes import *
class IveHeardOfThis(TeacherStudentsScene):
def construct(self):
point = VectorizedPoint()
point.move_to(3 * RIGHT + 2 * UP)
self.student_says(
"I've heard\\\\", "of this!",
student_index=1,
target_mode="hooray",
bubble_kwargs={
"height": 3,
"width": 3,
"direction": RIGHT,
},
run_time=1,
)
self.change_student_modes(
"thinking", "hooray", "thinking",
look_at_arg=point,
added_anims=[self.teacher.change, "happy"]
)
self.wait(3)
self.student_says(
"But who\\\\", "cares?",
student_index=1,
target_mode="maybe",
bubble_kwargs={
"direction": RIGHT,
"width": 3,
"height": 3,
},
run_time=1,
)
self.change_student_modes(
"pondering", "maybe", "pondering",
look_at_arg=point,
added_anims=[self.teacher.change, "guilty"]
)
self.wait(5)
class InFouriersShoes(PiCreatureScene, WriteHeatEquationTemplate):
def construct(self):
randy = self.pi_creature
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(4)
fourier.next_to(randy, RIGHT, LARGE_BUFF)
fourier.align_to(randy, DOWN)
equation = self.get_d1_equation()
equation.next_to(fourier, UP, MED_LARGE_BUFF)
decades = list(range(1740, 2040, 20))
time_line = NumberLine(
x_min=decades[0],
x_max=decades[-1],
tick_frequency=1,
tick_size=0.05,
longer_tick_multiple=4,
unit_size=0.2,
numbers_with_elongated_ticks=decades,
numbers_to_show=decades,
decimal_number_config={
"group_with_commas": False,
},
stroke_width=2,
)
time_line.add_numbers()
time_line.move_to(ORIGIN, RIGHT)
time_line.to_edge(UP)
triangle = ArrowTip(start_angle=-90 * DEGREES)
triangle.set_height(0.25)
triangle.move_to(time_line.n2p(2019), DOWN)
triangle.set_color(WHITE)
self.play(FadeInFrom(fourier, 2 * LEFT))
self.play(randy.change, "pondering")
self.wait()
self.play(
DrawBorderThenFill(triangle, run_time=1),
FadeInFromDown(equation),
FadeIn(time_line),
)
self.play(
Animation(triangle),
ApplyMethod(
time_line.shift,
time_line.n2p(2019) - time_line.n2p(1822),
run_time=5
),
)
self.wait()
class SineCurveIsUnrealistic(TeacherStudentsScene):
def construct(self):
self.student_says(
"But that would\\\\never happen!",
student_index=1,
bubble_kwargs={
"direction": RIGHT,
"height": 3,
"width": 4,
},
target_mode="angry"
)
self.change_student_modes(
"guilty", "angry", "hesitant",
added_anims=[
self.teacher.change, "tease"
]
)
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)

View file

@ -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),
@ -221,32 +251,21 @@ class FourierSeriesIllustraiton(Scene):
y_min=-1,
y_max=1,
)
axes2 = axes1.copy()
step_func = axes2.get_graph(
lambda x: (1 if x < 0.5 else -1),
discontinuities=[0.5],
color=YELLOW,
stroke_width=3,
axes1.x_axis.add_numbers(
0.5, 1,
number_config={"num_decimal_places": 1}
)
dot = Dot(axes2.c2p(0.5, 0), color=step_func.get_color())
dot.scale(0.5)
step_func.add(dot)
axes2.add(step_func)
axes2 = axes1.copy()
target_func_graph = self.get_target_func_graph(axes2)
axes2.add(target_func_graph)
arrow = Arrow(LEFT, RIGHT, color=WHITE)
VGroup(axes1, arrow, axes2).arrange(RIGHT).shift(UP)
def generate_nth_func(n):
return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
def generate_kth_partial_sum_func(k):
return lambda x: np.sum([
generate_nth_func(n)(x)
for n in n_range[:k]
])
group = VGroup(axes1, arrow, axes2)
group.arrange(RIGHT, buff=LARGE_BUFF)
group.shift(2 * UP)
sine_graphs = VGroup(*[
axes1.get_graph(generate_nth_func(n))
axes1.get_graph(self.generate_nth_func(n))
for n in n_range
])
sine_graphs.set_stroke(width=3)
@ -256,44 +275,43 @@ class FourierSeriesIllustraiton(Scene):
)
partial_sums = VGroup(*[
axes1.get_graph(generate_kth_partial_sum_func(k + 1))
axes1.get_graph(self.generate_kth_partial_sum_func(k + 1))
for k in range(len(n_range))
])
partial_sums.match_style(sine_graphs)
sum_tex = TexMobject(
"\\frac{4}{\\pi}"
"\\sum_{1, 3, 5, \\dots}"
"\\frac{1}{n} \\sin(2\\pi \\cdot n \\cdot x)"
)
sum_tex.next_to(partial_sums, DOWN, buff=0.7)
sum_tex = self.get_sum_tex()
sum_tex.next_to(axes1, DOWN, LARGE_BUFF)
sum_tex.shift(RIGHT)
eq = TexMobject("=")
step_tex = TexMobject(
"""
1 \\quad \\text{if $x < 0.5$} \\\\
0 \\quad \\text{if $x = 0.5$} \\\\
-1 \\quad \\text{if $x > 0.5$} \\\\
"""
)
lb = Brace(step_tex, LEFT, buff=SMALL_BUFF)
step_tex.add(lb)
step_tex.next_to(axes2, DOWN, buff=MED_LARGE_BUFF)
target_func_tex = self.get_target_func_tex()
target_func_tex.next_to(axes2, DOWN)
target_func_tex.match_y(sum_tex)
eq.move_to(midpoint(
step_tex.get_left(),
target_func_tex.get_left(),
sum_tex.get_right()
))
range_words = TextMobject(
"For $0 \\le x \\le 1$"
)
range_words.next_to(
VGroup(sum_tex, target_func_tex),
DOWN,
)
rects = it.chain(
[
SurroundingRectangle(sum_tex[0][i])
for i in [4, 6, 8]
SurroundingRectangle(piece)
for piece in self.get_sum_tex_pieces(sum_tex)
],
it.cycle([None])
)
self.add(axes1, arrow, axes2)
self.add(step_func)
self.add(sum_tex, eq, step_tex)
self.add(target_func_graph)
self.add(sum_tex, eq, target_func_tex)
self.add(range_words)
curr_partial_sum = axes1.get_graph(lambda x: 0)
curr_partial_sum.set_stroke(width=1)
@ -320,6 +338,101 @@ class FourierSeriesIllustraiton(Scene):
self.play(*anims2)
curr_partial_sum = partial_sum
def get_sum_tex(self):
return TexMobject(
"\\frac{4}{\\pi} \\left(",
"\\frac{\\cos(\\pi x)}{1}",
"-\\frac{\\cos(3\\pi x)}{3}",
"+\\frac{\\cos(5\\pi x)}{5}",
"- \\cdots \\right)"
).scale(0.75)
def get_sum_tex_pieces(self, sum_tex):
return sum_tex[1:4]
def get_target_func_tex(self):
step_tex = TexMobject(
"""
1 \\quad \\text{if $x < 0.5$} \\\\
0 \\quad \\text{if $x = 0.5$} \\\\
-1 \\quad \\text{if $x > 0.5$} \\\\
"""
)
lb = Brace(step_tex, LEFT, buff=SMALL_BUFF)
step_tex.add(lb)
return step_tex
def get_target_func_graph(self, axes):
step_func = axes.get_graph(
lambda x: (1 if x < 0.5 else -1),
discontinuities=[0.5],
color=YELLOW,
stroke_width=3,
)
dot = Dot(axes.c2p(0.5, 0), color=step_func.get_color())
dot.scale(0.5)
step_func.add(dot)
return step_func
# def generate_nth_func(self, n):
# return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
def generate_nth_func(self, n):
return lambda x: np.prod([
(4 / PI),
(1 / n) * (-1)**((n - 1) / 2),
np.cos(PI * n * x)
])
def generate_kth_partial_sum_func(self, k):
return lambda x: np.sum([
self.generate_nth_func(n)(x)
for n in self.n_range[:k]
])
class FourierSeriesOfLineIllustration(FourierSeriesIllustraiton):
CONFIG = {
"n_range": range(1, 31, 2)
}
def get_sum_tex(self):
return TexMobject(
"\\frac{8}{\\pi^2} \\left(",
"\\frac{\\cos(\\pi x)}{1^2}",
"+\\frac{\\cos(3\\pi x)}{3^2}",
"+\\frac{\\cos(5\\pi x)}{5^2}",
"+ \\cdots \\right)"
).scale(0.75)
# def get_sum_tex_pieces(self, sum_tex):
# return sum_tex[1:4]
def get_target_func_tex(self):
result = TexMobject("1 - 2x")
result.scale(1.5)
point = VectorizedPoint()
point.next_to(result, RIGHT, 1.5 * LARGE_BUFF)
# result.add(point)
return result
def get_target_func_graph(self, axes):
return axes.get_graph(
lambda x: 1 - 2 * x,
color=YELLOW,
stroke_width=3,
)
# def generate_nth_func(self, n):
# return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
def generate_nth_func(self, n):
return lambda x: np.prod([
(8 / PI**2),
(1 / n**2),
np.cos(PI * n * x)
])
class CircleAnimationOfF(FourierOfTrebleClef):
CONFIG = {
@ -334,94 +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 LastChapterWrapper(Scene):
class ExponentialDecay(PiCreatureScene):
def construct(self):
full_rect = FullScreenFadeRectangle(
fill_color=DARK_GREY,
fill_opacity=1,
)
rect = ScreenRectangle(height=6)
rect.set_stroke(WHITE, 2)
rect.set_fill(BLACK, 1)
title = TextMobject("Last chapter")
title.scale(2)
title.to_edge(UP)
rect.next_to(title, DOWN)
k = 0.2
mk_tex = "-0.2"
mk_tex_color = GREEN
t2c = {mk_tex: mk_tex_color}
self.add(full_rect)
self.play(
FadeIn(rect),
Write(title, run_time=2),
)
self.wait()
class ThreeMainObservations(Scene):
def construct(self):
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(5)
fourier.to_corner(DR)
fourier.shift(LEFT)
# 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=RIGHT,
height=3,
width=4,
direction=LEFT,
height=3.5,
width=3,
)
bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR)
observations = VGroup(
TextMobject(
"1)",
# "Sine waves",
# "H",
# "Heat equation",
),
TextMobject(
"2)",
# "Linearity"
),
TextMobject(
"3)",
# "Any$^{*}$ function is\\\\",
# "a sum of sine waves",
),
bubble.pin_to(randy)
bubble.set_fill(DARKER_GREY)
exp = TexMobject(
"Ce^{", mk_tex, "t}",
tex_to_color_map=t2c,
)
# heart = SuitSymbol("hearts")
# heart.replace(observations[0][2])
# observations[0][2].become(heart)
# observations[0][1].add(happiness)
# observations[2][2].align_to(
# observations[2][1], LEFT,
# )
exp.move_to(bubble.get_bubble_center())
observations.arrange(
DOWN,
aligned_edge=LEFT,
buff=LARGE_BUFF,
# Setup axes
axes = Axes(
x_min=0,
x_max=13,
y_min=-4,
y_max=4,
)
observations.set_height(FRAME_HEIGHT - 2)
observations.to_corner(UL, buff=LARGE_BUFF)
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)
self.add(fourier)
self.play(ShowCreation(bubble))
# 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(LaggedStart(*[
TransformFromCopy(bubble, observation)
for observation in observations
], lag_ratio=0.2))
self.play(
FadeOut(fourier),
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()
class NewSceneName(Scene):
def construct(self):
pass

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,465 @@
from manimlib.imports import *
from active_projects.ode.part2.wordy_scenes import *
class ThreeMainObservations(Scene):
def construct(self):
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(5)
fourier.to_corner(DR)
fourier.shift(LEFT)
bubble = ThoughtBubble(
direction=RIGHT,
height=3,
width=4,
)
bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR)
observations = VGroup(
TextMobject(
"1)",
# "Sine waves",
# "H",
# "Heat equation",
),
TextMobject(
"2)",
# "Linearity"
),
TextMobject(
"3)",
# "Any$^{*}$ function is\\\\",
# "a sum of sine waves",
),
)
# heart = SuitSymbol("hearts")
# heart.replace(observations[0][2])
# observations[0][2].become(heart)
# observations[0][1].add(happiness)
# observations[2][2].align_to(
# observations[2][1], LEFT,
# )
observations.arrange(
DOWN,
aligned_edge=LEFT,
buff=LARGE_BUFF,
)
observations.set_height(FRAME_HEIGHT - 2)
observations.to_corner(UL, buff=LARGE_BUFF)
self.add(fourier)
self.play(ShowCreation(bubble))
self.wait()
self.play(LaggedStart(*[
TransformFromCopy(bubble, observation)
for observation in observations
], lag_ratio=0.2))
self.play(
FadeOut(fourier),
FadeOut(bubble),
)
self.wait()
class LastChapterWrapper(Scene):
def construct(self):
full_rect = FullScreenFadeRectangle(
fill_color=DARK_GREY,
fill_opacity=1,
)
rect = ScreenRectangle(height=6)
rect.set_stroke(WHITE, 2)
rect.set_fill(BLACK, 1)
title = TextMobject("Last chapter")
title.scale(2)
title.to_edge(UP)
rect.next_to(title, DOWN)
self.add(full_rect)
self.play(
FadeIn(rect),
Write(title, run_time=2),
)
self.wait()
class ThreeConstraints(WriteHeatEquationTemplate):
def construct(self):
self.cross_out_solving()
self.show_three_conditions()
def cross_out_solving(self):
equation = self.get_d1_equation()
words = TextMobject("Solve this equation")
words.to_edge(UP)
equation.next_to(words, DOWN)
cross = Cross(words)
self.add(words, equation)
self.wait()
self.play(ShowCreation(cross))
self.wait()
self.equation = equation
self.to_remove = VGroup(words, cross)
def show_three_conditions(self):
equation = self.equation
to_remove = self.to_remove
title = TexMobject(
"\\text{Constraints }"
"T({x}, {t})"
"\\text{ must satisfy:}",
**self.tex_mobject_config
)
title.to_edge(UP)
items = VGroup(
TextMobject("1)", "The PDE"),
TextMobject("2)", "Boundary condition"),
TextMobject("3)", "Initial condition"),
)
items.scale(0.7)
items.arrange(RIGHT, buff=LARGE_BUFF)
items.set_width(FRAME_WIDTH - 2)
items.next_to(title, DOWN, LARGE_BUFF)
items[1].set_color(MAROON_B)
items[2].set_color(RED)
bc_paren = TextMobject("(Explained soon)")
bc_paren.scale(0.7)
bc_paren.next_to(items[1], DOWN)
self.play(
FadeInFromDown(title),
FadeOutAndShift(to_remove, UP),
equation.scale, 0.6,
equation.next_to, items[0], DOWN,
equation.shift_onto_screen,
LaggedStartMap(FadeIn, [
items[0],
items[1][0],
items[2][0],
])
)
self.wait()
self.play(Write(items[1][1]))
bc_paren.match_y(equation)
self.play(FadeInFrom(bc_paren, UP))
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()

View file

@ -6,6 +6,8 @@ from manimlib.utils.rate_functions import linear
from manimlib.utils.rate_functions import double_smooth
from manimlib.utils.rate_functions import smooth
import numpy as np
class ShowPartial(Animation):
"""
@ -120,6 +122,7 @@ class Write(DrawBorderThenFill):
class ShowIncreasingSubsets(Animation):
CONFIG = {
"suspend_mobject_updating": False,
"int_func": np.floor,
}
def __init__(self, group, **kwargs):
@ -128,5 +131,5 @@ class ShowIncreasingSubsets(Animation):
def interpolate_mobject(self, alpha):
n_submobs = len(self.all_submobs)
index = int(alpha * n_submobs)
index = int(self.int_func(alpha * n_submobs))
self.mobject.submobjects = self.all_submobs[:index]

View file

@ -332,6 +332,8 @@ class Camera(object):
points = self.transform_points_pre_display(
vmobject, vmobject.points
)
# TODO, shouldn't this be handled in transform_points_pre_display?
points = points - self.get_frame_center()
if len(points) == 0:
return

View file

@ -1,6 +1,8 @@
from manimlib.constants import *
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.rate_functions import smooth
from manimlib.utils.space_ops import get_norm
class AnimatedBoundary(VGroup):
@ -66,3 +68,31 @@ class AnimatedBoundary(VGroup):
for sm1, sm2 in zip(family1, family2):
sm1.pointwise_become_partial(sm2, a, b)
return self
class TracedPath(VMobject):
CONFIG = {
"stroke_width": 2,
"stroke_color": WHITE,
"min_distance_to_new_point": 0.1,
}
def __init__(self, traced_point_func, **kwargs):
super().__init__(**kwargs)
self.traced_point_func = traced_point_func
self.add_updater(lambda m: m.update_path())
def update_path(self):
new_point = self.traced_point_func()
if self.has_no_points():
self.start_new_path(new_point)
self.add_line_to(new_point)
else:
# Set the end to be the new point
self.points[-1] = new_point
# Second to last point
nppcc = self.n_points_per_cubic_curve
dist = get_norm(new_point - self.points[-nppcc])
if dist >= self.min_distance_to_new_point:
self.add_line_to(new_point)

View file

@ -242,6 +242,7 @@ class VMobject(Mobject):
def set_opacity(self, opacity, family=True):
self.set_fill(opacity=opacity, family=family)
self.set_stroke(opacity=opacity, family=family)
self.set_stroke(opacity=opacity, family=family, background=True)
return self
def fade(self, darkness=0.5, family=True):

View file

@ -43,7 +43,7 @@ def stage_scenes(module_name):
# }
# TODO, fix this
animation_dir = os.path.join(
consts.VIDEO_DIR, "ode", "part2", "1440p60"
consts.VIDEO_DIR, "ode", "part3", "1440p60"
)
#
files = os.listdir(animation_dir)