Files
manim/active_projects/ode/part1/phase_space.py
2019-03-25 09:47:45 -07:00

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