mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
1894 lines
54 KiB
Python
1894 lines
54 KiB
Python
from manimlib.imports import *
|
|
from active_projects.diffyq.part1.shared_constructs import *
|
|
|
|
|
|
class Pendulum(VGroup):
|
|
CONFIG = {
|
|
"length": 3,
|
|
"gravity": 9.8,
|
|
"weight_diameter": 0.5,
|
|
"initial_theta": 0.3,
|
|
"omega": 0,
|
|
"damping": 0.1,
|
|
"top_point": 2 * UP,
|
|
"rod_style": {
|
|
"stroke_width": 3,
|
|
"stroke_color": LIGHT_GREY,
|
|
"sheen_direction": UP,
|
|
"sheen_factor": 1,
|
|
},
|
|
"weight_style": {
|
|
"stroke_width": 0,
|
|
"fill_opacity": 1,
|
|
"fill_color": GREY_BROWN,
|
|
"sheen_direction": UL,
|
|
"sheen_factor": 0.5,
|
|
"background_stroke_color": BLACK,
|
|
"background_stroke_width": 3,
|
|
"background_stroke_opacity": 0.5,
|
|
},
|
|
"dashed_line_config": {
|
|
"num_dashes": 25,
|
|
"stroke_color": WHITE,
|
|
"stroke_width": 2,
|
|
},
|
|
"angle_arc_config": {
|
|
"radius": 1,
|
|
"stroke_color": WHITE,
|
|
"stroke_width": 2,
|
|
},
|
|
"velocity_vector_config": {
|
|
"color": RED,
|
|
},
|
|
"theta_label_height": 0.25,
|
|
"set_theta_label_height_cap": False,
|
|
"n_steps_per_frame": 100,
|
|
"include_theta_label": True,
|
|
"include_velocity_vector": False,
|
|
"velocity_vector_multiple": 0.5,
|
|
"max_velocity_vector_length_to_length_ratio": 0.5,
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.create_fixed_point()
|
|
self.create_rod()
|
|
self.create_weight()
|
|
self.rotating_group = VGroup(self.rod, self.weight)
|
|
self.create_dashed_line()
|
|
self.create_angle_arc()
|
|
if self.include_theta_label:
|
|
self.add_theta_label()
|
|
if self.include_velocity_vector:
|
|
self.add_velocity_vector()
|
|
|
|
self.set_theta(self.initial_theta)
|
|
self.update()
|
|
|
|
def create_fixed_point(self):
|
|
self.fixed_point_tracker = VectorizedPoint(self.top_point)
|
|
self.add(self.fixed_point_tracker)
|
|
return self
|
|
|
|
def create_rod(self):
|
|
rod = self.rod = Line(UP, DOWN)
|
|
rod.set_height(self.length)
|
|
rod.set_style(**self.rod_style)
|
|
rod.move_to(self.get_fixed_point(), UP)
|
|
self.add(rod)
|
|
|
|
def create_weight(self):
|
|
weight = self.weight = Circle()
|
|
weight.set_width(self.weight_diameter)
|
|
weight.set_style(**self.weight_style)
|
|
weight.move_to(self.rod.get_end())
|
|
self.add(weight)
|
|
|
|
def create_dashed_line(self):
|
|
line = self.dashed_line = DashedLine(
|
|
self.get_fixed_point(),
|
|
self.get_fixed_point() + self.length * DOWN,
|
|
**self.dashed_line_config
|
|
)
|
|
line.add_updater(
|
|
lambda l: l.move_to(self.get_fixed_point(), UP)
|
|
)
|
|
self.add_to_back(line)
|
|
|
|
def create_angle_arc(self):
|
|
self.angle_arc = always_redraw(lambda: Arc(
|
|
arc_center=self.get_fixed_point(),
|
|
start_angle=-90 * DEGREES,
|
|
angle=self.get_arc_angle_theta(),
|
|
**self.angle_arc_config,
|
|
))
|
|
self.add(self.angle_arc)
|
|
|
|
def get_arc_angle_theta(self):
|
|
# Might be changed in certain scenes
|
|
return self.get_theta()
|
|
|
|
def add_velocity_vector(self):
|
|
def make_vector():
|
|
omega = self.get_omega()
|
|
theta = self.get_theta()
|
|
mvlr = self.max_velocity_vector_length_to_length_ratio
|
|
max_len = mvlr * self.rod.get_length()
|
|
vvm = self.velocity_vector_multiple
|
|
multiple = np.clip(
|
|
vvm * omega, -max_len, max_len
|
|
)
|
|
vector = Vector(
|
|
multiple * RIGHT,
|
|
**self.velocity_vector_config,
|
|
)
|
|
vector.rotate(theta, about_point=ORIGIN)
|
|
vector.shift(self.rod.get_end())
|
|
return vector
|
|
|
|
self.velocity_vector = always_redraw(make_vector)
|
|
self.add(self.velocity_vector)
|
|
return self
|
|
|
|
def add_theta_label(self):
|
|
self.theta_label = always_redraw(self.get_label)
|
|
self.add(self.theta_label)
|
|
|
|
def get_label(self):
|
|
label = TexMobject("\\theta")
|
|
label.set_height(self.theta_label_height)
|
|
if self.set_theta_label_height_cap:
|
|
max_height = self.angle_arc.get_width()
|
|
if label.get_height() > max_height:
|
|
label.set_height(max_height)
|
|
top = self.get_fixed_point()
|
|
arc_center = self.angle_arc.point_from_proportion(0.5)
|
|
vect = arc_center - top
|
|
norm = get_norm(vect)
|
|
vect = normalize(vect) * (norm + self.theta_label_height)
|
|
label.move_to(top + vect)
|
|
return label
|
|
|
|
#
|
|
def get_theta(self):
|
|
theta = self.rod.get_angle() - self.dashed_line.get_angle()
|
|
theta = (theta + PI) % TAU - PI
|
|
return theta
|
|
|
|
def set_theta(self, theta):
|
|
self.rotating_group.rotate(
|
|
theta - self.get_theta()
|
|
)
|
|
self.rotating_group.shift(
|
|
self.get_fixed_point() - self.rod.get_start(),
|
|
)
|
|
return self
|
|
|
|
def get_omega(self):
|
|
return self.omega
|
|
|
|
def set_omega(self, omega):
|
|
self.omega = omega
|
|
return self
|
|
|
|
def get_fixed_point(self):
|
|
return self.fixed_point_tracker.get_location()
|
|
|
|
#
|
|
def start_swinging(self):
|
|
self.add_updater(Pendulum.update_by_gravity)
|
|
|
|
def end_swinging(self):
|
|
self.remove_updater(Pendulum.update_by_gravity)
|
|
|
|
def update_by_gravity(self, dt):
|
|
theta = self.get_theta()
|
|
omega = self.get_omega()
|
|
nspf = self.n_steps_per_frame
|
|
for x in range(nspf):
|
|
d_theta = omega * dt / nspf
|
|
d_omega = op.add(
|
|
-self.damping * omega,
|
|
-(self.gravity / self.length) * np.sin(theta),
|
|
) * dt / nspf
|
|
theta += d_theta
|
|
omega += d_omega
|
|
self.set_theta(theta)
|
|
self.set_omega(omega)
|
|
return self
|
|
|
|
|
|
class GravityVector(Vector):
|
|
CONFIG = {
|
|
"color": YELLOW,
|
|
"length_multiple": 1 / 9.8,
|
|
# TODO, continually update the length based
|
|
# on the pendulum's gravity?
|
|
}
|
|
|
|
def __init__(self, pendulum, **kwargs):
|
|
super().__init__(DOWN, **kwargs)
|
|
self.pendulum = pendulum
|
|
self.scale(self.length_multiple * pendulum.gravity)
|
|
self.attach_to_pendulum(pendulum)
|
|
|
|
def attach_to_pendulum(self, pendulum):
|
|
self.add_updater(lambda m: m.shift(
|
|
pendulum.weight.get_center() - self.get_start(),
|
|
))
|
|
|
|
def add_component_lines(self):
|
|
self.component_lines = always_redraw(self.create_component_lines)
|
|
self.add(self.component_lines)
|
|
|
|
def create_component_lines(self):
|
|
theta = self.pendulum.get_theta()
|
|
x_new = rotate(RIGHT, theta)
|
|
base = self.get_start()
|
|
tip = self.get_end()
|
|
vect = tip - base
|
|
corner = base + x_new * np.dot(vect, x_new)
|
|
kw = {"dash_length": 0.025}
|
|
return VGroup(
|
|
DashedLine(base, corner, **kw),
|
|
DashedLine(corner, tip, **kw),
|
|
)
|
|
|
|
|
|
class ThetaValueDisplay(VGroup):
|
|
CONFIG = {
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
class ThetaVsTAxes(Axes):
|
|
CONFIG = {
|
|
"x_min": 0,
|
|
"x_max": 8,
|
|
"y_min": -PI / 2,
|
|
"y_max": PI / 2,
|
|
"y_axis_config": {
|
|
"tick_frequency": PI / 8,
|
|
"unit_size": 1.5,
|
|
},
|
|
"number_line_config": {
|
|
"color": "#EEEEEE",
|
|
"stroke_width": 2,
|
|
"include_tip": False,
|
|
},
|
|
"graph_style": {
|
|
"stroke_color": GREEN,
|
|
"stroke_width": 3,
|
|
"fill_opacity": 0,
|
|
},
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.add_labels()
|
|
|
|
def add_axes(self):
|
|
self.axes = Axes(**self.axes_config)
|
|
self.add(self.axes)
|
|
|
|
def add_labels(self):
|
|
x_axis = self.get_x_axis()
|
|
y_axis = self.get_y_axis()
|
|
|
|
t_label = self.t_label = TexMobject("t")
|
|
t_label.next_to(x_axis.get_right(), UP, MED_SMALL_BUFF)
|
|
x_axis.label = t_label
|
|
x_axis.add(t_label)
|
|
theta_label = self.theta_label = TexMobject("\\theta(t)")
|
|
theta_label.next_to(y_axis.get_top(), UP, SMALL_BUFF)
|
|
y_axis.label = theta_label
|
|
y_axis.add(theta_label)
|
|
|
|
self.y_axis_label = theta_label
|
|
self.x_axis_label = t_label
|
|
|
|
x_axis.add_numbers()
|
|
y_axis.add(self.get_y_axis_coordinates(y_axis))
|
|
|
|
def get_y_axis_coordinates(self, y_axis):
|
|
texs = [
|
|
# "\\pi \\over 4",
|
|
# "\\pi \\over 2",
|
|
# "3 \\pi \\over 4",
|
|
# "\\pi",
|
|
"\\pi / 4",
|
|
"\\pi / 2",
|
|
"3 \\pi / 4",
|
|
"\\pi",
|
|
]
|
|
values = np.arange(1, 5) * PI / 4
|
|
labels = VGroup()
|
|
for pos_tex, pos_value in zip(texs, values):
|
|
neg_tex = "-" + pos_tex
|
|
neg_value = -1 * pos_value
|
|
for tex, value in (pos_tex, pos_value), (neg_tex, neg_value):
|
|
if value > self.y_max or value < self.y_min:
|
|
continue
|
|
symbol = TexMobject(tex)
|
|
symbol.scale(0.5)
|
|
point = y_axis.number_to_point(value)
|
|
symbol.next_to(point, LEFT, MED_SMALL_BUFF)
|
|
labels.add(symbol)
|
|
return labels
|
|
|
|
def get_live_drawn_graph(self, pendulum,
|
|
t_max=None,
|
|
t_step=1.0 / 60,
|
|
**style):
|
|
style = merge_dicts_recursively(self.graph_style, style)
|
|
if t_max is None:
|
|
t_max = self.x_max
|
|
|
|
graph = VMobject()
|
|
graph.set_style(**style)
|
|
|
|
graph.all_coords = [(0, pendulum.get_theta())]
|
|
graph.time = 0
|
|
graph.time_of_last_addition = 0
|
|
|
|
def update_graph(graph, dt):
|
|
graph.time += dt
|
|
if graph.time > t_max:
|
|
graph.remove_updater(update_graph)
|
|
return
|
|
new_coords = (graph.time, pendulum.get_theta())
|
|
if graph.time - graph.time_of_last_addition >= t_step:
|
|
graph.all_coords.append(new_coords)
|
|
graph.time_of_last_addition = graph.time
|
|
points = [
|
|
self.coords_to_point(*coords)
|
|
for coords in [*graph.all_coords, new_coords]
|
|
]
|
|
graph.set_points_smoothly(points)
|
|
|
|
graph.add_updater(update_graph)
|
|
return graph
|
|
|
|
|
|
# Scenes
|
|
class IntroducePendulum(PiCreatureScene, MovingCameraScene):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"length": 3,
|
|
"top_point": 4 * RIGHT,
|
|
"weight_diameter": 0.35,
|
|
"gravity": 20,
|
|
},
|
|
"theta_vs_t_axes_config": {
|
|
"y_max": PI / 4,
|
|
"y_min": -PI / 4,
|
|
"y_axis_config": {
|
|
"tick_frequency": PI / 16,
|
|
"unit_size": 2,
|
|
"tip_length": 0.3,
|
|
},
|
|
"x_max": 12,
|
|
"number_line_config": {
|
|
"stroke_width": 2,
|
|
}
|
|
},
|
|
}
|
|
|
|
def setup(self):
|
|
MovingCameraScene.setup(self)
|
|
PiCreatureScene.setup(self)
|
|
|
|
def construct(self):
|
|
self.add_pendulum()
|
|
# self.label_pi_creatures()
|
|
self.label_pendulum()
|
|
self.add_graph()
|
|
self.label_function()
|
|
self.show_graph_period()
|
|
self.show_length_and_gravity()
|
|
# self.tweak_length_and_gravity()
|
|
|
|
def create_pi_creatures(self):
|
|
randy = Randolph(color=BLUE_C)
|
|
morty = Mortimer(color=MAROON_E)
|
|
creatures = VGroup(randy, morty)
|
|
creatures.scale(0.5)
|
|
creatures.arrange(RIGHT, buff=2.5)
|
|
creatures.to_corner(DR)
|
|
return creatures
|
|
|
|
def add_pendulum(self):
|
|
pendulum = self.pendulum = Pendulum(**self.pendulum_config)
|
|
pendulum.start_swinging()
|
|
frame = self.camera_frame
|
|
frame.save_state()
|
|
frame.scale(0.5)
|
|
frame.move_to(pendulum.dashed_line)
|
|
|
|
self.add(pendulum, frame)
|
|
|
|
def label_pi_creatures(self):
|
|
randy, morty = self.pi_creatures
|
|
randy_label = TextMobject("Physics\\\\", "student")
|
|
morty_label = TextMobject("Physics\\\\", "teacher")
|
|
labels = VGroup(randy_label, morty_label)
|
|
labels.scale(0.5)
|
|
randy_label.next_to(randy, UP, LARGE_BUFF)
|
|
morty_label.next_to(morty, UP, LARGE_BUFF)
|
|
|
|
for label, pi in zip(labels, self.pi_creatures):
|
|
label.arrow = Arrow(
|
|
label.get_bottom(), pi.eyes.get_top()
|
|
)
|
|
label.arrow.set_color(WHITE)
|
|
label.arrow.set_stroke(width=5)
|
|
|
|
morty.labels = VGroup(
|
|
morty_label,
|
|
morty_label.arrow,
|
|
)
|
|
|
|
self.play(
|
|
FadeInFromDown(randy_label),
|
|
GrowArrow(randy_label.arrow),
|
|
randy.change, "hooray",
|
|
)
|
|
self.play(
|
|
Animation(self.pendulum.fixed_point_tracker),
|
|
TransformFromCopy(randy_label[0], morty_label[0]),
|
|
FadeIn(morty_label[1]),
|
|
GrowArrow(morty_label.arrow),
|
|
morty.change, "raise_right_hand",
|
|
)
|
|
self.wait(2)
|
|
|
|
def label_pendulum(self):
|
|
pendulum = self.pendulum
|
|
randy, morty = self.pi_creatures
|
|
label = pendulum.theta_label
|
|
rect = SurroundingRectangle(label, buff=0.5 * SMALL_BUFF)
|
|
rect.add_updater(lambda r: r.move_to(label))
|
|
|
|
for pi in randy, morty:
|
|
pi.add_updater(
|
|
lambda m: m.look_at(pendulum.weight)
|
|
)
|
|
|
|
self.play(randy.change, "pondering")
|
|
self.play(morty.change, "pondering")
|
|
self.wait(3)
|
|
randy.clear_updaters()
|
|
morty.clear_updaters()
|
|
self.play(
|
|
ShowCreationThenFadeOut(rect),
|
|
)
|
|
self.wait()
|
|
|
|
def add_graph(self):
|
|
axes = self.axes = ThetaVsTAxes(**self.theta_vs_t_axes_config)
|
|
axes.y_axis.label.next_to(axes.y_axis, UP, buff=0)
|
|
axes.to_corner(UL)
|
|
|
|
self.play(
|
|
Restore(
|
|
self.camera_frame,
|
|
rate_func=squish_rate_func(smooth, 0, 0.9),
|
|
),
|
|
DrawBorderThenFill(
|
|
axes,
|
|
rate_func=squish_rate_func(smooth, 0.5, 1),
|
|
lag_ratio=0.9,
|
|
),
|
|
Transform(
|
|
self.pendulum.theta_label.copy().clear_updaters(),
|
|
axes.y_axis.label.copy(),
|
|
remover=True,
|
|
rate_func=squish_rate_func(smooth, 0, 0.8),
|
|
),
|
|
run_time=3,
|
|
)
|
|
|
|
self.wait(1.5)
|
|
self.graph = axes.get_live_drawn_graph(self.pendulum)
|
|
self.add(self.graph)
|
|
|
|
def label_function(self):
|
|
hm_word = TextMobject("Simple harmonic motion")
|
|
hm_word.scale(1.25)
|
|
hm_word.to_edge(UP)
|
|
|
|
formula = TexMobject(
|
|
"=\\theta_0 \\cos(\\sqrt{g / L} t)"
|
|
)
|
|
formula.next_to(
|
|
self.axes.y_axis_label, RIGHT, SMALL_BUFF
|
|
)
|
|
formula.set_stroke(width=0, background=True)
|
|
|
|
self.play(FadeInFrom(hm_word, DOWN))
|
|
self.wait()
|
|
self.play(
|
|
Write(formula),
|
|
hm_word.to_corner, UR
|
|
)
|
|
self.wait(4)
|
|
|
|
def show_graph_period(self):
|
|
pendulum = self.pendulum
|
|
axes = self.axes
|
|
|
|
period = self.period = TAU * np.sqrt(
|
|
pendulum.length / pendulum.gravity
|
|
)
|
|
amplitude = pendulum.initial_theta
|
|
|
|
line = Line(
|
|
axes.coords_to_point(0, amplitude),
|
|
axes.coords_to_point(period, amplitude),
|
|
)
|
|
line.shift(SMALL_BUFF * RIGHT)
|
|
brace = Brace(line, UP, buff=SMALL_BUFF)
|
|
brace.add_to_back(brace.copy().set_style(BLACK, 10))
|
|
formula = get_period_formula()
|
|
formula.next_to(brace, UP, SMALL_BUFF)
|
|
|
|
self.period_formula = formula
|
|
self.period_brace = brace
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeInFromDown(formula),
|
|
)
|
|
self.wait(2)
|
|
|
|
def show_length_and_gravity(self):
|
|
formula = self.period_formula
|
|
L = formula.get_part_by_tex("L")
|
|
g = formula.get_part_by_tex("g")
|
|
|
|
rod = self.pendulum.rod
|
|
new_rod = rod.copy()
|
|
new_rod.set_stroke(BLUE, 7)
|
|
new_rod.add_updater(lambda r: r.put_start_and_end_on(
|
|
*rod.get_start_and_end()
|
|
))
|
|
|
|
g_vect = GravityVector(
|
|
self.pendulum,
|
|
length_multiple=0.5 / 9.8,
|
|
)
|
|
down_vectors = self.get_down_vectors()
|
|
down_vectors.set_color(YELLOW)
|
|
down_vectors.set_opacity(0.5)
|
|
|
|
self.play(
|
|
ShowCreationThenDestructionAround(L),
|
|
ShowCreation(new_rod),
|
|
)
|
|
self.play(FadeOut(new_rod))
|
|
|
|
self.play(
|
|
ShowCreationThenDestructionAround(g),
|
|
GrowArrow(g_vect),
|
|
)
|
|
self.play(self.get_down_vectors_animation(down_vectors))
|
|
self.wait(6)
|
|
|
|
self.gravity_vector = g_vect
|
|
|
|
def tweak_length_and_gravity(self):
|
|
pendulum = self.pendulum
|
|
axes = self.axes
|
|
graph = self.graph
|
|
brace = self.period_brace
|
|
formula = self.period_formula
|
|
g_vect = self.gravity_vector
|
|
randy, morty = self.pi_creatures
|
|
|
|
graph.clear_updaters()
|
|
period2 = self.period * np.sqrt(2)
|
|
period3 = self.period / np.sqrt(2)
|
|
amplitude = pendulum.initial_theta
|
|
graph2, graph3 = [
|
|
axes.get_graph(
|
|
lambda t: amplitude * np.cos(TAU * t / p),
|
|
color=RED,
|
|
)
|
|
for p in (period2, period3)
|
|
]
|
|
formula.add_updater(lambda m: m.next_to(
|
|
brace, UP, SMALL_BUFF
|
|
))
|
|
|
|
new_pendulum_config = dict(self.pendulum_config)
|
|
new_pendulum_config["length"] *= 2
|
|
new_pendulum_config["top_point"] += 3.5 * UP
|
|
# new_pendulum_config["initial_theta"] = pendulum.get_theta()
|
|
new_pendulum = Pendulum(**new_pendulum_config)
|
|
|
|
down_vectors = self.get_down_vectors()
|
|
|
|
self.play(randy.change, "happy")
|
|
self.play(
|
|
ReplacementTransform(pendulum, new_pendulum),
|
|
morty.change, "horrified",
|
|
morty.shift, 3 * RIGHT,
|
|
morty.labels.shift, 3 * RIGHT,
|
|
)
|
|
self.remove(morty, morty.labels)
|
|
g_vect.attach_to_pendulum(new_pendulum)
|
|
new_pendulum.start_swinging()
|
|
self.play(
|
|
ReplacementTransform(graph, graph2),
|
|
brace.stretch, np.sqrt(2), 0, {"about_edge": LEFT},
|
|
)
|
|
self.add(g_vect)
|
|
self.wait(3)
|
|
|
|
new_pendulum.gravity *= 4
|
|
g_vect.scale(2)
|
|
self.play(
|
|
FadeOut(graph2),
|
|
self.get_down_vectors_animation(down_vectors)
|
|
)
|
|
self.play(
|
|
FadeIn(graph3),
|
|
brace.stretch, 0.5, 0, {"about_edge": LEFT},
|
|
)
|
|
self.wait(6)
|
|
|
|
#
|
|
def get_down_vectors(self):
|
|
down_vectors = VGroup(*[
|
|
Vector(0.5 * DOWN)
|
|
for x in range(10 * 150)
|
|
])
|
|
down_vectors.arrange_in_grid(10, 150, buff=MED_SMALL_BUFF)
|
|
down_vectors.set_color_by_gradient(BLUE, RED)
|
|
# for vect in down_vectors:
|
|
# vect.shift(0.1 * np.random.random(3))
|
|
down_vectors.to_edge(RIGHT)
|
|
return down_vectors
|
|
|
|
def get_down_vectors_animation(self, down_vectors):
|
|
return LaggedStart(
|
|
*[
|
|
GrowArrow(v, rate_func=there_and_back)
|
|
for v in down_vectors
|
|
],
|
|
lag_ratio=0.0005,
|
|
run_time=2,
|
|
remover=True
|
|
)
|
|
|
|
|
|
class MultiplePendulumsOverlayed(Scene):
|
|
CONFIG = {
|
|
"initial_thetas": [
|
|
150 * DEGREES,
|
|
90 * DEGREES,
|
|
60 * DEGREES,
|
|
30 * DEGREES,
|
|
10 * DEGREES,
|
|
],
|
|
"weight_colors": [
|
|
PINK, RED, GREEN, BLUE, GREY,
|
|
],
|
|
"pendulum_config": {
|
|
"top_point": ORIGIN,
|
|
"length": 3,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
pendulums = VGroup(*[
|
|
Pendulum(
|
|
initial_theta=theta,
|
|
weight_style={
|
|
"fill_color": wc,
|
|
"fill_opacity": 0.5,
|
|
},
|
|
**self.pendulum_config,
|
|
)
|
|
for theta, wc in zip(
|
|
self.initial_thetas,
|
|
self.weight_colors,
|
|
)
|
|
])
|
|
for pendulum in pendulums:
|
|
pendulum.start_swinging()
|
|
pendulum.remove(pendulum.theta_label)
|
|
|
|
randy = Randolph(color=BLUE_C)
|
|
randy.to_corner(DL)
|
|
randy.add_updater(lambda r: r.look_at(pendulums[0].weight))
|
|
|
|
axes = ThetaVsTAxes(
|
|
x_max=20,
|
|
y_axis_config={
|
|
"unit_size": 0.5,
|
|
"tip_length": 0.3,
|
|
},
|
|
)
|
|
axes.to_corner(UL)
|
|
graphs = VGroup(*[
|
|
axes.get_live_drawn_graph(
|
|
pendulum,
|
|
stroke_color=pendulum.weight.get_color(),
|
|
stroke_width=1,
|
|
)
|
|
for pendulum in pendulums
|
|
])
|
|
|
|
self.add(pendulums)
|
|
self.add(axes, *graphs)
|
|
self.play(randy.change, "sassy")
|
|
self.wait(2)
|
|
self.play(Blink(randy))
|
|
self.wait(5)
|
|
self.play(randy.change, "angry")
|
|
self.play(Blink(randy))
|
|
self.wait(10)
|
|
|
|
|
|
class LowAnglePendulum(Scene):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"initial_theta": 20 * DEGREES,
|
|
"length": 2.0,
|
|
"damping": 0,
|
|
"top_point": ORIGIN,
|
|
},
|
|
"axes_config": {
|
|
"y_axis_config": {"unit_size": 0.75},
|
|
"x_axis_config": {
|
|
"unit_size": 0.5,
|
|
"numbers_to_show": range(2, 25, 2),
|
|
"number_scale_val": 0.5,
|
|
},
|
|
"x_max": 25,
|
|
"number_line_config": {
|
|
"tip_length": 0.3,
|
|
"stroke_width": 2,
|
|
}
|
|
},
|
|
"axes_corner": UL,
|
|
}
|
|
|
|
def construct(self):
|
|
pendulum = Pendulum(**self.pendulum_config)
|
|
axes = ThetaVsTAxes(**self.axes_config)
|
|
axes.center()
|
|
axes.to_corner(self.axes_corner, buff=LARGE_BUFF)
|
|
graph = axes.get_live_drawn_graph(pendulum)
|
|
|
|
L = pendulum.length
|
|
g = pendulum.gravity
|
|
theta0 = pendulum.initial_theta
|
|
prediction = axes.get_graph(
|
|
lambda t: theta0 * np.cos(t * np.sqrt(g / L))
|
|
)
|
|
dashed_prediction = DashedVMobject(prediction, num_dashes=300)
|
|
dashed_prediction.set_stroke(WHITE, 1)
|
|
prediction_formula = TexMobject(
|
|
"\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)"
|
|
)
|
|
prediction_formula.scale(0.75)
|
|
prediction_formula.next_to(
|
|
dashed_prediction, UP, SMALL_BUFF,
|
|
)
|
|
|
|
theta0 = prediction_formula.get_part_by_tex("\\theta_0")
|
|
theta0_brace = Brace(theta0, UP, buff=SMALL_BUFF)
|
|
theta0_brace.stretch(0.5, 1, about_edge=DOWN)
|
|
theta0_label = Integer(
|
|
pendulum.initial_theta * 180 / PI,
|
|
unit="^\\circ"
|
|
)
|
|
theta0_label.scale(0.75)
|
|
theta0_label.next_to(theta0_brace, UP, SMALL_BUFF)
|
|
|
|
group = VGroup(theta0_brace, theta0_label, prediction_formula)
|
|
group.shift_onto_screen(buff=MED_SMALL_BUFF)
|
|
|
|
self.add(axes, dashed_prediction, pendulum)
|
|
self.play(
|
|
ShowCreation(dashed_prediction, run_time=2),
|
|
FadeInFromDown(prediction_formula),
|
|
FadeInFromDown(theta0_brace),
|
|
FadeInFromDown(theta0_label),
|
|
)
|
|
self.play(
|
|
ShowCreationThenFadeAround(theta0_label),
|
|
ShowCreationThenFadeAround(pendulum.theta_label),
|
|
)
|
|
self.wait()
|
|
|
|
pendulum.start_swinging()
|
|
self.add(graph)
|
|
self.wait(30)
|
|
|
|
|
|
class ApproxWordsLowAnglePendulum(Scene):
|
|
def construct(self):
|
|
period = TexMobject(
|
|
"\\text{Period}", "\\approx",
|
|
"2\\pi \\sqrt{\\,{L} / {g}}",
|
|
**Lg_formula_config
|
|
)
|
|
checkmark = TexMobject("\\checkmark")
|
|
checkmark.set_color(GREEN)
|
|
checkmark.scale(2)
|
|
checkmark.next_to(period, RIGHT, MED_LARGE_BUFF)
|
|
|
|
self.add(period, checkmark)
|
|
|
|
|
|
class MediumAnglePendulum(LowAnglePendulum):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"initial_theta": 50 * DEGREES,
|
|
"n_steps_per_frame": 1000,
|
|
},
|
|
"axes_config": {
|
|
"y_axis_config": {"unit_size": 0.75},
|
|
"y_max": PI / 2,
|
|
"y_min": -PI / 2,
|
|
"number_line_config": {
|
|
"tip_length": 0.3,
|
|
"stroke_width": 2,
|
|
}
|
|
},
|
|
"pendulum_shift_vect": 1 * RIGHT,
|
|
}
|
|
|
|
|
|
class MediumHighAnglePendulum(MediumAnglePendulum):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"initial_theta": 90 * DEGREES,
|
|
"n_steps_per_frame": 1000,
|
|
},
|
|
}
|
|
|
|
|
|
class HighAnglePendulum(LowAnglePendulum):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"initial_theta": 175 * DEGREES,
|
|
"n_steps_per_frame": 1000,
|
|
"top_point": 1.5 * DOWN,
|
|
"length": 2,
|
|
},
|
|
"axes_config": {
|
|
"y_axis_config": {"unit_size": 0.5},
|
|
"y_max": PI,
|
|
"y_min": -PI,
|
|
"number_line_config": {
|
|
"tip_length": 0.3,
|
|
"stroke_width": 2,
|
|
}
|
|
},
|
|
"pendulum_shift_vect": 1 * RIGHT,
|
|
}
|
|
|
|
|
|
class VeryLowAnglePendulum(LowAnglePendulum):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"initial_theta": 10 * DEGREES,
|
|
"n_steps_per_frame": 1000,
|
|
"top_point": ORIGIN,
|
|
"length": 3,
|
|
},
|
|
"axes_config": {
|
|
"y_axis_config": {"unit_size": 2},
|
|
"y_max": PI / 4,
|
|
"y_min": -PI / 4,
|
|
"number_line_config": {
|
|
"tip_length": 0.3,
|
|
"stroke_width": 2,
|
|
}
|
|
},
|
|
"pendulum_shift_vect": 1 * RIGHT,
|
|
}
|
|
|
|
|
|
class WherePendulumLeads(PiCreatureScene):
|
|
def construct(self):
|
|
pendulum = Pendulum(
|
|
top_point=UP,
|
|
length=3,
|
|
gravity=20,
|
|
)
|
|
pendulum.start_swinging()
|
|
|
|
l_title = TextMobject("Linearization")
|
|
l_title.scale(1.5)
|
|
l_title.to_corner(UL)
|
|
c_title = TextMobject("Chaos")
|
|
c_title.scale(1.5)
|
|
c_title.move_to(l_title)
|
|
c_title.move_to(
|
|
c_title.get_center() * np.array([-1, 1, 1])
|
|
)
|
|
|
|
get_theta = pendulum.get_theta
|
|
spring = always_redraw(
|
|
lambda: ParametricFunction(
|
|
lambda t: np.array([
|
|
np.cos(TAU * t) + (1.4 + get_theta()) * t,
|
|
np.sin(TAU * t) - 0.5,
|
|
0,
|
|
]),
|
|
t_min=-0.5,
|
|
t_max=7,
|
|
color=GREY,
|
|
sheen_factor=1,
|
|
sheen_direction=UL,
|
|
).scale(0.2).to_edge(LEFT, buff=0)
|
|
)
|
|
spring_rect = SurroundingRectangle(
|
|
spring, buff=MED_LARGE_BUFF,
|
|
stroke_width=0,
|
|
fill_color=BLACK,
|
|
fill_opacity=0,
|
|
)
|
|
|
|
weight = Dot(radius=0.25)
|
|
weight.add_updater(lambda m: m.move_to(
|
|
spring.points[-1]
|
|
))
|
|
weight.set_color(BLUE)
|
|
weight.set_sheen(1, UL)
|
|
spring_system = VGroup(spring, weight)
|
|
|
|
linear_formula = TexMobject(
|
|
"\\frac{d \\vec{\\textbf{x}}}{dt}="
|
|
"A\\vec{\\textbf{x}}"
|
|
)
|
|
linear_formula.next_to(spring, UP, LARGE_BUFF)
|
|
linear_formula.match_x(l_title)
|
|
|
|
randy = self.pi_creature
|
|
randy.set_height(2)
|
|
randy.center()
|
|
randy.to_edge(DOWN)
|
|
randy.shift(3 * LEFT)
|
|
q_marks = TexMobject("???")
|
|
q_marks.next_to(randy, UP)
|
|
|
|
self.add(pendulum, randy)
|
|
self.play(
|
|
randy.change, "pondering", pendulum,
|
|
FadeInFromDown(q_marks, lag_ratio=0.3)
|
|
)
|
|
self.play(randy.look_at, pendulum)
|
|
self.wait(5)
|
|
self.play(
|
|
Animation(VectorizedPoint(pendulum.get_top())),
|
|
FadeOutAndShift(q_marks, UP, lag_ratio=0.3),
|
|
)
|
|
self.add(spring_system)
|
|
self.play(
|
|
FadeOut(spring_rect),
|
|
FadeInFrom(linear_formula, UP),
|
|
FadeInFromDown(l_title),
|
|
)
|
|
self.play(FadeInFromDown(c_title))
|
|
self.wait(8)
|
|
|
|
|
|
class LongDoublePendulum(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class AnalyzePendulumForce(MovingCameraScene):
|
|
CONFIG = {
|
|
"pendulum_config": {
|
|
"length": 5,
|
|
"top_point": 3.5 * UP,
|
|
"initial_theta": 60 * DEGREES,
|
|
"set_theta_label_height_cap": True,
|
|
},
|
|
"g_vect_config": {
|
|
"length_multiple": 0.25,
|
|
},
|
|
"tan_line_color": BLUE,
|
|
"perp_line_color": PINK,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_pendulum()
|
|
self.show_arc_length()
|
|
self.add_g_vect()
|
|
self.show_constraint()
|
|
self.break_g_vect_into_components()
|
|
self.show_angle_geometry()
|
|
self.show_gsin_formula()
|
|
self.show_sign()
|
|
self.show_acceleration_formula()
|
|
# self.ask_about_what_to_do()
|
|
|
|
# self.emphasize_theta()
|
|
# self.show_angular_velocity()
|
|
# self.show_angular_acceleration()
|
|
# self.circle_g_sin_formula()
|
|
|
|
def add_pendulum(self):
|
|
pendulum = Pendulum(**self.pendulum_config)
|
|
theta_tracker = ValueTracker(pendulum.get_theta())
|
|
pendulum.add_updater(lambda p: p.set_theta(
|
|
theta_tracker.get_value()
|
|
))
|
|
|
|
self.add(pendulum)
|
|
self.pendulum = pendulum
|
|
self.theta_tracker = theta_tracker
|
|
|
|
def show_arc_length(self):
|
|
pendulum = self.pendulum
|
|
angle = pendulum.get_theta()
|
|
height = pendulum.length
|
|
top = pendulum.get_fixed_point()
|
|
|
|
line = Line(UP, DOWN)
|
|
line.set_height(height)
|
|
line.move_to(top, UP)
|
|
arc = always_redraw(lambda: Arc(
|
|
start_angle=-90 * DEGREES,
|
|
angle=pendulum.get_theta(),
|
|
arc_center=pendulum.get_fixed_point(),
|
|
radius=pendulum.length,
|
|
stroke_color=GREEN,
|
|
))
|
|
|
|
brace = Brace(Line(ORIGIN, 5 * UP), RIGHT)
|
|
brace.point = VectorizedPoint(brace.get_right())
|
|
brace.add(brace.point)
|
|
brace.set_height(angle)
|
|
brace.move_to(ORIGIN, DL)
|
|
brace.apply_complex_function(np.exp)
|
|
brace.scale(height)
|
|
brace.rotate(-90 * DEGREES)
|
|
brace.move_to(arc)
|
|
brace.shift(MED_SMALL_BUFF * normalize(
|
|
arc.point_from_proportion(0.5) - top
|
|
))
|
|
x_sym = TexMobject("x")
|
|
x_sym.set_color(GREEN)
|
|
x_sym.next_to(brace.point, DR, buff=SMALL_BUFF)
|
|
|
|
rhs = TexMobject("=", "L", "\\theta")
|
|
rhs.set_color_by_tex("\\theta", BLUE)
|
|
rhs.next_to(x_sym, RIGHT)
|
|
rhs.shift(0.7 * SMALL_BUFF * UP)
|
|
line_L = TexMobject("L")
|
|
line_L.next_to(
|
|
pendulum.rod.get_center(), UR, SMALL_BUFF,
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(arc),
|
|
Rotate(line, angle, about_point=top),
|
|
UpdateFromAlphaFunc(
|
|
line, lambda m, a: m.set_stroke(
|
|
width=2 * there_and_back(a)
|
|
)
|
|
),
|
|
GrowFromPoint(
|
|
brace, line.get_bottom(),
|
|
path_arc=angle
|
|
),
|
|
)
|
|
self.play(FadeInFrom(x_sym, UP))
|
|
self.wait()
|
|
|
|
# Show equation
|
|
line.set_stroke(BLUE, 5)
|
|
self.play(
|
|
ShowCreationThenFadeOut(line),
|
|
FadeInFromDown(line_L)
|
|
)
|
|
self.play(
|
|
TransformFromCopy(
|
|
line_L, rhs.get_part_by_tex("L")
|
|
),
|
|
Write(rhs.get_part_by_tex("="))
|
|
)
|
|
self.play(
|
|
TransformFromCopy(
|
|
pendulum.theta_label,
|
|
rhs.get_parts_by_tex("\\theta"),
|
|
)
|
|
)
|
|
self.add(rhs)
|
|
|
|
x_eq = VGroup(x_sym, rhs)
|
|
|
|
self.play(
|
|
FadeOut(brace),
|
|
x_eq.rotate, angle / 2,
|
|
x_eq.next_to, arc.point_from_proportion(0.5),
|
|
UL, {"buff": -MED_SMALL_BUFF}
|
|
)
|
|
|
|
self.x_eq = x_eq
|
|
self.arc = arc
|
|
self.line_L = line_L
|
|
|
|
def add_g_vect(self):
|
|
pendulum = self.pendulum
|
|
|
|
g_vect = self.g_vect = GravityVector(
|
|
pendulum, **self.g_vect_config,
|
|
)
|
|
g_word = self.g_word = TextMobject("Gravity")
|
|
g_word.rotate(-90 * DEGREES)
|
|
g_word.scale(0.75)
|
|
g_word.add_updater(lambda m: m.next_to(
|
|
g_vect, RIGHT, buff=-SMALL_BUFF,
|
|
))
|
|
|
|
self.play(
|
|
GrowArrow(g_vect),
|
|
FadeInFrom(g_word, UP, lag_ratio=0.1),
|
|
)
|
|
self.wait()
|
|
|
|
def show_constraint(self):
|
|
pendulum = self.pendulum
|
|
|
|
arcs = VGroup()
|
|
for u in [-1, 2, -1]:
|
|
d_theta = 40 * DEGREES * u
|
|
arc = Arc(
|
|
start_angle=pendulum.get_theta() - 90 * DEGREES,
|
|
angle=d_theta,
|
|
radius=pendulum.length,
|
|
arc_center=pendulum.get_fixed_point(),
|
|
stroke_width=2,
|
|
stroke_color=YELLOW,
|
|
stroke_opacity=0.5,
|
|
)
|
|
self.play(
|
|
self.theta_tracker.increment_value, d_theta,
|
|
ShowCreation(arc)
|
|
)
|
|
arcs.add(arc)
|
|
self.play(FadeOut(arcs))
|
|
|
|
def break_g_vect_into_components(self):
|
|
g_vect = self.g_vect
|
|
g_vect.component_lines = always_redraw(
|
|
g_vect.create_component_lines
|
|
)
|
|
tan_line, perp_line = g_vect.component_lines
|
|
g_vect.tangent = always_redraw(lambda: Arrow(
|
|
tan_line.get_start(),
|
|
tan_line.get_end(),
|
|
buff=0,
|
|
color=self.tan_line_color,
|
|
))
|
|
g_vect.perp = always_redraw(lambda: Arrow(
|
|
perp_line.get_start(),
|
|
perp_line.get_end(),
|
|
buff=0,
|
|
color=self.perp_line_color,
|
|
))
|
|
|
|
self.play(ShowCreation(g_vect.component_lines))
|
|
self.play(GrowArrow(g_vect.tangent))
|
|
self.wait()
|
|
self.play(GrowArrow(g_vect.perp))
|
|
self.wait()
|
|
|
|
def show_angle_geometry(self):
|
|
g_vect = self.g_vect
|
|
|
|
arc = Arc(
|
|
start_angle=90 * DEGREES,
|
|
angle=self.pendulum.get_theta(),
|
|
radius=0.5,
|
|
arc_center=g_vect.get_end(),
|
|
)
|
|
q_mark = TexMobject("?")
|
|
q_mark.next_to(arc.get_center(), UL, SMALL_BUFF)
|
|
theta_label = TexMobject("\\theta")
|
|
theta_label.move_to(q_mark)
|
|
|
|
self.add(g_vect)
|
|
self.play(
|
|
ShowCreation(arc),
|
|
Write(q_mark)
|
|
)
|
|
self.play(ShowCreationThenFadeAround(q_mark))
|
|
self.wait()
|
|
self.play(ShowCreationThenFadeAround(
|
|
self.pendulum.theta_label
|
|
))
|
|
self.play(
|
|
TransformFromCopy(
|
|
self.pendulum.theta_label,
|
|
theta_label,
|
|
),
|
|
FadeOut(q_mark)
|
|
)
|
|
self.wait()
|
|
self.play(WiggleOutThenIn(g_vect.tangent))
|
|
self.play(WiggleOutThenIn(
|
|
Line(
|
|
*g_vect.get_start_and_end(),
|
|
buff=0,
|
|
).add_tip().match_style(g_vect),
|
|
remover=True
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(arc),
|
|
FadeOut(theta_label),
|
|
)
|
|
|
|
def show_gsin_formula(self):
|
|
g_vect = self.g_vect
|
|
g_word = self.g_word
|
|
g_word.clear_updaters()
|
|
|
|
g_term = self.g_term = TexMobject("-g")
|
|
g_term.add_updater(lambda m: m.next_to(
|
|
g_vect,
|
|
RIGHT if self.pendulum.get_theta() >= 0 else LEFT,
|
|
SMALL_BUFF
|
|
))
|
|
|
|
def create_vect_label(vect, tex, direction):
|
|
label = TexMobject(tex)
|
|
label.set_stroke(width=0, background=True)
|
|
label.add_background_rectangle()
|
|
label.scale(0.7)
|
|
max_width = 0.9 * vect.get_length()
|
|
if label.get_width() > max_width:
|
|
label.set_width(max_width)
|
|
angle = vect.get_angle()
|
|
angle = (angle + PI / 2) % PI - PI / 2
|
|
label.next_to(ORIGIN, direction, SMALL_BUFF)
|
|
label.rotate(angle, about_point=ORIGIN)
|
|
label.shift(vect.get_center())
|
|
return label
|
|
|
|
g_sin_label = always_redraw(lambda: create_vect_label(
|
|
g_vect.tangent, "-g\\sin(\\theta)", UP,
|
|
))
|
|
g_cos_label = always_redraw(lambda: create_vect_label(
|
|
g_vect.perp, "-g\\cos(\\theta)", DOWN,
|
|
))
|
|
|
|
self.play(
|
|
ReplacementTransform(g_word[0][0], g_term[0][1]),
|
|
FadeOut(g_word[0][1:]),
|
|
Write(g_term[0][0]),
|
|
)
|
|
self.add(g_term)
|
|
self.wait()
|
|
for label in g_sin_label, g_cos_label:
|
|
self.play(
|
|
GrowFromPoint(label[0], g_term.get_center()),
|
|
TransformFromCopy(g_term, label[1][:2]),
|
|
GrowFromPoint(label[1][2:], g_term.get_center()),
|
|
remover=True
|
|
)
|
|
self.add(label)
|
|
self.wait()
|
|
|
|
self.g_sin_label = g_sin_label
|
|
self.g_cos_label = g_cos_label
|
|
|
|
def show_sign(self):
|
|
get_theta = self.pendulum.get_theta
|
|
theta_decimal = DecimalNumber(include_sign=True)
|
|
theta_decimal.add_updater(lambda d: d.set_value(
|
|
get_theta()
|
|
))
|
|
theta_decimal.add_updater(lambda m: m.next_to(
|
|
self.pendulum.theta_label, DOWN
|
|
))
|
|
theta_decimal.add_updater(lambda m: m.set_color(
|
|
GREEN if get_theta() > 0 else RED
|
|
))
|
|
|
|
self.play(
|
|
FadeInFrom(theta_decimal, UP),
|
|
FadeOut(self.x_eq),
|
|
FadeOut(self.line_L),
|
|
)
|
|
self.set_theta(-60 * DEGREES, run_time=4)
|
|
self.set_theta(60 * DEGREES, run_time=4)
|
|
self.play(
|
|
FadeOut(theta_decimal),
|
|
FadeIn(self.x_eq),
|
|
)
|
|
|
|
def show_acceleration_formula(self):
|
|
x_eq = self.x_eq
|
|
g_sin_theta = self.g_sin_label
|
|
|
|
equation = TexMobject(
|
|
"a", "=",
|
|
"\\ddot", "x",
|
|
"=",
|
|
"-", "g", "\\sin\\big(", "\\theta", "\\big)",
|
|
)
|
|
equation.to_edge(LEFT)
|
|
|
|
second_deriv = equation[2:4]
|
|
x_part = equation.get_part_by_tex("x")
|
|
x_part.set_color(GREEN)
|
|
a_eq = equation[:2]
|
|
eq2 = equation.get_parts_by_tex("=")[1]
|
|
rhs = equation[5:]
|
|
|
|
second_deriv_L_form = TexMobject(
|
|
"L", "\\ddot", "\\theta"
|
|
)
|
|
second_deriv_L_form.move_to(second_deriv, DOWN)
|
|
eq3 = TexMobject("=")
|
|
eq3.rotate(90 * DEGREES)
|
|
eq3.next_to(second_deriv_L_form, UP)
|
|
|
|
g_L_frac = TexMobject(
|
|
"-", "{g", "\\over", "L}"
|
|
)
|
|
g_L_frac.move_to(rhs[:2], LEFT)
|
|
g_L_frac.shift(SMALL_BUFF * UP / 2)
|
|
|
|
mu_term = TexMobject(
|
|
"-\\mu", "\\dot", "\\theta",
|
|
)
|
|
mu_term.next_to(g_L_frac, LEFT)
|
|
mu_term.shift(SMALL_BUFF * UP / 2)
|
|
|
|
mu_brace = Brace(mu_term, UP)
|
|
mu_word = mu_brace.get_text("Air resistance")
|
|
|
|
for mob in equation, second_deriv_L_form, mu_term:
|
|
mob.set_color_by_tex("\\theta", BLUE)
|
|
|
|
self.play(
|
|
TransformFromCopy(x_eq[0], x_part),
|
|
Write(equation[:3]),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(eq2),
|
|
TransformFromCopy(g_sin_theta, rhs)
|
|
)
|
|
self.wait()
|
|
#
|
|
self.show_acceleration_at_different_angles()
|
|
#
|
|
self.play(
|
|
FadeInFromDown(second_deriv_L_form),
|
|
Write(eq3),
|
|
second_deriv.next_to, eq3, UP,
|
|
a_eq.shift, SMALL_BUFF * LEFT,
|
|
eq2.shift, SMALL_BUFF * RIGHT,
|
|
rhs.shift, SMALL_BUFF * RIGHT,
|
|
)
|
|
self.wait()
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(a_eq),
|
|
FadeOut(second_deriv),
|
|
FadeOut(eq3),
|
|
ReplacementTransform(
|
|
second_deriv_L_form.get_part_by_tex("L"),
|
|
g_L_frac.get_part_by_tex("L"),
|
|
),
|
|
ReplacementTransform(
|
|
equation.get_part_by_tex("-"),
|
|
g_L_frac.get_part_by_tex("-"),
|
|
),
|
|
ReplacementTransform(
|
|
equation.get_part_by_tex("g"),
|
|
g_L_frac.get_part_by_tex("g"),
|
|
),
|
|
Write(g_L_frac.get_part_by_tex("\\over")),
|
|
rhs[2:].next_to, g_L_frac, RIGHT, {"buff": SMALL_BUFF},
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(mu_term),
|
|
VGroup(eq2, second_deriv_L_form[1:]).next_to,
|
|
mu_term, LEFT,
|
|
)
|
|
self.play(
|
|
GrowFromCenter(mu_brace),
|
|
FadeInFromDown(mu_word),
|
|
)
|
|
|
|
def show_acceleration_at_different_angles(self):
|
|
to_fade = VGroup(
|
|
self.g_cos_label,
|
|
self.g_vect.perp,
|
|
)
|
|
new_comp_line_sytle = {
|
|
"stroke_width": 0.5,
|
|
"stroke_opacity": 0.25,
|
|
}
|
|
|
|
self.play(
|
|
FadeOut(self.x_eq),
|
|
to_fade.set_opacity, 0.25,
|
|
self.g_vect.component_lines.set_style,
|
|
new_comp_line_sytle
|
|
)
|
|
self.g_vect.component_lines.add_updater(
|
|
lambda m: m.set_style(**new_comp_line_sytle)
|
|
)
|
|
for mob in to_fade:
|
|
mob.add_updater(lambda m: m.set_opacity(0.25))
|
|
|
|
self.set_theta(0)
|
|
self.wait(2)
|
|
self.set_theta(89.9 * DEGREES, run_time=3)
|
|
self.wait(2)
|
|
self.set_theta(
|
|
60 * DEGREES,
|
|
FadeIn(self.x_eq),
|
|
run_time=2,
|
|
)
|
|
self.wait()
|
|
|
|
def ask_about_what_to_do(self):
|
|
g_vect = self.g_vect
|
|
g_sin_label = self.g_sin_label
|
|
angle = g_vect.tangent.get_angle()
|
|
angle = (angle - PI) % TAU
|
|
|
|
randy = You()
|
|
randy.to_corner(DL)
|
|
bubble = randy.get_bubble(
|
|
height=2,
|
|
width=3.5,
|
|
)
|
|
g_sin_copy = g_sin_label.copy()
|
|
g_sin_copy.remove(g_sin_copy[0])
|
|
g_sin_copy.generate_target()
|
|
g_sin_copy.target.scale(1 / 0.75)
|
|
g_sin_copy.target.rotate(-angle)
|
|
a_eq = TexMobject("a=")
|
|
thought_term = VGroup(a_eq, g_sin_copy.target)
|
|
thought_term.arrange(RIGHT, buff=SMALL_BUFF)
|
|
thought_term.move_to(bubble.get_bubble_center())
|
|
|
|
rect = SurroundingRectangle(g_sin_copy.target)
|
|
rect.rotate(angle)
|
|
rect.move_to(g_sin_label)
|
|
|
|
randy.save_state()
|
|
randy.fade(1)
|
|
self.play(randy.restore, randy.change, "pondering")
|
|
self.play(ShowCreationThenFadeOut(rect))
|
|
self.play(
|
|
ShowCreation(bubble),
|
|
Write(a_eq),
|
|
MoveToTarget(g_sin_copy),
|
|
randy.look_at, bubble,
|
|
)
|
|
thought_term.remove(g_sin_copy.target)
|
|
thought_term.add(g_sin_copy)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreationThenDestruction(
|
|
thought_term.copy().set_style(
|
|
stroke_color=YELLOW,
|
|
stroke_width=2,
|
|
fill_opacity=0,
|
|
),
|
|
run_time=2,
|
|
lag_ratio=0.2,
|
|
),
|
|
randy.change, "confused", thought_term,
|
|
)
|
|
self.play(Blink(randy))
|
|
self.play(
|
|
FadeOut(randy),
|
|
FadeOut(bubble),
|
|
thought_term.next_to, self.pendulum, DOWN, LARGE_BUFF
|
|
)
|
|
|
|
self.accleration_equation = thought_term
|
|
|
|
def emphasize_theta(self):
|
|
pendulum = self.pendulum
|
|
|
|
self.play(FocusOn(pendulum.theta_label))
|
|
self.play(Indicate(pendulum.theta_label))
|
|
|
|
pendulum_copy = pendulum.deepcopy()
|
|
pendulum_copy.clear_updaters()
|
|
pendulum_copy.fade(1)
|
|
pendulum_copy.start_swinging()
|
|
|
|
def new_updater(p):
|
|
p.set_theta(pendulum_copy.get_theta())
|
|
pendulum.add_updater(new_updater)
|
|
|
|
self.add(pendulum_copy)
|
|
self.wait(5)
|
|
pendulum_copy.end_swinging()
|
|
self.remove(pendulum_copy)
|
|
pendulum.remove_updater(new_updater)
|
|
self.update_mobjects(0)
|
|
|
|
def show_angular_velocity(self):
|
|
pass
|
|
|
|
def show_angular_acceleration(self):
|
|
pass
|
|
|
|
def circle_g_sin_formula(self):
|
|
self.play(
|
|
ShowCreationThenFadeAround(
|
|
self.accleration_equation
|
|
)
|
|
)
|
|
|
|
#
|
|
def set_theta(self, value, *added_anims, **kwargs):
|
|
kwargs["run_time"] = kwargs.get("run_time", 2)
|
|
self.play(
|
|
self.theta_tracker.set_value, value,
|
|
*added_anims,
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class BuildUpEquation(Scene):
|
|
CONFIG = {
|
|
"tex_config": {
|
|
"tex_to_color_map": {
|
|
"{a}": YELLOW,
|
|
"{v}": RED,
|
|
"{x}": GREEN,
|
|
"\\theta": BLUE,
|
|
"{L}": WHITE,
|
|
}
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
# self.add_center_line()
|
|
self.show_derivatives()
|
|
self.show_theta_double_dot_equation()
|
|
self.talk_about_sine_component()
|
|
self.add_air_resistance()
|
|
|
|
def add_center_line(self):
|
|
line = Line(UP, DOWN)
|
|
line.set_height(FRAME_HEIGHT)
|
|
line.set_stroke(WHITE, 1)
|
|
self.add(line)
|
|
|
|
def show_derivatives(self):
|
|
a_eq = TexMobject(
|
|
"{a}", "=", "{d{v} \\over dt}",
|
|
**self.tex_config,
|
|
)
|
|
v_eq = TexMobject(
|
|
"{v}", "=", "{d{x} \\over dt}",
|
|
**self.tex_config,
|
|
)
|
|
x_eq = TexMobject(
|
|
"{x} = {L} \\theta",
|
|
**self.tex_config,
|
|
)
|
|
eqs = VGroup(a_eq, v_eq, x_eq)
|
|
eqs.arrange(DOWN, buff=LARGE_BUFF)
|
|
eqs.to_corner(UL)
|
|
|
|
v_rhs = TexMobject(
|
|
"={L}{d\\theta \\over dt}",
|
|
"=", "{L}\\dot{\\theta}",
|
|
**self.tex_config,
|
|
)
|
|
|
|
v_rhs.next_to(v_eq, RIGHT, SMALL_BUFF)
|
|
v_rhs.shift(
|
|
UP * (v_eq[1].get_bottom()[1] - v_rhs[0].get_bottom()[1])
|
|
)
|
|
a_rhs = TexMobject(
|
|
"={L}{d", "\\dot{\\theta}", "\\over dt}",
|
|
"=", "{L}\\ddot{\\theta}",
|
|
**self.tex_config,
|
|
)
|
|
a_rhs.next_to(a_eq, RIGHT, SMALL_BUFF)
|
|
a_rhs.shift(
|
|
UP * (a_eq[1].get_bottom()[1] - a_rhs[0].get_bottom()[1])
|
|
)
|
|
|
|
# a_eq
|
|
self.play(Write(a_eq))
|
|
self.wait()
|
|
|
|
# v_eq
|
|
self.play(
|
|
TransformFromCopy(
|
|
a_eq.get_part_by_tex("{v}"),
|
|
v_eq.get_part_by_tex("{v}"),
|
|
)
|
|
)
|
|
self.play(TransformFromCopy(v_eq[:1], v_eq[1:]))
|
|
self.wait()
|
|
|
|
# x_eq
|
|
self.play(
|
|
TransformFromCopy(
|
|
v_eq.get_part_by_tex("{x}"),
|
|
x_eq.get_part_by_tex("{x}"),
|
|
)
|
|
)
|
|
self.play(Write(x_eq[1:]))
|
|
self.wait()
|
|
for tex in "L", "\\theta":
|
|
self.play(ShowCreationThenFadeAround(
|
|
x_eq.get_part_by_tex(tex)
|
|
))
|
|
self.wait()
|
|
|
|
# v_rhs
|
|
self.play(*[
|
|
TransformFromCopy(
|
|
x_eq.get_part_by_tex(tex),
|
|
v_rhs.get_part_by_tex(tex),
|
|
)
|
|
for tex in ("=", "{L}", "\\theta")
|
|
])
|
|
self.play(
|
|
TransformFromCopy(v_eq[-3], v_rhs[2]),
|
|
TransformFromCopy(v_eq[-1], v_rhs[4]),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(v_rhs[-5]),
|
|
TransformFromCopy(*v_rhs.get_parts_by_tex("{L}")),
|
|
TransformFromCopy(v_rhs[3:4], v_rhs[-3:])
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreationThenFadeAround(v_rhs[2:4]))
|
|
self.play(ShowCreationThenFadeAround(v_rhs[4]))
|
|
self.wait()
|
|
|
|
# a_rhs
|
|
self.play(*[
|
|
TransformFromCopy(
|
|
v_rhs.get_parts_by_tex(tex)[-1],
|
|
a_rhs.get_part_by_tex(tex),
|
|
)
|
|
for tex in ("=", "{L}", "\\theta", "\\dot")
|
|
])
|
|
self.play(
|
|
TransformFromCopy(a_eq[-3], a_rhs[2]),
|
|
TransformFromCopy(a_eq[-1], a_rhs[6]),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(a_rhs[-5]),
|
|
TransformFromCopy(*a_rhs.get_parts_by_tex("{L}")),
|
|
TransformFromCopy(a_rhs[3:4], a_rhs[-3:]),
|
|
)
|
|
self.wait()
|
|
|
|
self.equations = VGroup(
|
|
a_eq, v_eq, x_eq,
|
|
v_rhs, a_rhs,
|
|
)
|
|
|
|
def show_theta_double_dot_equation(self):
|
|
equations = self.equations
|
|
a_deriv = equations[0]
|
|
a_rhs = equations[-1][-5:].copy()
|
|
|
|
shift_vect = 1.5 * DOWN
|
|
|
|
equals = TexMobject("=")
|
|
equals.rotate(90 * DEGREES)
|
|
equals.next_to(a_deriv[0], UP, MED_LARGE_BUFF)
|
|
g_sin_eq = TexMobject(
|
|
"-", "g", "\\sin", "(", "\\theta", ")",
|
|
**self.tex_config,
|
|
)
|
|
g_sin_eq.next_to(
|
|
equals, UP,
|
|
buff=MED_LARGE_BUFF,
|
|
aligned_edge=LEFT,
|
|
)
|
|
g_sin_eq.to_edge(LEFT)
|
|
g_sin_eq.shift(shift_vect)
|
|
|
|
shift_vect += (
|
|
g_sin_eq[1].get_center() -
|
|
a_deriv[0].get_center()
|
|
)[0] * RIGHT
|
|
|
|
equals.shift(shift_vect)
|
|
a_rhs.shift(shift_vect)
|
|
|
|
self.play(
|
|
equations.shift, shift_vect,
|
|
Write(equals),
|
|
GrowFromPoint(
|
|
g_sin_eq, 2 * RIGHT + 3 * DOWN
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
a_rhs.next_to, g_sin_eq, RIGHT,
|
|
a_rhs.shift, SMALL_BUFF * UP,
|
|
)
|
|
self.wait()
|
|
|
|
# Fade equations
|
|
self.play(
|
|
FadeOut(equals),
|
|
equations.shift, DOWN,
|
|
equations.fade, 0.5,
|
|
)
|
|
|
|
# Rotate sides
|
|
equals, L, ddot, theta, junk = a_rhs
|
|
L_dd_theta = VGroup(L, ddot, theta)
|
|
minus, g, sin, lp, theta2, rp = g_sin_eq
|
|
m2, g2, over, L2 = frac = TexMobject("-", "{g", "\\over", "L}")
|
|
frac.next_to(equals, RIGHT)
|
|
|
|
self.play(
|
|
L_dd_theta.next_to, equals, LEFT,
|
|
L_dd_theta.shift, SMALL_BUFF * UP,
|
|
g_sin_eq.next_to, equals, RIGHT,
|
|
path_arc=PI / 2,
|
|
)
|
|
self.play(
|
|
ReplacementTransform(g, g2),
|
|
ReplacementTransform(minus, m2),
|
|
ReplacementTransform(L, L2),
|
|
Write(over),
|
|
g_sin_eq[2:].next_to, over, RIGHT, SMALL_BUFF,
|
|
)
|
|
self.wait()
|
|
|
|
# Surround
|
|
rect = SurroundingRectangle(VGroup(g_sin_eq, frac, ddot))
|
|
rect.stretch(1.1, 0)
|
|
dashed_rect = DashedVMobject(
|
|
rect, num_dashes=50, positive_space_ratio=1,
|
|
)
|
|
dashed_rect.shuffle()
|
|
dashed_rect.save_state()
|
|
dashed_rect.space_out_submobjects(1.1)
|
|
for piece in dashed_rect:
|
|
piece.rotate(90 * DEGREES)
|
|
dashed_rect.fade(1)
|
|
self.play(Restore(dashed_rect, lag_ratio=0.05))
|
|
dashed_rect.generate_target()
|
|
dashed_rect.target.space_out_submobjects(0.9)
|
|
dashed_rect.target.fade(1)
|
|
for piece in dashed_rect.target:
|
|
piece.rotate(90 * DEGREES)
|
|
self.play(MoveToTarget(
|
|
dashed_rect,
|
|
lag_ratio=0.05,
|
|
remover=True
|
|
))
|
|
self.wait()
|
|
|
|
self.main_equation = VGroup(
|
|
ddot, theta, equals,
|
|
m2, L2, over, g2,
|
|
sin, lp, theta2, rp,
|
|
)
|
|
|
|
def talk_about_sine_component(self):
|
|
main_equation = self.main_equation
|
|
gL_part = main_equation[4:7]
|
|
sin_part = main_equation[7:]
|
|
sin = sin_part[0]
|
|
|
|
morty = Mortimer(height=1.5)
|
|
morty.next_to(sin, DR, buff=LARGE_BUFF)
|
|
morty.add_updater(lambda m: m.look_at(sin))
|
|
|
|
self.play(ShowCreationThenFadeAround(gL_part))
|
|
self.wait()
|
|
self.play(ShowCreationThenFadeAround(sin_part))
|
|
self.wait()
|
|
|
|
self.play(FadeIn(morty))
|
|
sin.save_state()
|
|
self.play(
|
|
morty.change, "angry",
|
|
sin.next_to, morty, LEFT, {"aligned_edge": UP},
|
|
)
|
|
self.play(Blink(morty))
|
|
morty.clear_updaters()
|
|
self.play(
|
|
morty.change, "concerned_musician",
|
|
morty.look, DR,
|
|
)
|
|
self.play(Restore(sin))
|
|
self.play(FadeOut(morty))
|
|
self.wait()
|
|
|
|
# Emphasize theta as input
|
|
theta = sin_part[2]
|
|
arrow = Vector(0.5 * UP, color=WHITE)
|
|
arrow.next_to(theta, DOWN, SMALL_BUFF)
|
|
word = TextMobject("Input")
|
|
word.next_to(arrow, DOWN)
|
|
|
|
self.play(
|
|
FadeInFrom(word, UP),
|
|
GrowArrow(arrow)
|
|
)
|
|
self.play(
|
|
ShowCreationThenDestruction(
|
|
theta.copy().set_style(
|
|
fill_opacity=0,
|
|
stroke_width=2,
|
|
stroke_color=YELLOW,
|
|
),
|
|
lag_ratio=0.1,
|
|
)
|
|
)
|
|
self.play(FadeOut(arrow), FadeOut(word))
|
|
|
|
def add_air_resistance(self):
|
|
main_equation = self.main_equation
|
|
tdd_eq = main_equation[:3]
|
|
rhs = main_equation[3:]
|
|
|
|
new_term = TexMobject(
|
|
"-", "\\mu", "\\dot{", "\\theta}",
|
|
)
|
|
new_term.set_color_by_tex("\\theta", BLUE)
|
|
new_term.move_to(main_equation)
|
|
new_term.shift(0.5 * SMALL_BUFF * UP)
|
|
new_term[0].align_to(rhs[0], UP)
|
|
|
|
brace = Brace(new_term, DOWN)
|
|
words = brace.get_text("Air resistance")
|
|
|
|
self.play(
|
|
FadeInFromDown(new_term),
|
|
tdd_eq.next_to, new_term, LEFT,
|
|
tdd_eq.align_to, tdd_eq, UP,
|
|
rhs.next_to, new_term, RIGHT,
|
|
rhs.align_to, rhs, UP,
|
|
)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(words)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class SimpleDampenedPendulum(Scene):
|
|
def construct(self):
|
|
pendulum = Pendulum(
|
|
top_point=ORIGIN,
|
|
initial_theta=150 * DEGREES,
|
|
mu=0.5,
|
|
)
|
|
self.add(pendulum)
|
|
pendulum.start_swinging()
|
|
self.wait(20)
|
|
|
|
|
|
class NewSceneName(Scene):
|
|
def construct(self):
|
|
pass
|