mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 06:22:54 +08:00
Up to 3 body problem illustration
This commit is contained in:
@ -37,4 +37,9 @@ ALL_SCENE_CLASSES = [
|
|||||||
ShowHighVelocityCase,
|
ShowHighVelocityCase,
|
||||||
TweakMuInFormula,
|
TweakMuInFormula,
|
||||||
TweakMuInVectorField,
|
TweakMuInVectorField,
|
||||||
|
FromODEToVectorField,
|
||||||
|
LorenzVectorField,
|
||||||
|
ThreeBodiesInSpace,
|
||||||
|
ThreeBodySymbols,
|
||||||
|
AskAboutActuallySolving,
|
||||||
]
|
]
|
||||||
|
@ -836,20 +836,17 @@ class IntroduceVectorField(VisualizeStates):
|
|||||||
|
|
||||||
#
|
#
|
||||||
def get_vector_symbol(self, tex1, tex2):
|
def get_vector_symbol(self, tex1, tex2):
|
||||||
return Matrix(
|
t2c = {
|
||||||
[[tex1], [tex2]],
|
"{\\theta}": BLUE,
|
||||||
include_background_rectangle=True,
|
"{\\dot\\theta}": YELLOW,
|
||||||
bracket_h_buff=SMALL_BUFF,
|
"{\\omega}": YELLOW,
|
||||||
bracket_v_buff=SMALL_BUFF,
|
"{\\ddot\\theta}": RED,
|
||||||
|
}
|
||||||
|
return get_vector_symbol(
|
||||||
|
tex1, tex2,
|
||||||
element_to_mobject_config={
|
element_to_mobject_config={
|
||||||
"tex_to_color_map": {
|
"tex_to_color_map": t2c,
|
||||||
"{\\theta}": BLUE,
|
}
|
||||||
"{\\dot\\theta}": YELLOW,
|
|
||||||
"{\\omega}": YELLOW,
|
|
||||||
"{\\ddot\\theta}": RED,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
element_alignment_corner=ORIGIN,
|
|
||||||
).scale(0.9)
|
).scale(0.9)
|
||||||
|
|
||||||
def vector_field_func(self, point):
|
def vector_field_func(self, point):
|
||||||
@ -1001,9 +998,118 @@ class ShowHighVelocityCase(ShowPendulumPhaseFlow, MovingCameraScene):
|
|||||||
|
|
||||||
class TweakMuInFormula(Scene):
|
class TweakMuInFormula(Scene):
|
||||||
def construct(self):
|
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):
|
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.teacher.change, "maybe"
|
||||||
)
|
)
|
||||||
self.wait(8)
|
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_config = {
|
||||||
"tex_to_color_map": {
|
"tex_to_color_map": {
|
||||||
"{\\theta}": BLUE,
|
"{\\theta}": BLUE,
|
||||||
|
"{\\dot\\theta}": YELLOW,
|
||||||
|
"{\\ddot\\theta}": RED,
|
||||||
"{t}": WHITE,
|
"{t}": WHITE,
|
||||||
|
"{\\mu}": WHITE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ode = TexMobject(
|
ode = TexMobject(
|
||||||
"\\ddot {\\theta}({t})", "=",
|
"{\\ddot\\theta}({t})", "=",
|
||||||
"-\\mu \\dot {\\theta}({t})",
|
"-{\\mu} {\\dot\\theta}({t})",
|
||||||
"-{g \\over L} \\sin\\big({\\theta}({t})\\big)",
|
"-{g \\over L} \\sin\\big({\\theta}({t})\\big)",
|
||||||
**tex_config,
|
**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,
|
-np.sqrt(g / L) * np.sin(theta) - mu * omega,
|
||||||
0,
|
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
|
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):
|
class NewSceneName(Scene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
pass
|
pass
|
||||||
|
@ -139,3 +139,124 @@ class SetAsideSeekingSolution(Scene):
|
|||||||
eyes.blink,
|
eyes.blink,
|
||||||
rate_func=squish_rate_func(there_and_back)
|
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