mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 05:52:34 +08:00
Up to 3 body problem illustration
This commit is contained in:
@ -37,4 +37,9 @@ ALL_SCENE_CLASSES = [
|
||||
ShowHighVelocityCase,
|
||||
TweakMuInFormula,
|
||||
TweakMuInVectorField,
|
||||
FromODEToVectorField,
|
||||
LorenzVectorField,
|
||||
ThreeBodiesInSpace,
|
||||
ThreeBodySymbols,
|
||||
AskAboutActuallySolving,
|
||||
]
|
||||
|
@ -836,20 +836,17 @@ class IntroduceVectorField(VisualizeStates):
|
||||
|
||||
#
|
||||
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,
|
||||
t2c = {
|
||||
"{\\theta}": BLUE,
|
||||
"{\\dot\\theta}": YELLOW,
|
||||
"{\\omega}": YELLOW,
|
||||
"{\\ddot\\theta}": RED,
|
||||
}
|
||||
return get_vector_symbol(
|
||||
tex1, tex2,
|
||||
element_to_mobject_config={
|
||||
"tex_to_color_map": {
|
||||
"{\\theta}": BLUE,
|
||||
"{\\dot\\theta}": YELLOW,
|
||||
"{\\omega}": YELLOW,
|
||||
"{\\ddot\\theta}": RED,
|
||||
},
|
||||
},
|
||||
element_alignment_corner=ORIGIN,
|
||||
"tex_to_color_map": t2c,
|
||||
}
|
||||
).scale(0.9)
|
||||
|
||||
def vector_field_func(self, point):
|
||||
@ -1001,9 +998,118 @@ class ShowHighVelocityCase(ShowPendulumPhaseFlow, MovingCameraScene):
|
||||
|
||||
class TweakMuInFormula(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
ode = get_ode()
|
||||
ode.to_edge(DOWN, buff=LARGE_BUFF)
|
||||
mu = ode.get_part_by_tex("\\mu")
|
||||
lil_rect = SurroundingRectangle(mu, buff=0.5 * SMALL_BUFF)
|
||||
lil_rect.stretch(1.2, 1, about_edge=DOWN)
|
||||
lil_rect.set_stroke(PINK, 2)
|
||||
|
||||
interval = UnitInterval()
|
||||
interval.add_numbers(
|
||||
*np.arange(0, 1.2, 0.2)
|
||||
)
|
||||
interval.next_to(ode, UP, LARGE_BUFF)
|
||||
big_rect_seed = SurroundingRectangle(interval, buff=MED_SMALL_BUFF)
|
||||
big_rect_seed.stretch(1.5, 1, about_edge=DOWN)
|
||||
big_rect_seed.stretch(1.2, 0)
|
||||
big_rect = VGroup(*[
|
||||
DashedLine(v1, v2)
|
||||
for v1, v2 in adjacent_pairs(big_rect_seed.get_vertices())
|
||||
])
|
||||
big_rect.set_stroke(PINK, 2)
|
||||
|
||||
arrow = Arrow(
|
||||
lil_rect.get_top(),
|
||||
big_rect_seed.point_from_proportion(0.65),
|
||||
buff=SMALL_BUFF,
|
||||
)
|
||||
arrow.match_color(lil_rect)
|
||||
|
||||
mu_tracker = ValueTracker(0.1)
|
||||
get_mu = mu_tracker.get_value
|
||||
|
||||
triangle = Triangle(
|
||||
start_angle=-90 * DEGREES,
|
||||
stroke_width=0,
|
||||
fill_opacity=1,
|
||||
fill_color=WHITE,
|
||||
)
|
||||
triangle.set_height(0.2)
|
||||
triangle.add_updater(lambda t: t.next_to(
|
||||
interval.number_to_point(get_mu()),
|
||||
UP, buff=0,
|
||||
))
|
||||
|
||||
equation = VGroup(
|
||||
TexMobject("\\mu = "),
|
||||
DecimalNumber(),
|
||||
)
|
||||
equation.add_updater(
|
||||
lambda e: e.arrange(RIGHT).next_to(
|
||||
triangle, UP, SMALL_BUFF,
|
||||
).shift(0.4 * RIGHT)
|
||||
)
|
||||
equation[-1].add_updater(
|
||||
lambda d: d.set_value(get_mu()).shift(0.05 * UL)
|
||||
)
|
||||
|
||||
self.add(ode)
|
||||
self.play(ShowCreation(lil_rect))
|
||||
self.play(
|
||||
GrowFromPoint(interval, mu.get_center()),
|
||||
GrowFromPoint(triangle, mu.get_center()),
|
||||
GrowFromPoint(equation, mu.get_center()),
|
||||
TransformFromCopy(lil_rect, big_rect),
|
||||
ShowCreation(arrow)
|
||||
)
|
||||
self.wait()
|
||||
self.play(mu_tracker.set_value, 0.9, run_time=5)
|
||||
self.wait()
|
||||
|
||||
|
||||
class TweakMuInVectorField(Scene):
|
||||
class TweakMuInVectorField(ShowPendulumPhaseFlow):
|
||||
def construct(self):
|
||||
pass
|
||||
self.initialize_plane()
|
||||
plane = self.plane
|
||||
self.add(plane)
|
||||
|
||||
mu_tracker = ValueTracker(0.1)
|
||||
get_mu = mu_tracker.get_value
|
||||
|
||||
def vector_field_func(p):
|
||||
x, y = plane.point_to_coords(p)
|
||||
mu = get_mu()
|
||||
g = self.big_pendulum_config.get("gravity")
|
||||
L = self.big_pendulum_config.get("length")
|
||||
return pendulum_vector_field_func(
|
||||
x * RIGHT + y * UP,
|
||||
mu=mu, g=g, L=L
|
||||
)
|
||||
|
||||
def get_vector_field():
|
||||
return VectorField(
|
||||
vector_field_func,
|
||||
**self.vector_field_config,
|
||||
)
|
||||
|
||||
field = always_redraw(get_vector_field)
|
||||
self.add(field)
|
||||
|
||||
self.play(
|
||||
mu_tracker.set_value, 0.9,
|
||||
run_time=5,
|
||||
)
|
||||
field.suspend_updating()
|
||||
|
||||
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)
|
||||
|
@ -195,3 +195,17 @@ class ProveTeacherWrong(TeacherStudentsScene):
|
||||
self.teacher.change, "maybe"
|
||||
)
|
||||
self.wait(8)
|
||||
|
||||
|
||||
class AskAboutActuallySolving(Scene):
|
||||
def construct(self):
|
||||
ode = get_ode()
|
||||
ode.to_corner(UL)
|
||||
morty = self.teacher
|
||||
|
||||
self.student_says(
|
||||
"Yeah, yeah, but how do\\\\"
|
||||
"you acutally \\emph{solve} it?",
|
||||
target_mode="sassy",
|
||||
added_anims=[morty.change, "thinking"],
|
||||
)
|
||||
|
@ -20,12 +20,15 @@ def get_ode():
|
||||
tex_config = {
|
||||
"tex_to_color_map": {
|
||||
"{\\theta}": BLUE,
|
||||
"{\\dot\\theta}": YELLOW,
|
||||
"{\\ddot\\theta}": RED,
|
||||
"{t}": WHITE,
|
||||
"{\\mu}": WHITE,
|
||||
}
|
||||
}
|
||||
ode = TexMobject(
|
||||
"\\ddot {\\theta}({t})", "=",
|
||||
"-\\mu \\dot {\\theta}({t})",
|
||||
"{\\ddot\\theta}({t})", "=",
|
||||
"-{\\mu} {\\dot\\theta}({t})",
|
||||
"-{g \\over L} \\sin\\big({\\theta}({t})\\big)",
|
||||
**tex_config,
|
||||
)
|
||||
@ -39,3 +42,15 @@ def pendulum_vector_field_func(point, mu=0.1, g=9.8, L=3):
|
||||
-np.sqrt(g / L) * np.sin(theta) - mu * omega,
|
||||
0,
|
||||
])
|
||||
|
||||
|
||||
def get_vector_symbol(*texs, **kwargs):
|
||||
config = {
|
||||
"include_background_rectangle": True,
|
||||
"bracket_h_buff": SMALL_BUFF,
|
||||
"bracket_v_buff": SMALL_BUFF,
|
||||
"element_alignment_corner": ORIGIN,
|
||||
}
|
||||
config.update(kwargs)
|
||||
array = [[tex] for tex in texs]
|
||||
return Matrix(array, **config)
|
||||
|
@ -841,6 +841,168 @@ class BreakingSecondOrderIntoTwoFirstOrder(IntroduceVectorField):
|
||||
return system
|
||||
|
||||
|
||||
class FromODEToVectorField(Scene):
|
||||
def construct(self):
|
||||
matrix_config = {
|
||||
"bracket_v_buff": 2 * SMALL_BUFF,
|
||||
"element_to_mobject_config": {
|
||||
"tex_to_color_map": {
|
||||
"x": GREEN,
|
||||
"y": RED,
|
||||
"z": BLUE,
|
||||
},
|
||||
}
|
||||
}
|
||||
vect = get_vector_symbol(
|
||||
"x(t)", "y(t)", "z(t)",
|
||||
**matrix_config,
|
||||
)
|
||||
d_vect = get_vector_symbol(
|
||||
"\\sigma\\big(y(t) - x(t)\\big)",
|
||||
"x(t)\\big(\\rho - z(t)\\big) - y(t)",
|
||||
"x(t)y(t) - \\beta z(t)",
|
||||
**matrix_config
|
||||
)
|
||||
equation = VGroup(
|
||||
TexMobject("d \\over dt").scale(1.5),
|
||||
vect,
|
||||
TexMobject("="),
|
||||
d_vect
|
||||
)
|
||||
equation.scale(0.8)
|
||||
equation.arrange(RIGHT)
|
||||
equation.to_edge(UP)
|
||||
|
||||
arrow = Vector(DOWN, color=YELLOW)
|
||||
arrow.next_to(equation, DOWN)
|
||||
|
||||
self.add(equation)
|
||||
self.play(ShowCreation(arrow))
|
||||
self.wait()
|
||||
|
||||
|
||||
class LorenzVectorField(ExternallyAnimatedScene):
|
||||
pass
|
||||
|
||||
|
||||
class ThreeBodiesInSpace(SpecialThreeDScene):
|
||||
CONFIG = {
|
||||
"masses": [1, 2, 3],
|
||||
"G": 0.5,
|
||||
"play_time": 60,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.add_axes()
|
||||
self.add_bodies()
|
||||
self.add_trajectories()
|
||||
self.let_play()
|
||||
|
||||
def add_axes(self):
|
||||
axes = self.axes = self.get_axes()
|
||||
self.add(axes)
|
||||
|
||||
def add_bodies(self):
|
||||
bodies = self.bodies = VGroup()
|
||||
velocity_vectors = VGroup()
|
||||
|
||||
for mass in self.masses:
|
||||
body = self.get_sphere(
|
||||
checkerboard_colors=[DARK_BROWN, DARK_BROWN],
|
||||
stroke_width=0.1,
|
||||
)
|
||||
body.mass = mass
|
||||
body.set_width(0.2 * mass)
|
||||
|
||||
point = np.dot(
|
||||
2 * (np.random.random(3) - 0.5),
|
||||
[RIGHT, UP, OUT]
|
||||
)
|
||||
velocity = normalize(np.cross(point, OUT))
|
||||
body.move_to(point)
|
||||
body.velocity = velocity
|
||||
body.add_updater(self.update_body)
|
||||
|
||||
vect = self.get_velocity_vector_mob(body)
|
||||
|
||||
bodies.add(body)
|
||||
velocity_vectors.add(vect)
|
||||
|
||||
self.add(body)
|
||||
# self.add(vect)
|
||||
|
||||
total_mass = np.sum([body.mass for body in bodies])
|
||||
center_of_mass = reduce(op.add, [
|
||||
body.mass * body.get_center() / total_mass
|
||||
for body in bodies
|
||||
])
|
||||
average_momentum = reduce(op.add, [
|
||||
body.mass * body.velocity / total_mass
|
||||
for body in bodies
|
||||
])
|
||||
for body in bodies:
|
||||
body.shift(-center_of_mass)
|
||||
body.velocity -= average_momentum
|
||||
|
||||
def add_trajectories(self):
|
||||
def update_trajectory(traj, dt):
|
||||
new_point = traj.body.get_center()
|
||||
if get_norm(new_point - traj.points[-1]) > 0.01:
|
||||
traj.add_smooth_curve_to(new_point)
|
||||
|
||||
for body in self.bodies:
|
||||
traj = VMobject()
|
||||
traj.body = body
|
||||
traj.start_new_path(body.get_center())
|
||||
traj.set_stroke(WHITE, 1)
|
||||
traj.add_updater(update_trajectory)
|
||||
self.add(traj, body)
|
||||
|
||||
def let_play(self):
|
||||
self.move_camera(
|
||||
phi=70 * DEGREES,
|
||||
theta=-110 * DEGREES,
|
||||
run_time=3,
|
||||
)
|
||||
self.begin_ambient_camera_rotation()
|
||||
for x in range(6):
|
||||
self.wait(self.play_time / 6)
|
||||
|
||||
#
|
||||
def get_velocity_vector_mob(self, body):
|
||||
def draw_vector():
|
||||
center = body.get_center()
|
||||
vect = Arrow(
|
||||
center,
|
||||
center + body.velocity,
|
||||
buff=0,
|
||||
color=RED,
|
||||
)
|
||||
vect.set_shade_in_3d(True)
|
||||
return vect
|
||||
# length = vect.get_length()
|
||||
# if length > 2:
|
||||
# vect.scale(
|
||||
# 2 / length,
|
||||
# about_point=vect.get_start(),
|
||||
# )
|
||||
return always_redraw(draw_vector)
|
||||
|
||||
def update_body(self, body, dt):
|
||||
G = self.G
|
||||
acceleration = np.zeros(3)
|
||||
for body2 in self.bodies:
|
||||
if body2 is body:
|
||||
continue
|
||||
diff = body2.get_center() - body.get_center()
|
||||
m2 = body2.mass
|
||||
R = get_norm(diff)
|
||||
acceleration += G * m2 * diff / (R**3)
|
||||
|
||||
body.shift(body.velocity * dt)
|
||||
body.velocity += acceleration * dt
|
||||
|
||||
|
||||
class NewSceneName(Scene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
@ -139,3 +139,124 @@ class SetAsideSeekingSolution(Scene):
|
||||
eyes.blink,
|
||||
rate_func=squish_rate_func(there_and_back)
|
||||
)
|
||||
|
||||
|
||||
class ThreeBodySymbols(Scene):
|
||||
def construct(self):
|
||||
self.init_coord_groups()
|
||||
self.introduce_coord_groups()
|
||||
self.count_coords()
|
||||
|
||||
def init_coord_groups(self):
|
||||
kwargs = {
|
||||
"bracket_v_buff": 2 * SMALL_BUFF
|
||||
}
|
||||
positions = VGroup(*[
|
||||
get_vector_symbol(*[
|
||||
"{}_{}".format(s, i)
|
||||
for s in "xyz"
|
||||
], **kwargs)
|
||||
for i in range(1, 4)
|
||||
])
|
||||
velocities = VGroup(*[
|
||||
get_vector_symbol(*[
|
||||
"p^{}_{}".format(s, i)
|
||||
for s in "xyz"
|
||||
], **kwargs)
|
||||
for i in range(1, 4)
|
||||
])
|
||||
groups = VGroup(positions, velocities)
|
||||
colors = [GREEN, RED, BLUE]
|
||||
for group in groups:
|
||||
for matrix in group:
|
||||
matrix.coords = matrix.get_entries()
|
||||
for coord, color in zip(matrix.coords, colors):
|
||||
coord.set_color(color)
|
||||
group.arrange(RIGHT)
|
||||
groups.arrange(DOWN, buff=LARGE_BUFF)
|
||||
groups.to_edge(LEFT)
|
||||
|
||||
self.coord_groups = groups
|
||||
|
||||
def introduce_coord_groups(self):
|
||||
groups = self.coord_groups
|
||||
x_group, p_group = groups
|
||||
x_word = TextMobject("Positions")
|
||||
p_word = TextMobject("Momenta")
|
||||
words = VGroup(x_word, p_word)
|
||||
for word, group in zip(words, groups):
|
||||
word.next_to(group, UP)
|
||||
|
||||
rect_groups = VGroup()
|
||||
for group in groups:
|
||||
rect_group = VGroup(*[
|
||||
SurroundingRectangle(
|
||||
VGroup(*[
|
||||
tm.coords[i]
|
||||
for tm in group
|
||||
]),
|
||||
color=WHITE,
|
||||
stroke_width=2,
|
||||
)
|
||||
for i in range(3)
|
||||
])
|
||||
rect_groups.add(rect_group)
|
||||
|
||||
self.play(
|
||||
*[
|
||||
LaggedStartMap(
|
||||
FadeInFrom, group,
|
||||
lambda m: (m, UP),
|
||||
run_time=1,
|
||||
)
|
||||
for group in groups
|
||||
],
|
||||
*map(FadeInFromDown, words),
|
||||
)
|
||||
for rect_group in rect_groups:
|
||||
self.play(
|
||||
ShowCreationThenFadeOut(
|
||||
rect_group,
|
||||
lag_ratio=0.5,
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
def count_coords(self):
|
||||
coord_copies = VGroup()
|
||||
for group in self.coord_groups:
|
||||
for tex_mob in group:
|
||||
for coord in tex_mob.coords:
|
||||
coord_copy = coord.copy()
|
||||
coord_copy.set_stroke(
|
||||
WHITE, 2, background=True
|
||||
)
|
||||
coord_copies.add(coord_copy)
|
||||
|
||||
count = Integer()
|
||||
count_word = TextMobject("18", "degrees \\\\ of freedom")[1]
|
||||
count_group = VGroup(count, count_word)
|
||||
count_group.arrange(
|
||||
RIGHT,
|
||||
aligned_edge=DOWN,
|
||||
)
|
||||
count_group.scale(1.5)
|
||||
count_group.next_to(
|
||||
self.coord_groups, RIGHT,
|
||||
aligned_edge=DOWN,
|
||||
)
|
||||
count.add_updater(
|
||||
lambda m: m.set_value(len(coord_copies))
|
||||
)
|
||||
count.add_updater(
|
||||
lambda m: m.next_to(count_word[0][0], LEFT, aligned_edge=DOWN)
|
||||
)
|
||||
|
||||
self.add(count_group)
|
||||
self.play(
|
||||
# ChangeDecimalToValue(count, len(coord_copies)),
|
||||
ShowIncreasingSubsets(coord_copies),
|
||||
run_time=1.5,
|
||||
rate_func=linear,
|
||||
)
|
||||
self.play(FadeOut(coord_copies))
|
||||
|
Reference in New Issue
Block a user