Files
2019-03-20 17:38:25 -07:00

852 lines
24 KiB
Python

from big_ol_pile_of_manim_imports import *
from active_projects.ode.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,
"n_steps_per_frame": 100,
}
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()
self.add_theta_label()
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
)
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_theta(),
**self.angle_arc_config,
))
self.add(self.angle_arc)
def add_velocity_vector(self):
def make_vector():
omega = self.get_omega()
theta = self.get_theta()
vector = Vector(
0.5 * omega * 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):
label = self.theta_label = TexMobject("\\theta")
label.set_height(self.theta_label_height)
def update_label(l):
top = self.get_fixed_point()
arc_center = self.angle_arc.point_from_proportion(0.5)
vect = arc_center - top
vect = normalize(vect) * (1 + self.theta_label_height)
l.move_to(top + vect)
label.add_updater(update_label)
self.add(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.rod.get_end() - 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(tip, corner, **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)
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,
},
"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,
},
"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.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()
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))
self.add(rect)
self.play(
ShowCreationThenFadeOut(rect),
ShowCreationThenDestruction(
label.copy().set_style(
fill_opacity=0,
stroke_color=PINK,
stroke_width=2,
)
),
randy.change, "pondering",
morty.change, "pondering",
)
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),
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(2)
self.graph = axes.get_live_drawn_graph(self.pendulum)
self.add(self.graph)
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 = TexMobject(
"\\sqrt{\\,", "2\\pi", "L", "/", "g", "}",
tex_to_color_map={
"L": BLUE,
"g": YELLOW,
}
)
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,
)
self.play(ShowCreationThenDestructionAround(L))
dot = Dot(fill_opacity=0.25)
dot.move_to(L)
self.play(
ShowCreation(new_rod),
dot.move_to, new_rod,
dot.fade, 1,
)
self.remove(dot)
self.play(FadeOut(new_rod))
self.wait()
self.play(ShowCreationThenDestructionAround(g))
dot.move_to(g)
dot.set_fill(opacity=0.5)
self.play(
GrowArrow(g_vect),
dot.move_to, g_vect,
dot.fade, 1,
)
self.remove(dot)
self.wait(2)
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 = 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)
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),
LaggedStart(*[
GrowArrow(v, rate_func=there_and_back)
for v in down_vectors
], lag_ratio=0.0005, run_time=2, remover=True)
)
self.play(
FadeIn(graph3),
brace.stretch, 0.5, 0, {"about_edge": LEFT},
)
self.wait(6)
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 BuildUpEquation(Scene):
def construct(self):
pass
class NewSceneName(Scene):
def construct(self):
pass