mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 08:54:38 +08:00
1010 lines
30 KiB
Python
1010 lines
30 KiB
Python
from big_ol_pile_of_manim_imports import *
|
|
from active_projects.ode.part1.shared_constructs import *
|
|
from active_projects.ode.part1.pendulum import Pendulum
|
|
|
|
|
|
# TODO: Arguably separate the part showing many
|
|
# configurations with the part showing just one.
|
|
class VisualizeStates(Scene):
|
|
CONFIG = {
|
|
"coordinate_plane_config": {
|
|
"y_line_frequency": PI / 2,
|
|
# "x_line_frequency": PI / 2,
|
|
"x_line_frequency": 1,
|
|
"y_axis_config": {
|
|
# "unit_size": 1.75,
|
|
"unit_size": 1,
|
|
},
|
|
"y_max": 4,
|
|
"faded_line_ratio": 4,
|
|
"background_line_style": {
|
|
"stroke_width": 1,
|
|
},
|
|
},
|
|
"little_pendulum_config": {
|
|
"length": 1,
|
|
"gravity": 4.9,
|
|
"weight_diameter": 0.3,
|
|
"include_theta_label": False,
|
|
"include_velocity_vector": True,
|
|
"angle_arc_config": {
|
|
"radius": 0.2,
|
|
},
|
|
"velocity_vector_config": {
|
|
"max_tip_length_to_length_ratio": 0.35,
|
|
"max_stroke_width_to_length_ratio": 6,
|
|
},
|
|
"velocity_vector_multiple": 0.25,
|
|
"max_velocity_vector_length_to_length_ratio": 0.8,
|
|
},
|
|
"big_pendulum_config": {
|
|
"length": 1.6,
|
|
"gravity": 4.9,
|
|
"damping": 0.2,
|
|
"weight_diameter": 0.3,
|
|
"include_velocity_vector": True,
|
|
"angle_arc_config": {
|
|
"radius": 0.5,
|
|
},
|
|
"initial_theta": 80 * DEGREES,
|
|
"omega": -1,
|
|
"set_theta_label_height_cap": True,
|
|
},
|
|
"n_thetas": 11,
|
|
"n_omegas": 7,
|
|
# "n_thetas": 5,
|
|
# "n_omegas": 3,
|
|
"initial_grid_wait_time": 15,
|
|
}
|
|
|
|
def construct(self):
|
|
self.initialize_plane()
|
|
|
|
simple = False
|
|
if simple:
|
|
self.add(self.plane)
|
|
else:
|
|
self.initialize_grid_of_states()
|
|
self.show_all_states_evolving()
|
|
self.show_grid_of_states_creation()
|
|
self.collapse_grid_into_points()
|
|
self.show_state_with_pair_of_numbers()
|
|
self.show_acceleration_dependence()
|
|
self.show_evolution_from_a_start_state()
|
|
|
|
def initialize_grid_of_states(self):
|
|
pendulums = VGroup()
|
|
rects = VGroup()
|
|
state_grid = VGroup()
|
|
thetas = self.get_initial_thetas()
|
|
omegas = self.get_initial_omegas()
|
|
for omega in omegas:
|
|
row = VGroup()
|
|
for theta in thetas:
|
|
rect = Rectangle(
|
|
height=3,
|
|
width=3,
|
|
stroke_color=WHITE,
|
|
stroke_width=2,
|
|
fill_color=DARKER_GREY,
|
|
fill_opacity=1,
|
|
)
|
|
pendulum = Pendulum(
|
|
initial_theta=theta,
|
|
omega=omega,
|
|
top_point=rect.get_center(),
|
|
**self.little_pendulum_config,
|
|
)
|
|
pendulum.add_velocity_vector()
|
|
pendulums.add(pendulum)
|
|
rects.add(rect)
|
|
state = VGroup(rect, pendulum)
|
|
state.rect = rect
|
|
state.pendulum = pendulum
|
|
row.add(state)
|
|
row.arrange(RIGHT, buff=0)
|
|
state_grid.add(row)
|
|
state_grid.arrange(UP, buff=0)
|
|
|
|
state_grid.set_height(FRAME_HEIGHT)
|
|
state_grid.center()
|
|
state_grid.save_state(use_deepcopy=True)
|
|
|
|
self.state_grid = state_grid
|
|
self.pendulums = pendulums
|
|
|
|
def initialize_plane(self):
|
|
plane = self.plane = NumberPlane(
|
|
**self.coordinate_plane_config
|
|
)
|
|
plane.axis_labels = VGroup(
|
|
plane.get_x_axis_label(
|
|
"\\theta", RIGHT, UL, buff=SMALL_BUFF
|
|
),
|
|
plane.get_y_axis_label(
|
|
"\\dot \\theta", UP, DR, buff=SMALL_BUFF
|
|
).set_color(YELLOW),
|
|
)
|
|
for label in plane.axis_labels:
|
|
label.add_background_rectangle()
|
|
plane.add(plane.axis_labels)
|
|
|
|
plane.y_axis.add_numbers(direction=DL)
|
|
|
|
x_axis = plane.x_axis
|
|
label_texs = ["\\pi \\over 2", "\\pi", "3\\pi \\over 2", "\\tau"]
|
|
values = [PI / 2, PI, 3 * PI / 2, TAU]
|
|
x_axis.coordinate_labels = VGroup()
|
|
x_axis.add(x_axis.coordinate_labels)
|
|
for value, label_tex in zip(values, label_texs):
|
|
for u in [-1, 1]:
|
|
tex = label_tex
|
|
if u < 0:
|
|
tex = "-" + tex
|
|
label = TexMobject(tex)
|
|
label.scale(0.5)
|
|
if label.get_height() > 0.4:
|
|
label.set_height(0.4)
|
|
point = x_axis.number_to_point(u * value)
|
|
label.next_to(point, DR, SMALL_BUFF)
|
|
x_axis.coordinate_labels.add(label)
|
|
return plane
|
|
|
|
def show_all_states_evolving(self):
|
|
state_grid = self.state_grid
|
|
pendulums = self.pendulums
|
|
|
|
for pendulum in pendulums:
|
|
pendulum.start_swinging()
|
|
|
|
self.add(state_grid)
|
|
self.wait(self.initial_grid_wait_time)
|
|
|
|
def show_grid_of_states_creation(self):
|
|
self.remove(self.state_grid)
|
|
self.initialize_grid_of_states() # Again
|
|
state_grid = self.state_grid
|
|
|
|
title = TextMobject("All states")
|
|
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
self.all_states_title = title
|
|
|
|
state_grid.set_height(
|
|
FRAME_HEIGHT - title.get_height() - 2 * MED_SMALL_BUFF
|
|
)
|
|
state_grid.to_edge(DOWN, buff=0)
|
|
|
|
def place_at_top(state):
|
|
state.set_height(3)
|
|
state.center()
|
|
state.next_to(title, DOWN)
|
|
|
|
middle_row = state_grid[len(state_grid) // 2]
|
|
middle_row_copy = middle_row.deepcopy()
|
|
right_column_copy = VGroup(*[
|
|
row[-1] for row in state_grid
|
|
]).deepcopy()
|
|
|
|
for state in it.chain(middle_row_copy, right_column_copy):
|
|
place_at_top(state)
|
|
|
|
self.add(title)
|
|
self.play(
|
|
ShowIncreasingSubsets(middle_row),
|
|
ShowIncreasingSubsets(middle_row_copy),
|
|
run_time=2,
|
|
rate_func=linear,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowIncreasingSubsets(state_grid),
|
|
ShowIncreasingSubsets(right_column_copy),
|
|
run_time=2,
|
|
rate_func=linear,
|
|
)
|
|
self.remove(middle_row_copy)
|
|
self.remove(middle_row)
|
|
self.add(state_grid)
|
|
self.remove(right_column_copy)
|
|
self.play(
|
|
ReplacementTransform(
|
|
right_column_copy[-1],
|
|
state_grid[-1][-1],
|
|
remover=True
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
def collapse_grid_into_points(self):
|
|
state_grid = self.state_grid
|
|
plane = self.plane
|
|
|
|
dots = VGroup()
|
|
for row in state_grid:
|
|
for state in row:
|
|
dot = Dot(
|
|
self.get_state_point(state.pendulum),
|
|
radius=0.05,
|
|
color=YELLOW,
|
|
background_stroke_width=3,
|
|
background_stroke_color=BLACK,
|
|
)
|
|
dot.state = state
|
|
dots.add(dot)
|
|
|
|
self.add(plane)
|
|
self.remove(state_grid)
|
|
flat_state_group = VGroup(*it.chain(*state_grid))
|
|
flat_dot_group = VGroup(*it.chain(*dots))
|
|
self.clear() # The nuclear option
|
|
self.play(
|
|
ShowCreation(plane),
|
|
FadeOut(self.all_states_title),
|
|
LaggedStart(*[
|
|
TransformFromCopy(m1, m2)
|
|
for m1, m2 in zip(flat_state_group, flat_dot_group)
|
|
], lag_ratio=0.1, run_time=4)
|
|
)
|
|
self.clear() # Again, not sure why I need this
|
|
self.add(plane, dots)
|
|
self.wait()
|
|
|
|
self.state_dots = dots
|
|
|
|
def show_state_with_pair_of_numbers(self):
|
|
axis_labels = self.plane.axis_labels
|
|
|
|
state = self.get_flexible_state_picture()
|
|
dot = self.get_state_controlling_dot(state)
|
|
h_line = always_redraw(
|
|
lambda: self.get_tracking_h_line(dot.get_center())
|
|
)
|
|
v_line = always_redraw(
|
|
lambda: self.get_tracking_v_line(dot.get_center())
|
|
)
|
|
|
|
self.add(dot)
|
|
anims = [GrowFromPoint(state, dot.get_center())]
|
|
if hasattr(self, "state_dots"):
|
|
anims.append(FadeOut(self.state_dots))
|
|
self.play(*anims)
|
|
|
|
for line, label in zip([h_line, v_line], axis_labels):
|
|
# self.add(line, dot)
|
|
self.play(
|
|
ShowCreation(line),
|
|
ShowCreationThenFadeAround(label),
|
|
run_time=1
|
|
)
|
|
for vect in LEFT, 3 * UP:
|
|
self.play(
|
|
ApplyMethod(
|
|
dot.shift, vect,
|
|
rate_func=there_and_back,
|
|
run_time=2,
|
|
)
|
|
)
|
|
self.wait()
|
|
for vect in 2 * LEFT, 3 * UP, 2 * RIGHT, 2 * DOWN:
|
|
self.play(dot.shift, vect, run_time=1.5)
|
|
self.wait()
|
|
|
|
self.state = state
|
|
self.state_dot = dot
|
|
self.h_line = h_line
|
|
self.v_line = v_line
|
|
|
|
def show_acceleration_dependence(self):
|
|
ode = get_ode()
|
|
thetas = ode.get_parts_by_tex("\\theta")
|
|
thetas[0].set_color(RED)
|
|
thetas[1].set_color(YELLOW)
|
|
ode.move_to(
|
|
FRAME_WIDTH * RIGHT / 4 +
|
|
FRAME_HEIGHT * UP / 4,
|
|
)
|
|
ode.add_background_rectangle_to_submobjects()
|
|
|
|
self.play(Write(ode))
|
|
self.wait()
|
|
self.play(FadeOut(ode))
|
|
|
|
def show_evolution_from_a_start_state(self):
|
|
state = self.state
|
|
dot = self.state_dot
|
|
|
|
self.play(
|
|
Rotating(
|
|
dot,
|
|
about_point=dot.get_center() + UR,
|
|
rate_func=smooth,
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
# Show initial trajectory
|
|
state.pendulum.clear_updaters(recursive=False)
|
|
self.tie_dot_position_to_state(dot, state)
|
|
state.pendulum.start_swinging()
|
|
trajectory = self.get_evolving_trajectory(dot)
|
|
self.add(trajectory)
|
|
for x in range(20):
|
|
self.wait()
|
|
|
|
# Talk through start
|
|
trajectory.suspend_updating()
|
|
state.pendulum.end_swinging()
|
|
dot.clear_updaters()
|
|
self.tie_state_to_dot_position(state, dot)
|
|
|
|
alphas = np.linspace(0, 0.1, 1000)
|
|
index = np.argmin([
|
|
trajectory.point_from_proportion(a)[1]
|
|
for a in alphas
|
|
])
|
|
alpha = alphas[index]
|
|
sub_traj = trajectory.copy()
|
|
sub_traj.suspend_updating()
|
|
sub_traj.pointwise_become_partial(trajectory, 0, alpha)
|
|
sub_traj.match_style(trajectory)
|
|
sub_traj.set_stroke(width=3)
|
|
|
|
self.wait()
|
|
self.play(dot.move_to, sub_traj.get_start())
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(sub_traj),
|
|
UpdateFromFunc(
|
|
dot, lambda d: d.move_to(sub_traj.get_end())
|
|
),
|
|
run_time=6,
|
|
)
|
|
self.wait()
|
|
|
|
# Comment on physical velocity vs. space position
|
|
v_vect = state.pendulum.velocity_vector
|
|
v_line_copy = self.v_line.copy()
|
|
v_line_copy.clear_updaters()
|
|
v_line_copy.set_stroke(PINK, 5)
|
|
td_label = self.plane.axis_labels[1]
|
|
y_axis_copy = self.plane.y_axis.copy()
|
|
y_axis_copy.submobjects = []
|
|
y_axis_copy.match_style(v_line_copy)
|
|
|
|
self.play(ShowCreationThenFadeAround(v_vect))
|
|
self.play(
|
|
ShowCreationThenFadeAround(td_label),
|
|
ShowCreationThenFadeOut(y_axis_copy)
|
|
)
|
|
self.play(ShowCreationThenFadeOut(v_line_copy))
|
|
self.wait()
|
|
|
|
# Abstract vs. physical
|
|
abstract = TextMobject("Abstract")
|
|
abstract.add_background_rectangle()
|
|
abstract.scale(2)
|
|
abstract.to_corner(UR)
|
|
physical = TextMobject("Physical")
|
|
physical.next_to(state.get_top(), DOWN)
|
|
|
|
self.play(
|
|
ApplyMethod(
|
|
self.plane.set_stroke, YELLOW, 0.5,
|
|
rate_func=there_and_back,
|
|
lag_ratio=0.2,
|
|
),
|
|
FadeInFromDown(abstract),
|
|
Animation(state),
|
|
)
|
|
self.wait()
|
|
self.play(FadeInFromDown(physical))
|
|
self.wait()
|
|
|
|
# Continue on spiral
|
|
sub_traj.resume_updating()
|
|
state.pendulum.clear_updaters(recursive=False)
|
|
state.pendulum.start_swinging()
|
|
dot.clear_updaters()
|
|
self.tie_dot_position_to_state(dot, state)
|
|
self.wait(20)
|
|
|
|
#
|
|
def get_initial_thetas(self):
|
|
angle = 3 * PI / 4
|
|
return np.linspace(-angle, angle, self.n_thetas)
|
|
|
|
def get_initial_omegas(self):
|
|
return np.linspace(-1.5, 1.5, self.n_omegas)
|
|
|
|
def get_state(self, pendulum):
|
|
return (pendulum.get_theta(), pendulum.get_omega())
|
|
|
|
def get_state_point(self, pendulum):
|
|
return self.plane.coords_to_point(
|
|
*self.get_state(pendulum)
|
|
)
|
|
|
|
def get_flexible_state_picture(self):
|
|
buff = MED_SMALL_BUFF
|
|
height = FRAME_HEIGHT / 2 - buff
|
|
rect = Square(
|
|
side_length=height,
|
|
stroke_color=WHITE,
|
|
stroke_width=2,
|
|
fill_color="#111111",
|
|
fill_opacity=1,
|
|
)
|
|
rect.to_corner(UL, buff=buff / 2)
|
|
pendulum = Pendulum(
|
|
top_point=rect.get_center(),
|
|
**self.big_pendulum_config
|
|
)
|
|
pendulum.fixed_point_tracker.add_updater(
|
|
lambda m: m.move_to(rect.get_center())
|
|
)
|
|
|
|
state = VGroup(rect, pendulum)
|
|
state.rect = rect
|
|
state.pendulum = pendulum
|
|
return state
|
|
|
|
def get_state_dot(self, state):
|
|
dot = Dot(color=PINK)
|
|
dot.move_to(self.get_state_point(state.pendulum))
|
|
return dot
|
|
|
|
def get_state_controlling_dot(self, state):
|
|
dot = self.get_state_dot(state)
|
|
self.tie_state_to_dot_position(state, dot)
|
|
return dot
|
|
|
|
def tie_state_to_dot_position(self, state, dot):
|
|
def update_pendulum(pend):
|
|
theta, omega = self.plane.point_to_coords(
|
|
dot.get_center()
|
|
)
|
|
pend.set_theta(theta)
|
|
pend.set_omega(omega)
|
|
return pend
|
|
state.pendulum.add_updater(update_pendulum)
|
|
state.pendulum.get_arc_angle_theta = \
|
|
lambda: self.plane.x_axis.point_to_number(dot.get_center())
|
|
return self
|
|
|
|
def tie_dot_position_to_state(self, dot, state):
|
|
dot.add_updater(lambda d: d.move_to(
|
|
self.get_state_point(state.pendulum)
|
|
))
|
|
return dot
|
|
|
|
def get_tracking_line(self, point, axis, color=WHITE):
|
|
number = axis.point_to_number(point)
|
|
axis_point = axis.number_to_point(number)
|
|
return DashedLine(
|
|
axis_point, point,
|
|
dash_length=0.025,
|
|
color=color,
|
|
)
|
|
|
|
def get_tracking_h_line(self, point):
|
|
return self.get_tracking_line(
|
|
point, self.plane.y_axis, WHITE,
|
|
)
|
|
|
|
def get_tracking_v_line(self, point):
|
|
return self.get_tracking_line(
|
|
point, self.plane.x_axis, YELLOW,
|
|
)
|
|
|
|
def get_evolving_trajectory(self, mobject):
|
|
trajectory = VMobject()
|
|
trajectory.start_new_path(mobject.get_center())
|
|
trajectory.set_stroke(RED, 1)
|
|
|
|
def update_trajectory(traj):
|
|
point = mobject.get_center()
|
|
if get_norm(trajectory.points[-1] == point) > 0.05:
|
|
traj.add_smooth_curve_to(point)
|
|
trajectory.add_updater(update_trajectory)
|
|
return trajectory
|
|
|
|
|
|
class IntroduceVectorField(VisualizeStates):
|
|
CONFIG = {
|
|
"vector_field_config": {
|
|
"max_magnitude": 3,
|
|
# "delta_x": 2,
|
|
# "delta_y": 2,
|
|
},
|
|
"big_pendulum_config": {
|
|
"initial_theta": -80 * DEGREES,
|
|
"omega": 1,
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
self.initialize_plane()
|
|
self.add_flexible_state()
|
|
self.initialize_vector_field()
|
|
self.add_equation()
|
|
self.preview_vector_field()
|
|
self.write_vector_derivative()
|
|
self.interpret_first_coordinate()
|
|
self.interpret_second_coordinate()
|
|
self.show_full_vector_field()
|
|
self.show_trajectory()
|
|
|
|
def initialize_plane(self):
|
|
super().initialize_plane()
|
|
self.add(self.plane)
|
|
|
|
def initialize_vector_field(self):
|
|
self.vector_field = VectorField(
|
|
self.vector_field_func,
|
|
**self.vector_field_config,
|
|
)
|
|
self.vector_field.sort(get_norm)
|
|
|
|
def add_flexible_state(self):
|
|
self.state = self.get_flexible_state_picture()
|
|
self.add(self.state)
|
|
|
|
def add_equation(self):
|
|
ode = get_ode()
|
|
ode.set_width(self.state.get_width() - MED_LARGE_BUFF)
|
|
ode.next_to(self.state.get_top(), DOWN, SMALL_BUFF)
|
|
thetas = ode.get_parts_by_tex("\\theta")
|
|
thetas[0].set_color(RED)
|
|
thetas[1].set_color(YELLOW)
|
|
ode_word = TextMobject("Differential equation")
|
|
ode_word.match_width(ode)
|
|
ode_word.next_to(ode, DOWN)
|
|
|
|
self.play(
|
|
FadeInFrom(ode, 0.5 * DOWN),
|
|
FadeInFrom(ode_word, 0.5 * UP),
|
|
)
|
|
|
|
self.ode = ode
|
|
self.ode_word = ode_word
|
|
|
|
def preview_vector_field(self):
|
|
vector_field = self.vector_field
|
|
|
|
growth = LaggedStartMap(
|
|
GrowArrow, vector_field,
|
|
run_time=3,
|
|
lag_ratio=0.01,
|
|
)
|
|
self.add(
|
|
growth.mobject,
|
|
vector_field,
|
|
self.state, self.ode, self.ode_word
|
|
)
|
|
|
|
self.play(growth)
|
|
self.wait()
|
|
self.play(FadeOut(vector_field))
|
|
self.remove(growth.mobject)
|
|
|
|
def write_vector_derivative(self):
|
|
state = self.state
|
|
plane = self.plane
|
|
|
|
dot = self.get_state_dot(state)
|
|
|
|
# Vector
|
|
vect = Arrow(
|
|
plane.coords_to_point(0, 0),
|
|
dot.get_center(),
|
|
buff=0,
|
|
color=dot.get_color()
|
|
)
|
|
vect_sym, d_vect_sym = [
|
|
self.get_vector_symbol(
|
|
"{" + a + "\\theta}(t)",
|
|
"{" + b + "\\theta}(t)",
|
|
)
|
|
for a, b in [("", "\\dot"), ("\\dot", "\\ddot")]
|
|
]
|
|
# vect_sym.get_entries()[1][0][1].set_color(YELLOW)
|
|
# d_vect_sym.get_entries()[0][0][1].set_color(YELLOW)
|
|
# d_vect_sym.get_entries()[1][0][1].set_color(RED)
|
|
vect_sym.next_to(vect.get_end(), UP, MED_LARGE_BUFF)
|
|
time_inputs = VGroup(*[
|
|
e[-1][-2] for e in vect_sym.get_entries()
|
|
])
|
|
|
|
# Derivative
|
|
ddt = TexMobject("d \\over dt")
|
|
ddt.set_height(0.9 * vect_sym.get_height())
|
|
ddt.next_to(vect_sym, LEFT)
|
|
ddt.set_stroke(BLACK, 5, background=True)
|
|
equals = TexMobject("=")
|
|
equals.add_background_rectangle()
|
|
equals.next_to(vect_sym, RIGHT, SMALL_BUFF)
|
|
d_vect_sym.next_to(equals, RIGHT, SMALL_BUFF)
|
|
|
|
# Little vector
|
|
angle_tracker = ValueTracker(0)
|
|
mag_tracker = ValueTracker(0.75)
|
|
d_vect = always_redraw(
|
|
lambda: Vector(
|
|
rotate_vector(
|
|
mag_tracker.get_value() * RIGHT,
|
|
angle_tracker.get_value(),
|
|
),
|
|
color=WHITE
|
|
).shift(dot.get_center()),
|
|
)
|
|
d_vect_magnitude_factor_tracker = ValueTracker(2)
|
|
real_d_vect = always_redraw(
|
|
lambda: self.vector_field.get_vector(
|
|
dot.get_center()
|
|
).scale(
|
|
d_vect_magnitude_factor_tracker.get_value(),
|
|
about_point=dot.get_center()
|
|
)
|
|
)
|
|
|
|
# Show vector
|
|
self.play(TransformFromCopy(state[1], vect))
|
|
self.play(FadeInFromDown(vect_sym))
|
|
self.wait()
|
|
self.play(ReplacementTransform(vect, dot))
|
|
self.wait()
|
|
self.play(LaggedStartMap(
|
|
ShowCreationThenFadeAround, time_inputs,
|
|
lag_ratio=0.1,
|
|
))
|
|
self.wait()
|
|
|
|
# Write Derivative
|
|
self.play(Write(ddt))
|
|
self.play(
|
|
plane.y_axis.numbers.fade, 1,
|
|
FadeInFrom(equals, LEFT),
|
|
TransformFromCopy(vect_sym, d_vect_sym)
|
|
)
|
|
self.wait()
|
|
|
|
# Show as little vector
|
|
equation_group = VGroup(
|
|
ddt, vect_sym, equals, d_vect_sym
|
|
)
|
|
self.play(
|
|
# equation_group.shift, 4 * DOWN,
|
|
equation_group.to_edge, RIGHT, LARGE_BUFF,
|
|
GrowArrow(d_vect),
|
|
)
|
|
self.wait()
|
|
self.play(angle_tracker.set_value, 120 * DEGREES)
|
|
self.play(mag_tracker.set_value, 1.5)
|
|
self.wait()
|
|
|
|
# Highlight new vector
|
|
self.play(
|
|
ShowCreationThenFadeAround(d_vect_sym),
|
|
FadeOut(d_vect)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(d_vect_sym, real_d_vect),
|
|
dot.set_color, WHITE,
|
|
)
|
|
self.wait()
|
|
|
|
# Take a walk
|
|
trajectory = VMobject()
|
|
trajectory.start_new_path(dot.get_center())
|
|
dt = 0.01
|
|
for x in range(130):
|
|
p = trajectory.points[-1]
|
|
dp_dt = self.vector_field_func(p)
|
|
trajectory.add_smooth_curve_to(p + dp_dt * dt)
|
|
self.tie_state_to_dot_position(state, dot)
|
|
self.play(
|
|
MoveAlongPath(dot, trajectory),
|
|
run_time=5,
|
|
rate_func=bezier([0, 0, 1, 1]),
|
|
)
|
|
|
|
self.state_dot = dot
|
|
self.d_vect = real_d_vect
|
|
self.equation_group = equation_group
|
|
self.d_vect_magnitude_factor_tracker = d_vect_magnitude_factor_tracker
|
|
|
|
def interpret_first_coordinate(self):
|
|
equation = self.equation_group
|
|
ddt, vect_sym, equals, d_vect_sym = equation
|
|
dot = self.state_dot
|
|
|
|
first_components_copy = VGroup(
|
|
vect_sym.get_entries()[0],
|
|
d_vect_sym.get_entries()[0],
|
|
).copy()
|
|
rect = SurroundingRectangle(first_components_copy)
|
|
rect.set_stroke(YELLOW, 2)
|
|
|
|
equation.save_state()
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
equation.fade, 0.5,
|
|
Animation(first_components_copy),
|
|
)
|
|
self.wait()
|
|
dot.save_state()
|
|
self.play(dot.shift, 2 * UP)
|
|
self.wait()
|
|
self.play(dot.shift, 6 * DOWN)
|
|
self.wait()
|
|
self.play(dot.restore)
|
|
self.wait()
|
|
|
|
self.play(
|
|
equation.restore,
|
|
FadeOut(rect),
|
|
)
|
|
self.remove(first_components_copy)
|
|
|
|
def interpret_second_coordinate(self):
|
|
equation = self.equation_group
|
|
ddt, vect_sym, equals, d_vect_sym = equation
|
|
|
|
second_components = VGroup(
|
|
vect_sym.get_entries()[1],
|
|
d_vect_sym.get_entries()[1],
|
|
)
|
|
rect = SurroundingRectangle(second_components)
|
|
rect.set_stroke(YELLOW, 2)
|
|
|
|
expanded_derivative = self.get_vector_symbol(
|
|
"{\\dot\\theta}(t)",
|
|
"-\\mu {\\dot\\theta}(t)" +
|
|
"-(g / L) \\sin\\big({\\theta}(t)\\big)",
|
|
)
|
|
expanded_derivative.move_to(d_vect_sym)
|
|
expanded_derivative.to_edge(RIGHT, MED_SMALL_BUFF)
|
|
equals2 = TexMobject("=")
|
|
equals2.next_to(expanded_derivative, LEFT, SMALL_BUFF)
|
|
|
|
equation.save_state()
|
|
self.play(
|
|
ShowCreation(rect),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFrom(expanded_derivative, LEFT),
|
|
FadeIn(equals2),
|
|
equation.next_to, equals2, LEFT, SMALL_BUFF,
|
|
MaintainPositionRelativeTo(rect, equation),
|
|
VFadeOut(rect),
|
|
)
|
|
self.wait()
|
|
|
|
self.full_equation = VGroup(
|
|
*equation, equals2, expanded_derivative,
|
|
)
|
|
|
|
def show_full_vector_field(self):
|
|
vector_field = self.vector_field
|
|
state = self.state
|
|
ode = self.ode
|
|
ode_word = self.ode_word
|
|
equation = self.full_equation
|
|
d_vect = self.d_vect
|
|
dot = self.state_dot
|
|
|
|
equation.generate_target()
|
|
equation.target.scale(0.7)
|
|
equation.target.to_edge(DOWN, LARGE_BUFF)
|
|
equation.target.to_edge(LEFT, MED_SMALL_BUFF)
|
|
equation_rect = BackgroundRectangle(equation.target)
|
|
|
|
growth = LaggedStartMap(
|
|
GrowArrow, vector_field,
|
|
run_time=3,
|
|
lag_ratio=0.01,
|
|
)
|
|
self.add(
|
|
growth.mobject,
|
|
state, ode, ode_word,
|
|
equation_rect, equation, dot,
|
|
d_vect,
|
|
)
|
|
self.play(
|
|
growth,
|
|
FadeIn(equation_rect),
|
|
MoveToTarget(equation),
|
|
self.d_vect_magnitude_factor_tracker.set_value, 1,
|
|
)
|
|
|
|
def show_trajectory(self):
|
|
state = self.state
|
|
dot = self.state_dot
|
|
|
|
state.pendulum.clear_updaters(recursive=False)
|
|
self.tie_dot_position_to_state(dot, state)
|
|
state.pendulum.start_swinging()
|
|
|
|
trajectory = self.get_evolving_trajectory(dot)
|
|
trajectory.set_stroke(WHITE, 3)
|
|
|
|
self.add(trajectory, dot)
|
|
self.wait(25)
|
|
|
|
#
|
|
def get_vector_symbol(self, tex1, tex2):
|
|
return Matrix(
|
|
[[tex1], [tex2]],
|
|
include_background_rectangle=True,
|
|
bracket_h_buff=SMALL_BUFF,
|
|
bracket_v_buff=SMALL_BUFF,
|
|
element_to_mobject_config={
|
|
"tex_to_color_map": {
|
|
"{\\theta}": BLUE,
|
|
"{\\dot\\theta}": YELLOW,
|
|
"{\\omega}": YELLOW,
|
|
"{\\ddot\\theta}": RED,
|
|
},
|
|
},
|
|
element_alignment_corner=ORIGIN,
|
|
).scale(0.9)
|
|
|
|
def vector_field_func(self, point):
|
|
x, y = self.plane.point_to_coords(point)
|
|
mu, g, L = [
|
|
self.big_pendulum_config.get(key)
|
|
for key in ["damping", "gravity", "length"]
|
|
]
|
|
return pendulum_vector_field_func(
|
|
x * RIGHT + y * UP,
|
|
mu=mu, g=g, L=L
|
|
)
|
|
|
|
def ask_about_change(self):
|
|
state = self.state
|
|
|
|
dot = self.get_state_dot(state)
|
|
d_vect = Vector(0.75 * RIGHT, color=WHITE)
|
|
d_vect.shift(dot.get_center())
|
|
q_mark = always_redraw(
|
|
lambda: TexMobject("?").move_to(
|
|
d_vect.get_end() + 0.4 * rotate_vector(
|
|
d_vect.get_vector(), 90 * DEGREES,
|
|
),
|
|
)
|
|
)
|
|
|
|
self.play(TransformFromCopy(state[1], dot))
|
|
self.tie_state_to_dot_position(state, dot)
|
|
self.play(
|
|
GrowArrow(d_vect),
|
|
FadeInFromDown(q_mark)
|
|
)
|
|
for x in range(4):
|
|
angle = 90 * DEGREES
|
|
self.play(
|
|
Rotate(
|
|
d_vect, angle,
|
|
about_point=d_vect.get_start(),
|
|
)
|
|
)
|
|
self.play(
|
|
dot.shift,
|
|
0.3 * d_vect.get_vector(),
|
|
rate_func=there_and_back,
|
|
)
|
|
|
|
|
|
class ShowPendulumPhaseFlow(IntroduceVectorField):
|
|
CONFIG = {
|
|
"coordinate_plane_config": {
|
|
"x_axis_config": {
|
|
"unit_size": 0.8,
|
|
},
|
|
"x_max": 9,
|
|
"x_min": -9,
|
|
},
|
|
"flow_time": 20,
|
|
}
|
|
|
|
def construct(self):
|
|
self.initialize_plane()
|
|
self.initialize_vector_field()
|
|
plane = self.plane
|
|
field = self.vector_field
|
|
self.add(plane, field)
|
|
|
|
stream_lines = StreamLines(
|
|
field.func,
|
|
delta_x=0.3,
|
|
delta_y=0.3,
|
|
)
|
|
animated_stream_lines = AnimatedStreamLines(
|
|
stream_lines,
|
|
line_anim_class=ShowPassingFlashWithThinningStrokeWidth,
|
|
)
|
|
|
|
self.add(animated_stream_lines)
|
|
self.wait(self.flow_time)
|
|
|
|
|
|
class ShowHighVelocityCase(ShowPendulumPhaseFlow, MovingCameraScene):
|
|
CONFIG = {
|
|
"coordinate_plane_config": {
|
|
"x_max": 15,
|
|
"x_min": -15,
|
|
},
|
|
"vector_field_config": {
|
|
"x_max": 15,
|
|
},
|
|
"big_pendulum_config": {
|
|
"max_velocity_vector_length_to_length_ratio": 1,
|
|
},
|
|
}
|
|
|
|
def setup(self):
|
|
MovingCameraScene.setup(self)
|
|
|
|
def construct(self):
|
|
self.initialize_plane()
|
|
self.add(self.plane)
|
|
self.initialize_vector_field()
|
|
self.add(self.vector_field)
|
|
self.add_flexible_state()
|
|
self.show_trajectory()
|
|
|
|
def show_trajectory(self):
|
|
state = self.state
|
|
plane = self.plane
|
|
field = self.vector_field
|
|
frame = self.camera_frame
|
|
|
|
state.to_edge(DOWN, buff=SMALL_BUFF),
|
|
start_point = plane.coords_to_point(0, 4)
|
|
dot = self.get_state_controlling_dot(state)
|
|
dot.move_to(start_point)
|
|
state.update()
|
|
|
|
traj = VMobject()
|
|
traj.start_new_path(start_point)
|
|
dt = 0.01
|
|
total_time = 25
|
|
for x in range(int(total_time / dt)):
|
|
end = traj.points[-1]
|
|
dp_dt = field.func(end)
|
|
traj.add_smooth_curve_to(end + dp_dt * dt)
|
|
|
|
traj.set_stroke(WHITE, 2)
|
|
|
|
self.add(traj, dot)
|
|
self.play(
|
|
ShowCreation(
|
|
traj,
|
|
rate_func=linear,
|
|
),
|
|
UpdateFromFunc(
|
|
dot, lambda d: d.move_to(traj.points[-1])
|
|
),
|
|
ApplyMethod(
|
|
frame.shift, TAU * RIGHT,
|
|
rate_func=squish_rate_func(
|
|
smooth, 0, 0.3,
|
|
)
|
|
),
|
|
MaintainPositionRelativeTo(state.rect, frame),
|
|
run_time=total_time,
|
|
)
|
|
|
|
|
|
class TweakMuInFormula(Scene):
|
|
def construct(self):
|
|
pass
|
|
|
|
|
|
class TweakMuInVectorField(Scene):
|
|
def construct(self):
|
|
pass
|