mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 05:52:34 +08:00
VisualizeStates Scene
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
from active_projects.ode.part1.pendulum import *
|
||||
from active_projects.ode.part1.staging import *
|
||||
from active_projects.ode.part1.pi_scenes import *
|
||||
from active_projects.ode.part1.phase_space import *
|
||||
from active_projects.ode.part1.wordy_scenes import *
|
||||
|
||||
OUTPUT_DIRECTORY = "ode/part1"
|
||||
@ -28,5 +29,6 @@ ALL_SCENE_CLASSES = [
|
||||
ODEvsPDEinFrames,
|
||||
ProveTeacherWrong,
|
||||
SetAsideSeekingSolution,
|
||||
ReferencePiCollisionStateSpaces,
|
||||
VisualizeStates,
|
||||
]
|
||||
|
@ -43,6 +43,10 @@ class Pendulum(VGroup):
|
||||
"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):
|
||||
@ -53,7 +57,10 @@ class Pendulum(VGroup):
|
||||
self.rotating_group = VGroup(self.rod, self.weight)
|
||||
self.create_dashed_line()
|
||||
self.create_angle_arc()
|
||||
self.add_theta_label()
|
||||
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()
|
||||
@ -89,17 +96,27 @@ class Pendulum(VGroup):
|
||||
self.angle_arc = always_redraw(lambda: Arc(
|
||||
arc_center=self.get_fixed_point(),
|
||||
start_angle=-90 * DEGREES,
|
||||
angle=self.get_theta(),
|
||||
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(
|
||||
0.5 * omega * RIGHT,
|
||||
multiple * RIGHT,
|
||||
**self.velocity_vector_config,
|
||||
)
|
||||
vector.rotate(theta, about_point=ORIGIN)
|
||||
@ -124,7 +141,8 @@ class Pendulum(VGroup):
|
||||
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)
|
||||
norm = get_norm(vect)
|
||||
vect = normalize(vect) * (norm + self.theta_label_height)
|
||||
label.move_to(top + vect)
|
||||
return label
|
||||
|
||||
|
506
active_projects/ode/part1/phase_space.py
Normal file
506
active_projects/ode/part1/phase_space.py
Normal file
@ -0,0 +1,506 @@
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
from active_projects.ode.part1.shared_constructs import *
|
||||
from active_projects.ode.part1.pendulum import Pendulum
|
||||
|
||||
|
||||
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.7,
|
||||
"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": 3,
|
||||
"n_omegas": 5,
|
||||
"initial_grid_wait_time": 15,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.initialize_grid_of_states()
|
||||
self.initialize_plane()
|
||||
|
||||
simple = True
|
||||
if simple:
|
||||
self.add(self.plane)
|
||||
else:
|
||||
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):
|
||||
state_grid = self.state_grid
|
||||
pendulums = self.pendulums
|
||||
|
||||
title = TextMobject("All states")
|
||||
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
||||
self.all_states_title = title
|
||||
|
||||
self.remove(state_grid)
|
||||
state_grid.restore()
|
||||
for pendulum in pendulums:
|
||||
pendulum.end_swinging()
|
||||
|
||||
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,
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
ShowIncreasingSubsets(state_grid),
|
||||
ShowIncreasingSubsets(right_column_copy),
|
||||
run_time=2,
|
||||
)
|
||||
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)
|
||||
self.play(
|
||||
ShowCreation(plane),
|
||||
LaggedStart(*[
|
||||
TransformFromCopy(m1, m2)
|
||||
for m1, m2 in zip(
|
||||
VGroup(*it.chain(*state_grid)),
|
||||
VGroup(*it.chain(*dots)),
|
||||
)
|
||||
], lag_ratio=0.1, run_time=4)
|
||||
)
|
||||
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 * DR:
|
||||
self.play(dot.shift, vect)
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
# 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, 100)
|
||||
alphas = np.linspace(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.scale(2)
|
||||
abstract.to_corner(UR)
|
||||
physical = TextMobject("Physical")
|
||||
physical.next_to(state.get_top(), DOWN)
|
||||
|
||||
self.play(
|
||||
ApplyMethod(
|
||||
self.plane.set_stroke, YELLOW, 1,
|
||||
rate_func=there_and_back,
|
||||
lag_ratio=0.01,
|
||||
),
|
||||
Write(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):
|
||||
height = (FRAME_HEIGHT - SMALL_BUFF) / 2
|
||||
rect = Square(
|
||||
side_length=height,
|
||||
stroke_color=WHITE,
|
||||
stroke_width=2,
|
||||
fill_color="#111111",
|
||||
fill_opacity=1,
|
||||
)
|
||||
rect.to_corner(UL, buff=SMALL_BUFF / 2)
|
||||
pendulum = Pendulum(
|
||||
top_point=rect.get_center(),
|
||||
**self.big_pendulum_config
|
||||
)
|
||||
|
||||
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 not np.all(trajectory.points[-1] == point):
|
||||
traj.add_smooth_curve_to(point)
|
||||
trajectory.add_updater(update_trajectory)
|
||||
return trajectory
|
||||
|
||||
|
||||
class NewSceneName(Scene):
|
||||
def construct(self):
|
||||
pass
|
@ -768,6 +768,11 @@ class ODEvsPDEinFrames(Scene):
|
||||
pass
|
||||
|
||||
|
||||
class VisualizeStates(Scene):
|
||||
class ReferencePiCollisionStateSpaces(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class NewSceneName(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
Reference in New Issue
Block a user