mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
3188 lines
95 KiB
Python
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)
|