Up to 3 body problem illustration

This commit is contained in:
Grant Sanderson
2019-03-25 16:44:08 -07:00
parent 9c381d15b6
commit 9a452f3fee
6 changed files with 441 additions and 18 deletions

View File

@ -37,4 +37,9 @@ ALL_SCENE_CLASSES = [
ShowHighVelocityCase,
TweakMuInFormula,
TweakMuInVectorField,
FromODEToVectorField,
LorenzVectorField,
ThreeBodiesInSpace,
ThreeBodySymbols,
AskAboutActuallySolving,
]

View File

@ -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)

View File

@ -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"],
)

View File

@ -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)

View File

@ -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

View File

@ -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))