3b1b-manim/active_projects/clacks/solution1.py
2019-01-24 21:47:40 -08:00

3188 lines
95 KiB
Python

from big_ol_pile_of_manim_imports import *
from active_projects.clacks.question import *
from old_projects.div_curl import ShowTwoPopulations
class FromPuzzleToSolution(MovingCameraScene):
def construct(self):
big_rect = FullScreenFadeRectangle()
big_rect.set_fill(DARK_GREY, 0.5)
self.add(big_rect)
rects = VGroup(ScreenRectangle(), ScreenRectangle())
rects.set_height(3)
rects.arrange_submobjects(RIGHT, buff=2)
titles = VGroup(
TextMobject("Puzzle"),
TextMobject("Solution"),
)
images = Group(
ImageMobject("BlocksAndWallExampleMass16"),
ImageMobject("AnalyzeCircleGeometry"),
)
for title, rect, image in zip(titles, rects, images):
title.scale(1.5)
title.next_to(rect, UP)
image.replace(rect)
self.add(image, rect, title)
frame = self.camera_frame
frame.save_state()
self.play(
frame.replace, images[0],
run_time=3
)
self.wait()
self.play(Restore(frame, run_time=3))
self.play(
frame.replace, images[1],
run_time=3,
)
self.wait()
class BlocksAndWallExampleMass16(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 16,
"velocity": -1.5,
},
},
"wait_time": 25,
}
class Mass16WithElasticLabel(Mass1e1WithElasticLabel):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 16,
}
},
}
class BlocksAndWallExampleMass64(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 64,
"velocity": -1.5,
},
},
"wait_time": 25,
}
class BlocksAndWallExampleMass1e4(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 1e4,
"velocity": -1.5,
},
},
"wait_time": 25,
}
class BlocksAndWallExampleMassMillion(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 1e6,
"velocity": -0.9,
"label_text": "$100^{3}$ kg"
},
},
"wait_time": 30,
"million_fade_time": 4,
"min_time_between_sounds": 0.002,
}
def setup(self):
super().setup()
self.add_million_label()
def add_million_label(self):
first_label = self.blocks.block1.label
brace = Brace(first_label[:-2], UP, buff=SMALL_BUFF)
new_label = TexMobject("1{,}000{,}000")
new_label.next_to(brace, UP, buff=SMALL_BUFF)
new_label.add(brace)
new_label.set_color(YELLOW)
def update_label(label):
d_time = self.get_time() - self.million_fade_time
opacity = smooth(d_time)
label.set_fill(opacity=d_time)
new_label.add_updater(update_label)
first_label.add(new_label)
class BlocksAndWallExampleMassTrillion(BlocksAndWallExample):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"mass": 1e12,
"velocity": -1,
},
},
"wait_time": 30,
"min_time_between_sounds": 0.001,
}
class First6DigitsOfPi(DigitsOfPi):
CONFIG = {"n_digits": 6}
class FavoritesInDescription(Scene):
def construct(self):
words = TextMobject("(See the description for \\\\ some favorites)")
words.scale(1.5)
self.add(words)
class V1EqualsV2Line(Scene):
def construct(self):
line = Line(LEFT, 7 * RIGHT)
eq = TexMobject("v_1", "=", "v_2")
eq.set_color_by_tex("v_", RED)
eq.next_to(RIGHT, UR, SMALL_BUFF)
self.play(
Write(eq, run_time=1),
ShowCreation(line),
)
self.wait()
class PhaseSpaceTitle(Scene):
def construct(self):
title = TextMobject("Phase space")
title.scale(1.5)
title.to_edge(UP)
rect = ScreenRectangle(height=6)
rect.next_to(title, DOWN)
self.add(rect)
self.play(Write(title, run_time=1))
self.wait()
class AskAboutFindingNewVelocities(Scene):
CONFIG = {
"floor_y": -3,
"wall_x": -6.5,
"wall_height": 7,
"block1_config": {
"mass": 10,
"fill_color": BLUE_E,
"velocity": -1,
},
"block2_config": {"mass": 1},
"block1_start_x": 7,
"block2_start_x": 3,
"v_arrow_scale_value": 1.0,
"is_halted": False,
}
def setup(self):
self.add_clack_sound_file()
def construct(self):
self.add_clack_sound_file()
self.add_floor()
self.add_wall()
self.add_blocks()
self.add_velocity_labels()
self.ask_about_transfer()
self.show_ms_and_vs()
self.show_value_on_equations()
def add_clack_sound_file(self):
self.clack_file = os.path.join(SOUND_DIR, "clack.wav")
def add_floor(self):
floor = self.floor = Line(
self.wall_x * RIGHT,
(FRAME_WIDTH / 2) * RIGHT,
)
floor.shift(self.floor_y * UP)
self.add(floor)
def add_wall(self):
wall = self.wall = Wall(height=self.wall_height)
wall.move_to(
self.wall_x * RIGHT + self.floor_y * UP,
DR,
)
self.add(wall)
def add_blocks(self):
block1 = self.block1 = Block(**self.block1_config)
block2 = self.block2 = Block(**self.block2_config)
blocks = self.blocks = VGroup(block1, block2)
block1.move_to(self.block1_start_x * RIGHT + self.floor_y * UP, DOWN)
block2.move_to(self.block2_start_x * RIGHT + self.floor_y * UP, DOWN)
self.add_velocity_phase_space_point()
# Add arrows
for block in blocks:
arrow = Vector(self.block_to_v_vector(block))
arrow.set_color(RED)
arrow.set_stroke(BLACK, 1, background=True)
arrow.move_to(block.get_center(), RIGHT)
block.arrow = arrow
block.add(arrow)
block.v_label = DecimalNumber(
block.velocity,
num_decimal_places=2,
background_stroke_width=2,
)
block.v_label.set_color(RED)
block.add(block.v_label)
# Add updater
blocks.add_updater(self.update_blocks)
self.add(
blocks,
block2.arrow, block1.arrow,
block2.v_label, block1.v_label,
)
def add_velocity_phase_space_point(self):
self.vps_point = VectorizedPoint([
np.sqrt(self.block1.mass) * self.block1.velocity,
np.sqrt(self.block2.mass) * self.block2.velocity,
0
])
def add_velocity_labels(self):
v_labels = self.get_next_velocity_labels()
self.add(v_labels)
def ask_about_transfer(self):
energy_expression, momentum_expression = \
self.get_energy_and_momentum_expressions()
energy_words = TextMobject("Conservation of energy:")
energy_words.move_to(UP)
energy_words.to_edge(LEFT, buff=1.5)
momentum_words = TextMobject("Conservation of momentum:")
momentum_words.next_to(
energy_words, DOWN,
buff=0.7,
)
energy_expression.next_to(energy_words, RIGHT, MED_LARGE_BUFF)
momentum_expression.next_to(energy_expression, DOWN)
momentum_expression.next_to(momentum_words, RIGHT)
velocity_labels = self.all_velocity_labels
randy = Randolph(height=2)
randy.next_to(velocity_labels, DR)
randy.save_state()
randy.fade(1)
# Up to collisions
self.go_through_next_collision(include_velocity_label_animation=True)
self.play(
randy.restore,
randy.change, "pondering", velocity_labels[0],
)
self.halt()
self.play(randy.look_at, velocity_labels[-1])
self.play(Blink(randy))
self.play(randy.change, "confused")
self.play(Blink(randy))
self.wait()
self.play(
FadeInFrom(energy_words, RIGHT),
FadeInFromDown(energy_expression),
FadeOut(randy),
)
self.wait()
self.play(
FadeInFrom(momentum_words, RIGHT),
FadeInFromDown(momentum_expression)
)
self.wait()
self.energy_expression = energy_expression
self.energy_words = energy_words
self.momentum_expression = momentum_expression
self.momentum_words = momentum_words
def show_ms_and_vs(self):
block1 = self.block1
block2 = self.block2
energy_expression = self.energy_expression
momentum_expression = self.momentum_expression
for block in self.blocks:
block.shift_onto_screen()
m1_labels = VGroup(
block1.label,
energy_expression.get_part_by_tex("m_1"),
momentum_expression.get_part_by_tex("m_1"),
)
m2_labels = VGroup(
block2.label,
energy_expression.get_part_by_tex("m_2"),
momentum_expression.get_part_by_tex("m_2"),
)
v1_labels = VGroup(
block1.v_label,
energy_expression.get_part_by_tex("v_1"),
momentum_expression.get_part_by_tex("v_1"),
)
v2_labels = VGroup(
block2.v_label,
energy_expression.get_part_by_tex("v_2"),
momentum_expression.get_part_by_tex("v_2"),
)
label_groups = VGroup(
m1_labels, m2_labels,
v1_labels, v2_labels,
)
for group in label_groups:
group.rects = VGroup(*map(
SurroundingRectangle,
group
))
for group in label_groups:
self.play(LaggedStart(
ShowCreation, group.rects,
lag_ratio=0.8,
run_time=1,
))
self.play(FadeOut(group.rects))
def show_value_on_equations(self):
energy_expression = self.energy_expression
momentum_expression = self.momentum_expression
energy_text = VGroup(energy_expression, self.energy_words)
momentum_text = VGroup(momentum_expression, self.momentum_words)
block1 = self.block1
block2 = self.block2
block1.save_state()
block2.save_state()
v_terms, momentum_v_terms = [
VGroup(*[
expr.get_part_by_tex("v_{}".format(d))
for d in [1, 2]
])
for expr in [energy_expression, momentum_expression]
]
v_braces = VGroup(*[
Brace(term, UP, buff=SMALL_BUFF)
for term in v_terms
])
v_decimals = VGroup(*[DecimalNumber(0) for x in range(2)])
def update_v_decimals(v_decimals):
values = self.get_velocities()
for decimal, value, brace in zip(v_decimals, values, v_braces):
decimal.set_value(value)
decimal.next_to(brace, UP, SMALL_BUFF)
update_v_decimals(v_decimals)
energy_const_brace, momentum_const_brace = [
Brace(
expr.get_part_by_tex("const"), UP,
buff=SMALL_BUFF,
)
for expr in [energy_expression, momentum_expression]
]
sqrt_m_vect = np.array([
np.sqrt(self.block1.mass),
np.sqrt(self.block2.mass),
0
])
def get_energy():
return 0.5 * get_norm(self.vps_point.get_location())**2
def get_momentum():
return np.dot(self.vps_point.get_location(), sqrt_m_vect)
energy_decimal = DecimalNumber(get_energy())
energy_decimal.next_to(energy_const_brace, UP, SMALL_BUFF)
momentum_decimal = DecimalNumber(get_momentum())
momentum_decimal.next_to(momentum_const_brace, UP, SMALL_BUFF)
VGroup(
energy_const_brace, energy_decimal,
momentum_const_brace, momentum_decimal,
).set_color(YELLOW)
self.play(
ShowCreationThenFadeAround(energy_expression),
momentum_text.set_fill, {"opacity": 0.25},
FadeOut(self.all_velocity_labels),
)
self.play(*[
*map(GrowFromCenter, v_braces),
*map(VFadeIn, v_decimals),
GrowFromCenter(energy_const_brace),
FadeIn(energy_decimal),
])
energy_decimal.add_updater(
lambda m: m.set_value(get_energy())
)
v_decimals.add_updater(update_v_decimals)
self.add(v_decimals)
self.unhalt()
self.vps_point.save_state()
for x in range(8):
self.go_through_next_collision()
energy_decimal.clear_updaters()
momentum_decimal.set_value(get_momentum())
self.halt()
self.play(*[
momentum_text.set_fill, {"opacity": 1},
FadeOut(energy_text),
FadeOut(energy_const_brace),
FadeOut(energy_decimal),
GrowFromCenter(momentum_const_brace),
FadeIn(momentum_decimal),
*[
ApplyMethod(b.next_to, vt, UP, SMALL_BUFF)
for b, vt in zip(v_braces, momentum_v_terms)
],
])
self.unhalt()
self.vps_point.restore()
momentum_decimal.add_updater(
lambda m: m.set_value(get_momentum())
)
momentum_decimal.add_updater(
lambda m: m.next_to(momentum_const_brace, UP, SMALL_BUFF)
)
for x in range(9):
self.go_through_next_collision()
self.wait(10)
# Helpers
def get_energy_and_momentum_expressions(self):
tex_to_color_map = {
"v_1": RED_B,
"v_2": RED_B,
"m_1": BLUE_C,
"m_2": BLUE_C,
}
energy_expression = TexMobject(
"\\frac{1}{2} m_1 (v_1)^2 + ",
"\\frac{1}{2} m_2 (v_2)^2 = ",
"\\text{const.}",
tex_to_color_map=tex_to_color_map,
)
momentum_expression = TexMobject(
"m_1 v_1 + m_2 v_2 =", "\\text{const.}",
tex_to_color_map=tex_to_color_map
)
return VGroup(
energy_expression,
momentum_expression,
)
def go_through_next_collision(self, include_velocity_label_animation=False):
block2 = self.block2
if block2.velocity >= 0:
self.wait_until(self.blocks_are_hitting)
self.add_sound(self.clack_file)
self.transfer_momentum()
edge = RIGHT
else:
self.wait_until(self.block2_is_hitting_wall)
self.add_sound(self.clack_file)
self.reflect_block2()
edge = LEFT
anims = [Flash(block2.get_edge_center(edge))]
if include_velocity_label_animation:
anims.append(self.get_next_velocity_labels_animation())
self.play(*anims, run_time=0.5)
def get_next_velocity_labels_animation(self):
return FadeInFrom(
self.get_next_velocity_labels(),
LEFT,
run_time=0.5
)
def get_next_velocity_labels(self, v1=None, v2=None):
new_labels = self.get_velocity_labels(v1, v2)
if hasattr(self, "all_velocity_labels"):
arrow = Vector(RIGHT)
arrow.next_to(self.all_velocity_labels)
new_labels.next_to(arrow, RIGHT)
new_labels.add(arrow)
else:
self.all_velocity_labels = VGroup()
self.all_velocity_labels.add(new_labels)
return new_labels
def get_velocity_labels(self, v1=None, v2=None):
default_vs = self.get_velocities()
v1 = v1 or default_vs[0]
v2 = v2 or default_vs[1]
labels = VGroup(
TexMobject("v_1 = {:.2f}".format(v1)),
TexMobject("v_2 = {:.2f}".format(v2)),
)
labels.arrange_submobjects(
DOWN,
buff=MED_SMALL_BUFF,
aligned_edge=LEFT,
)
labels.scale(0.9)
for label in labels:
label[:2].set_color(RED)
labels.next_to(self.wall, RIGHT)
labels.to_edge(UP, buff=MED_SMALL_BUFF)
return labels
def update_blocks(self, blocks, dt):
for block, velocity in zip(blocks, self.get_velocities()):
block.velocity = velocity
if not self.is_halted:
block.shift(block.velocity * dt * RIGHT)
center = block.get_center()
block.arrow.put_start_and_end_on(
center,
center + self.block_to_v_vector(block),
)
max_height = 0.25
block.v_label.set_value(block.velocity)
if block.v_label.get_height() > max_height:
block.v_label.set_height(max_height)
block.v_label.next_to(
block.arrow.get_start(), UP,
buff=SMALL_BUFF,
)
return blocks
def block_to_v_vector(self, block):
return block.velocity * self.v_arrow_scale_value * RIGHT
def blocks_are_hitting(self):
x1 = self.block1.get_left()[0]
x2 = self.block2.get_right()[0]
buff = 0.01
return (x1 < x2 + buff)
def block2_is_hitting_wall(self):
x2 = self.block2.get_left()[0]
buff = 0.01
return (x2 < self.wall_x + buff)
def get_velocities(self):
m1 = self.block1.mass
m2 = self.block2.mass
vps_coords = self.vps_point.get_location()
return [
vps_coords[0] / np.sqrt(m1),
vps_coords[1] / np.sqrt(m2),
]
def transfer_momentum(self):
m1 = self.block1.mass
m2 = self.block2.mass
theta = np.arctan(np.sqrt(m2 / m1))
self.reflect_block2()
self.vps_point.rotate(2 * theta, about_point=ORIGIN)
def reflect_block2(self):
self.vps_point.points[:, 1] *= -1
def halt(self):
self.is_halted = True
def unhalt(self):
self.is_halted = False
class IntroduceVelocityPhaseSpace(AskAboutFindingNewVelocities):
CONFIG = {
"wall_height": 1.5,
"floor_y": -3.5,
"block1_start_x": 5,
"block2_start_x": 0,
"axes_config": {
"x_axis_config": {
"x_min": -5.5,
"x_max": 6,
},
"y_axis_config": {
"x_min": -3.5,
"x_max": 4,
},
"number_line_config": {
"unit_size": 0.7,
},
},
"momentum_line_scale_factor": 4,
}
def construct(self):
self.add_wall_floor_and_blocks()
self.show_two_equations()
self.draw_axes()
self.draw_ellipse()
self.rescale_axes()
self.show_starting_point()
self.show_initial_collide()
self.ask_about_where_to_land()
self.show_conservation_of_momentum_equation()
self.show_momentum_line()
self.reiterate_meaning_of_line_and_circle()
self.reshow_first_jump()
self.show_bounce_off_wall()
self.show_reflection_about_x()
self.show_remaining_collisions()
def add_wall_floor_and_blocks(self):
self.add_floor()
self.add_wall()
self.add_blocks()
self.halt()
def show_two_equations(self):
equations = self.get_energy_and_momentum_expressions()
equations.arrange_submobjects(DOWN, buff=LARGE_BUFF)
equations.shift(UP)
v1_terms, v2_terms = v_terms = VGroup(*[
VGroup(*[
expr.get_parts_by_tex(tex)
for expr in equations
])
for tex in ("v_1", "v_2")
])
for eq in equations:
eq.highlighted_copy = eq.copy()
eq.highlighted_copy.set_fill(opacity=0)
eq.highlighted_copy.set_stroke(YELLOW, 3)
self.add(equations)
self.play(
ShowCreation(equations[0].highlighted_copy),
run_time=0.75,
)
self.play(
FadeOut(equations[0].highlighted_copy),
ShowCreation(equations[1].highlighted_copy),
run_time=0.75,
)
self.play(
FadeOut(equations[1].highlighted_copy),
run_time=0.75,
)
self.play(LaggedStart(
Indicate, v_terms,
lag_ratio=0.75,
rate_func=there_and_back,
))
self.wait()
self.equations = equations
def draw_axes(self):
equations = self.equations
energy_expression, momentum_expression = equations
axes = self.axes = Axes(**self.axes_config)
axes.to_edge(UP, buff=SMALL_BUFF)
axes.set_stroke(width=2)
# Axes labels
x_axis_labels = VGroup(
TexMobject("x = ", "v_1"),
TexMobject("x = ", "\\sqrt{m_1}", "\\cdot", "v_1"),
)
y_axis_labels = VGroup(
TexMobject("y = ", "v_2"),
TexMobject("y = ", "\\sqrt{m_2}", "\\cdot", "v_2"),
)
axis_labels = self.axis_labels = VGroup(x_axis_labels, y_axis_labels)
for label_group in axis_labels:
for label in label_group:
label.set_color_by_tex("v_", RED)
label.set_color_by_tex("m_", BLUE)
for label in x_axis_labels:
label.next_to(axes.x_axis.get_right(), UP)
for label in y_axis_labels:
label.next_to(axes.y_axis.get_top(), DR)
# Introduce axes and labels
self.play(
equations.scale, 0.8,
equations.to_corner, UL, {"buff": MED_SMALL_BUFF},
Write(axes),
)
self.wait()
self.play(
momentum_expression.set_fill, {"opacity": 0.2},
Indicate(energy_expression, scale_factor=1.05),
)
self.wait()
for n in range(2):
tex = "v_{}".format(n + 1)
self.play(
TransformFromCopy(
energy_expression.get_part_by_tex(tex),
axis_labels[n][0].get_part_by_tex(tex),
),
FadeInFromDown(axis_labels[n][0][0]),
)
# Show vps_dot
vps_dot = self.vps_dot = Dot(color=RED)
vps_dot.set_stroke(BLACK, 2, background=True)
vps_dot.add_updater(
lambda m: m.move_to(axes.coords_to_point(
*self.get_velocities()
))
)
vps_point = self.vps_point
vps_point.save_state()
kwargs = {
"path_arc": PI / 3,
"run_time": 2,
}
target_locations = [
6 * RIGHT + 2 * UP,
6 * RIGHT + 2 * DOWN,
6 * LEFT + 1 * UP,
]
self.add(vps_dot)
for target_location in target_locations:
self.play(
vps_point.move_to, target_location,
**kwargs,
)
self.play(Restore(vps_point, **kwargs))
self.wait()
def draw_ellipse(self):
vps_dot = self.vps_dot
vps_point = self.vps_point
axes = self.axes
energy_expression = self.equations[0]
ellipse = self.ellipse = Circle(color=YELLOW)
ellipse.set_stroke(BLACK, 5, background=True)
ellipse.rotate(PI)
mass_ratio = self.block1.mass / self.block2.mass
ellipse.replace(
Polygon(*[
axes.coords_to_point(x, y * np.sqrt(mass_ratio))
for x, y in [(1, 0), (0, 1), (-1, 0), (0, -1)]
]),
stretch=True
)
self.play(Indicate(energy_expression, scale_factor=1.05))
self.add(ellipse, vps_dot)
self.play(
ShowCreation(ellipse),
Rotating(vps_point, about_point=ORIGIN),
run_time=6,
rate_func=lambda t: smooth(t, 3),
)
self.wait()
def rescale_axes(self):
ellipse = self.ellipse
axis_labels = self.axis_labels
equations = self.equations
vps_point = self.vps_point
vps_dot = self.vps_dot
vps_dot.clear_updaters()
vps_dot.add_updater(
lambda m: m.move_to(ellipse.get_left())
)
mass_ratio = self.block1.mass / self.block2.mass
brief_circle = ellipse.copy()
brief_circle.stretch(np.sqrt(mass_ratio), 0)
brief_circle.set_stroke(WHITE, 2)
xy_equation = self.xy_equation = TexMobject(
"\\frac{1}{2}",
"\\left(", "x^2", "+", "y^2", "\\right)",
"=", "\\text{const.}"
)
xy_equation.scale(0.8)
xy_equation.next_to(equations[0], DOWN)
self.play(ShowCreationThenFadeOut(brief_circle))
for i, labels, block in zip(it.count(), axis_labels, self.blocks):
self.play(ShowCreationThenFadeAround(labels[0]))
self.play(
ReplacementTransform(labels[0][0], labels[1][0]),
ReplacementTransform(labels[0][-1], labels[1][-1]),
FadeInFromDown(labels[1][1:-1]),
ellipse.stretch, np.sqrt(block.mass), i,
)
self.wait()
vps_dot.clear_updaters()
vps_dot.add_updater(
lambda m: m.move_to(self.axes.coords_to_point(
*self.vps_point.get_location()[:2]
))
)
self.play(
FadeInFrom(xy_equation, UP),
FadeOut(equations[1])
)
self.wait()
curr_x = vps_point.get_location()[0]
for x in [0.5 * curr_x, 2 * curr_x, curr_x]:
axes_center = self.axes.coords_to_point(0, 0)
self.play(
vps_point.move_to, x * RIGHT,
UpdateFromFunc(
ellipse,
lambda m: m.set_width(
2 * get_norm(
vps_dot.get_center() - axes_center,
),
).move_to(axes_center)
),
run_time=2,
)
self.wait()
def show_starting_point(self):
vps_dot = self.vps_dot
block1, block2 = self.blocks
self.unhalt()
self.wait(3)
self.halt()
self.play(ShowCreationThenFadeAround(vps_dot))
self.wait()
def show_initial_collide(self):
self.unhalt()
self.go_through_next_collision()
self.wait()
self.halt()
self.wait()
def ask_about_where_to_land(self):
self.play(
Rotating(
self.vps_point,
about_point=ORIGIN,
run_time=6,
rate_func=lambda t: smooth(t, 3),
),
)
self.wait(2)
def show_conservation_of_momentum_equation(self):
equations = self.equations
energy_expression, momentum_expression = equations
momentum_expression.set_fill(opacity=1)
momentum_expression.shift(MED_SMALL_BUFF * UP)
momentum_expression.shift(MED_SMALL_BUFF * LEFT)
xy_equation = self.xy_equation
momentum_xy_equation = self.momentum_xy_equation = TexMobject(
"\\sqrt{m_1}", "x", "+",
"\\sqrt{m_2}", "y", "=",
"\\text{const.}",
)
momentum_xy_equation.set_color_by_tex("m_", BLUE)
momentum_xy_equation.scale(0.8)
momentum_xy_equation.next_to(
momentum_expression, DOWN,
buff=MED_LARGE_BUFF,
aligned_edge=RIGHT,
)
self.play(
FadeOut(xy_equation),
energy_expression.set_fill, {"opacity": 0.2},
FadeInFromDown(momentum_expression)
)
self.play(ShowCreationThenFadeAround(momentum_expression))
self.wait()
self.play(FadeInFrom(momentum_xy_equation, UP))
self.wait()
def show_momentum_line(self):
vps_dot = self.vps_dot
m1 = self.block1.mass
m2 = self.block2.mass
line = Line(np.sqrt(m2) * LEFT, np.sqrt(m1) * DOWN)
line.scale(self.momentum_line_scale_factor)
line.set_stroke(GREEN, 3)
line.move_to(vps_dot)
slope_label = TexMobject(
"\\text{Slope =}", "-\\sqrt{\\frac{m_1}{m_2}}"
)
slope_label.scale(0.8)
slope_label.next_to(vps_dot, LEFT, LARGE_BUFF)
slope_arrow = Arrow(
slope_label.get_right(),
line.point_from_proportion(0.45),
buff=SMALL_BUFF,
)
slope_group = VGroup(line, slope_label, slope_arrow)
foreground_mobs = VGroup(
self.equations[1], self.momentum_xy_equation,
self.blocks, self.vps_dot
)
for mob in foreground_mobs:
if isinstance(mob, TexMobject):
mob.set_stroke(BLACK, 3, background=True)
self.add(line, *foreground_mobs)
self.play(ShowCreation(line))
self.play(
FadeInFrom(slope_label, RIGHT),
GrowArrow(slope_arrow),
)
self.wait()
self.add(slope_group, *foreground_mobs)
self.play(slope_group.shift, 4 * RIGHT, run_time=3)
self.play(slope_group.shift, 5 * LEFT, run_time=3)
self.play(
slope_group.shift, RIGHT,
run_time=1,
rate_func=lambda t: t**4,
)
self.wait()
self.momentum_line = line
self.slope_group = slope_group
def reiterate_meaning_of_line_and_circle(self):
line_vect = self.momentum_line.get_vector()
vps_point = self.vps_point
for x in [0.25, -0.5, 0.25]:
self.play(
vps_point.shift, x * line_vect,
run_time=2
)
self.wait()
self.play(Rotating(
vps_point,
about_point=ORIGIN,
rate_func=lambda t: smooth(t, 3),
))
self.wait()
def reshow_first_jump(self):
vps_point = self.vps_point
curr_point = vps_point.get_location()
start_point = get_norm(curr_point) * LEFT
for n in range(8):
vps_point.move_to(
[start_point, curr_point][n % 2]
)
self.wait(0.5)
self.wait()
def show_bounce_off_wall(self):
self.unhalt()
self.go_through_next_collision()
self.halt()
def show_reflection_about_x(self):
vps_point = self.vps_point
curr_location = vps_point.get_location()
old_location = np.array(curr_location)
old_location[1] *= -1
# self.play(
# ApplyMethod(
# self.block2.move_to, self.wall.get_corner(DR), DL,
# path_arc=30 * DEGREES,
# )
# )
for n in range(4):
self.play(
vps_point.move_to,
[old_location, curr_location][n % 2]
)
self.wait()
group = VGroup(
self.ellipse,
self.lines[-1],
self.vps_dot.copy().clear_updaters()
)
for x in range(2):
self.play(
Rotate(
group, PI, RIGHT,
about_point=self.axes.coords_to_point(0, 0)
),
)
self.remove(group[-1])
def show_remaining_collisions(self):
line = self.momentum_line
# slope_group = self.slope_group
vps_dot = self.vps_dot
axes = self.axes
slope = np.sqrt(self.block2.mass / self.block1.mass)
end_region = Polygon(
axes.coords_to_point(0, 0),
axes.coords_to_point(10, 0),
axes.coords_to_point(10, slope * 10),
stroke_width=0,
fill_color=GREEN,
fill_opacity=0.3
)
self.unhalt()
for x in range(7):
self.go_through_next_collision()
if x == 0:
self.halt()
self.play(line.move_to, vps_dot)
self.wait()
self.unhalt()
self.play(FadeIn(end_region))
self.go_through_next_collision()
self.wait(5)
# Helpers
def add_update_line(self, func):
if not hasattr(self, "lines"):
self.lines = VGroup()
if hasattr(self, "vps_dot"):
old_vps_point = self.vps_dot.get_center()
func()
self.vps_dot.update()
new_vps_point = self.vps_dot.get_center()
line = Line(old_vps_point, new_vps_point)
line.set_stroke(WHITE, 2)
self.add(line)
self.lines.add(line)
else:
func()
def transfer_momentum(self):
self.add_update_line(super().transfer_momentum)
def reflect_block2(self):
self.add_update_line(super().reflect_block2)
class IntroduceVelocityPhaseSpaceWith16(IntroduceVelocityPhaseSpace):
CONFIG = {
"block1_config": {
"mass": 16,
"velocity": -0.5,
},
"momentum_line_scale_factor": 0,
}
class SimpleRect(Scene):
def construct(self):
self.add(Rectangle(width=6, height=2, color=WHITE))
class SurprisedRandy(Scene):
def construct(self):
randy = Randolph()
self.play(FadeIn(randy))
self.play(randy.change, "surprised", 3 * UR)
self.play(Blink(randy))
self.wait()
self.play(randy.change, "pondering", 3 * UR)
self.play(Blink(randy))
self.wait(2)
self.play(FadeOut(randy))
class HuntForPi(TeacherStudentsScene):
def construct(self):
self.student_says(
"Hunt for $\\pi$!",
bubble_kwargs={"direction": LEFT},
target_mode="hooray"
)
self.change_all_student_modes(
"hooray",
added_anims=[self.teacher.change, "happy"]
)
self.wait()
class StretchBySqrt10(Scene):
def construct(self):
arrow = DoubleArrow(2 * LEFT, 2 * RIGHT)
arrow.tip[1].shift(0.05 * LEFT)
value = TexMobject("\\sqrt{10}")
value.next_to(arrow, UP)
arrow.save_state()
arrow.stretch(0, 0)
self.play(
Restore(arrow),
Write(value, run_time=1),
)
self.wait()
class XCoordNegative(Scene):
def construct(self):
rect = Rectangle(height=4, width=4)
rect.set_stroke(width=0)
rect.set_fill(RED, 0.5)
rect.save_state()
rect.stretch(0, 0, about_edge=RIGHT)
self.play(Restore(rect))
self.wait()
class YCoordZero(Scene):
def construct(self):
rect = Rectangle(height=4, width=8)
rect.set_stroke(width=0)
rect.set_fill(WHITE, 0.5)
rect.save_state()
self.play(
rect.stretch, 0.01, 1,
rect.set_fill, {"opacity": 1}
)
self.wait()
class CircleDiagramFromSlidingBlocks(Scene):
CONFIG = {
"BlocksAndWallSceneClass": BlocksAndWallExampleMass1e1,
"circle_config": {
"radius": 2,
"stroke_color": YELLOW,
"stroke_width": 3,
},
"lines_style": {
"stroke_color": WHITE,
"stroke_width": 2,
},
"axes_config": {
"style": {
"stroke_color": LIGHT_GREY,
"stroke_width": 1,
},
"width": 5,
"height": 4.5,
},
"end_zone_style": {
"stroke_width": 0,
"fill_color": GREEN,
"fill_opacity": 0.3,
},
}
def construct(self):
sliding_blocks_scene = self.BlocksAndWallSceneClass(
show_flash_animations=False,
write_to_movie=False,
wait_time=0,
)
blocks = sliding_blocks_scene.blocks
times = [pair[1] for pair in blocks.clack_data]
self.mass_ratio = 1 / blocks.mass_ratio
self.show_circle_lines(
times=times,
slope=(-1 / np.sqrt(blocks.mass_ratio))
)
def show_circle_lines(self, times, slope):
circle = self.get_circle()
axes = self.get_axes()
lines = self.get_lines(circle.radius, slope)
end_zone = self.get_end_zone()
dot = Dot(color=RED, radius=0.06)
dot.move_to(lines[0].get_start())
self.add(end_zone, axes, circle, dot)
last_time = 0
for time, line in zip(times, lines):
if time > 300:
time = last_time + 1
self.wait(time - last_time)
last_time = time
dot.move_to(line.get_end())
self.add(line, dot)
self.wait()
def get_circle(self):
circle = Circle(**self.circle_config)
circle.rotate(PI) # Nice to have start point on left
return circle
def get_axes(self):
config = self.axes_config
axes = VGroup(
Line(LEFT, RIGHT).set_width(config["width"]),
Line(DOWN, UP).set_height(config["height"])
)
axes.set_style(**config["style"])
return axes
def get_lines(self, radius, slope):
theta = np.arctan(-1 / slope)
n_clacks = int(PI / theta)
points = []
for n in range(n_clacks + 1):
theta_mult = (n + 1) // 2
angle = 2 * theta * theta_mult
if n % 2 == 0:
angle *= -1
new_point = radius * np.array([
-np.cos(angle), -np.sin(angle), 0
])
points.append(new_point)
lines = VGroup(*[
Line(p1, p2)
for p1, p2 in zip(points, points[1:])
])
lines.set_style(**self.lines_style)
return lines
def get_end_zone(self):
slope = 1 / np.sqrt(self.mass_ratio)
x = self.axes_config["width"] / 2
zone = Polygon(
ORIGIN, x * RIGHT, x * RIGHT + slope * x * UP,
)
zone.set_style(**self.end_zone_style)
return zone
class CircleDiagramFromSlidingBlocksSameMass(CircleDiagramFromSlidingBlocks):
CONFIG = {
"BlocksAndWallSceneClass": BlocksAndWallExampleSameMass
}
class CircleDiagramFromSlidingBlocksSameMass1e1(CircleDiagramFromSlidingBlocks):
CONFIG = {
"BlocksAndWallSceneClass": BlocksAndWallExampleMass1e1
}
class CircleDiagramFromSlidingBlocks1e2(CircleDiagramFromSlidingBlocks):
CONFIG = {
"BlocksAndWallSceneClass": BlocksAndWallExampleMass1e2
}
class CircleDiagramFromSlidingBlocks1e4(CircleDiagramFromSlidingBlocks):
CONFIG = {
"BlocksAndWallSceneClass": BlocksAndWallExampleMass1e4
}
class AnnouncePhaseDiagram(CircleDiagramFromSlidingBlocks):
def construct(self):
pd_words = TextMobject("Phase diagram")
pd_words.scale(1.5)
pd_words.move_to(self.hold_up_spot, DOWN)
pd_words_border = pd_words.copy()
pd_words_border.set_stroke(YELLOW, 2)
pd_words_border.set_fill(opacity=0)
simple_words = TextMobject("Simple but powerful")
simple_words.next_to(pd_words, UP, LARGE_BUFF)
simple_words.shift(LEFT)
simple_words.set_color(BLUE)
simple_arrow = Arrow(
simple_words.get_bottom(),
pd_words.get_top(),
color=simple_words.get_color(),
)
self.play(
self.teacher.change, "raise_right_hand",
FadeInFromDown(pd_words)
)
self.change_student_modes(
"pondering", "thinking", "pondering",
added_anims=[ShowCreationThenFadeOut(pd_words_border)]
)
self.wait()
self.play(
FadeInFrom(simple_words, RIGHT),
GrowArrow(simple_arrow),
self.teacher.change, "hooray",
)
self.change_student_modes(
"thinking", "happy", "thinking",
)
self.wait(3)
class AnalyzeCircleGeometry(CircleDiagramFromSlidingBlocks, MovingCameraScene):
CONFIG = {
"mass_ratio": 16,
"circle_config": {
"radius": 3,
},
"axes_config": {
"width": FRAME_WIDTH,
"height": FRAME_HEIGHT,
},
"lines_style": {
"stroke_width": 2,
},
}
def construct(self):
self.add_mass_ratio_label()
self.add_circle_with_lines()
self.show_equal_arc_lengths()
self.use_arc_lengths_to_count()
self.focus_on_three_points()
self.show_likewise_for_all_jumps()
self.drop_arc_for_each_hop()
self.try_adding_one_more_arc()
self.zoom_out()
def add_mass_ratio_label(self, mass_ratio=None):
mass_ratio = mass_ratio or self.mass_ratio
mass_ratio_label = TextMobject(
"Mass ratio =", "{:,} : 1".format(mass_ratio)
)
mass_ratio_label.to_corner(UL, buff=MED_SMALL_BUFF)
self.add(mass_ratio_label)
self.mass_ratio_label = mass_ratio_label
def add_circle_with_lines(self):
circle = self.get_circle()
axes = self.get_axes()
axes_labels = self.get_axes_labels(axes)
slope = -np.sqrt(self.mass_ratio)
lines = self.get_lines(
radius=circle.radius,
slope=slope,
)
end_zone = self.get_end_zone()
end_zone_words = TextMobject("End zone")
end_zone_words.set_height(0.25)
end_zone_words.next_to(ORIGIN, UP, SMALL_BUFF)
end_zone_words.to_edge(RIGHT, buff=MED_SMALL_BUFF)
end_zone_words.set_color(GREEN)
self.add(
axes, axes_labels,
circle, end_zone, end_zone_words,
)
self.play(ShowCreation(lines, run_time=3, rate_func=None))
self.wait()
self.set_variables_as_attrs(
circle, axes, lines,
end_zone, end_zone_words,
)
def show_equal_arc_lengths(self):
circle = self.circle
radius = circle.radius
theta = self.theta = np.arctan(1 / np.sqrt(self.mass_ratio))
n_arcs = int(PI / (2 * theta))
lower_arcs = VGroup(*[
Arc(
start_angle=(PI + n * 2 * theta),
angle=(2 * theta),
radius=radius
)
for n in range(n_arcs + 1)
])
lower_arcs[0::2].set_color(RED)
lower_arcs[1::2].set_color(BLUE)
upper_arcs = lower_arcs.copy()
upper_arcs.rotate(PI, axis=RIGHT, about_point=ORIGIN)
upper_arcs[0::2].set_color(BLUE)
upper_arcs[1::2].set_color(RED)
all_arcs = VGroup(*it.chain(*zip(lower_arcs, upper_arcs)))
if int(PI / theta) % 2 == 1:
all_arcs.remove(all_arcs[-1])
arc_copies = lower_arcs.copy()
for arc_copy in arc_copies:
arc_copy.generate_target()
for arc in arc_copies:
arc.target.rotate(-(arc.start_angle - PI + theta))
equal_signs = VGroup(*[
TexMobject("=") for x in range(len(lower_arcs))
])
equal_signs.scale(0.8)
for sign in equal_signs:
sign.generate_target()
movers = VGroup(*it.chain(*zip(
arc_copies, equal_signs
)))
movers.remove(movers[-1])
mover_targets = VGroup(*[mover.target for mover in movers])
mover_targets.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
mover_targets.next_to(ORIGIN, DOWN)
mover_targets.to_edge(LEFT)
equal_signs.scale(0)
equal_signs.fade(1)
equal_signs.move_to(mover_targets)
all_arcs.save_state()
for arc in all_arcs:
arc.rotate(90 * DEGREES)
arc.fade(1)
arc.set_stroke(width=20)
self.play(Restore(
all_arcs, submobject_mode="lagged_start",
run_time=2,
))
self.wait()
self.play(LaggedStart(MoveToTarget, movers))
self.wait()
self.arcs_equation = movers
self.lower_arcs = lower_arcs
self.upper_arcs = upper_arcs
self.all_arcs = all_arcs
def use_arc_lengths_to_count(self):
all_arcs = self.all_arcs
lines = self.lines
arc_counts = VGroup()
for n, arc in enumerate(all_arcs):
count_mob = Integer(n + 1)
count_mob.scale(0.75)
buff = SMALL_BUFF
if len(all_arcs) > 100:
count_mob.scale(0.1)
count_mob.set_stroke(WHITE, 0.25)
buff = 0.4 * SMALL_BUFF
point = arc.point_from_proportion(0.5)
count_mob.next_to(point, normalize(point), buff)
arc_counts.add(count_mob)
self.play(
FadeOut(lines),
FadeOut(all_arcs),
FadeOut(self.arcs_equation),
)
self.play(
ShowIncreasingSubsets(all_arcs),
ShowIncreasingSubsets(lines),
ShowIncreasingSubsets(arc_counts),
run_time=5,
rate_func=bezier([0, 0, 1, 1])
)
self.wait()
for group in all_arcs, arc_counts:
targets = VGroup()
for elem in group:
elem.generate_target()
targets.add(elem.target)
targets.space_out_submobjects(1.2)
kwargs = {
"rate_func": there_and_back,
"run_time": 3,
}
self.play(
LaggedStart(MoveToTarget, all_arcs, **kwargs),
LaggedStart(MoveToTarget, arc_counts, **kwargs),
)
self.arc_counts = arc_counts
def focus_on_three_points(self):
lines = self.lines
arcs = self.all_arcs
arc_counts = self.arc_counts
theta = self.theta
arc = arcs[4]
lines.save_state()
line_pair = lines[3:5]
lines_to_fade = VGroup(*lines[:3], *lines[5:])
three_points = [
line_pair[0].get_start(),
line_pair[1].get_start(),
line_pair[1].get_end(),
]
three_dots = VGroup(*map(Dot, three_points))
three_dots.set_color(RED)
theta_arc = Arc(
radius=1,
start_angle=-90 * DEGREES,
angle=theta
)
theta_arc.shift(three_points[1])
theta_label = TexMobject("\\theta")
theta_label.next_to(theta_arc, DOWN, SMALL_BUFF)
center_lines = VGroup(
Line(three_points[0], ORIGIN),
Line(ORIGIN, three_points[2]),
)
center_lines.match_style(line_pair)
two_theta_arc = Arc(
radius=1,
start_angle=(center_lines[0].get_angle() + PI),
angle=2 * theta
)
two_theta_label = TexMobject("2\\theta")
arc_center = two_theta_arc.point_from_proportion(0.5)
two_theta_label.next_to(
arc_center, normalize(arc_center), SMALL_BUFF
)
two_theta_label.shift(SMALL_BUFF * RIGHT)
to_fade = VGroup(arc_counts, arcs, lines_to_fade)
self.play(
LaggedStart(
FadeOut, VGroup(*to_fade.family_members_with_points())
)
)
lines_to_fade.fade(1)
self.play(FadeInFromLarge(three_dots[0]))
self.play(TransformFromCopy(*three_dots[:2]))
self.play(TransformFromCopy(*three_dots[1:3]))
self.wait()
self.play(
ShowCreation(theta_arc),
FadeInFrom(theta_label, UP)
)
self.wait()
self.play(
line_pair.set_stroke, WHITE, 1,
TransformFromCopy(line_pair, center_lines),
TransformFromCopy(theta_arc, two_theta_arc),
TransformFromCopy(theta_label, two_theta_label),
)
self.wait()
self.play(
TransformFromCopy(two_theta_arc, arc),
two_theta_label.move_to, 2.7 * arc_center,
)
self.wait()
self.three_dots = three_dots
self.theta_group = VGroup(theta_arc, theta_label)
self.center_lines_group = VGroup(
center_lines, two_theta_arc,
)
self.two_theta_label = two_theta_label
def show_likewise_for_all_jumps(self):
lines = self.lines
arcs = self.all_arcs
every_other_line = lines[::2]
self.play(
Restore(
lines,
submobject_mode="lagged_start",
run_time=2
),
FadeOut(self.center_lines_group),
FadeOut(self.three_dots),
)
self.play(LaggedStart(
ApplyFunction, every_other_line,
lambda line: (
lambda l: l.scale(10 / l.get_length()).set_stroke(BLUE, 3),
line
)
))
self.play(Restore(lines))
self.wait()
# Shift theta label
last_point = lines[3].get_end()
last_arc = arcs[4]
two_theta_label = self.two_theta_label
theta_group_copy = self.theta_group.copy()
for line, arc in zip(lines[5:10:2], arcs[6:11:2]):
new_point = line.get_end()
arc_point = arc.point_from_proportion(0.5)
self.play(
theta_group_copy.shift, new_point - last_point,
two_theta_label.move_to, 1.1 * arc_point,
FadeIn(arc),
FadeOut(last_arc),
)
self.wait()
last_point = new_point
last_arc = arc
self.play(
FadeOut(theta_group_copy),
FadeOut(two_theta_label),
FadeOut(last_arc),
)
self.wait()
def drop_arc_for_each_hop(self):
lines = self.lines
arcs = self.all_arcs
two_theta_labels = VGroup()
wedges = VGroup()
for arc in arcs:
label = TexMobject("2\\theta")
label.scale(0.8)
label.move_to(1.1 * arc.point_from_proportion(0.5))
two_theta_labels.add(label)
wedge = arc.copy()
wedge.add_control_points([
*3 * [ORIGIN],
*3 * [wedge.points[0]]
])
wedge.set_stroke(width=0)
wedge.set_fill(arc.get_color(), 0.2)
wedges.add(wedge)
self.remove(lines)
for line, arc, label, wedge in zip(lines, arcs, two_theta_labels, wedges):
self.play(
ShowCreation(line),
FadeInFrom(arc, normalize(arc.get_center())),
FadeInFrom(label, normalize(arc.get_center())),
FadeIn(wedge),
)
self.wedges = wedges
self.two_theta_labels = two_theta_labels
def try_adding_one_more_arc(self):
wedges = self.wedges
theta = self.theta
last_wedge = wedges[-1]
new_wedge = last_wedge.copy()
new_wedge.set_color(PURPLE)
new_wedge.set_stroke(WHITE, 1)
self.play(FadeIn(new_wedge))
for angle in [-2 * theta, 4 * DEGREES, -2 * DEGREES]:
self.play(Rotate(new_wedge, angle, about_point=ORIGIN))
self.wait()
self.play(
new_wedge.shift, 10 * RIGHT,
rate_func=running_start,
path_arc=-30 * DEGREES,
)
self.remove(new_wedge)
def zoom_out(self):
frame = self.camera_frame
self.play(
frame.scale, 2, {"about_point": (TOP + RIGHT_SIDE)},
run_time=3
)
self.wait()
# Helpers
def get_axes_labels(self, axes):
axes_labels = VGroup(
TexMobject("x = ", "\\sqrt{m_1}", "\\cdot", "v_1"),
TexMobject("y = ", "\\sqrt{m_2}", "\\cdot", "v_2"),
)
for label in axes_labels:
label.set_height(0.4)
axes_labels[0].next_to(ORIGIN, DOWN, SMALL_BUFF)
axes_labels[0].to_edge(RIGHT, MED_SMALL_BUFF)
axes_labels[1].next_to(ORIGIN, RIGHT, SMALL_BUFF)
axes_labels[1].to_edge(UP, SMALL_BUFF)
return axes_labels
class InscribedAngleTheorem(Scene):
def construct(self):
self.add_title()
self.show_circle()
self.let_point_vary()
def add_title(self):
title = TextMobject("Inscribed angle theorem")
title.scale(1.5)
title.to_edge(UP)
self.add(title)
self.title = title
def show_circle(self):
# Boy is this over engineered...
circle = self.circle = Circle(
color=BLUE,
radius=2,
)
center_dot = Dot(circle.get_center(), color=WHITE)
self.add(circle, center_dot)
angle_trackers = self.angle_trackers = VGroup(
ValueTracker(TAU / 4),
ValueTracker(PI),
ValueTracker(-TAU / 4),
)
def get_point(angle):
return circle.point_from_proportion(
(angle % TAU) / TAU
)
def get_dot(angle):
dot = Dot(get_point(angle))
dot.set_color(RED)
dot.set_stroke(BLACK, 3, background=True)
return dot
def get_dots():
return VGroup(*[
get_dot(at.get_value())
for at in angle_trackers
])
def update_labels(labels):
center = circle.get_center()
for dot, label in zip(dots, labels):
label.move_to(
center + 1.2 * (dot.get_center() - center)
)
def get_lines():
lines = VGroup(*[
Line(d1.get_center(), d2.get_center())
for d1, d2 in zip(dots, dots[1:])
])
lines.set_stroke(WHITE, 3)
return lines
def get_center_lines():
points = [
dots[0].get_center(),
circle.get_center(),
dots[2].get_center(),
]
lines = VGroup(*[
Line(p1, p2)
for p1, p2 in zip(points, points[1:])
])
lines.set_stroke(LIGHT_GREY, 3)
return lines
def get_angle_label(lines, tex, reduce_angle=True):
a1 = (lines[0].get_angle() + PI) % TAU
a2 = lines[1].get_angle()
diff = (a2 - a1)
if reduce_angle:
diff = ((diff + PI) % TAU) - PI
point = lines[0].get_end()
arc = Arc(
start_angle=a1,
angle=diff,
radius=0.5,
)
arc.shift(point)
arc_center = arc.point_from_proportion(0.5)
label = TexMobject(tex)
vect = (arc_center - point)
vect = (0.3 + get_norm(vect)) * normalize(vect)
label.move_to(point + vect)
return VGroup(arc, label)
def get_theta_label():
return get_angle_label(lines, "\\theta")
def get_2theta_label():
return get_angle_label(center_lines, "2\\theta", False)
dots = get_dots()
lines = get_lines()
center_lines = get_center_lines()
labels = VGroup(*[
TexMobject("P_{}".format(n + 1))
for n in range(3)
])
update_labels(labels)
theta_label = get_theta_label()
two_theta_label = get_2theta_label()
self.play(
FadeInFromDown(labels[0]),
FadeInFromLarge(dots[0]),
)
self.play(
TransformFromCopy(*labels[:2]),
TransformFromCopy(*dots[:2]),
ShowCreation(lines[0]),
)
self.play(
ShowCreation(lines[1]),
TransformFromCopy(*labels[1:3]),
TransformFromCopy(*dots[1:3]),
Write(theta_label),
)
self.wait()
# Add updaters
labels.add_updater(update_labels)
dots.add_updater(lambda m: m.become(get_dots()))
lines.add_updater(lambda m: m.become(get_lines()))
center_lines.add_updater(lambda m: m.become(get_center_lines()))
theta_label.add_updater(lambda m: m.become(get_theta_label()))
two_theta_label.add_updater(lambda m: m.become(get_2theta_label()))
self.add(labels, lines, dots, theta_label)
# Further animations
self.play(
angle_trackers[0].set_value, TAU / 8,
)
self.play(
angle_trackers[2].set_value, -TAU / 8,
)
self.wait()
center_lines.update()
two_theta_label.update()
self.play(
TransformFromCopy(lines.copy().clear_updaters(), center_lines),
TransformFromCopy(theta_label.copy().clear_updaters(), two_theta_label),
)
self.wait()
self.add(center_lines, two_theta_label)
def let_point_vary(self):
p1_tracker, p2_tracker, p3_tracker = self.angle_trackers
kwargs = {"run_time": 2}
for angle in [TAU / 4, 3 * TAU / 4]:
self.play(
p2_tracker.set_value, angle,
**kwargs
)
self.wait()
self.play(
p1_tracker.set_value, PI,
**kwargs
)
self.wait()
self.play(
p3_tracker.set_value, TAU / 3,
**kwargs
)
self.wait()
self.play(
p2_tracker.set_value, 7 * TAU / 8,
**kwargs
)
self.wait()
class SimpleSlopeLabel(Scene):
def construct(self):
label = TexMobject(
"\\text{Slope}", "=",
"-\\frac{\\sqrt{m_1}}{\\sqrt{m_2}}"
)
vector = Vector(DOWN + 2 * LEFT, color=WHITE)
vector.move_to(label[0].get_bottom(), UR)
vector.shift(SMALL_BUFF * DOWN)
self.play(
Write(label),
GrowArrow(vector),
)
self.wait()
class AddTwoThetaManyTimes(Scene):
def construct(self):
expression = TexMobject(
"2\\theta", "+",
"2\\theta", "+",
"2\\theta", "+",
"\\cdots", "+",
"2\\theta",
"<", "2\\pi",
)
expression.to_corner(UL)
brace = Brace(expression[:-2], DOWN)
question = brace.get_text("Max number of times?")
question.set_color(YELLOW)
central_question_group = self.get_central_question()
simplified, lil_brace, new_question = central_question_group
central_question_group.next_to(question, DOWN, LARGE_BUFF)
new_question.align_to(question, LEFT)
for n in range(5):
self.add(expression[:2 * n + 1])
self.wait(0.25)
self.play(
FadeInFrom(expression[-2:], LEFT),
GrowFromCenter(brace),
FadeInFrom(question, UP)
)
self.wait(3)
self.play(
TransformFromCopy(expression[:-2], simplified[:3]),
TransformFromCopy(expression[-2:], simplified[3:]),
TransformFromCopy(brace, lil_brace),
)
self.play(Write(new_question))
self.wait()
self.central_question_group = central_question_group
self.show_example()
def get_central_question(self, brace_vect=DOWN):
expression = TexMobject(
"N", "\\cdot", "\\theta", "<", "\\pi"
)
N = expression[0]
N.set_color(BLUE)
brace = Brace(N, brace_vect, buff=SMALL_BUFF)
question = brace.get_text(
"Maximal integer?",
)
question.set_color(YELLOW)
result = VGroup(expression, brace, question)
result.to_corner(UL)
return result
def show_example(self):
equation = self.get_changable_equation(0.01, n_decimal_places=2)
expression, brace, question = self.central_question_group
N_mob, dot_theta_eq, rhs, comp_pi = equation
equation.next_to(expression, DOWN, 2, aligned_edge=LEFT)
self.play(
TransformFromCopy(expression[0], N_mob),
TransformFromCopy(expression[1:4], dot_theta_eq),
TransformFromCopy(expression[4], rhs),
TransformFromCopy(expression[4], comp_pi),
)
self.wait()
self.play(
ChangeDecimalToValue(N_mob, 314, run_time=5)
)
self.wait()
self.play(ChangeDecimalToValue(N_mob, 315))
self.wait()
self.play(ChangeDecimalToValue(N_mob, 314))
self.wait()
self.play(ShowCreationThenFadeAround(N_mob))
#
def get_changable_equation(self, value, tex_string=None, n_decimal_places=10):
int_mob = Integer(1)
int_mob.set_color(BLUE)
formatter = "({:0." + str(n_decimal_places) + "f})"
tex_string = tex_string or formatter.format(value)
tex_mob = TexMobject("\\cdot", tex_string, "=")
rhs = DecimalNumber(value, num_decimal_places=n_decimal_places)
def align_number(mob):
y0 = mob[0].get_center()[1]
y1 = tex_mob[1][1:-1].get_center()[1]
mob.shift((y1 - y0) * UP)
int_mob.add_updater(
lambda m: m.next_to(tex_mob, LEFT, SMALL_BUFF)
)
int_mob.add_updater(align_number)
rhs.add_updater(
lambda m: m.set_value(value * int_mob.get_value())
)
rhs.add_updater(
lambda m: m.next_to(tex_mob, RIGHT, SMALL_BUFF)
)
rhs.add_updater(align_number)
def get_comp_pi():
if rhs.get_value() < np.pi:
result = TexMobject("< \\pi")
result.set_color(GREEN)
elif rhs.get_value() > np.pi:
result = TexMobject("> \\pi")
result.set_color(RED)
else:
result = TexMobject("= \\pi")
result.next_to(rhs, RIGHT, 2 * SMALL_BUFF)
result[1].scale(1.5, about_edge=LEFT)
return result
comp_pi = updating_mobject_from_func(get_comp_pi)
return VGroup(int_mob, tex_mob, rhs, comp_pi)
class AskAboutTheta(TeacherStudentsScene):
def construct(self):
self.student_says(
"But what is $\\theta$?",
target_mode="raise_left_hand",
)
self.change_student_modes(
"confused", "sassy", "raise_left_hand",
added_anims=[self.teacher.change, "happy"]
)
self.wait(3)
class ComputeThetaFor1e4(AnalyzeCircleGeometry):
CONFIG = {
"mass_ratio": 100,
}
def construct(self):
self.add_mass_ratio_label()
self.add_circle_with_three_lines()
self.write_slope()
self.show_tangent()
def add_circle_with_three_lines(self):
circle = self.get_circle()
axes = self.get_axes()
slope = -np.sqrt(self.mass_ratio)
lines = self.get_lines(
radius=circle.radius,
slope=slope,
)
end_zone = self.get_end_zone()
axes_labels = self.get_axes_labels(axes)
axes.add(axes_labels)
lines_to_fade = VGroup(*lines[:11], *lines[13:])
two_lines = lines[11:13]
theta = self.theta = np.arctan(-1 / slope)
arc = Arc(
start_angle=(-90 * DEGREES),
angle=theta,
radius=2,
arc_center=two_lines[0].get_end(),
)
theta_label = TexMobject("\\theta")
theta_label.scale(0.8)
theta_label.next_to(arc, DOWN, SMALL_BUFF)
self.add(end_zone, axes, circle)
self.play(ShowCreation(lines, rate_func=None))
self.play(
lines_to_fade.set_stroke, WHITE, 1, 0.3,
ShowCreation(arc),
FadeInFrom(theta_label, UP)
)
self.two_lines = two_lines
self.lines = lines
self.circle = circle
self.axes = axes
self.theta_label_group = VGroup(theta_label, arc)
def write_slope(self):
line = self.two_lines[1]
slope_label = TexMobject(
"\\text{Slope}", "=",
"\\frac{\\text{rise}}{\\text{run}}", "=",
"\\frac{-\\sqrt{m_1}}{\\sqrt{m_2}}", "=", "-10"
)
for mob in slope_label:
mob.add_to_back(mob.copy().set_stroke(BLACK, 6))
slope_label.next_to(line.get_center(), UR, buff=1)
slope_arrow = Arrow(
slope_label[0].get_bottom(),
line.point_from_proportion(0.45),
color=RED,
buff=SMALL_BUFF,
)
new_line = line.copy().set_color(RED)
self.play(
FadeInFromDown(slope_label[:3]),
ShowCreation(new_line),
GrowArrow(slope_arrow),
)
self.remove(new_line)
line.match_style(new_line)
self.play(
FadeInFrom(slope_label[3:5], LEFT)
)
self.wait()
self.play(
Write(slope_label[5]),
TransformFromCopy(
self.mass_ratio_label[1][:3],
slope_label[6]
)
)
self.wait()
self.slope_label = slope_label
self.slope_arrow = slope_arrow
def show_tangent(self):
l1, l2 = self.two_lines
theta = self.theta
theta_label_group = self.theta_label_group
tan_equation = TexMobject(
"\\tan", "(", "\\theta", ")", "=",
"{\\text{run}", "\\over", "-\\text{rise}}", "=",
"\\frac{1}{10}",
)
tan_equation.scale(0.9)
tan_equation.to_edge(LEFT, buff=MED_SMALL_BUFF)
tan_equation.shift(2 * UP)
run_word = tan_equation.get_part_by_tex("run")
rise_word = tan_equation.get_part_by_tex("rise")
p1, p2 = l1.get_start(), l1.get_end()
p3 = p1 + get_norm(p2 - p1) * np.tan(theta) * RIGHT
triangle = Polygon(p1, p2, p3)
triangle.set_stroke(width=0)
triangle.set_fill(GREEN, 0.5)
opposite = Line(p1, p3)
adjacent = Line(p1, p2)
opposite.set_stroke(BLUE, 3)
adjacent.set_stroke(PINK, 3)
arctan_equation = TexMobject(
"\\theta", "=", "\\arctan", "(", "1 / 10", ")"
)
arctan_equation.next_to(tan_equation, DOWN, MED_LARGE_BUFF)
self.play(
FadeInFromDown(tan_equation[:8]),
)
self.play(
TransformFromCopy(theta_label_group[1], opposite),
run_word.set_color, opposite.get_color()
)
self.play(WiggleOutThenIn(run_word))
self.play(
TransformFromCopy(opposite, adjacent),
rise_word.set_color, adjacent.get_color()
)
self.play(WiggleOutThenIn(rise_word))
self.wait()
self.play(TransformFromCopy(
self.slope_label[-1],
tan_equation[-2:],
))
self.wait(2)
indices = [2, 4, 0, 1, -1, 3]
movers = VGroup(*[tan_equation[i] for i in indices]).copy()
for mover, target in zip(movers, arctan_equation):
mover.target = target
# Swap last two
sm = movers.submobjects
sm[-1], sm[-2] = sm[-2], sm[-1]
self.play(LaggedStart(
Transform, movers[:-1],
lambda m: (m, m.target),
lag_ratio=1,
run_time=1,
path_arc=PI / 6,
))
self.play(MoveToTarget(movers[-1]))
self.remove(movers)
self.add(arctan_equation)
self.play(ShowCreationThenFadeAround(arctan_equation))
self.wait()
class ThetaChart(Scene):
def construct(self):
self.create_columns()
self.populate_columns()
self.show_values()
self.highlight_example(2)
self.highlight_example(3)
def create_columns(self):
titles = VGroup(*[
TextMobject("Mass ratio"),
TextMobject("$\\theta$ formula"),
TextMobject("$\\theta$ value"),
])
titles.scale(1.5)
titles.arrange_submobjects(RIGHT, buff=1.5)
titles[1].shift(MED_SMALL_BUFF * LEFT)
titles[2].shift(MED_SMALL_BUFF * RIGHT)
titles.to_corner(UL)
lines = VGroup()
for t1, t2 in zip(titles, titles[1:]):
line = Line(TOP, BOTTOM)
x = np.mean([t1.get_center()[0], t2.get_center()[0]])
line.shift(x * RIGHT)
lines.add(line)
h_line = Line(LEFT_SIDE, RIGHT_SIDE)
h_line.next_to(titles, DOWN)
h_line.to_edge(LEFT, buff=0)
lines.add(h_line)
lines.set_stroke(WHITE, 1)
self.play(
LaggedStart(FadeInFromDown, titles),
LaggedStart(ShowCreation, lines, lag_ratio=0.8),
)
self.h_line = h_line
self.titles = titles
def populate_columns(self):
top_h_line = self.h_line
x_vals = [t.get_center()[0] for t in self.titles]
entries = [
(
"$m_1$ : $m_2$",
"$\\arctan(\\sqrt{m_2} / \\sqrt{m_1})$",
""
)
] + [
(
"{:,} : 1".format(10**(2 * exp)),
"$\\arctan(1 / {:,})$".format(10**exp),
self.get_theta_decimal(exp),
)
for exp in [1, 2, 3, 4, 5]
]
h_lines = VGroup(top_h_line)
entry_mobs = VGroup()
for entry in entries:
mobs = VGroup(*map(TextMobject, entry))
for mob, x in zip(mobs, x_vals):
mob.shift(x * RIGHT)
delta_y = (mobs.get_height() / 2) + MED_SMALL_BUFF
y = h_lines[-1].get_center()[1] - delta_y
mobs.shift(y * UP)
mobs[0].set_color(BLUE)
mobs[2].set_color(YELLOW)
entry_mobs.add(mobs)
h_line = DashedLine(LEFT_SIDE, RIGHT_SIDE)
h_line.shift((y - delta_y) * UP)
h_lines.add(h_line)
self.play(
LaggedStart(
FadeInFromDown,
VGroup(*[em[:2] for em in entry_mobs]),
),
LaggedStart(ShowCreation, h_lines[1:]),
lag_ratio=0.1,
run_time=5,
)
self.entry_mobs = entry_mobs
self.h_lines = h_lines
def show_values(self):
values = VGroup(*[em[2] for em in self.entry_mobs])
for value in values:
self.play(LaggedStart(
FadeIn, value,
lag_ratio=0.1,
run_time=0.5
))
self.wait(0.5)
def highlight_example(self, exp):
entry_mobs = self.entry_mobs
example = entry_mobs[exp]
other_entries = VGroup(*entry_mobs[:exp], *entry_mobs[exp + 1:])
value = example[-1]
rhs = TexMobject("\\approx {:}".format(10**(-exp)))
rhs.next_to(value, RIGHT)
rhs.to_edge(RIGHT, buff=MED_SMALL_BUFF)
value.generate_target()
value.target.set_fill(opacity=1)
value.target.scale(0.9)
value.target.next_to(rhs, LEFT, SMALL_BUFF)
self.play(
other_entries.set_fill, {"opacity": 0.25},
example.set_fill, {"opacity": 1},
ShowCreationThenFadeAround(example)
)
self.wait()
self.play(
MoveToTarget(value),
Write(rhs),
)
self.wait()
value.add(rhs)
def get_theta_decimal(self, exp):
theta = np.arctan(10**(-exp))
rounded_theta = np.floor(1e10 * theta) / 1e10
return "{:0.10f}\\dots".format(rounded_theta)
class CentralQuestionFor1e2(AddTwoThetaManyTimes):
CONFIG = {
"exp": 2,
}
def construct(self):
exp = self.exp
question = self.get_central_question(UP)
pi_value = TexMobject(" = {:0.10f}\\dots".format(PI))
pi_value.next_to(question[0][-1], RIGHT, SMALL_BUFF)
pi_value.shift(0.3 * SMALL_BUFF * UP)
question.add(pi_value)
max_count = int(PI * 10**exp)
question.center().to_edge(UP)
self.add(question)
b10_equation = self.get_changable_equation(
10**(-exp), n_decimal_places=exp
)
b10_equation.next_to(question, DOWN, buff=1.5)
arctan_equation = self.get_changable_equation(
np.arctan(10**(-exp)), n_decimal_places=10,
)
arctan_equation.next_to(b10_equation, DOWN, MED_LARGE_BUFF)
eq_centers = [
eq[1][2].get_center()
for eq in [b10_equation, arctan_equation]
]
arctan_equation.shift((eq_centers[1][0] - eq_centers[1][0]) * RIGHT)
# b10_brace = Brace(b10_equation[1][1][1:-1], UP)
arctan_brace = Brace(arctan_equation[1][1][1:-1], DOWN)
# b10_tex = b10_brace.get_tex("1 / 10")
arctan_tex = arctan_brace.get_tex(
"\\theta = \\arctan(1 / {:,})".format(10**exp)
)
int_mobs = b10_equation[0], arctan_equation[0]
self.add(*b10_equation, *arctan_equation)
# self.add(b10_brace, b10_tex)
self.add(arctan_brace, arctan_tex)
self.wait()
self.play(*[
ChangeDecimalToValue(int_mob, max_count, run_time=8)
for int_mob in int_mobs
])
self.wait()
self.play(*[
ChangeDecimalToValue(int_mob, max_count + 1, run_time=1)
for int_mob in int_mobs
])
self.wait()
self.play(*[
ChangeDecimalToValue(int_mob, max_count, run_time=1)
for int_mob in int_mobs
])
self.play(ShowCreationThenFadeAround(int_mobs[1]))
self.wait()
class AnalyzeCircleGeometry1e2(AnalyzeCircleGeometry):
CONFIG = {
"mass_ratio": 100,
}
class CentralQuestionFor1e3(CentralQuestionFor1e2):
CONFIG = {"exp": 3}
class AskAboutArctanOfSmallValues(TeacherStudentsScene):
def construct(self):
self.add_title()
equation1 = TexMobject(
"\\arctan", "(", "x", ")", "\\approx", "x"
)
equation1.set_color_by_tex("arctan", YELLOW)
equation2 = TexMobject(
"x", "\\approx", "\\tan", "(", "x", ")",
)
equation2.set_color_by_tex("tan", BLUE)
for mob in equation1, equation2:
mob.move_to(self.hold_up_spot, DOWN)
self.play(
FadeInFromDown(equation1),
self.teacher.change, "raise_right_hand",
self.get_student_changes(
"erm", "sassy", "confused"
)
)
self.look_at(3 * UL)
self.play(equation1.shift, UP)
self.play(
TransformFromCopy(
VGroup(*[equation1[i] for i in (2, 4, 5)]),
VGroup(*[equation2[i] for i in (0, 1, 4)]),
)
)
self.play(
TransformFromCopy(
VGroup(*[equation1[i] for i in (0, 1, 3)]),
VGroup(*[equation2[i] for i in (2, 3, 5)]),
),
self.get_student_changes(
"confused", "erm", "sassy",
),
)
self.look_at(3 * UL)
self.wait(3)
# self.student_says("Why?", target_mode="maybe")
# self.wait(3)
def add_title(self):
title = TextMobject("For small $x$")
subtitle = TextMobject("(e.g. $x = 0.001$)")
subtitle.scale(0.75)
subtitle.next_to(title, DOWN)
title.add(subtitle)
# title.scale(1.5)
# title.to_edge(UP, buff=MED_SMALL_BUFF)
title.move_to(self.hold_up_spot)
title.to_edge(UP)
self.add(title)
class ActanAndTanGraphs(GraphScene):
CONFIG = {
"x_min": -PI / 8,
"x_max": 5 * PI / 8,
"y_min": -PI / 8,
"y_max": 4 * PI / 8,
"x_tick_frequency": PI / 8,
"x_leftmost_tick": -PI / 8,
"y_tick_frequency": PI / 8,
"y_leftmost_tick": -PI / 8,
"x_axis_width": 10,
"y_axis_height": 7,
"graph_origin": 2.5 * DOWN + 5 * LEFT,
"num_graph_anchor_points": 500,
}
def construct(self):
self.setup_axes()
axes = self.axes
labels = VGroup(
TexMobject("\\pi / 8"),
TexMobject("\\pi / 4"),
TexMobject("3\\pi / 8"),
TexMobject("\\pi / 2"),
)
for n, label in zip(it.count(1), labels):
label.scale(0.75)
label.next_to(self.coords_to_point(n * PI / 8, 0), DOWN)
self.add(label)
id_graph = self.get_graph(lambda x: x, x_max=1.5)
arctan_graph = self.get_graph(np.arctan, x_max=1.5)
tan_graph = self.get_graph(np.tan, x_max=1.5)
graphs = VGroup(id_graph, arctan_graph, tan_graph)
id_label = TexMobject("f(x) = x")
arctan_label = TexMobject("\\arctan(x)")
tan_label = TexMobject("\\tan(x)")
labels = VGroup(id_label, arctan_label, tan_label)
for label, graph in zip(labels, graphs):
label.match_color(graph)
label.next_to(graph.points[-1], RIGHT)
if label.get_bottom()[1] > FRAME_HEIGHT / 2:
label.next_to(graph.point_from_proportion(0.75), LEFT)
arctan_x_tracker = ValueTracker(3 * PI / 8)
arctan_v_line = updating_mobject_from_func(
lambda: self.get_vertical_line_to_graph(
arctan_x_tracker.get_value(),
arctan_graph,
line_class=DashedLine,
color=WHITE,
)
)
tan_x_tracker = ValueTracker(2 * PI / 8)
tan_v_line = updating_mobject_from_func(
lambda: self.get_vertical_line_to_graph(
tan_x_tracker.get_value(),
tan_graph,
line_class=DashedLine,
color=WHITE,
)
)
self.add(axes)
self.play(
ShowCreation(id_graph),
Write(id_label)
)
self.play(
ShowCreation(arctan_graph),
Write(arctan_label)
)
self.add(arctan_v_line)
self.play(arctan_x_tracker.set_value, 0, run_time=2)
self.wait()
self.play(
TransformFromCopy(arctan_graph, tan_graph),
TransformFromCopy(arctan_label, tan_label),
)
self.add(tan_v_line)
self.play(tan_x_tracker.set_value, 0, run_time=2)
self.wait()
class UnitCircleIntuition(Scene):
def construct(self):
self.draw_unit_circle()
self.show_angle()
self.show_fraction()
self.show_fraction_approximation()
def draw_unit_circle(self):
unit_size = 2.5
axes = Axes(
number_line_config={"unit_size": unit_size},
x_min=-2.5, x_max=2.5,
y_min=-1.5, y_max=1.5,
)
axes.set_stroke(width=1)
self.add(axes)
radius_line = Line(ORIGIN, axes.coords_to_point(1, 0))
radius_line.set_color(BLUE)
r_label = TexMobject("1")
r_label.add_updater(
lambda m: m.next_to(radius_line.get_center(), DOWN, SMALL_BUFF)
)
circle = Circle(radius=unit_size, color=WHITE)
self.add(radius_line, r_label)
self.play(
Rotating(radius_line, about_point=ORIGIN),
ShowCreation(circle),
run_time=2,
rate_func=smooth,
)
self.radius_line = radius_line
self.r_label = r_label
self.circle = circle
self.axes = axes
def show_angle(self):
circle = self.circle
tan_eq = TexMobject(
"\\tan", "(", "\\theta", ")", "=",
tex_to_color_map={"\\theta": RED},
)
tan_eq.next_to(ORIGIN, RIGHT, LARGE_BUFF)
tan_eq.to_edge(UP, buff=LARGE_BUFF)
theta_tracker = ValueTracker(0)
get_theta = theta_tracker.get_value
def get_r_line():
return Line(
circle.get_center(),
circle.get_point_from_angle(get_theta())
)
r_line = updating_mobject_from_func(get_r_line)
def get_arc(radius=None, **kwargs):
if radius is None:
alpha = inverse_interpolate(0, 20 * DEGREES, get_theta())
radius = interpolate(2, 1, alpha)
return Arc(
radius=radius,
start_angle=0,
angle=get_theta(),
arc_center=circle.get_center(),
**kwargs
)
arc = updating_mobject_from_func(get_arc)
self.circle_arc = updating_mobject_from_func(
lambda: get_arc(radius=circle.radius, color=RED)
)
def get_theta_label():
label = TexMobject("\\theta")
label.set_height(min(arc.get_height(), 0.3))
label.set_color(RED)
center = circle.get_center()
vect = arc.point_from_proportion(0.5) - center
vect = (get_norm(vect) + 2 * SMALL_BUFF) * normalize(vect)
label.move_to(center + vect)
return label
theta_label = updating_mobject_from_func(get_theta_label)
def get_height_line():
p2 = circle.get_point_from_angle(get_theta())
p1 = np.array(p2)
p1[1] = circle.get_center()[1]
return Line(
p1, p2,
stroke_color=YELLOW,
stroke_width=3,
)
self.height_line = updating_mobject_from_func(get_height_line)
def get_width_line():
p2 = circle.get_center()
p1 = circle.get_point_from_angle(get_theta())
p1[1] = p2[1]
return Line(
p1, p2,
stroke_color=PINK,
stroke_width=3,
)
self.width_line = updating_mobject_from_func(get_width_line)
def get_h_label():
label = TexMobject("h")
height_line = self.height_line
label.match_color(height_line)
label.set_height(min(height_line.get_height(), 0.3))
label.set_stroke(BLACK, 3, background=True)
label.next_to(height_line, RIGHT, SMALL_BUFF)
return label
self.h_label = updating_mobject_from_func(get_h_label)
def get_w_label():
label = TexMobject("w")
width_line = self.width_line
label.match_color(width_line)
label.next_to(width_line, DOWN, SMALL_BUFF)
return label
self.w_label = updating_mobject_from_func(get_w_label)
self.add(r_line, theta_label, arc, self.radius_line)
self.play(
FadeInFromDown(tan_eq),
theta_tracker.set_value, 20 * DEGREES,
)
self.wait()
self.tan_eq = tan_eq
self.theta_tracker = theta_tracker
def show_fraction(self):
height_line = self.height_line
width_line = self.width_line
h_label = self.h_label
w_label = self.w_label
tan_eq = self.tan_eq
rhs = TexMobject(
"{\\text{height}", "\\over", "\\text{width}}"
)
rhs.next_to(tan_eq, RIGHT)
rhs.get_part_by_tex("height").match_color(height_line)
rhs.get_part_by_tex("width").match_color(width_line)
for mob in [height_line, width_line, h_label, w_label]:
mob.update()
self.play(
ShowCreation(height_line.copy().clear_updaters(), remover=True),
FadeInFrom(h_label.copy().clear_updaters(), RIGHT, remover=True),
Write(rhs[:2])
)
self.add(height_line, h_label)
self.play(
ShowCreation(width_line.copy().clear_updaters(), remover=True),
FadeInFrom(w_label.copy().clear_updaters(), UP, remover=True),
self.r_label.fade, 1,
Write(rhs[2])
)
self.add(width_line, w_label)
self.wait()
self.rhs = rhs
def show_fraction_approximation(self):
theta_tracker = self.theta_tracker
approx_rhs = TexMobject(
"\\approx", "{\\theta", "\\over", "1}",
)
height, over1, width = self.rhs
approx, theta, over2, one = approx_rhs
approx_rhs.set_color_by_tex("\\theta", RED)
approx_rhs.next_to(self.rhs, RIGHT, MED_SMALL_BUFF)
self.play(theta_tracker.set_value, 5 * DEGREES)
self.play(Write(VGroup(approx, over2)))
self.wait()
self.play(Indicate(width))
self.play(TransformFromCopy(width, one))
self.wait()
self.play(Indicate(height))
self.play(TransformFromCopy(height, theta))
self.wait()
class TangentTaylorSeries(TeacherStudentsScene):
def construct(self):
series = TexMobject(
"\\tan", "(", "\\theta", ")", "=", "\\theta", "+",
"\\frac{1}{3}", "\\theta", "^3", "+",
"\\frac{2}{15}", "\\theta", "^5", "+", "\\cdots",
tex_to_color_map={"\\theta": YELLOW},
)
series.move_to(2 * UP)
series.move_to(self.hold_up_spot, DOWN)
series_error = series[7:]
series_error_rect = SurroundingRectangle(series_error)
example = TexMobject(
"\\tan", "\\left(", "\\frac{1}{100}", "\\right)",
"=", "\\frac{1}{100}", "+",
"\\frac{1}{3}", "\\left(",
"\\frac{1}{1{,}000{,}000}",
"\\right)", "+",
"\\frac{2}{15}", "\\left(",
"\\frac{1}{10{,}000{,}000{,}000}",
"\\right)", "+", "\\cdots",
)
example.set_color_by_tex("\\frac{1}{1", BLUE)
example.set_width(FRAME_WIDTH - 1)
example.next_to(self.students, UP, buff=2)
example.shift_onto_screen()
error = example[7:]
error_rect = SurroundingRectangle(error)
error_rect.set_color(RED)
error_decimal = DecimalNumber(
np.tan(0.01) - 0.01,
num_decimal_places=15,
)
error_decimal.next_to(error_rect, DOWN)
approx = TexMobject("\\approx")
approx.next_to(error_decimal, LEFT)
error_decimal.add(approx)
error_decimal.match_color(error_rect)
self.play(
FadeInFromDown(series),
self.teacher.change, "raise_right_hand",
)
self.play(
ShowCreation(series_error_rect),
self.get_student_changes(*3 * ["pondering"])
)
self.play(FadeOut(series_error_rect))
self.play(
series.center, series.to_edge, UP,
)
self.look_at(series)
self.play(
TransformFromCopy(series[:8], example[:8]),
TransformFromCopy(series[8], example[9]),
TransformFromCopy(series[10:12], example[11:13]),
TransformFromCopy(series[12], example[14]),
TransformFromCopy(series[14:], example[16:]),
*map(GrowFromCenter, [example[i] for i in (8, 10, 13, 15)])
)
self.change_student_modes("happy", "confused", "sad")
self.play(ShowCreation(error_rect))
self.play(ShowIncreasingSubsets(error_decimal))
self.change_all_student_modes("hooray")
self.wait(3)
class AnalyzeCircleGeometry1e4(AnalyzeCircleGeometry):
CONFIG = {
"mass_ratio": 10000,
}
class SumUpWrapper(Scene):
def construct(self):
title = TextMobject("To sum up:")
title.scale(1.5)
title.to_edge(UP)
screen_rect = ScreenRectangle(height=6)
screen_rect.set_fill(BLACK, 1)
screen_rect.next_to(title, DOWN)
self.add(FullScreenFadeRectangle(
fill_color=DARK_GREY,
fill_opacity=0.5
))
self.play(
FadeInFromDown(title),
FadeIn(screen_rect),
)
self.wait()
class ConservationLawSummary(Scene):
def construct(self):
energy_eq = TexMobject(
"\\frac{1}{2}", "m_1", "(", "v_1", ")", "^2", "+",
"\\frac{1}{2}", "m_2", "(", "v_2", ")", "^2", "=",
"\\text{const.}",
)
energy_word = TextMobject("Energy")
energy_word.scale(2)
circle = Circle(color=YELLOW, radius=2)
energy_group = VGroup(energy_word, energy_eq, circle)
momentum_eq = TexMobject(
"m_1", "v_1", "+", "m_2", "v_2", "=",
"\\text{const.}",
)
momentum_word = TextMobject("Momentum")
momentum_word.scale(2)
line = Line(ORIGIN, RIGHT + np.sqrt(10) * DOWN)
line.set_color(GREEN)
momentum_group = VGroup(momentum_word, momentum_eq, line)
equations = VGroup(energy_eq, momentum_eq)
words = VGroup(energy_word, momentum_word)
for equation in equations:
equation.set_color_by_tex("m_", BLUE)
equation.set_color_by_tex("v_", RED)
words.arrange_submobjects(
DOWN, buff=3,
)
words.to_edge(LEFT, buff=1.5)
for group in energy_group, momentum_group:
arrow = Arrow(
LEFT, 2 * RIGHT,
rectangular_stem_width=0.1,
tip_length=0.5,
color=WHITE
)
arrow.next_to(group[0], RIGHT)
group[1].next_to(group[0], DOWN)
group[2].next_to(arrow, RIGHT)
group[2].set_stroke(width=6)
group.add(arrow)
# line.scale(4, about_edge=DR)
red_energy_word = energy_word.copy()
red_energy_word.set_fill(opacity=0)
red_energy_word.set_stroke(RED, 2)
self.add(energy_group, momentum_group)
self.wait()
self.play(
LaggedStart(
ShowCreationThenDestruction,
red_energy_word
),
)
for color in [RED, BLUE, PINK, YELLOW]:
self.play(ShowCreation(
circle.copy().set_color(color),
))
class FinalCommentsOnPhaseSpace(Scene):
def construct(self):
self.add_title()
self.show_related_fields()
self.state_to_point()
self.puzzle_as_remnant()
def add_title(self):
title = self.title = TextMobject("Phase space")
title.scale(2)
title.to_edge(UP)
title.set_color(YELLOW)
self.play(Write(title))
def show_related_fields(self):
title = self.title
images = Group(
ImageMobject("ClacksThumbnail"),
ImageMobject("PictoralODE"),
# ImageMobject("DoublePendulumStart"),
ImageMobject("MobiusStrip"),
)
colors = [BLUE_D, GREY_BROWN, BLUE_C]
for image, color in zip(images, colors):
image.set_height(2.5)
image.add(SurroundingRectangle(
image,
color=color,
stroke_width=5,
buff=0,
))
images.arrange_submobjects(RIGHT)
images.move_to(DOWN)
arrows = VGroup(*[
Arrow(
title.get_bottom(), image.get_top(),
color=WHITE,
)
for image in images
])
for image, arrow in zip(images, arrows):
self.play(
GrowArrow(arrow),
GrowFromPoint(image, title.get_bottom()),
)
self.wait()
self.wait()
self.to_fade = Group(images, arrows)
def state_to_point(self):
state = TextMobject("State")
arrow = Arrow(
2 * LEFT, 2 * RIGHT,
color=WHITE,
rectangular_stem_width=0.1,
tip_length=0.5
)
point = TextMobject("Point")
dynamics = TextMobject("Dynamics")
geometry = TextMobject("Geometry")
words = VGroup(state, point, dynamics, geometry)
for word in words:
word.scale(2)
group = VGroup(state, arrow, point)
group.arrange_submobjects(RIGHT, buff=MED_LARGE_BUFF)
group.move_to(2.5 * DOWN)
dynamics.move_to(state, RIGHT)
geometry.move_to(point, LEFT)
self.play(
FadeOutAndShift(self.to_fade, UP),
FadeInFrom(state, UP)
)
self.play(
GrowArrow(arrow),
FadeInFrom(point, LEFT)
)
self.wait(2)
for w1, w2 in [(state, dynamics), (point, geometry)]:
self.play(
FadeOutAndShift(w1, UP),
FadeInFrom(w2, DOWN),
)
self.wait()
self.wait()
def puzzle_as_remnant(self):
pass
class AltShowTwoPopulations(ShowTwoPopulations):
CONFIG = {
"count_word_scale_val": 2,
}
class SimpleTeacherHolding(TeacherStudentsScene):
def construct(self):
self.play(self.teacher.change, "raise_right_hand")
self.change_all_student_modes("pondering")
self.wait(3)
class EndScreen(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
"Juan Benet",
"Vassili Philippov",
"Burt Humburg",
"Matt Russell",
"soekul",
"Richard Barthel",
"Nathan Jessurun",
"Ali Yahya",
"dave nicponski",
"Yu Jun",
"Kaustuv DeBiswas",
"Yana Chernobilsky",
"Lukas Biewald",
"Arthur Zey",
"Roy Larson",
"Joseph Kelly",
"Peter Mcinerney",
"Scott Walter, Ph.D.",
"Magnus Lysfjord",
"Evan Phillips",
"Graham",
"Mauricio Collares",
"Quantopian",
"Jordan Scales",
"Lukas -krtek.net- Novy",
"John Shaughnessy",
"Joseph John Cox",
"Ryan Atallah",
"Britt Selvitelle",
"Jonathan Wilson",
"Randy C. Will",
"Magnus Dahlström",
"David Gow",
"J",
"Luc Ritchie",
"Rish Kundalia",
"Bob Sanderson",
"Mathew Bramson",
"Mustafa Mahdi",
"Robert Teed",
"Cooper Jones",
"Jeff Linse",
"John Haley",
"Boris Veselinovich",
"Andrew Busey",
"Awoo",
"Linh Tran",
"Ripta Pasay",
"David Clark",
"Mathias Jansson",
"Clark Gaebel",
"Bernd Sing",
"Jason Hise",
"Ankalagon",
"Dave B",
"Ted Suzman",
"Chris Connett",
"Eric Younge",
"1stViewMaths",
"Jacob Magnuson",
"Jonathan Eppele",
"Delton Ding",
"James Hughes",
"Stevie Metke",
"Yaw Etse",
"John Griffith",
"Magister Mugit",
"Ludwig Schubert",
"Giovanni Filippi",
"Matt Langford",
"Matt Roveto",
"Jameel Syed",
"Richard Burgmann",
"Solara570",
"Alexis Olson",
"Jeff Straathof",
"John V Wertheim",
"Sindre Reino Trosterud",
"Song Gao",
"Peter Ehrnstrom",
"Valeriy Skobelev",
"Art Ianuzzi",
"Michael Faust",
"Omar Zrien",
"Adrian Robinson",
"Federico Lebron",
"Kai-Siang Ang",
"Michael Hardel",
"Nero Li",
"Ryan Williams",
"Charles Southerland",
"Devarsh Desai",
"Hal Hildebrand",
"Jan Pijpers",
"L0j1k",
"Mark B Bahu",
"Márton Vaitkus",
"Richard Comish",
"Zach Cardwell",
"Brian Staroselsky",
"Matthew Cocke",
"Christian Kaiser",
"Danger Dai",
"Dave Kester",
"eaglle",
"Florian Chudigiewitsch",
"Roobie",
"Xavier Bernard",
"YinYangBalance.Asia",
"Eryq Ouithaqueue",
"Kanan Gill",
"j eduardo perez",
"Antonio Juarez",
"Owen Campbell-Moore",
],
}
class SolutionThumbnail(Thumbnail):
CONFIG = {
"sliding_blocks_config": {
"block1_config": {
"label_text": "$100^{d}$ kg",
},
"collect_clack_data": False,
},
}
def add_text(self):
word = TextMobject("Solution")
question = TextMobject("How many collisions?")
word.set_width(7)
question.match_width(word)
question.next_to(word, UP)
group = VGroup(word, question)
group.to_edge(UP, buff=MED_LARGE_BUFF)
word.set_color(RED)
question.set_color(YELLOW)
group.set_stroke(RED, 2, background=True)
self.add(group)