Latest scenes for diffyq part 3

This commit is contained in:
Grant Sanderson 2019-06-13 09:27:35 -07:00
parent 503b2bc896
commit caa4efdfa5
6 changed files with 1369 additions and 74 deletions

View file

@ -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,
]

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,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):

View file

@ -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)

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),
@ -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()

View file

@ -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

View file

@ -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()