3b1b-manim/old_projects/wallis.py
2019-05-02 20:36:14 -07:00

5307 lines
172 KiB
Python

# -*- coding: utf-8 -*-
from manimlib.imports import *
from once_useful_constructs.light import AmbientLight
from once_useful_constructs.light import Lighthouse
from once_useful_constructs.light import SwitchOn
from functools import reduce
# from once_useful_constructs.light import LightSource
PRODUCT_COLOR = BLUE
DEFAULT_OPACITY_FUNCTION = inverse_power_law(1, 1.5, 1, 4)
CHEAP_AMBIENT_LIGHT_CONFIG = {
"num_levels": 5,
"radius": 0.25,
"opacity_function": DEFAULT_OPACITY_FUNCTION,
}
HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG = {
"opacity_function": DEFAULT_OPACITY_FUNCTION,
"num_levels": 100,
"radius": 5,
"max_opacity": 0.8,
"color": PRODUCT_COLOR,
}
def get_chord_f_label(chord, arg="f", direction=DOWN):
chord_f = TextMobject("Chord(", "$%s$" % arg, ")", arg_separator="")
chord_f.set_color_by_tex("$%s$" % arg, YELLOW)
chord_f.add_background_rectangle()
chord_f.next_to(chord.get_center(), direction, SMALL_BUFF)
angle = ((chord.get_angle() + TAU / 2) % TAU) - TAU / 2
if np.abs(angle) > TAU / 4:
angle += TAU / 2
chord_f.rotate(angle, about_point=chord.get_center())
chord_f.angle = angle
return chord_f
class WallisNumeratorDenominatorGenerator(object):
def __init__(self):
self.n = 0
def __iter__(self):
return self
def __next__(self):
return next(self)
def __next__(self):
n = self.n
self.n += 1
if n % 2 == 0:
return (n + 2, n + 1)
else:
return (n + 1, n + 2)
def get_wallis_product(n_terms=6, show_result=True):
tex_mob_args = []
nd_generator = WallisNumeratorDenominatorGenerator()
for x in range(n_terms):
numerator, denominator = next(nd_generator)
tex_mob_args += [
"{%d" % numerator, "\\over", "%d}" % denominator, "\\cdot"
]
tex_mob_args[-1] = "\\cdots"
if show_result:
tex_mob_args += ["=", "{\\pi", "\\over", "2}"]
result = TexMobject(*tex_mob_args)
return result
def get_wallis_product_numerical_terms(n_terms=20):
result = []
nd_generator = WallisNumeratorDenominatorGenerator()
for x in range(n_terms):
n, d = next(nd_generator)
result.append(float(n) / d)
return result
# Scenes
class Introduction(Scene):
def construct(self):
n_terms = 10
number_line = NumberLine(
x_min=0,
x_max=2,
unit_size=5,
tick_frequency=0.25,
numbers_with_elongated_ticks=[0, 1, 2],
color=LIGHT_GREY,
)
number_line.add_numbers()
number_line.move_to(DOWN)
numerical_terms = get_wallis_product_numerical_terms(400)
partial_products = np.cumprod(numerical_terms)
curr_product = partial_products[0]
arrow = Vector(DOWN, color=YELLOW)
def get_arrow_update():
return ApplyFunction(
lambda mob: mob.next_to(
number_line.number_to_point(curr_product),
UP, SMALL_BUFF
),
arrow,
)
get_arrow_update().update(1)
decimal = DecimalNumber(
curr_product, num_decimal_places=5, show_ellipsis=True)
decimal.next_to(arrow, UP, SMALL_BUFF, submobject_to_align=decimal[:5])
decimal_anim = ChangingDecimal(
decimal,
lambda a: number_line.point_to_number(arrow.get_center()),
tracked_mobject=arrow
)
product_mob = get_wallis_product(n_terms)
product_mob.to_edge(UP)
rects = VGroup(*[
SurroundingRectangle(product_mob[:n])
for n in list(range(3, 4 * n_terms, 4)) + [4 * n_terms]
])
rect = rects[0].copy()
pi_halves_arrow = Vector(UP, color=BLUE)
pi_halves_arrow.next_to(
number_line.number_to_point(np.pi / 2), DOWN, SMALL_BUFF
)
pi_halves_term = TexMobject("\\pi / 2")
pi_halves_term.next_to(pi_halves_arrow, DOWN)
self.add(product_mob, number_line, rect, arrow, decimal)
self.add(pi_halves_arrow, pi_halves_term)
for n in range(1, len(rects)):
curr_product = partial_products[n]
self.play(
get_arrow_update(),
decimal_anim,
Transform(rect, rects[n]),
run_time=0.5
)
self.wait(0.5)
for n in range(len(rects), len(numerical_terms), 31):
curr_product = partial_products[n]
self.play(
get_arrow_update(),
decimal_anim,
run_time=0.25
)
curr_product = np.pi / 2
self.play(
get_arrow_update(),
decimal_anim,
run_time=0.5
)
self.wait()
class TableOfContents(Scene):
def construct(self):
topics = VGroup(
TextMobject("The setup"),
TextMobject("Circle geometry with complex polynomials"),
TextMobject("Proof of the Wallis product"),
TextMobject("Formalities not discussed"),
TextMobject(
"Generalizing this proof to get \\\\ the product formula for sine"),
)
for topic in topics:
dot = Dot(color=BLUE)
dot.next_to(topic, LEFT)
topic.add(dot)
topics.arrange(
DOWN, aligned_edge=LEFT, buff=LARGE_BUFF
)
self.add(topics)
self.wait()
for i in range(len(topics)):
self.play(
topics[i + 1:].set_fill, {"opacity": 0.25},
topics[:i].set_fill, {"opacity": 0.25},
topics[i].set_fill, {"opacity": 1},
)
self.wait(2)
class SourcesOfOriginality(TeacherStudentsScene):
def construct(self):
self.mention_excitement()
self.break_down_value_of_math_presentations()
self.where_we_fit_in()
def mention_excitement(self):
self.teacher_says(
"This one came about \\\\ a bit differently...",
target_mode="speaking",
run_time=1
)
self.change_student_modes("happy", "confused", "erm")
self.wait(2)
def break_down_value_of_math_presentations(self):
title = TextMobject("The value of a", "math", "presentation")
title.to_edge(UP, buff=MED_SMALL_BUFF)
value_of, math, presentation = title
MATH_COLOR = YELLOW
COMMUNICATION_COLOR = BLUE
big_rect = self.big_rect = Rectangle(
width=title.get_width() + 2 * MED_LARGE_BUFF,
height=3.5,
color=WHITE
)
big_rect.next_to(title, DOWN)
left_rect, right_rect = self.left_rect, self.right_rect = [
Rectangle(
height=big_rect.get_height() - 2 * SMALL_BUFF,
width=0.5 * big_rect.get_width() - 2 * SMALL_BUFF,
color=color
)
for color in (MATH_COLOR, COMMUNICATION_COLOR)
]
right_rect.flip()
left_rect.next_to(big_rect.get_left(), RIGHT, SMALL_BUFF)
right_rect.next_to(big_rect.get_right(), LEFT, SMALL_BUFF)
underlying_math = TextMobject("Underlying", "math")
underlying_math.set_color(MATH_COLOR)
communication = TextMobject("Communication")
communication.set_color(COMMUNICATION_COLOR)
VGroup(underlying_math, communication).scale(0.75)
underlying_math.next_to(left_rect.get_top(), DOWN, SMALL_BUFF)
communication.next_to(right_rect.get_top(), DOWN, SMALL_BUFF)
formula = TexMobject(
"\\sum_{n = 1}^\\infty \\frac{1}{n^2} = \\frac{\\pi^2}{2}",
)
formula.scale(0.75)
formula.next_to(underlying_math, DOWN)
based_on_wastlund = TextMobject(
"Previous video based on\\\\",
"a paper by Johan W\\\"{a}stlund"
)
based_on_wastlund.set_width(
left_rect.get_width() - MED_SMALL_BUFF)
based_on_wastlund.next_to(formula, DOWN, MED_LARGE_BUFF)
communication_parts = TextMobject("Visuals, narrative, etc.")
communication_parts.scale(0.75)
communication_parts.next_to(communication, DOWN, MED_LARGE_BUFF)
lighthouse = Lighthouse(height=0.5)
lighthouse.next_to(communication_parts, DOWN, LARGE_BUFF)
ambient_light = AmbientLight(
num_levels=200,
radius=5,
opacity_function=DEFAULT_OPACITY_FUNCTION,
)
ambient_light.move_source_to(lighthouse.get_top())
big_rect.save_state()
big_rect.stretch(0, 1)
big_rect.stretch(0.5, 0)
big_rect.move_to(title)
self.play(
FadeInFromDown(title),
RemovePiCreatureBubble(
self.teacher,
target_mode="raise_right_hand",
look_at_arg=title,
),
self.get_student_changes(
*["pondering"] * 3,
look_at_arg=title
)
)
self.play(big_rect.restore)
self.play(*list(map(ShowCreation, [left_rect, right_rect])))
self.wait()
self.play(
math.match_color, left_rect,
ReplacementTransform(VGroup(math.copy()), underlying_math)
)
self.play(FadeIn(formula))
self.play(
presentation.match_color, right_rect,
ReplacementTransform(presentation.copy(), communication)
)
self.play(
FadeIn(communication_parts),
FadeIn(lighthouse),
SwitchOn(ambient_light)
)
self.play(self.teacher.change, "tease")
self.wait()
self.play(
FadeIn(based_on_wastlund),
self.get_student_changes(
"sassy", "erm", "plain",
look_at_arg=based_on_wastlund
),
)
self.wait()
self.math_content = VGroup(formula, based_on_wastlund)
def where_we_fit_in(self):
right_rect = self.right_rect
left_rect = self.left_rect
points = [
right_rect.get_left() + SMALL_BUFF * RIGHT,
right_rect.get_corner(UL),
right_rect.get_corner(UR),
right_rect.get_right() + SMALL_BUFF * LEFT,
right_rect.get_corner(DR),
right_rect.get_bottom() + SMALL_BUFF * UP,
right_rect.get_corner(DL),
]
added_points = [
left_rect.get_bottom(),
left_rect.get_corner(DL),
left_rect.get_corner(DL) + 1.25 * UP,
left_rect.get_bottom() + 1.25 * UP,
]
blob1, blob2 = VMobject(), VMobject()
blob1.set_points_smoothly(points + [points[0]])
blob1.append_points(3 * len(added_points) * [points[0]])
blob2.set_points_smoothly(points + added_points + [points[0]])
for blob in blob1, blob2:
blob.set_stroke(width=0)
blob.set_fill(BLUE, opacity=0.5)
our_contribution = TextMobject("Our target \\\\ contribution")
our_contribution.scale(0.75)
our_contribution.to_corner(UR)
arrow = Arrow(
our_contribution.get_bottom(),
right_rect.get_right() + MED_LARGE_BUFF * LEFT,
color=BLUE
)
wallis_product = get_wallis_product(n_terms=4)
wallis_product.set_width(
left_rect.get_width() - 2 * MED_LARGE_BUFF)
wallis_product.move_to(self.math_content, UP)
wallis_product_name = TextMobject("``Wallis product''")
wallis_product_name.scale(0.75)
wallis_product_name.next_to(wallis_product, DOWN, MED_SMALL_BUFF)
new_proof = TextMobject("New proof")
new_proof.next_to(wallis_product_name, DOWN, MED_LARGE_BUFF)
self.play(
DrawBorderThenFill(blob1),
Write(our_contribution),
GrowArrow(arrow),
)
self.wait(2)
self.play(FadeOut(self.math_content))
self.play(
FadeIn(wallis_product),
Write(wallis_product_name, run_time=1)
)
self.wait(2)
self.play(
Transform(blob1, blob2, path_arc=-90 * DEGREES),
FadeIn(new_proof),
self.teacher.change, "hooray",
)
self.change_all_student_modes("hooray", look_at_arg=new_proof)
self.wait(5)
class Six(Scene):
def construct(self):
six = TexMobject("6")
six.add_background_rectangle(opacity = 1)
six.background_rectangle.stretch(1.5, 0)
six.set_height(7)
self.add(six)
class SridharWatchingScene(PiCreatureScene):
CONFIG = {
"default_pi_creature_kwargs": {
"color": YELLOW_E,
"flip_at_start": False,
},
}
def construct(self):
laptop = Laptop()
laptop.scale(1.8)
laptop.to_corner(DR)
sridhar = self.pi_creature
sridhar.next_to(laptop, LEFT, SMALL_BUFF, DOWN)
bubble = ThoughtBubble()
bubble.flip()
bubble.pin_to(sridhar)
basel = TexMobject(
"{1", "\\over", "1^2}", "+"
"{1", "\\over", "2^2}", "+"
"{1", "\\over", "3^2}", "+", "\\cdots",
"= \\frac{\\pi^2}{6}"
)
wallis = get_wallis_product(n_terms=4)
VGroup(basel, wallis).scale(0.7)
basel.move_to(bubble.get_bubble_center())
basel.to_edge(UP, buff=MED_SMALL_BUFF)
wallis.next_to(basel, DOWN, buff=0.75)
arrow = TexMobject("\\updownarrow")
arrow.move_to(VGroup(basel, wallis))
basel.set_color(YELLOW)
wallis.set_color(BLUE)
self.play(LaggedStartMap(DrawBorderThenFill, laptop))
self.play(sridhar.change, "pondering", laptop.screen)
self.wait()
self.play(ShowCreation(bubble))
self.play(LaggedStartMap(FadeIn, basel))
self.play(
ReplacementTransform(basel.copy(), wallis),
GrowFromPoint(arrow, arrow.get_top())
)
self.wait(4)
self.play(sridhar.change, "thinking", wallis)
self.wait(4)
self.play(LaggedStartMap(
ApplyFunction,
VGroup(*list(laptop) + [bubble, basel, arrow, wallis, sridhar]),
lambda mob: (lambda m: m.set_color(BLACK).fade(1).scale(0.8), mob),
run_time=3,
))
class ShowProduct(Scene):
def construct(self):
self.setup_axes()
self.setup_wallis_product()
self.show_larger_terms()
self.show_smaller_terms()
self.interleave_terms()
self.show_answer()
def setup_axes(self):
axes = self.axes = self.get_axes(unit_size=0.75)
self.add(axes)
def setup_wallis_product(self):
full_wallis_product = get_wallis_product(n_terms=16, show_result=False)
wallis_product_parts = VGroup(*[
full_wallis_product[i:i + 4]
for i in range(0, len(full_wallis_product), 4)
])
larger_parts = self.larger_parts = wallis_product_parts[::2]
larger_parts.set_color(YELLOW)
dots = TexMobject("\\cdots")
dots.move_to(larger_parts[-1][-1], LEFT)
larger_parts[-1][-1].submobjects = dots.submobjects
smaller_parts = self.smaller_parts = wallis_product_parts[1::2]
smaller_parts.set_color(BLUE)
for parts in larger_parts, smaller_parts:
parts.arrange(RIGHT, buff=2 * SMALL_BUFF)
# Move around the dots
for part1, part2 in zip(parts, parts[1:]):
dot = part1.submobjects.pop(-1)
part2.add_to_back(dot)
larger_parts.to_edge(UP)
smaller_parts.next_to(larger_parts, DOWN, LARGE_BUFF)
self.wallis_product_terms = get_wallis_product_numerical_terms(40)
def show_larger_terms(self):
axes = self.axes
parts = self.larger_parts
terms = self.wallis_product_terms[::2]
partial_products = np.cumprod(terms)
dots = VGroup(*[
Dot(axes.coords_to_point(n + 1, prod))
for n, prod in enumerate(partial_products)
])
dots.match_color(parts)
lines = VGroup(*[
Line(d1.get_center(), d2.get_center())
for d1, d2 in zip(dots, dots[1:])
])
braces = VGroup(*[
Brace(parts[:n + 1], DOWN)
for n in range(len(parts))
])
brace = braces[0].copy()
decimal = DecimalNumber(partial_products[0], num_decimal_places=4)
decimal.next_to(brace, DOWN)
self.add(brace, decimal, dots[0], parts[0])
tuples = list(zip(parts[1:], lines, dots[1:], partial_products[1:], braces[1:]))
for part, line, dot, prod, new_brace in tuples:
self.play(
FadeIn(part),
Transform(brace, new_brace),
ChangeDecimalToValue(
decimal, prod,
position_update_func=lambda m: m.next_to(brace, DOWN)
),
ShowCreation(line),
GrowFromCenter(dot, rate_func=squish_rate_func(smooth, 0.5, 1)),
run_time=0.5,
)
self.wait(0.5)
N = len(parts)
self.play(
LaggedStartMap(ShowCreation, lines[N - 1:], lag_ratio=0.2),
LaggedStartMap(FadeIn, dots[N:], lag_ratio=0.2),
brace.stretch, 1.2, 0, {"about_edge": LEFT},
ChangeDecimalToValue(
decimal, partial_products[-1],
position_update_func=lambda m: m.next_to(brace, DOWN)
),
run_time=4,
rate_func=linear,
)
self.play(
FadeOut(brace),
ChangeDecimalToValue(
decimal, partial_products[-1] + 2,
position_update_func=lambda m: m.next_to(brace, DOWN)
),
UpdateFromFunc(
decimal, lambda d: d.shift(self.frame_duration * RIGHT)
),
UpdateFromAlphaFunc(
decimal, lambda d, a: d.set_fill(opacity=1 - a)
),
)
self.remove(decimal)
self.graph_to_remove = VGroup(dots, lines)
def show_smaller_terms(self):
larger_parts = self.larger_parts
larger_parts.save_state()
larger_parts_mover = larger_parts.copy()
larger_parts.fade(0.5)
smaller_parts = self.smaller_parts
for parts in larger_parts_mover, smaller_parts:
parts.denominators = VGroup(
parts[0][2],
*[part[3] for part in parts[1:]]
)
vect = op.sub(
smaller_parts.denominators[1].get_left(),
smaller_parts.denominators[0].get_left(),
)
smaller_parts.denominators.shift(vect)
self.play(
larger_parts_mover.move_to, smaller_parts, LEFT,
FadeOut(self.graph_to_remove)
)
self.play(
larger_parts_mover.denominators.shift, -vect,
smaller_parts.denominators.shift, -vect,
UpdateFromAlphaFunc(
larger_parts_mover,
lambda m, a: m.set_fill(opacity=1 - a),
remover=True
),
UpdateFromAlphaFunc(
smaller_parts,
lambda m, a: m.set_fill(opacity=a)
),
)
# Rescale axes
new_axes = self.get_axes(unit_size=1.5)
self.play(ReplacementTransform(self.axes, new_axes))
axes = self.axes = new_axes
# Show graph
terms = self.wallis_product_terms[1::2]
partial_products = np.cumprod(terms)[:15]
dots = VGroup(*[
Dot(axes.coords_to_point(n + 1, prod))
for n, prod in enumerate(partial_products)
])
dots.match_color(smaller_parts)
lines = VGroup(*[
Line(d1.get_center(), d2.get_center())
for d1, d2 in zip(dots, dots[1:])
])
self.play(
ShowCreation(lines),
LaggedStartMap(FadeIn, dots, lag_ratio=0.1),
run_time=3,
rate_func=linear,
)
self.wait(2)
self.play(FadeOut(VGroup(dots, lines)))
def interleave_terms(self):
larger_parts = self.larger_parts
smaller_parts = self.smaller_parts
index = 6
larger_parts.restore()
for parts in larger_parts, smaller_parts:
parts.prefix = parts[:index]
parts.suffix = parts[index:]
parts.prefix.generate_target()
larger_parts.fade(0.5)
full_product = VGroup(*it.chain(
*list(zip(larger_parts.prefix.target, smaller_parts.prefix.target))
))
for i, tex, vect in (0, "\\cdot", LEFT), (-1, "\\cdots", RIGHT):
part = smaller_parts.prefix.target[i]
dot = TexMobject(tex)
dot.match_color(part)
dot.next_to(part, vect, buff=2 * SMALL_BUFF)
part.add(dot)
full_product.arrange(RIGHT, buff=2 * SMALL_BUFF)
full_product.to_edge(UP)
for parts in larger_parts, smaller_parts:
self.play(
MoveToTarget(parts.prefix),
FadeOut(parts.suffix)
)
self.wait()
# Dots and lines
# In poor form, this is modified copy-pasted from show_larger_terms
axes = self.axes
parts = full_product
terms = self.wallis_product_terms
partial_products = np.cumprod(terms)
partial_products_iter = iter(partial_products)
print(partial_products)
dots = VGroup(*[
Dot(axes.coords_to_point(n + 1, prod))
for n, prod in enumerate(partial_products)
])
dots.set_color(GREEN)
lines = VGroup(*[
Line(d1.get_center(), d2.get_center())
for d1, d2 in zip(dots, dots[1:])
])
braces = VGroup(*[
Brace(parts[:n + 1], DOWN)
for n in range(len(parts))
])
brace = braces[0].copy()
decimal = DecimalNumber(next(partial_products_iter), num_decimal_places=4)
decimal.next_to(brace, DOWN)
self.play(*list(map(FadeIn, [brace, decimal, dots[0]])))
tuples = list(zip(lines, dots[1:], braces[1:]))
for line, dot, new_brace in tuples:
self.play(
Transform(brace, new_brace),
ChangeDecimalToValue(
decimal, next(partial_products_iter),
position_update_func=lambda m: m.next_to(brace, DOWN)
),
ShowCreation(line),
GrowFromCenter(dot, rate_func=squish_rate_func(smooth, 0.5, 1)),
run_time=0.5,
)
self.wait(0.5)
def get_decimal_anim():
return ChangeDecimalToValue(
decimal, next(partial_products_iter),
run_time=1,
rate_func=squish_rate_func(smooth, 0, 0.5),
)
self.play(
FadeIn(lines[len(parts) - 1:]),
FadeIn(dots[len(parts):]),
get_decimal_anim()
)
for x in range(3):
self.play(get_decimal_anim())
self.partial_product_decimal = decimal
self.get_decimal_anim = get_decimal_anim
def show_answer(self):
decimal = self.partial_product_decimal
axes = self.axes
pi_halves = TexMobject("{\\pi", "\\over", "2}")
pi_halves.scale(1.5)
pi_halves.move_to(decimal, UP)
randy = Randolph(height=1.7)
randy.next_to(decimal, DL)
randy.change("confused")
randy.save_state()
randy.change("plain")
randy.fade(1)
h_line = DashedLine(
axes.coords_to_point(0, np.pi / 2),
axes.coords_to_point(20, np.pi / 2),
color=RED
)
self.play(
ShowCreation(h_line),
randy.restore,
self.get_decimal_anim()
)
self.play(Blink(randy), self.get_decimal_anim())
self.play(self.get_decimal_anim())
self.play(
self.get_decimal_anim(),
UpdateFromAlphaFunc(
decimal,
lambda m, a: m.set_fill(opacity=1 - a)
),
ReplacementTransform(randy, pi_halves[0]),
Write(pi_halves[1:]),
)
self.remove(decimal)
self.wait()
# Helpers
def get_axes(self, unit_size):
y_max = 7
axes = Axes(
x_min=-1,
x_max=12.5,
y_min=-0.5,
y_max=y_max + 0.25,
y_axis_config={
"unit_size": unit_size,
"numbers_with_elongated_ticks": list(range(1, y_max + 1)),
"tick_size": 0.05,
},
)
axes.shift(6 * LEFT + 3 * DOWN - axes.coords_to_point(0, 0))
axes.y_axis.label_direction = LEFT
axes.y_axis.add_numbers(*list(range(1, y_max + 1)))
return axes
class TeacherShowing(TeacherStudentsScene):
def construct(self):
screen = self.screen
screen.set_height(4)
screen.next_to(self.students, UP, MED_LARGE_BUFF, RIGHT)
self.play(
ShowCreation(screen),
self.teacher.change, "raise_right_hand", screen,
self.get_student_changes(
*["pondering"] * 3,
look_at_arg=screen
)
)
self.wait(5)
class DistanceProductScene(MovingCameraScene):
CONFIG = {
"ambient_light_config": HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG,
"circle_color": BLUE,
"circle_radius": 3,
"num_lighthouses": 6,
"lighthouse_height": 0.5,
"ignored_lighthouse_indices": [],
"observer_config": {
"color": MAROON_B,
"mode": "pondering",
"height": 0.25,
"flip_at_start": True,
},
"observer_fraction": 1.0 / 3,
"d_label_height": 0.35,
"numeric_distance_label_height": 0.25,
"default_product_column_top": FRAME_WIDTH * RIGHT / 4 + 1.5 * UP,
"include_lighthouses": True,
"include_distance_labels_background_rectangle": True,
}
def setup(self):
super(DistanceProductScene, self).setup()
self.circle = Circle(
color=self.circle_color,
radius=self.circle_radius,
)
def get_circle_point_at_proportion(self, alpha):
radius = self.get_radius()
center = self.circle.get_center()
angle = alpha * TAU
unit_circle_point = np.cos(angle) * RIGHT + np.sin(angle) * UP
return radius * unit_circle_point + center
def get_lh_points(self):
return np.array([
self.get_circle_point_at_proportion(fdiv(i, self.num_lighthouses))
for i in range(self.num_lighthouses)
if i not in self.ignored_lighthouse_indices
])
def get_observer_point(self, fraction=None):
if fraction is None:
fraction = self.observer_fraction
return self.get_circle_point_at_proportion(fraction / self.num_lighthouses)
def get_observer(self):
observer = self.observer = PiCreature(**self.observer_config)
observer.next_to(self.get_observer_point(), RIGHT, buff=SMALL_BUFF)
return observer
def get_observer_dot(self):
self.observer_dot = Dot(
self.get_observer_point(),
color=self.observer_config["color"]
)
return self.observer_dot
def get_lighthouses(self):
self.lighthouses = VGroup()
for point in self.get_lh_points():
lighthouse = Lighthouse()
lighthouse.set_height(self.lighthouse_height)
lighthouse.move_to(point)
self.lighthouses.add(lighthouse)
return self.lighthouses
def get_lights(self):
self.lights = VGroup()
for point in self.get_lh_points():
light = AmbientLight(
source_point=VectorizedPoint(point),
**self.ambient_light_config
)
self.lights.add(light)
return self.lights
def get_distance_lines(self, start_point=None, line_class=Line):
if start_point is None:
start_point = self.get_observer_point()
lines = VGroup(*[
line_class(start_point, point)
for point in self.get_lh_points()
])
lines.set_stroke(width=2)
self.distance_lines = lines
return self.distance_lines
def get_symbolic_distance_labels(self):
if not hasattr(self, "distance_lines"):
self.get_distance_lines()
self.d_labels = VGroup()
for i, line in enumerate(self.distance_lines):
d_label = TexMobject("d_%d" % i)
d_label.set_height(self.d_label_height)
vect = rotate_vector(line.get_vector(), 90 * DEGREES)
vect *= 2.5 * SMALL_BUFF / get_norm(vect)
d_label.move_to(line.get_center() + vect)
self.d_labels.add(d_label)
return self.d_labels
def get_numeric_distance_labels(self, lines=None, num_decimal_places=3, show_ellipsis=True):
radius = self.circle.get_width() / 2
if lines is None:
if not hasattr(self, "distance_lines"):
self.get_distance_lines()
lines = self.distance_lines
labels = self.numeric_distance_labels = VGroup()
for line in lines:
label = DecimalNumber(
line.get_length() / radius,
num_decimal_places=num_decimal_places,
show_ellipsis=show_ellipsis,
include_background_rectangle=self.include_distance_labels_background_rectangle,
)
label.set_height(self.numeric_distance_label_height)
max_width = 0.5 * max(line.get_length(), 0.1)
if label.get_width() > max_width:
label.set_width(max_width)
angle = (line.get_angle() % TAU) - TAU / 2
if np.abs(angle) > TAU / 4:
angle += np.sign(angle) * np.pi
label.angle = angle
label.next_to(line.get_center(), UP, SMALL_BUFF)
label.rotate(angle, about_point=line.get_center())
labels.add(label)
return labels
def get_distance_product_column(self, column_top=None, labels=None, fraction=None):
if column_top is None:
column_top = self.default_product_column_top
if labels is None:
if not hasattr(self, "numeric_distance_labels"):
self.get_numeric_distance_labels()
labels = self.numeric_distance_labels
stacked_labels = labels.copy()
for label in stacked_labels:
label.rotate(-label.angle)
label.set_height(self.numeric_distance_label_height)
stacked_labels.arrange(DOWN)
stacked_labels.move_to(column_top, UP)
h_line = Line(LEFT, RIGHT)
h_line.set_width(1.5 * stacked_labels.get_width())
h_line.next_to(stacked_labels, DOWN, aligned_edge=RIGHT)
times = TexMobject("\\times")
times.next_to(h_line, UP, SMALL_BUFF, aligned_edge=LEFT)
product_decimal = DecimalNumber(
self.get_distance_product(fraction),
num_decimal_places=3,
show_ellipsis=True,
include_background_rectangle=self.include_distance_labels_background_rectangle,
)
product_decimal.set_height(self.numeric_distance_label_height)
product_decimal.next_to(h_line, DOWN)
product_decimal.align_to(stacked_labels, RIGHT)
product_decimal[1].set_color(BLUE)
self.distance_product_column = VGroup(
stacked_labels, h_line, times, product_decimal
)
return self.distance_product_column
def get_fractional_arc(self, fraction, start_fraction=0):
arc = Arc(
angle=fraction * TAU,
start_angle=start_fraction * TAU,
radius=self.get_radius(),
)
arc.shift(self.circle.get_center())
return arc
def get_halfway_indication_arcs(self):
fraction = 0.5 / self.num_lighthouses
arcs = VGroup(
self.get_fractional_arc(fraction),
self.get_fractional_arc(-fraction, start_fraction=2 * fraction),
)
arcs.set_stroke(YELLOW, 4)
return arcs
def get_circle_group(self):
group = VGroup(self.circle)
if not hasattr(self, "observer_dot"):
self.get_observer_dot()
if not hasattr(self, "observer"):
self.get_observer()
if not hasattr(self, "lighthouses"):
self.get_lighthouses()
if not hasattr(self, "lights"):
self.get_lights()
group.add(self.observer_dot, self.observer)
if self.include_lighthouses:
group.add(self.lighthouses)
group.add(self.lights)
return group
def setup_lighthouses_and_observer(self):
self.add(*self.get_circle_group())
# Numerical results
def get_radius(self):
return self.circle.get_width() / 2.0
def get_distance_product(self, fraction=None):
radius = self.get_radius()
observer_point = self.get_observer_point(fraction)
distances = [
get_norm(point - observer_point) / radius
for point in self.get_lh_points()
]
return reduce(op.mul, distances, 1.0)
# Animating methods
def add_numeric_distance_labels(self, show_line_creation=True):
anims = []
if not hasattr(self, "distance_lines"):
self.get_distance_lines()
if not hasattr(self, "numeric_distance_labels"):
self.get_numeric_distance_labels()
if show_line_creation:
anims.append(LaggedStartMap(ShowCreation, self.distance_lines))
anims.append(LaggedStartMap(FadeIn, self.numeric_distance_labels))
self.play(*anims)
def show_distance_product_in_column(self, **kwargs):
group = self.get_distance_product_column(**kwargs)
stacked_labels, h_line, times, product_decimal = group
labels = self.numeric_distance_labels
self.play(ReplacementTransform(labels.copy(), stacked_labels))
self.play(
ShowCreation(h_line),
Write(times)
)
self.play(
ReplacementTransform(
stacked_labels.copy(),
VGroup(product_decimal)
)
)
class IntroduceDistanceProduct(DistanceProductScene):
CONFIG = {
"ambient_light_config": {"color": YELLOW},
}
def construct(self):
self.draw_circle_with_points()
self.turn_into_lighthouses_and_observer()
self.show_sum_of_inverse_squares()
self.transition_to_lemma_1()
def draw_circle_with_points(self):
circle = self.circle
lh_dots = self.lh_dots = VGroup(*[
Dot(point) for point in self.get_lh_points()
])
lh_dot_arrows = VGroup(*[
Arrow(*[
interpolate(circle.get_center(), dot.get_center(), a)
for a in (0.6, 0.9)
], buff=0)
for dot in lh_dots
])
evenly_space_dots_label = TextMobject("Evenly-spaced \\\\ dots")
evenly_space_dots_label.set_width(0.5 * circle.get_width())
evenly_space_dots_label.move_to(circle)
special_dot = self.special_dot = self.get_observer_dot()
special_dot_arrow = Vector(DL)
special_dot_arrow.next_to(special_dot, UR, SMALL_BUFF)
special_dot_arrow.match_color(special_dot)
special_dot_label = TextMobject("Special dot")
special_dot_label.next_to(
special_dot_arrow.get_start(), UP, SMALL_BUFF)
special_dot_label.match_color(special_dot)
special_dot.save_state()
special_dot.next_to(special_dot_arrow, UR)
special_dot.set_fill(opacity=0)
self.play(ShowCreation(circle))
self.play(
LaggedStartMap(ShowCreation, lh_dots),
LaggedStartMap(GrowArrow, lh_dot_arrows),
Write(evenly_space_dots_label)
)
self.wait()
self.play(
special_dot.restore,
GrowArrow(special_dot_arrow),
Write(special_dot_label, run_time=1),
FadeOut(VGroup(lh_dot_arrows, evenly_space_dots_label))
)
self.wait()
self.play(FadeOut(VGroup(special_dot_arrow, special_dot_label)))
def turn_into_lighthouses_and_observer(self):
lighthouses = self.get_lighthouses()
lights = self.get_lights()
observer = self.get_observer()
observer.save_state()
observer.set_height(2)
observer.change_mode("happy")
observer.to_edge(RIGHT)
self.play(
LaggedStartMap(FadeOut, self.lh_dots),
LaggedStartMap(FadeIn, lighthouses),
LaggedStartMap(SwitchOn, lights),
)
self.wait()
self.play(FadeIn(observer))
self.play(observer.restore)
self.wait()
def show_sum_of_inverse_squares(self):
lines = self.get_distance_lines()
labels = self.get_symbolic_distance_labels()
sum_of_inverse_squares = TexMobject(*it.chain(*[
["{1", "\\over", "(", "d_%d" % i, ")", "^2}", "+"]
for i in range(len(lines))
]))
sum_of_inverse_squares.submobjects.pop(-1)
sum_of_inverse_squares.to_edge(UP)
d_terms = sum_of_inverse_squares.get_parts_by_tex("d_")
d_terms.set_color(YELLOW)
plusses = sum_of_inverse_squares.get_parts_by_tex("+")
last_term = sum_of_inverse_squares[-6:]
non_d_terms = VGroup(*[m for m in sum_of_inverse_squares if m not in d_terms and m not in last_term])
brace = Brace(sum_of_inverse_squares, DOWN)
brace_text = brace.get_text("Total intensity of light")
arrow = Vector(DOWN, color=WHITE).next_to(brace, DOWN)
basel_sum = TexMobject(
"{1 \\over 1^2} + ",
"{1 \\over 2^2} + ",
"{1 \\over 3^2} + ",
"{1 \\over 4^2} + ",
"\\cdots",
)
basel_sum.next_to(arrow, DOWN)
basel_cross = Cross(basel_sum)
useful_for = TextMobject("Useful for")
useful_for.next_to(arrow, RIGHT)
wallis_product = TexMobject(
"{2 \\over 1} \\cdot", "{2 \\over 3} \\cdot",
"{4 \\over 3} \\cdot", "{4 \\over 5} \\cdot",
"{6 \\over 5} \\cdot", "{6 \\over 7} \\cdot",
"\\cdots"
)
wallis_product.move_to(basel_sum)
light_rings = VGroup(*it.chain(*self.lights))
self.play(
LaggedStartMap(ShowCreation, lines),
LaggedStartMap(Write, labels),
)
circle_group = VGroup(*self.get_top_level_mobjects())
self.wait()
self.play(
ReplacementTransform(labels[-1].copy(), last_term[3]),
Write(VGroup(*it.chain(last_term[:3], last_term[4:])))
)
self.remove(last_term)
self.add(last_term)
self.wait()
self.play(
Write(non_d_terms),
ReplacementTransform(
labels[:-1].copy(),
d_terms[:-1],
),
circle_group.scale, 0.8, {"about_point": FRAME_Y_RADIUS * DOWN}
)
self.wait()
self.play(LaggedStartMap(
ApplyMethod, light_rings,
lambda m: (m.set_fill, {"opacity": 2 * m.get_fill_opacity()}),
rate_func=there_and_back,
run_time=3,
))
self.wait()
# Mention useful just to basel problem
circle_group.save_state()
v_point = VectorizedPoint(
FRAME_X_RADIUS * LEFT + FRAME_Y_RADIUS * DOWN)
self.play(
circle_group.next_to, v_point, UR, {
"submobject_to_align": self.circle},
circle_group.scale, 0.5, {"about_point": v_point.get_center()},
)
self.play(
GrowFromCenter(brace),
Write(brace_text)
)
self.wait()
self.play(
FadeOut(brace_text),
GrowArrow(arrow),
FadeIn(useful_for),
FadeIn(basel_sum),
)
self.wait()
self.play(
ShowCreation(basel_cross),
FadeOut(VGroup(arrow, useful_for, brace))
)
basel_group = VGroup(basel_sum, basel_cross)
self.play(
basel_group.scale, 0.5,
basel_group.to_corner, DR,
)
self.play(Write(wallis_product))
self.wait()
# Transition to distance product
self.play(
circle_group.restore,
wallis_product.match_width, basel_sum,
wallis_product.next_to, basel_sum, UP, {"aligned_edge": RIGHT},
)
self.play(
d_terms.shift, 0.75 * d_terms.get_height() * UP,
d_terms.set_color, PRODUCT_COLOR,
light_rings.set_fill, PRODUCT_COLOR,
*[
FadeOut(mob)
for mob in sum_of_inverse_squares
if mob not in d_terms and mob not in plusses
]
)
self.wait()
self.play(
FadeOut(plusses),
d_terms.arrange, RIGHT, 0.25 * SMALL_BUFF,
d_terms.move_to, sum_of_inverse_squares, DOWN,
)
self.wait()
# Label distance product
brace = Brace(d_terms, UP, buff=SMALL_BUFF)
distance_product_label = brace.get_text("``Distance product''")
self.play(
GrowFromCenter(brace),
Write(distance_product_label)
)
line_copies = lines.copy().set_color(RED)
self.play(LaggedStartMap(ShowCreationThenDestruction, line_copies))
self.wait()
self.play(LaggedStartMap(
ApplyFunction, light_rings,
lambda mob: (
lambda m: m.shift(
MED_SMALL_BUFF * UP).set_fill(opacity=2 * m.get_fill_opacity()),
mob
),
rate_func=wiggle,
run_time=6,
))
self.wait()
def transition_to_lemma_1(self):
self.lighthouse_height = Lemma1.CONFIG["lighthouse_height"]
self.circle_radius = Lemma1.CONFIG["circle_radius"]
self.observer_fraction = Lemma1.CONFIG["observer_fraction"]
self.ambient_light_config["color"] = BLUE
circle = self.circle
lighthouses = self.lighthouses
lights = self.lights
circle.generate_target()
circle.target.set_width(2 * self.circle_radius)
circle.target.to_corner(DL)
self.circle = circle.target
new_lighthouses = self.get_lighthouses()
new_lights = self.get_lights()
self.clear()
self.play(
MoveToTarget(circle),
Transform(lighthouses, new_lighthouses),
Transform(lights, new_lights),
ApplyMethod(
self.observer_dot.move_to,
self.get_circle_point_at_proportion(
self.observer_fraction / self.num_lighthouses
)
),
MaintainPositionRelativeTo(self.observer, self.observer_dot),
)
class Lemma1(DistanceProductScene):
CONFIG = {
"circle_radius": 2.5,
"observer_fraction": 0.5,
"lighthouse_height": 0.25,
"lemma_text": "distance product = 2",
"include_distance_labels_background_rectangle": False,
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
}
def construct(self):
self.add_title()
self.add_circle_group()
self.state_lemma_premise()
self.show_product()
self.wiggle_observer()
def add_title(self):
title = self.title = TextMobject("Two lemmas:")
title.set_color(YELLOW)
title.to_edge(UP, buff=MED_SMALL_BUFF)
self.add(title)
def add_circle_group(self):
self.circle.to_corner(DL)
circle_group = self.get_circle_group()
self.play(LaggedStartMap(FadeIn, VGroup(
*circle_group.family_members_with_points())))
def state_lemma_premise(self):
premise = TextMobject(
"Lemma 1: If observer is halfway between lighthouses,")
self.premise = premise
premise.next_to(self.title, DOWN)
frac = 1.0 / self.num_lighthouses
arc1, arc2 = arcs = VGroup(VMobject(), VMobject())
arc1.pointwise_become_partial(self.circle, 0, frac / 2)
arc2.pointwise_become_partial(self.circle, frac / 2, frac)
arc1.reverse_points()
arcs.set_stroke(YELLOW, 5)
show_arcs = ShowCreationThenDestruction(
arcs,
lag_ratio=0,
run_time=2,
)
self.play(Write(premise), show_arcs, run_time=2)
self.wait()
self.play(show_arcs)
self.wait()
def show_product(self):
lemma = TextMobject(self.lemma_text)
lemma.set_color(BLUE)
lemma.next_to(self.premise, DOWN)
self.add_numeric_distance_labels()
self.play(Write(lemma, run_time=1))
self.show_distance_product_in_column()
self.wait()
def wiggle_observer(self):
# Overwriting existing method
self.get_observer_point = lambda dummy=None: self.observer_dot.get_center()
center = self.circle.get_center()
observer_angle = angle_of_vector(self.get_observer_point() - center)
observer_angle_tracker = ValueTracker(observer_angle)
def update_dot(dot):
dot.move_to(self.get_circle_point_at_proportion(
observer_angle_tracker.get_value() / TAU
))
def update_distance_lines(lines):
new_lines = self.get_distance_lines(start_point=self.get_observer_point())
lines.submobjects = new_lines.submobjects
def update_numeric_distance_labels(labels):
new_labels = self.get_numeric_distance_labels(self.distance_lines)
labels.submobjects = new_labels.submobjects
def update_distance_product_column(column):
new_column = self.get_distance_product_column()
column.submobjects = new_column.submobjects
self.remove(*VGroup(
self.observer, self.observer_dot,
self.distance_lines,
self.numeric_distance_labels,
self.distance_product_column,
).get_family())
self.play(
ApplyMethod(
observer_angle_tracker.set_value, observer_angle + 0.05 * TAU,
rate_func=wiggle
),
UpdateFromFunc(self.observer_dot, update_dot),
MaintainPositionRelativeTo(self.observer, self.observer_dot),
UpdateFromFunc(self.distance_lines, update_distance_lines),
UpdateFromFunc(self.numeric_distance_labels, update_numeric_distance_labels),
UpdateFromFunc(self.distance_product_column, update_distance_product_column),
run_time=5
)
self.distance_product_column[-1].set_color(BLUE).scale_in_place(1.05)
self.wait()
class Lemma1With7Lighthouses(Lemma1):
CONFIG = {
"num_lighthouses": 7,
}
class Lemma1With8Lighthouses(Lemma1):
CONFIG = {
"num_lighthouses": 8,
}
class Lemma1With9Lighthouses(Lemma1):
CONFIG = {
"num_lighthouses": 9,
}
class Lemma2(Lemma1):
CONFIG = {
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
"lemma_text": "distance product = \\# Initial lighthouses"
}
def construct(self):
self.add_title()
self.add_circle_group()
self.state_lemma_premise()
self.replace_first_lighthouse()
self.show_product()
self.wiggle_observer()
def state_lemma_premise(self):
premise = self.premise = TextMobject(
"Lemma 2: If the observer replaces a lighthouse,"
)
premise.next_to(self.title, DOWN)
self.play(Write(premise, run_time=1))
def replace_first_lighthouse(self):
dot = self.observer_dot
observer_anim = MaintainPositionRelativeTo(self.observer, dot)
lighthouse_group = VGroup(self.lighthouses[0], self.lights[0])
point = self.get_lh_points()[0]
self.play(
lighthouse_group.shift, 5 * RIGHT,
lighthouse_group.fade, 1,
run_time=1.5,
rate_func=running_start,
remover=True,
)
self.play(
dot.move_to, point,
observer_anim,
path_arc=(-120 * DEGREES),
)
self.wait()
self.ignored_lighthouse_indices = [0]
self.observer_fraction = 0
for group in self.lighthouses, self.lights:
self.lighthouses.submobjects.pop(0)
class Lemma2With7Lighthouses(Lemma2):
CONFIG = {
"num_lighthouses": 7,
}
class Lemma2With8Lighthouses(Lemma2):
CONFIG = {
"num_lighthouses": 8,
}
class Lemma2With9Lighthouses(Lemma2):
CONFIG = {
"num_lighthouses": 9,
}
class ConfusedPiCreature(Scene):
def construct(self):
randy = Randolph(color=GREY_BROWN)
self.add(randy)
self.play(Blink(randy))
self.play(randy.change, "confused")
self.play(Blink(randy))
self.wait()
class FromGeometryToAlgebra(DistanceProductScene):
CONFIG = {
"num_lighthouses": 7,
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
}
def construct(self):
self.setup_lights()
self.point_out_evenly_spaced()
self.transition_to_complex_plane()
self.discuss_powers()
self.raise_everything_to_the_nth()
def setup_lights(self):
circle = self.circle
circle.set_height(5, about_edge=DOWN)
lights = self.get_lights()
dots = VGroup(*[Dot(point) for point in self.get_lh_points()])
for dot, light in zip(dots, lights):
light.add_to_back(dot)
self.add(circle, lights)
def point_out_evenly_spaced(self):
circle = self.circle
step = 1.0 / self.num_lighthouses / 2
alpha_range = np.arange(0, 1 + step, step)
arcs = VGroup(*[
VMobject().pointwise_become_partial(circle, a1, a2)
for a1, a2 in zip(alpha_range, alpha_range[1:])
])
arcs.set_stroke(YELLOW, 5)
for arc in arcs[::2]:
arc.reverse_points()
arcs_anim = ShowCreationThenDestruction(
arcs, lag_ratio=0, run_time=2
)
spacing_words = self.spacing_words = TextMobject("Evenly-spaced")
spacing_words.set_width(self.get_radius())
spacing_words.move_to(circle)
arrows = self.get_arrows()
geometric_words = self.geometric_words = TextMobject(
"Geometric property")
geometric_words.to_edge(UP)
geometric_words.add_background_rectangle()
self.add(geometric_words)
self.play(
FadeIn(spacing_words),
arcs_anim,
*list(map(GrowArrow, arrows))
)
self.play(FadeOut(arrows), arcs_anim)
self.wait()
def transition_to_complex_plane(self):
plane = self.complex_plane = ComplexPlane(
unit_size=2, y_radius=6, x_radius=9,
)
plane.shift(1.5 * RIGHT)
plane.add_coordinates()
origin = plane.number_to_point(0)
h_line = Line(plane.number_to_point(-1), plane.number_to_point(1))
circle = self.circle
circle_group = VGroup(circle, self.lights)
circle_group.generate_target()
circle_group.target.scale(h_line.get_width() / circle.get_width())
circle_group.target.shift(
origin - circle_group.target[0].get_center()
)
circle_group.target[0].set_stroke(RED)
geometric_words = self.geometric_words
geometric_words.generate_target()
arrow = TexMobject("\\rightarrow")
arrow.add_background_rectangle()
algebraic_words = TextMobject("Algebraic property")
algebraic_words.add_background_rectangle()
word_group = VGroup(geometric_words.target, arrow, algebraic_words)
word_group.arrange(RIGHT)
word_group.move_to(origin)
word_group.to_edge(UP)
unit_circle_words = TextMobject("Unit circle", "")
unit_circle_words.match_color(circle_group.target[0])
for part in unit_circle_words:
part.add_background_rectangle()
unit_circle_words.next_to(origin, UP)
complex_plane_words = TextMobject("Complex Plane")
self.complex_plane_words = complex_plane_words
complex_plane_words.move_to(word_group)
complex_plane_words.add_background_rectangle()
roots_of_unity_words = TextMobject("Roots of\\\\", "unity")
roots_of_unity_words.move_to(origin)
roots_of_unity_words.set_color(YELLOW)
for part in roots_of_unity_words:
part.add_background_rectangle()
self.play(
Write(plane),
MoveToTarget(circle_group),
FadeOut(self.spacing_words),
MoveToTarget(geometric_words),
FadeIn(arrow),
FadeIn(algebraic_words),
)
word_group.submobjects[0] = geometric_words
self.play(Write(unit_circle_words, run_time=1))
# Show complex values
outer_arrows = self.outer_arrows = self.get_arrows()
for arrow, point in zip(outer_arrows, self.get_lh_points()):
arrow.rotate(np.pi, about_point=point)
outer_arrow = self.outer_arrow = outer_arrows[3].copy()
values = list(map(plane.point_to_number, self.get_lh_points()))
complex_decimal = self.complex_decimal = DecimalNumber(
values[3],
num_decimal_places=3,
include_background_rectangle=True
)
complex_decimal.next_to(outer_arrow.get_start(), LEFT, SMALL_BUFF)
complex_decimal_rect = SurroundingRectangle(complex_decimal)
complex_decimal_rect.fade(1)
self.play(
FadeIn(complex_plane_words),
FadeOut(word_group),
FadeIn(complex_decimal),
FadeIn(outer_arrow)
)
self.wait(2)
self.play(
ChangeDecimalToValue(
complex_decimal, values[1],
tracked_mobject=complex_decimal_rect
),
complex_decimal_rect.next_to, outer_arrows[1].get_start(
), UP, SMALL_BUFF,
Transform(outer_arrow, outer_arrows[1]),
run_time=1.5
)
self.wait()
arrows = self.get_arrows()
arrows.set_color(YELLOW)
self.play(
ReplacementTransform(unit_circle_words, roots_of_unity_words),
LaggedStartMap(GrowArrow, arrows)
)
self.wait()
self.play(
complex_plane_words.move_to, word_group,
LaggedStartMap(FadeOut, VGroup(*it.chain(
arrows, roots_of_unity_words
)))
)
# Turn decimal into z
x_term = self.x_term = TexMobject("x")
x_term.add_background_rectangle()
x_term.move_to(complex_decimal, DOWN)
x_term.shift(0.5 * SMALL_BUFF * (DR))
self.play(ReplacementTransform(complex_decimal, x_term))
def discuss_powers(self):
x_term = self.x_term
outer_arrows = self.outer_arrows
outer_arrows.add(outer_arrows[0].copy())
plane = self.complex_plane
origin = plane.number_to_point(0)
question = TextMobject("What is $x^2$")
question.next_to(x_term, RIGHT, LARGE_BUFF)
question.set_color(YELLOW)
lh_points = list(self.get_lh_points())
lh_points.append(lh_points[0])
lines = VGroup(*[
Line(origin, point)
for point in lh_points
])
lines.set_color(GREEN)
step = 1.0 / self.num_lighthouses
angle_arcs = VGroup(*[
Arc(angle=alpha * TAU, radius=0.35).shift(origin)
for alpha in np.arange(0, 1 + step, step)
])
angle_labels = VGroup()
for i, arc in enumerate(angle_arcs):
label = TexMobject("(%d / %d)\\tau" % (i, self.num_lighthouses))
label.scale(0.5)
label.add_background_rectangle()
point = arc.point_from_proportion(0.5)
point += 1.2 * (point - origin)
label.move_to(point)
angle_labels.add(label)
if i == 0:
label.shift(0.75 * label.get_height() * DOWN)
line = self.angle_line = lines[1].copy()
line_ghost = DashedLine(line.get_start(), line.get_end())
self.ghost_angle_line = line_ghost
line_ghost.set_stroke(line.get_color(), 2)
angle_arc = angle_arcs[1].copy()
angle_label = angle_labels[1].copy()
angle_label.shift(0.25 * SMALL_BUFF * DR)
magnitude_label = TexMobject("1")
magnitude_label.next_to(line.get_center(), UL, buff=SMALL_BUFF)
power_labels = VGroup()
for i, arrow in enumerate(outer_arrows[:-1]):
label = TexMobject("x^%d" % i)
label.next_to(
arrow.get_start(), -arrow.get_vector(),
submobject_to_align=label[0]
)
label.add_background_rectangle()
power_labels.add(label)
power_labels[0].next_to(outer_arrows[-1].get_start(), UR, SMALL_BUFF)
power_labels.submobjects[1] = x_term
L_labels = self.L_labels = VGroup(*[
TexMobject("L_%d" % i).move_to(power_label, DOWN).add_background_rectangle(
opacity=1
)
for i, power_label in enumerate(power_labels)
])
# Ask about squaring
self.play(Write(question))
self.wait()
self.play(
ShowCreation(line),
Write(magnitude_label)
)
self.wait()
self.play(
ShowCreation(angle_arc),
Write(angle_label)
)
self.wait()
self.add(line_ghost)
for i in list(range(2, self.num_lighthouses)) + [0]:
anims = [
Transform(angle_arc, angle_arcs[i]),
Transform(angle_label, angle_labels[i]),
Transform(line, lines[i], path_arc=TAU / self.num_lighthouses),
]
if i == 2:
anims.append(FadeOut(magnitude_label))
if i == 3:
anims.append(FadeOut(question))
self.play(*anims)
new_anims = [
GrowArrow(outer_arrows[i]),
Write(power_labels[i]),
]
if i == 2:
new_anims.append(FadeOut(self.complex_plane_words))
self.play(*new_anims)
self.wait()
self.play(ReplacementTransform(power_labels, L_labels))
self.wait()
self.play(
Rotate(self.lights, TAU / self.num_lighthouses / 2),
rate_func=wiggle
)
self.wait()
self.play(
FadeOut(angle_arc),
FadeOut(angle_label),
*list(map(ShowCreationThenDestruction, lines))
)
self.wait()
def raise_everything_to_the_nth(self):
func_label = TexMobject("L \\rightarrow L^7")
func_label.set_color(YELLOW)
func_label.to_corner(UL, buff=LARGE_BUFF)
func_label.add_background_rectangle()
polynomial_scale_factor = 0.8
polynomial = TexMobject("x^%d - 1" % self.num_lighthouses, "=", "0")
polynomial.scale(polynomial_scale_factor)
polynomial.next_to(func_label, UP)
polynomial.to_edge(LEFT)
factored_polynomial = TexMobject(
"(x-L_0)(x-L_1)\\cdots(x-L_{%d - 1})" % self.num_lighthouses, "=", "0"
)
factored_polynomial.scale(polynomial_scale_factor)
factored_polynomial.next_to(polynomial, DOWN, aligned_edge=LEFT)
for group in polynomial, factored_polynomial:
for part in group:
part.add_background_rectangle()
origin = self.complex_plane.number_to_point(0)
lights = self.lights
lights.save_state()
rotations = []
for i, light in enumerate(lights):
rotations.append(Rotating(
light,
radians=(i * TAU - i * TAU / self.num_lighthouses),
about_point=origin,
rate_func=bezier([0, 0, 1, 1]),
))
self.play(Write(func_label, run_time=1))
for i, rotation in enumerate(rotations[:4]):
if i == 3:
rect = SurroundingRectangle(polynomial)
rect.set_color(YELLOW)
self.play(
FadeIn(polynomial),
ShowCreationThenDestruction(rect)
)
self.play(
rotation,
run_time=np.sqrt(i + 1)
)
self.play(*rotations[4:], run_time=3)
self.wait()
self.play(lights.restore)
self.play(
FadeOut(func_label),
FadeIn(factored_polynomial)
)
self.wait(3)
self.play(
factored_polynomial[0].next_to, polynomial[1], RIGHT, 1.5 * SMALL_BUFF,
FadeOut(polynomial[2]),
FadeOut(factored_polynomial[1:]),
)
# Comment on formula
formula = VGroup(polynomial[0], polynomial[1], factored_polynomial[0])
rect = SurroundingRectangle(formula)
brace = Brace(factored_polynomial[0], DOWN)
brace2 = Brace(polynomial[0], DOWN)
morty = PiCreature(color=GREY_BROWN)
morty.scale(0.5)
morty.next_to(brace.get_center(), DL, buff=LARGE_BUFF)
L1_rhs = TexMobject("= \\cos(\\tau / 7) + \\\\", "\\sin(\\tau / 7)i")
L1_rhs.next_to(self.L_labels[1], RIGHT, aligned_edge=UP)
for part in L1_rhs:
part.add_background_rectangle()
self.play(ShowCreation(rect))
self.play(FadeOut(rect))
self.wait()
self.play(GrowFromCenter(brace))
self.play(FadeIn(morty))
self.play(morty.change, "horrified", brace)
self.play(Blink(morty))
self.wait()
self.play(
Write(L1_rhs),
morty.change, "confused", L1_rhs
)
self.play(Blink(morty))
self.wait()
self.play(
Transform(brace, brace2),
morty.change, "hooray", brace2
)
self.play(Blink(morty))
self.wait()
# Nothing special about 7
new_lights = self.lights.copy()
new_lights.rotate(
TAU / self.num_lighthouses / 2,
about_point=origin
)
sevens = VGroup(polynomial[0][1][1], factored_polynomial[0][1][-4])
n_terms = VGroup()
for seven in sevens:
n_term = TexMobject("N")
n_term.replace(seven, dim_to_match=1)
n_term.scale(0.9)
n_term.shift(0.25 * SMALL_BUFF * DR)
n_terms.add(n_term)
self.play(LaggedStartMap(FadeOut, VGroup(*it.chain(
L1_rhs, self.outer_arrows, self.L_labels, self.outer_arrow,
self.angle_line, self.ghost_angle_line
))))
self.play(LaggedStartMap(SwitchOn, new_lights), morty.look_at, new_lights)
self.play(Transform(sevens, n_terms))
self.wait()
self.play(Blink(morty))
self.wait()
#
def get_arrows(self):
return VGroup(*[
Arrow(
interpolate(self.circle.get_center(), point, 0.6),
interpolate(self.circle.get_center(), point, 0.9),
buff=0
)
for point in self.get_lh_points()
])
class PlugObserverIntoPolynomial(DistanceProductScene):
CONFIG = {
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
"num_lighthouses": 7,
# This makes it look slightly better, but renders much slower
"add_lights_in_foreground": True,
}
def construct(self):
self.add_plane()
self.add_circle_group()
self.label_roots()
self.add_polynomial()
self.point_out_rhs()
self.introduce_observer()
self.raise_observer_to_the_N()
def add_plane(self):
plane = self.plane = ComplexPlane(
unit_size=2,
y_radius=5,
)
plane.shift(DOWN)
plane.add_coordinates()
plane.coordinate_labels.submobjects.pop(-4)
self.origin = plane.number_to_point(0)
self.add(plane)
def add_circle_group(self):
self.circle.set_color(RED)
self.circle.set_width(
2 * get_norm(self.plane.number_to_point(1) - self.origin)
)
self.circle.move_to(self.origin)
lights = self.lights = self.get_lights()
dots = VGroup(*[
Dot(point) for point in self.get_lh_points()
])
for dot, light in zip(dots, lights):
light.add_to_back(dot)
self.add(self.circle, lights)
if self.add_lights_in_foreground:
self.add_foreground_mobject(lights)
def label_roots(self):
origin = self.origin
labels = VGroup(*[
TexMobject("L_%d" % d)
for d in range(self.num_lighthouses)
])
self.root_labels = labels
points = self.get_lh_points()
for label, point in zip(labels, points):
label.move_to(interpolate(origin, point, 1.2))
labels[0].align_to(origin, UP)
labels[0].shift(SMALL_BUFF * DOWN)
self.add(labels)
def add_polynomial(self, arg="x"):
self.polynomial = self.get_polynomial_equation(arg)
self.add(self.polynomial)
def point_out_rhs(self):
rhs = self.get_polynomial_rhs(self.polynomial)
brace = Brace(rhs, DOWN, buff=SMALL_BUFF)
brace_text = brace.get_text(
"Useful for distance product", buff=SMALL_BUFF)
brace_text.set_color(YELLOW)
brace_text.add_background_rectangle()
self.play(
GrowFromCenter(brace),
Write(brace_text)
)
self.wait()
self.play(FadeOut(VGroup(brace, brace_text)))
def introduce_observer(self):
dot = self.observer_dot = Dot()
dot.move_to(self.plane.coords_to_point(1.6, 0.8))
observer = PiCreature(**self.observer_config)
observer.move_to(dot)
dot.match_color(observer)
vect = 2 * DOWN + LEFT
vect /= get_norm(vect)
arrow = self.arrow = Vector(0.5 * vect)
arrow.next_to(observer, -vect, buff=SMALL_BUFF)
arrow.set_color(WHITE)
full_name = TextMobject("Observer")
var_name = self.var_name = TexMobject("O")
for mob in full_name, var_name:
mob.match_color(observer)
mob.next_to(arrow.get_start(), UP, SMALL_BUFF)
mob.add_background_rectangle()
complex_decimal = DecimalNumber(0, include_background_rectangle=True)
equals = TexMobject("=")
complex_decimal_animation = ChangingDecimal(
complex_decimal,
lambda a: self.plane.point_to_number(dot.get_center()),
position_update_func=lambda m: m.next_to(equals, RIGHT, SMALL_BUFF)
)
complex_decimal_animation.update(0)
equals_decimal = VGroup(equals, complex_decimal)
equals_decimal.next_to(var_name, RIGHT)
new_polynomial = self.get_polynomial_equation("O")
O_terms = new_polynomial.get_parts_by_tex("O")
lhs, poly_eq, rhs = self.get_polynomial_split(new_polynomial)
lhs_rect = SurroundingRectangle(lhs, color=YELLOW)
rhs_rect = SurroundingRectangle(rhs, color=YELLOW)
self.lhs, self.rhs = lhs, rhs
self.lhs_rect, self.rhs_rect = lhs_rect, rhs_rect
lines = self.lines = self.get_lines()
lines_update = self.lines_update = UpdateFromFunc(
lines, lambda l: Transform(l, self.get_lines()).update(1)
)
anims_for_dot_movement = self.anims_for_dot_movement = [
MaintainPositionRelativeTo(arrow, dot),
MaintainPositionRelativeTo(var_name, arrow),
MaintainPositionRelativeTo(equals, var_name),
complex_decimal_animation,
lines_update,
]
self.play(
FadeInFrom(observer, direction=-vect),
GrowArrow(arrow)
)
self.play(Write(full_name))
self.wait()
self.play(
ReplacementTransform(full_name[0], var_name[0]),
ReplacementTransform(full_name[1][0], var_name[1][0]),
FadeOut(full_name[1][1:]),
ReplacementTransform(observer, dot),
FadeIn(equals_decimal)
)
self.add_foreground_mobject(dot)
# Substitute
self.wait()
self.play(
ReplacementTransform(var_name.copy(), O_terms),
ReplacementTransform(self.polynomial, new_polynomial)
)
self.polynomial = new_polynomial
self.wait()
# Show distances
self.play(ShowCreation(rhs_rect))
self.play(
LaggedStartMap(ShowCreation, lines),
Animation(dot)
)
self.play(
Rotating(
dot,
radians=TAU,
rate_func=smooth,
about_point=dot.get_center() + MED_LARGE_BUFF * LEFT,
run_time=4
),
*anims_for_dot_movement
)
self.wait()
self.remove(rhs_rect)
self.play(ReplacementTransform(rhs_rect.copy(), lhs_rect))
self.wait()
# Move onto circle
angle = self.observer_angle = TAU / self.num_lighthouses / 3.0
target_point = self.plane.number_to_point(
np.exp(complex(0, angle))
)
self.play(
dot.move_to, target_point,
*anims_for_dot_movement
)
self.play(FadeOut(VGroup(
equals, complex_decimal,
var_name, arrow,
)))
def raise_observer_to_the_N(self):
dot = self.observer_dot
origin = self.origin
radius = self.get_radius()
text_scale_val = 0.8
question = TextMobject(
"What fraction \\\\", "between $L_0$ and $L_1$", "?",
arg_separator=""
)
question.scale(text_scale_val)
question.next_to(dot, RIGHT)
question.add_background_rectangle_to_submobjects()
f_words = TextMobject("$f$", "of the way")
third_words = TextMobject("$\\frac{1}{3}$", "of the way")
for words in f_words, third_words:
words.scale(text_scale_val)
words.move_to(question[0])
words[0].set_color(YELLOW)
words.add_background_rectangle()
obs_angle = self.observer_angle
full_angle = TAU / self.num_lighthouses
def get_arc(angle):
result = Arc(angle=angle, radius=radius,
color=YELLOW, stroke_width=4)
result.shift(origin)
return result
arc = get_arc(obs_angle)
O_to_N_arc = get_arc(obs_angle * self.num_lighthouses)
O_to_N_dot = dot.copy().move_to(O_to_N_arc.point_from_proportion(1))
O_to_N_arrow = Vector(0.5 * DR).next_to(O_to_N_dot, UL, SMALL_BUFF)
O_to_N_arrow.set_color(WHITE)
O_to_N_label = TexMobject("O", "^N")
O_to_N_label.set_color_by_tex("O", dot.get_color())
O_to_N_label.next_to(O_to_N_arrow.get_start(), UP, SMALL_BUFF)
O_to_N_label.shift(SMALL_BUFF * RIGHT)
O_to_N_group = VGroup(O_to_N_arc, O_to_N_arrow, O_to_N_label)
around_circle_words = TextMobject("around the circle")
around_circle_words.scale(text_scale_val)
around_circle_words.add_background_rectangle()
around_circle_words.next_to(self.circle.get_top(), UR)
chord = Line(O_to_N_dot.get_center(), self.circle.get_right())
chord.set_stroke(GREEN)
chord_halves = VGroup(
Line(chord.get_center(), chord.get_start()),
Line(chord.get_center(), chord.get_end()),
)
chord_halves.set_stroke(WHITE, 5)
chord_label = TexMobject("|", "O", "^N", "-", "1", "|")
chord_label.set_color_by_tex("O", MAROON_B)
chord_label.add_background_rectangle()
chord_label.next_to(chord.get_center(), DOWN, SMALL_BUFF)
chord_label.rotate(
chord.get_angle(), about_point=chord.get_center()
)
numeric_chord_label = DecimalNumber(
np.sqrt(3),
num_decimal_places=4,
include_background_rectangle=True,
show_ellipsis=True,
)
numeric_chord_label.rotate(chord.get_angle())
numeric_chord_label.move_to(chord_label)
self.play(
FadeIn(question),
ShowCreation(arc),
)
for angle in [full_angle - obs_angle, -full_angle, obs_angle]:
last_angle = angle_of_vector(dot.get_center() - origin)
self.play(
self.lines_update,
UpdateFromAlphaFunc(
arc, lambda arc, a: Transform(
arc, get_arc(last_angle + a * angle)
).update(1)
),
Rotate(dot, angle, about_point=origin),
run_time=2
)
self.play(
FadeOut(question[0]),
FadeOut(question[2]),
FadeIn(f_words),
)
self.wait()
self.play(
FadeOut(self.lines),
FadeOut(self.root_labels),
)
self.play(
ReplacementTransform(dot.copy(), O_to_N_dot),
ReplacementTransform(arc, O_to_N_arc),
path_arc=O_to_N_arc.angle - arc.angle,
)
self.add_foreground_mobject(O_to_N_dot)
self.play(
FadeIn(O_to_N_label),
GrowArrow(O_to_N_arrow),
)
self.wait()
self.play(
FadeOut(question[1]),
f_words.next_to, around_circle_words, UP, SMALL_BUFF,
FadeIn(around_circle_words)
)
self.wait()
self.play(
FadeIn(chord_label[0]),
ReplacementTransform(self.lhs.copy(), chord_label[1]),
ShowCreation(chord)
)
self.wait()
# Talk through current example
light_rings = VGroup(*it.chain(self.lights))
self.play(LaggedStartMap(
ApplyMethod, light_rings,
lambda m: (m.shift, MED_SMALL_BUFF * UP),
rate_func=wiggle
))
self.play(
FadeOut(around_circle_words),
FadeIn(question[1]),
ReplacementTransform(f_words, third_words)
)
self.play(
Rotate(dot, 0.05 * TAU, about_point=origin, rate_func=wiggle)
)
self.wait(2)
self.play(ReplacementTransform(
dot.copy(), O_to_N_dot, path_arc=TAU / 3))
self.play(
third_words.next_to, around_circle_words, UP, SMALL_BUFF,
FadeIn(around_circle_words),
FadeOut(question[1])
)
self.wait()
self.play(Indicate(self.lhs))
for x in range(2):
self.play(ShowCreationThenDestruction(chord_halves))
self.play(
FadeOut(chord_label),
FadeIn(numeric_chord_label)
)
self.wait()
self.remove(self.lhs_rect)
self.play(
FadeOut(chord),
FadeOut(numeric_chord_label),
FadeOut(O_to_N_group),
FadeIn(self.lines),
ReplacementTransform(self.lhs_rect.copy(), self.rhs_rect)
)
self.wait()
# Add new lights
for light in self.lights:
light[1:].fade(0.5)
added_lights = self.lights.copy()
added_lights.rotate(full_angle / 2, about_point=origin)
new_lights = VGroup(*it.chain(*list(zip(self.lights, added_lights))))
self.num_lighthouses *= 2
dot.generate_target()
dot.target.move_to(self.get_circle_point_at_proportion(
obs_angle / TAU / 2
))
dot.save_state()
dot.move_to(dot.target)
new_lines = self.get_lines()
dot.restore()
self.play(Transform(self.lights, new_lights))
self.play(
MoveToTarget(dot),
Transform(self.lines, new_lines)
)
self.wait()
self.play(
third_words.next_to, question[1], UP, SMALL_BUFF,
FadeOut(around_circle_words),
FadeIn(question[1]),
)
self.wait()
chord_group = VGroup(chord, numeric_chord_label[1])
chord_group.set_color(YELLOW)
self.add_foreground_mobjects(*chord_group)
self.play(
FadeIn(chord),
FadeIn(numeric_chord_label),
)
self.wait()
# Helpers
def get_polynomial_equation(self, var="x", color=None):
if color is None:
color = self.observer_config["color"]
equation = TexMobject(
"\\left(", var, "^N", "-", "1", "\\right)", "=",
"\\left(", var, "-", "L_0", "\\right)",
"\\left(", var, "-", "L_1", "\\right)",
"\\cdots",
"\\left(", var, "-", "L_{N-1}", "\\right)",
)
equation.set_color_by_tex(var, color)
equation.to_edge(UP)
equation.add_background_rectangle()
return equation
def get_polynomial_rhs(self, polynomial):
return self.get_polynomial_split(polynomial)[2]
def get_polynomial_lhs(self, polynomial):
return self.get_polynomial_split(polynomial)[0]
def get_polynomial_split(self, polynomial):
eq = polynomial.get_part_by_tex("=")
i = polynomial[1].submobjects.index(eq)
return polynomial[1][:i], polynomial[1][i], polynomial[1][i + 1:]
def get_lines(self, start_point=None):
return self.get_distance_lines(
start_point=start_point,
line_class=DashedLine
)
def get_observer_point(self, dummy_arg=None):
return self.observer_dot.get_center()
class PlugObserverIntoPolynomial5Lighthouses(PlugObserverIntoPolynomial):
CONFIG = {
"num_lighthouses": 5,
}
class PlugObserverIntoPolynomial3Lighthouses(PlugObserverIntoPolynomial):
CONFIG = {
"num_lighthouses": 3,
}
class PlugObserverIntoPolynomial2Lighthouses(PlugObserverIntoPolynomial):
CONFIG = {
"num_lighthouses": 2,
}
class DefineChordF(Scene):
def construct(self):
radius = 2.5
full_chord_f = TextMobject(
"``", "Chord(", "$f$", ")", "''", arg_separator="")
full_chord_f.set_color_by_tex("$f$", YELLOW)
full_chord_f.to_edge(UP)
chord_f = full_chord_f[1:-1]
chord_f.generate_target()
circle = Circle(radius=2.5)
circle.set_color(RED)
radius_line = Line(circle.get_center(), circle.get_right())
one_label = TexMobject("1")
one_label.next_to(radius_line, DOWN, SMALL_BUFF)
chord = Line(*[circle.point_from_proportion(f) for f in [0, 1. / 3]])
chord.set_color(YELLOW)
chord_third = TextMobject("Chord(", "$1/3$", ")", arg_separator="")
chord_third.set_color_by_tex("1/3", YELLOW)
for term in chord_third, chord_f.target:
term.next_to(chord.get_center(), UP, SMALL_BUFF)
chord_angle = chord.get_angle() + np.pi
term.rotate(chord_angle, about_point=chord.get_center())
brace = Brace(Line(ORIGIN, TAU * UP / 3), RIGHT, buff=0)
brace.generate_target()
brace.target.stretch(0.5, 0)
brace.target.apply_complex_function(np.exp)
VGroup(brace, brace.target).scale(radius)
brace.next_to(circle.get_right(), RIGHT, SMALL_BUFF, DOWN)
brace.scale(0.5, about_edge=DOWN)
brace.target.move_to(brace, DR)
brace.target.shift(2 * SMALL_BUFF * LEFT)
f_label = TexMobject("f")
f_label.set_color(YELLOW)
point = circle.point_from_proportion(1.0 / 6)
f_label.move_to(point + 0.4 * (point - circle.get_center()))
third_label = TexMobject("\\frac{1}{3}")
third_label.scale(0.7)
third_label.move_to(f_label)
third_label.match_color(f_label)
alphas = np.linspace(0, 1, 4)
third_arcs = VGroup(*[
VMobject().pointwise_become_partial(circle, a1, a2)
for a1, a2 in zip(alphas, alphas[1:])
])
third_arcs.set_color_by_gradient(BLUE, PINK, GREEN)
# Terms for sine formula
origin = circle.get_center()
height = DashedLine(origin, chord.get_center())
half_chords = VGroup(
Line(chord.get_start(), chord.get_center()),
Line(chord.get_end(), chord.get_center()),
)
half_chords.set_color_by_gradient(BLUE, PINK)
alt_radius_line = Line(origin, chord.get_end())
alt_radius_line.set_color(WHITE)
angle_arc = Arc(
radius=0.3,
angle=TAU / 6,
)
angle_arc.shift(origin)
angle_label = TexMobject("\\frac{f}{2}", "2\\pi")
angle_label[0][0].set_color(YELLOW)
angle_label.scale(0.6)
angle_label.next_to(angle_arc, RIGHT, SMALL_BUFF, DOWN)
angle_label.shift(SMALL_BUFF * UR)
circle_group = VGroup(
circle, chord, radius_line, one_label,
brace, f_label, chord_f,
half_chords, height,
angle_arc, angle_label,
)
formula = TexMobject(
"= 2 \\cdot \\sin\\left(\\frac{f}{2} 2\\pi \\right)",
"= 2 \\cdot \\sin\\left(f \\pi \\right)",
)
for part in formula:
part[7].set_color(YELLOW)
# Draw circle and chord
self.add(radius_line, circle, one_label)
self.play(Write(full_chord_f))
self.play(ShowCreation(chord))
self.play(
MoveToTarget(chord_f),
FadeOut(VGroup(full_chord_f[0], full_chord_f[-1]))
)
self.play(GrowFromEdge(brace, DOWN))
self.play(MoveToTarget(brace, path_arc=TAU / 3))
self.play(Write(f_label))
self.wait(2)
# Show third
self.remove(chord_f, f_label)
self.play(
ReplacementTransform(chord_f.copy(), chord_third),
ReplacementTransform(f_label.copy(), third_label),
)
chord_copies = VGroup()
last_chord = chord
for color in PINK, BLUE:
chord_copy = last_chord.copy()
old_color = chord_copy.get_color()
self.play(
Rotate(chord_copy, -TAU / 6, about_point=last_chord.get_end()),
UpdateFromAlphaFunc(
chord_copy,
lambda m, a: m.set_stroke(
interpolate_color(old_color, color, a))
)
)
chord_copy.reverse_points()
last_chord = chord_copy
chord_copies.add(chord_copy)
self.wait()
self.play(
FadeOut(chord_copies),
ReplacementTransform(chord_third, chord_f),
ReplacementTransform(third_label, f_label),
)
# Show sine formula
top_chord_f = chord_f.copy()
top_chord_f.generate_target()
top_chord_f.target.rotate(-chord_angle)
top_chord_f.target.center().to_edge(UP, buff=LARGE_BUFF)
top_chord_f.target.shift(3 * LEFT)
formula.next_to(top_chord_f.target, RIGHT)
self.play(
ShowCreation(height),
FadeIn(half_chords),
ShowCreation(angle_arc),
Write(angle_label)
)
self.wait()
self.play(
MoveToTarget(top_chord_f),
circle_group.shift, 1.5 * DOWN,
)
self.play(Write(formula[0], run_time=1))
self.wait()
self.play(ReplacementTransform(
formula[0].copy(), formula[1],
path_arc=45 * DEGREES
))
self.wait()
class DistanceProductIsChordF(PlugObserverIntoPolynomial):
CONFIG = {
"include_lighthouses": False,
"num_lighthouses": 8,
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
# "add_lights_in_foreground": False,
}
def construct(self):
self.add_plane()
self.add_circle_group()
self.add_polynomial("O")
self.show_all_animations()
def show_all_animations(self):
fraction = self.observer_fraction = 0.3
circle = self.circle
O_dot = self.observer_dot = Dot()
O_dot.set_color(self.observer_config["color"])
O_to_N_dot = O_dot.copy()
O_dot.move_to(self.get_circle_point_at_proportion(
fraction / self.num_lighthouses))
O_to_N_dot.move_to(self.get_circle_point_at_proportion(fraction))
for dot, vect, tex in [(O_dot, DL, "O"), (O_to_N_dot, DR, "O^N")]:
arrow = Vector(0.5 * vect, color=WHITE)
arrow.next_to(dot.get_center(), -vect, SMALL_BUFF)
label = TexMobject(tex)
O_part = label[0]
O_part.match_color(dot)
label.add_background_rectangle()
label.next_to(arrow.get_start(), -vect, buff=0,
submobject_to_align=O_part)
dot.arrow = arrow
dot.label = label
self.add_foreground_mobject(dot)
self.add(arrow, label)
# For the transition to f = 1 / 2
dot.generate_target()
fraction_words = VGroup(
TextMobject("$f$", "of the way"),
TextMobject("between lighthouses")
)
fraction_words.scale(0.8)
fraction_words[0][0].set_color(YELLOW)
fraction_words.arrange(DOWN, SMALL_BUFF, aligned_edge=LEFT)
fraction_words.next_to(O_dot.label, RIGHT)
list(map(TexMobject.add_background_rectangle, fraction_words))
f_arc, new_arc = [
Arc(
angle=(TAU * f / self.num_lighthouses),
radius=self.get_radius(),
color=YELLOW,
).shift(circle.get_center())
for f in (fraction, 0.5)
]
self.add(f_arc)
lines = self.lines = self.get_lines()
labels = self.get_numeric_distance_labels()
black_rect = Rectangle(height=6, width=3.5)
black_rect.set_stroke(width=0)
black_rect.set_fill(BLACK, 0.8)
black_rect.to_corner(DL, buff=0)
colum_group = self.get_distance_product_column(
column_top=black_rect.get_top() + MED_SMALL_BUFF * DOWN
)
stacked_labels, h_line, times, product_decimal = colum_group
chord = Line(*[
self.get_circle_point_at_proportion(f)
for f in (0, fraction)
])
chord.set_stroke(YELLOW)
chord_f = get_chord_f_label(chord)
chord_f_as_product = chord_f.copy()
chord_f_as_product.generate_target()
chord_f_as_product.target.rotate(-chord_f_as_product.angle)
chord_f_as_product.target.scale(0.8)
chord_f_as_product.target.move_to(product_decimal, RIGHT)
# Constructs for the case f = 1 / 2
new_chord = Line(circle.get_right(), circle.get_left())
new_chord.match_style(chord)
chord_half = get_chord_f_label(new_chord, "1/2")
f_terms = VGroup(fraction_words[0][1][0], chord_f_as_product[1][1])
half_terms = VGroup(*[
TexMobject("\\frac{1}{2}").scale(0.6).set_color(YELLOW).move_to(f)
for f in f_terms
])
half_terms[1].move_to(chord_f_as_product.target[1][1])
O_dot.target.move_to(self.get_circle_point_at_proportion(
0.5 / self.num_lighthouses))
O_to_N_dot .target.move_to(circle.get_left())
self.observer_dot = O_dot.target
new_lines = self.get_lines()
changing_decimals = []
radius = self.get_radius()
for decimal, line in zip(stacked_labels, new_lines):
changing_decimals.append(
ChangeDecimalToValue(decimal, line.get_length() / radius)
)
equals_two_terms = VGroup(*[
TexMobject("=2").next_to(mob, DOWN, SMALL_BUFF)
for mob in (chord_half, chord_f_as_product.target)
])
# Animations
self.play(Write(fraction_words))
self.wait()
self.play(
LaggedStartMap(ShowCreation, lines),
LaggedStartMap(FadeIn, labels),
)
self.play(
FadeIn(black_rect),
ReplacementTransform(labels.copy(), stacked_labels),
ShowCreation(h_line),
Write(times),
)
self.wait(2)
self.add_foreground_mobjects(
chord_f[1], chord, O_dot, O_to_N_dot
)
self.play(
FadeOut(labels),
ShowCreation(chord),
FadeIn(chord_f),
)
self.play(MoveToTarget(chord_f_as_product))
self.wait(2)
# Transition to f = 1 / 2
self.play(
Transform(lines, new_lines),
Transform(f_arc, new_arc),
Transform(chord, new_chord),
chord_f.rotate, -chord_f.angle,
chord_f.move_to, chord_half,
MoveToTarget(O_dot),
MoveToTarget(O_to_N_dot),
MaintainPositionRelativeTo(O_dot.arrow, O_dot),
MaintainPositionRelativeTo(O_dot.label, O_dot),
MaintainPositionRelativeTo(O_to_N_dot.arrow, O_to_N_dot),
MaintainPositionRelativeTo(O_to_N_dot.label, O_to_N_dot),
*changing_decimals,
path_arc=(45 * DEGREES),
run_time=2
)
self.play(
Transform(chord_f, chord_half),
Transform(f_terms, half_terms),
)
self.wait()
for term in equals_two_terms:
term.add_background_rectangle()
self.add_foreground_mobject(term[1])
self.play(
Write(equals_two_terms)
)
self.wait()
class ProveLemma2(PlugObserverIntoPolynomial):
CONFIG = {
"include_lighthouses": False,
"num_lighthouses": 8,
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
# "add_lights_in_foreground": False,
}
def construct(self):
self.add_plane()
self.add_circle_group()
self.add_polynomial("O")
self.replace_first_lighthouse()
self.rearrange_polynomial()
self.plug_in_one()
def replace_first_lighthouse(self):
light_to_remove = self.lights[0]
dot = self.observer_dot = Dot(color=self.observer_config["color"])
dot.move_to(self.get_circle_point_at_proportion(
0.5 / self.num_lighthouses))
arrow = Vector(0.5 * DL, color=WHITE)
arrow.next_to(dot, UR, SMALL_BUFF)
O_label = self.O_dot_label = TexMobject("O")
O_label.match_color(dot)
O_label.add_background_rectangle()
O_label.next_to(arrow, UR, SMALL_BUFF)
# First, move the lighthouse
self.add_foreground_mobject(dot)
self.play(
dot.move_to, light_to_remove,
MaintainPositionRelativeTo(arrow, dot),
MaintainPositionRelativeTo(O_label, dot),
path_arc=-TAU / 2
)
black_rect = Rectangle(
height=6, width=3.5,
stroke_width=0,
fill_color=BLACK,
fill_opacity=1,
)
black_rect.to_corner(DL, buff=0)
lines = self.get_lines(self.circle.get_right())
labels = self.get_numeric_distance_labels()
column_group = self.get_distance_product_column(
black_rect.get_top() + MED_SMALL_BUFF * DOWN
)
stacked_labels, h_line, times, product_decimal = column_group
q_marks = self.q_marks = TextMobject("???")
q_marks.move_to(product_decimal, LEFT)
q_marks.match_color(product_decimal)
zero_rects = VGroup(
*list(map(SurroundingRectangle, [dot, stacked_labels[0]])))
self.play(
LaggedStartMap(ShowCreation, lines),
LaggedStartMap(FadeIn, labels),
)
self.play(
FadeIn(black_rect),
ShowCreation(h_line),
Write(times),
ReplacementTransform(labels.copy(), stacked_labels)
)
self.wait()
self.play(ReplacementTransform(
stacked_labels.copy(),
VGroup(product_decimal)
))
self.wait()
self.add_foreground_mobject(zero_rects)
self.play(*list(map(ShowCreation, zero_rects)))
self.wait(2)
self.play(
VGroup(light_to_remove, zero_rects[0]
).shift, FRAME_WIDTH * RIGHT / 2,
path_arc=-60 * DEGREES,
rate_func=running_start,
remover=True
)
self.play(
VGroup(stacked_labels[0], zero_rects[1]).shift, 4 * LEFT,
rate_func=running_start,
remover=True,
)
self.remove_foreground_mobjects(zero_rects)
self.play(
FadeOut(product_decimal),
FadeIn(q_marks)
)
self.play(FadeOut(labels))
self.wait()
def rearrange_polynomial(self):
dot = self.observer_dot
lhs, equals, rhs = self.get_polynomial_split(self.polynomial)
polynomial_background = self.polynomial[0]
first_factor = rhs[:5]
remaining_factors = rhs[5:]
equals_remaining_factors = VGroup(equals, remaining_factors)
# first_factor_rect = SurroundingRectangle(first_factor)
lhs_rect = SurroundingRectangle(lhs)
frac_line = Line(LEFT, RIGHT, color=WHITE)
frac_line.match_width(lhs, stretch=True)
frac_line.next_to(lhs, DOWN, SMALL_BUFF)
O_minus_1 = TexMobject("\\left(", "O", "-", "1", "\\right)")
O_minus_1.next_to(frac_line, DOWN, SMALL_BUFF)
new_lhs_background = BackgroundRectangle(
VGroup(lhs, O_minus_1), buff=SMALL_BUFF)
new_lhs_rect = SurroundingRectangle(VGroup(lhs, O_minus_1))
roots_of_unity_circle = VGroup(*[
Circle(radius=0.2, color=YELLOW).move_to(point)
for point in self.get_lh_points()
])
for circle in roots_of_unity_circle:
circle.save_state()
circle.scale(4)
circle.fade(1)
self.play(ShowCreation(lhs_rect))
self.add_foreground_mobject(roots_of_unity_circle)
self.play(LaggedStartMap(
ApplyMethod, roots_of_unity_circle,
lambda m: (m.restore,)
))
self.wait()
frac_line_copy = frac_line.copy()
self.play(
FadeIn(new_lhs_background),
polynomial_background.stretch, 0.8, 0,
polynomial_background.move_to, frac_line_copy, LEFT,
equals_remaining_factors.arrange, RIGHT, SMALL_BUFF,
equals_remaining_factors.next_to, frac_line_copy, RIGHT, MED_SMALL_BUFF,
ReplacementTransform(first_factor, O_minus_1,
path_arc=-90 * DEGREES),
ShowCreation(frac_line),
Animation(lhs),
ReplacementTransform(lhs_rect, new_lhs_rect),
)
self.play(
roots_of_unity_circle[0].shift, FRAME_WIDTH * RIGHT / 2,
path_arc=(-60 * DEGREES),
rate_func=running_start,
remover=True
)
# Expand rhs
expanded_rhs = self.expanded_rhs = TexMobject(
"=", "1", "+",
"O", "+",
"O", "^2", "+",
"\\cdots",
"O", "^{N-1}"
)
expanded_rhs.next_to(frac_line, RIGHT)
expanded_rhs.shift(LEFT)
expanded_rhs.scale(0.9)
expanded_rhs.set_color_by_tex("O", dot.get_color())
self.play(
polynomial_background.stretch, 1.8, 0, {"about_edge": LEFT},
FadeIn(expanded_rhs),
equals_remaining_factors.scale, 0.9,
equals_remaining_factors.next_to, expanded_rhs,
VGroup(
new_lhs_background, lhs, frac_line, O_minus_1,
new_lhs_rect,
).shift, LEFT,
)
self.wait()
def plug_in_one(self):
expanded_rhs = self.expanded_rhs
O_terms = expanded_rhs.get_parts_by_tex("O")
ones = VGroup(*[
TexMobject("1").move_to(O_term, RIGHT)
for O_term in O_terms
])
ones.match_color(O_terms[0])
equals_1 = TexMobject("= 1")
equals_1.next_to(self.O_dot_label, RIGHT, SMALL_BUFF)
brace = Brace(expanded_rhs[1:], DOWN)
N_term = brace.get_text("N")
product = DecimalNumber(
self.num_lighthouses,
num_decimal_places=3,
show_ellipsis=True
)
product.move_to(self.q_marks, LEFT)
self.play(Write(equals_1))
self.play(
FocusOn(brace),
GrowFromCenter(brace)
)
self.wait(2)
self.play(ReplacementTransform(O_terms, ones))
self.wait()
self.play(Write(N_term))
self.play(FocusOn(product))
self.play(
FadeOut(self.q_marks),
FadeIn(product)
)
self.wait()
class LocalMathematician(PiCreatureScene):
def construct(self):
randy, mathy = self.pi_creatures
screen = ScreenRectangle(height=2)
screen.to_corner(UL)
screen.fade(1)
mathy_name = TextMobject("Local \\\\ mathematician")
mathy_name.next_to(mathy, LEFT, LARGE_BUFF)
arrow = Arrow(mathy_name, mathy)
self.play(
Animation(screen),
mathy.change, "pondering",
PiCreatureSays(
randy, "Check these \\\\ out!",
target_mode="surprised",
bubble_kwargs={"height": 3, "width": 4},
look_at_arg=screen,
),
)
self.play(
Animation(screen),
RemovePiCreatureBubble(
randy,
target_mode="raise_right_hand",
look_at_arg=screen,
)
)
self.wait(2)
self.play(
PiCreatureSays(
mathy, "Ah yes, consider \\\\ $x^n - 1$ over $\\mathds{C}$...",
look_at_arg=randy.eyes
),
randy.change, "happy", mathy.eyes
)
self.wait(3)
def create_pi_creatures(self):
randy = Randolph().flip()
mathy = Mathematician()
randy.scale(0.9)
randy.to_edge(DOWN).shift(4 * RIGHT)
mathy.to_edge(DOWN).shift(4 * LEFT)
return randy, mathy
class ArmedWithTwoKeyFacts(TeacherStudentsScene, DistanceProductScene):
CONFIG = {
"num_lighthouses": 6,
"ambient_light_config": {
"opacity_function": inverse_power_law(1, 1, 1, 6),
"radius": 1,
"num_levels": 100,
"max_opacity": 1,
},
}
def setup(self):
TeacherStudentsScene.setup(self)
DistanceProductScene.setup(self)
def construct(self):
circle1 = self.circle
circle1.set_height(1.5)
circle1.to_corner(UL)
circle2 = circle1.copy()
circle2.next_to(circle1, DOWN, MED_LARGE_BUFF)
wallis_product = get_wallis_product(n_terms=8)
N = self.num_lighthouses
labels = VGroup()
for circle, f, dp in (circle1, 0.5, "2"), (circle2, 0, "N"):
self.circle = circle
lights = self.get_lights()
if f == 0:
lights.submobjects.pop(0)
observer = Dot(color=MAROON_B)
frac = f / N
point = self.get_circle_point_at_proportion(frac)
observer.move_to(point)
lines = self.get_distance_lines(point, line_class=DashedLine)
label = TextMobject("Distance product = %s" % dp)
label.scale(0.7)
label.next_to(circle, RIGHT)
labels.add(label)
group = VGroup(lines, observer, label)
self.play(
FadeIn(circle),
LaggedStartMap(FadeIn, VGroup(*it.chain(lights))),
LaggedStartMap(
FadeIn, VGroup(
*it.chain(group.family_members_with_points()))
),
self.teacher.change, "raise_right_hand",
self.get_student_changes(*["pondering"] * 3)
)
wallis_product.move_to(labels).to_edge(RIGHT)
self.play(
LaggedStartMap(FadeIn, wallis_product),
self.teacher.change_mode, "hooray",
self.get_student_changes(
*["thinking"] * 3, look_at_arg=wallis_product)
)
self.wait(2)
class Sailor(PiCreature):
CONFIG = {
"flip_at_start": True,
"color": YELLOW_D,
"hat_height_factor": 1.0 / 6,
}
def __init__(self, *args, **kwargs):
PiCreature.__init__(self, *args, **kwargs)
height = self.get_height() * self.hat_height_factor
sailor_hat = SVGMobject(file_name="sailor_hat", height=height)
# Rhombus is a horrible hack...
rhombus = Polygon(
UP, UP + 2 * RIGHT,
1.75 * RIGHT + 0.5 * UP, 0.5 * RIGHT + 0.1 * DOWN,
1.25 * LEFT + 0.15 * DOWN,
)
rhombus.set_fill(BLACK, opacity=1)
rhombus.set_stroke(width=0)
rhombus.set_height(sailor_hat.get_height() / 3)
rhombus.rotate(5 * DEGREES)
rhombus.move_to(sailor_hat, DR)
rhombus.shift(0.05 * sailor_hat.get_width() * LEFT)
sailor_hat.add_to_back(rhombus)
sailor_hat.rotate(-15 * DEGREES)
sailor_hat.move_to(self.eyes.get_center(), DOWN)
sailor_hat.shift(
0.1 * self.eyes.get_width() * RIGHT,
0.1 * self.eyes.get_height() * UP,
)
self.add(sailor_hat)
class KeeperAndSailor(DistanceProductScene, PiCreatureScene):
CONFIG = {
"num_lighthouses": 9,
"circle_radius": 2.75,
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
"add_lights_in_foreground": False, # Keep this way
"text_scale_val": 0.7,
"observer_fraction": 0.5,
"keeper_color": BLUE,
"sailor_color": YELLOW_D,
"include_distance_labels_background_rectangle": False,
"big_circle_center": FRAME_WIDTH * LEFT / 2 + SMALL_BUFF * RIGHT,
}
def setup(self):
DistanceProductScene.setup(self)
PiCreatureScene.setup(self)
self.remove(*self.pi_creatures)
def construct(self):
self.place_lighthouses()
self.introduce_observers()
self.write_distance_product_fraction()
self.break_down_distance_product_by_parts()
self.grow_circle_and_N()
self.show_limit_for_each_fraction()
self.show_limit_of_lhs()
def place_lighthouses(self):
circle = self.circle
circle.to_corner(DL)
circle.shift(MED_SMALL_BUFF * UR)
circle.set_color(RED)
lighthouses = self.get_lighthouses()
lights = self.get_lights()
for light in lights:
dot = Dot(radius=0.06).move_to(light)
dot.match_color(light)
light.add_to_back(dot)
origin = circle.get_center()
arrows = VGroup(*[
Arrow(0.6 * (p - origin), 0.9 * (p - origin), buff=0).shift(origin)
for p in self.get_lh_points()
])
arrows.set_color(WHITE)
words = TextMobject("N evenly-spaced \\\\ lighthouses")
words.scale(0.8)
words.move_to(origin)
self.add(circle)
if self.add_lights_in_foreground:
self.add_foreground_mobject(lights)
self.add_foreground_mobject(words)
self.play(
LaggedStartMap(FadeIn, VGroup(*it.chain(lights))),
LaggedStartMap(FadeIn, lighthouses),
LaggedStartMap(GrowArrow, arrows),
)
self.remove_foreground_mobjects(words)
self.play(FadeOut(words), FadeOut(arrows))
self.wait()
def introduce_observers(self):
keeper, sailor = observers = self.observers
keeper.target_point = self.get_keeper_point()
sailor.target_point = self.get_sailor_point()
for pi, text in (keeper, "Keeper"), (sailor, "Sailor"):
pi.title = TextMobject(text)
pi.title.next_to(pi, DOWN)
pi.dot = Dot()
pi.dot.match_color(pi)
pi.dot.next_to(pi, LEFT)
pi.dot.set_fill(opacity=0)
self.play(LaggedStartMap(
Succession, observers,
lambda m: (FadeIn, m, ApplyMethod, m.change, "wave_1")
))
for pi in observers:
self.play(
FadeIn(pi.title),
pi.change, "plain"
)
self.wait()
if self.add_lights_in_foreground:
self.add_foreground_mobjects(keeper, keeper.dot, keeper.title)
for pi in observers:
self.play(
pi.set_height, 0.5,
pi.next_to, pi.target_point, RIGHT, SMALL_BUFF,
pi.dot.move_to, pi.target_point,
pi.dot.set_fill, {"opacity": 1},
pi.title.scale, self.text_scale_val,
pi.title.next_to, pi.target_point, RIGHT, {"buff": 0.6},
)
if pi is sailor:
arcs = self.get_halfway_indication_arcs()
self.play(*list(map(ShowCreationThenDestruction, arcs)))
self.wait()
def write_distance_product_fraction(self):
fraction = self.distance_product_fraction = TexMobject(
"{\\text{Keeper's distance product}", "\\over",
"\\text{Sailor's distance product}}"
)
fraction.scale(self.text_scale_val)
fraction.to_corner(UR)
keeper_lines = self.get_distance_lines(
self.get_keeper_point(),
line_class=DashedLine
)
sailor_lines = self.get_distance_lines(
self.get_sailor_point(),
line_class=DashedLine
)
sailor_line_lengths = self.get_numeric_distance_labels(sailor_lines)
keeper_line_lengths = self.get_numeric_distance_labels(keeper_lines)
sailor_dp_column, keeper_dp_column = [
self.get_distance_product_column(
4 * RIGHT + 1.5 * UP, labels, frac
)
for labels, frac in [
(sailor_line_lengths, 0.5),
(keeper_line_lengths, 0),
]
]
sailor_dp_decimal = sailor_dp_column[-1]
sailor_dp_decimal_rect = SurroundingRectangle(sailor_dp_decimal)
keeper_dp_decimal = keeper_dp_column[-1]
keeper_dp_decimal_rect = SurroundingRectangle(keeper_dp_decimal)
keeper_top_zero_rect = SurroundingRectangle(keeper_dp_column[0][0])
# stacked_labels, h_line, times, product_decimal = column
# Define result fraction
equals = self.distance_product_equals = TexMobject("=")
result_fraction = self.result_fraction = TexMobject(
"{N", "{\\text{distance} \\choose \\text{between obs.}}", "\\over", "2}"
)
N, dist, frac_line, two = result_fraction
result_fraction.to_corner(UR)
equals.next_to(frac_line, LEFT)
for part in result_fraction:
part.save_state()
part.generate_target()
div = TexMobject("/")
first_denom = VGroup(two.target, div, dist)
first_denom.arrange(RIGHT, buff=SMALL_BUFF)
first_denom.move_to(two, UP)
N.next_to(frac_line, UP, SMALL_BUFF)
# Define terms to be removed
first_light_group = VGroup(self.lights[0], self.lighthouses[0])
keeper_top_zero_group = VGroup(
keeper_dp_column[0][0], keeper_top_zero_rect)
new_keeper_dp_decimal = DecimalNumber(
self.num_lighthouses,
num_decimal_places=3,
)
new_keeper_dp_decimal.replace(keeper_dp_decimal, dim_to_match=1)
new_keeper_dp_decimal.set_color(YELLOW)
self.play(*list(map(ShowCreation, keeper_lines)))
self.play(ReplacementTransform(
keeper_lines.copy(), VGroup(fraction[0])
))
self.play(FadeOut(keeper_lines))
self.play(*list(map(ShowCreation, sailor_lines)))
self.play(
ReplacementTransform(
sailor_lines.copy(),
VGroup(fraction[2])
),
ShowCreation(fraction[1])
)
self.wait()
self.play(LaggedStartMap(FadeIn, sailor_line_lengths))
self.play(ReplacementTransform(
sailor_line_lengths.copy(), sailor_dp_column[0]
))
self.play(FadeIn(sailor_dp_column[1:]))
self.play(ShowCreation(sailor_dp_decimal_rect))
self.play(
fraction.next_to, equals, LEFT,
FadeIn(equals),
ShowCreation(frac_line),
ReplacementTransform(sailor_dp_decimal.copy(), two),
FadeOut(sailor_dp_decimal_rect)
)
self.wait()
# Note, sailor_lines and sailor_line_lengths get changed here
self.remove(*list(sailor_lines) + list(sailor_line_lengths))
self.play(
FadeOut(sailor_dp_column),
ReplacementTransform(sailor_lines.deepcopy(), keeper_lines),
ReplacementTransform(
sailor_line_lengths.deepcopy(), keeper_line_lengths),
)
self.play(ReplacementTransform(
keeper_line_lengths.copy(), keeper_dp_column[0]
))
self.play(FadeIn(keeper_dp_column[1:]))
self.wait()
self.play(
ShowCreation(keeper_dp_decimal_rect),
ShowCreation(keeper_top_zero_rect)
)
self.wait(2)
# Remove first lighthouse
self.play(
first_light_group.shift, 0.6 * FRAME_WIDTH * RIGHT,
keeper_top_zero_group.shift, 0.4 * FRAME_WIDTH * RIGHT,
FadeOut(keeper_dp_decimal),
FadeOut(keeper_dp_decimal_rect),
path_arc=-30 * DEGREES,
rate_func=running_start,
)
self.remove(first_light_group, keeper_top_zero_group)
self.wait()
self.play(ReplacementTransform(
keeper_dp_column[0][1:].copy(),
VGroup(new_keeper_dp_decimal),
))
self.wait()
self.play(ReplacementTransform(new_keeper_dp_decimal.copy(), N,))
self.wait(2)
sailor_lines[0].set_color(RED)
sailor_line_lengths[0].set_color(RED)
sailor_line_lengths[0].set_stroke(RED, 1)
self.remove(*list(keeper_lines) + list(keeper_line_lengths))
self.play(
ReplacementTransform(keeper_lines.copy(), sailor_lines),
ReplacementTransform(
keeper_line_lengths.copy(), sailor_line_lengths),
FadeOut(keeper_dp_column[:-1]),
FadeOut(new_keeper_dp_decimal),
)
self.play(
Rotate(sailor_line_lengths[0], 30 * DEGREES, rate_func=wiggle)
)
self.wait()
self.play(
ReplacementTransform(sailor_lines[0].copy(), dist),
FadeIn(div),
MoveToTarget(two),
)
self.wait()
self.play(
two.restore,
FadeOut(div),
dist.restore,
N.restore,
)
self.play(
FadeOut(sailor_lines),
FadeOut(sailor_line_lengths),
)
self.wait()
def break_down_distance_product_by_parts(self):
result_fraction = self.result_fraction
result_fraction_rect = SurroundingRectangle(result_fraction)
product_parts = self.product_parts = TexMobject(
"{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot",
"{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot",
"{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdots",
)
product_parts.set_color_by_tex_to_color_map({
"K": BLUE,
"S": YELLOW,
})
product_parts.set_width(0.4 * FRAME_WIDTH)
product_parts.next_to(result_fraction, DOWN, LARGE_BUFF, RIGHT)
product_parts.shift(MED_SMALL_BUFF * RIGHT)
sailor_lines = self.get_sailor_lines()
sailor_lines.save_state()
keeper_lines = self.get_keeper_lines()
keeper_lines.save_state()
sailor_length_braces = VGroup(VMobject()) # Add fluff first object
keeper_length_braces = VGroup(VMobject()) # Add fluff first object
triplets = [
("S", sailor_length_braces, DOWN),
("K", keeper_length_braces, UP),
]
for char, brace_group, vect in triplets:
for part in product_parts.get_parts_by_tex(char):
brace = Brace(part, vect, buff=SMALL_BUFF)
brace.match_color(part)
brace_group.add(brace)
# Animations
self.replace_lighthouses_with_labels()
self.play(
LaggedStartMap(FadeIn, product_parts),
LaggedStartMap(FadeIn, sailor_lines,
rate_func=there_and_back, remover=True),
LaggedStartMap(FadeIn, keeper_lines,
rate_func=there_and_back, remover=True),
)
sailor_lines.restore()
keeper_lines.restore()
self.wait()
keeper_line = self.keeper_line = keeper_lines[1].copy()
sailor_line = self.sailor_line = sailor_lines[1].copy()
keeper_brace = keeper_length_braces[1].copy()
sailor_brace = sailor_length_braces[1].copy()
self.play(
ShowCreation(keeper_line),
GrowFromCenter(keeper_brace),
)
self.wait()
self.play(
ShowCreation(sailor_line),
GrowFromCenter(sailor_brace),
)
self.wait()
for i in range(2, 4):
self.play(
Transform(keeper_line, keeper_lines[i]),
Transform(keeper_brace, keeper_length_braces[i]),
)
self.play(
Transform(sailor_line, sailor_lines[i]),
Transform(sailor_brace, sailor_length_braces[i]),
)
self.wait()
for i in range(4, self.num_lighthouses):
anims = [
Transform(keeper_line, keeper_lines[i]),
Transform(sailor_line, sailor_lines[i]),
]
if i == 4:
anims += [
FadeOut(sailor_brace),
FadeOut(keeper_brace),
]
self.play(*anims)
self.play(FocusOn(result_fraction))
self.play(ShowPassingFlash(result_fraction_rect))
self.wait(3)
def grow_circle_and_N(self, circle_scale_factor=2, N_multiple=3, added_anims=None):
if added_anims is None:
added_anims = []
circle = self.circle
lights = self.lights
labels = self.lighthouse_labels
keeper = self.keeper
sailor = self.sailor
half_N = self.num_lighthouses / 2
anims = []
circle.generate_target()
for pi in keeper, sailor:
for mob in pi, pi.dot, pi.title:
mob.generate_target()
circle.target.scale(circle_scale_factor)
circle.target.move_to(self.big_circle_center)
self.circle = circle.target
anims.append(MoveToTarget(circle))
self.num_lighthouses = int(N_multiple * self.num_lighthouses)
new_lights = self.get_lights()
for light in new_lights:
light.scale(1.0 / circle_scale_factor)
new_labels = self.get_light_labels()
anims.append(ReplacementTransform(labels[1:], new_labels[1:]))
if hasattr(self, "keeper_line"):
keeper_line = self.keeper_line
sailor_line = self.sailor_line
self.keeper_lines = self.get_keeper_lines()
self.sailor_lines = self.get_sailor_lines()
anims += [
Transform(keeper_line, self.keeper_lines[-1]),
Transform(sailor_line, self.sailor_lines[-1]),
]
for group in lights, labels, new_lights, new_labels:
group[0].fade(1)
for mob in lights, labels:
for x in range(len(new_lights) - len(mob)):
mob.submobjects.insert(
half_N + 1, VectorizedPoint(circle.get_left()))
anims.append(ReplacementTransform(lights, new_lights))
keeper.dot.target.move_to(self.get_keeper_point())
sailor.dot.target.move_to(self.get_sailor_point())
for pi in keeper, sailor:
pi.target.scale(0)
pi.target.move_to(pi.dot.target)
pi.title.target.scale(0.85)
pi.title.target.next_to(pi.dot.target, RIGHT, SMALL_BUFF)
anims += [
MoveToTarget(part)
for pi in self.observers
for part in [pi, pi.dot, pi.title]
]
anims += added_anims
self.circle = circle
self.play(*anims, run_time=2)
if self.add_lights_in_foreground:
self.remove_foreground_mobjects(*self.lights)
self.remove_foreground_mobjects(*self.lighthouse_labels)
self.add_foreground_mobjects(new_lights, new_labels)
self.wait()
self.lights = new_lights
self.lighthouse_labels = new_labels
def show_limit_for_each_fraction(self):
product_parts = self.product_parts
keeper_line = self.keeper_line
keeper_lines = self.keeper_lines
sailor_line = self.sailor_line
sailor_lines = self.sailor_lines
labels = self.lighthouse_labels
center = self.circle.get_center()
center_dot = Dot(center)
lh_points = self.get_lh_points()
sailor_point = self.get_sailor_point()
keeper_point = self.get_keeper_point()
def get_angle_mob(p1, p2):
angle1 = angle_of_vector(p1 - center)
angle2 = angle_of_vector(p2 - center)
arc = Arc(start_angle=angle1, angle=(angle2 - angle1), radius=1)
arc.shift(center)
return VGroup(
center_dot,
Line(center, p1),
Line(center, p2),
arc,
)
angle_mob = get_angle_mob(lh_points[1], keeper_point)
ratios = VGroup(*[
product_parts[i:i + 3]
for i in [0, 4, 8]
])
term_rects = self.get_term_rects(ratios)
limit_fractions = VGroup(
TexMobject("{2", "\\over", "1}"),
TexMobject("{4", "\\over", "3}"),
TexMobject("{6", "\\over", "5}"),
)
limit_arrows = VGroup()
for rect, fraction in zip(term_rects, limit_fractions):
fraction.next_to(rect, DOWN, LARGE_BUFF)
arrow = Arrow(rect, fraction, color=WHITE)
limit_arrows.add(arrow)
approx = TexMobject("\\approx")
approx.scale(1.5)
approx.rotate(90 * DEGREES)
approx.move_to(limit_arrows[0])
braces = self.get_all_circle_braces()
# Show first lighthouse
term_rect = term_rects[0].copy()
self.play(
Transform(keeper_line, keeper_lines[1]),
Transform(sailor_line, sailor_lines[1]),
FadeIn(term_rect),
path_arc=-180 * DEGREES
)
self.wait(2)
self.play(
FadeOut(VGroup(keeper_line, sailor_line)),
FadeIn(braces[:2]),
FadeIn(angle_mob)
)
self.wait()
self.play(Transform(angle_mob, get_angle_mob(
lh_points[1], sailor_point)))
self.wait(2)
self.play(
Write(approx),
ReplacementTransform(ratios[0].copy(), limit_fractions[0]),
FadeOut(angle_mob)
)
self.wait()
self.play(ReplacementTransform(approx, limit_arrows[0]))
self.let_N_approach_infinity(braces[:2])
# Show second lighthouse
self.play(
Transform(term_rect, term_rects[1]),
ReplacementTransform(limit_arrows[0].copy(), limit_arrows[1]),
FadeIn(braces[2:4])
)
for group, color in (braces[:4], self.keeper_color), (braces[1:4], self.sailor_color):
self.play(
group.scale, 0.95, {"about_point": center},
group.set_color, color,
rate_func=there_and_back
)
self.wait(0.5)
self.play(
ReplacementTransform(ratios[1].copy(), limit_fractions[1])
)
self.wait()
# Show third lighthouse
braces[4:6].set_color(YELLOW)
self.play(
Transform(term_rect, term_rects[2]),
ReplacementTransform(limit_arrows[1].copy(), limit_arrows[2]),
FadeIn(braces[4:6]),
braces[1:4].set_color, YELLOW,
ReplacementTransform(limit_fractions[1].copy(), limit_fractions[2])
)
self.let_N_approach_infinity(braces[:6])
self.wait()
# Set up for lighthouse "before" keeper
ccw_product_group = VGroup(
product_parts, limit_arrows, limit_fractions)
cw_product_parts = TexMobject(
"\\cdots", "{|L_{-3} - K|", "\\over", "|L_{-3} - S|}",
"\\cdot", "{|L_{-2} - K|", "\\over", "|L_{-2} - S|}",
"\\cdot", "{|L_{-1} - K|", "\\over", "|L_{-1} - S|}",
)
cw_product_parts.match_height(product_parts)
cw_product_parts.set_color_by_tex_to_color_map({
"K": BLUE,
"S": YELLOW,
})
cw_product_parts.move_to(ratios, RIGHT)
cw_ratios = VGroup(*[cw_product_parts[i:i + 3] for i in (9, 5, 1)])
cw_term_rects = self.get_term_rects(cw_ratios)
cw_limit_fractions = VGroup(
TexMobject("{2", "\\over", "3}"),
TexMobject("{4", "\\over", "5}"),
TexMobject("{6", "\\over", "7}"),
)
cw_limit_arrows = VGroup()
for rect, fraction in zip(cw_term_rects, cw_limit_fractions):
fraction.next_to(rect, DOWN, LARGE_BUFF)
arrow = Arrow(rect, fraction, color=WHITE)
cw_limit_arrows.add(arrow)
cw_product_parts.save_state()
cw_product_parts.next_to(product_parts, RIGHT, LARGE_BUFF)
cw_label_rects = self.get_term_rects(labels[-1:-5:-1])
cw_label_rects.set_color(RED)
braces[-8:].set_color(BLUE)
braces[0].set_color(YELLOW)
def show_braces(n):
cw_group = braces[-2 * n:]
for group in cw_group, VGroup(braces[0], *cw_group):
self.play(
group.scale, 0.95, {"about_point": center},
rate_func=there_and_back
)
self.wait(0.5)
# Animated clockwise-from-keeper terms
self.play(
ccw_product_group.scale, 0.5, {"about_edge": UL},
ccw_product_group.to_corner, UL,
FadeOut(term_rect),
FadeOut(braces[:6]),
cw_product_parts.restore,
)
term_rect = cw_term_rects[0].copy()
self.play(LaggedStartMap(ShowCreationThenDestruction, cw_label_rects))
self.wait()
self.play(
FadeIn(term_rect),
FadeIn(braces[-2:]),
FadeIn(braces[0]),
)
show_braces(1)
self.play(
GrowArrow(cw_limit_arrows[0]),
FadeIn(cw_limit_fractions[0])
)
self.wait()
# Second and third lighthouse before
self.play(
Transform(term_rect, cw_term_rects[1]),
ReplacementTransform(
cw_limit_arrows[0].copy(), cw_limit_arrows[1]),
FadeIn(braces[-4:-2]),
Write(cw_limit_fractions[1])
)
show_braces(2)
self.wait()
self.play(
Transform(term_rect, cw_term_rects[2]),
ReplacementTransform(
cw_limit_arrows[1].copy(), cw_limit_arrows[2]),
FadeIn(braces[-6:-4]),
Write(cw_limit_fractions[2])
)
show_braces(3)
self.let_N_approach_infinity(VGroup(braces[0], *braces[-6:]))
self.wait()
# Organize fractions
fractions = VGroup(*it.chain(*list(zip(
limit_fractions, cw_limit_fractions,
))))
fractions.generate_target()
wallis_product = VGroup()
dots = VGroup()
for fraction in fractions.target:
fraction.match_height(cw_limit_fractions[0])
wallis_product.add(fraction)
dot = TexMobject("\\cdot")
wallis_product.add(dot)
dots.add(dot)
final_dot = TexMobject("\\cdots")
for group in wallis_product, dots:
group.submobjects[-1] = final_dot
wallis_product.arrange(RIGHT, buff=MED_SMALL_BUFF)
wallis_product.to_edge(RIGHT)
self.play(
FadeOut(limit_arrows),
FadeOut(cw_limit_arrows),
FadeOut(braces[-6:]),
FadeOut(braces[0]),
FadeOut(term_rect),
)
self.play(
cw_product_parts.scale, 0.5,
cw_product_parts.next_to, product_parts, DOWN, {
"aligned_edge": LEFT},
MoveToTarget(fractions),
Write(dots),
run_time=2,
path_arc=90 * DEGREES
)
self.wait()
self.wallis_product = VGroup(dots, fractions)
self.observers_brace = braces[0]
def show_limit_of_lhs(self):
brace = self.observers_brace
wallis_product = self.wallis_product
result_fraction = self.result_fraction
N, dist, over, two = result_fraction
distance_product_equals = self.distance_product_equals
result_rect = SurroundingRectangle(result_fraction)
result_rect.set_color(WHITE)
equals = TexMobject("=")
equals.next_to(brace, LEFT, SMALL_BUFF)
approx1, approx2, approx3 = [TexMobject("\\approx") for x in range(3)]
approx1.next_to(brace, LEFT, SMALL_BUFF)
half_two_pi_over_N = TexMobject(
"{1", "\\over", "2}", "{2", "\\pi", "\\over", "N}",
)
pi = half_two_pi_over_N.get_part_by_tex("\\pi")
half_two_pi_over_N.next_to(approx1, LEFT)
approx2.next_to(half_two_pi_over_N, LEFT, SMALL_BUFF)
approx3.move_to(distance_product_equals)
pi_over_N = TexMobject("(", "\\pi", "/", "N", ")")
pi_over_N.next_to(N, RIGHT)
N_shift = MED_LARGE_BUFF * RIGHT
pi_over_N.shift(N_shift)
pi_halves = TexMobject("{\\pi", "\\over", "2}")
pi_halves.next_to(result_rect, DOWN, LARGE_BUFF)
pi_halves.shift(RIGHT)
pi_halves_arrow = Arrow(
result_rect.get_bottom(),
pi_halves.get_top(),
color=WHITE,
buff=SMALL_BUFF
)
last_equals = TexMobject("=")
last_equals.next_to(pi_halves, LEFT)
self.play(ShowCreation(result_rect))
self.wait()
self.play(
dist.next_to, equals, LEFT,
FadeIn(equals),
GrowFromCenter(brace),
)
self.wait()
approx2.next_to(dist, LEFT, SMALL_BUFF)
half_two_pi_over_N.next_to(approx2, LEFT)
self.play(
Write(half_two_pi_over_N),
FadeIn(approx2)
)
self.wait()
self.play(
FadeOut(half_two_pi_over_N[:4]),
pi.shift, SMALL_BUFF * LEFT,
)
self.wait()
self.play(
ReplacementTransform(
half_two_pi_over_N[-3:].copy(),
pi_over_N[1:4]
),
FadeIn(pi_over_N[0]),
FadeIn(pi_over_N[-1]),
N.shift, N_shift * RIGHT,
ReplacementTransform(distance_product_equals, approx3)
)
self.wait()
self.play(
GrowArrow(pi_halves_arrow),
wallis_product.shift, DOWN,
)
self.play(Write(pi_halves))
self.wait(2)
self.play(
wallis_product.next_to, last_equals, LEFT, 2 * SMALL_BUFF,
FadeIn(last_equals)
)
final_rect = SurroundingRectangle(
VGroup(wallis_product, pi_halves),
buff=MED_SMALL_BUFF
)
final_rect.set_color(YELLOW)
self.play(ShowCreation(final_rect))
self.wait(2)
#
def let_N_approach_infinity(self, braces=None, factor=4, run_time=5, zoom_in_after=False):
lights = self.lights
labels = self.lighthouse_labels
keeper, sailor = self.observers
circle = self.circle
if braces is None:
braces = VGroup()
start_fraction = 1.0 / self.num_lighthouses
target_fraction = start_fraction / factor
half_N = self.num_lighthouses / 2
fraction_tracker = ValueTracker(start_fraction)
def get_fraction():
return fraction_tracker.get_value()
def get_ks_distance():
return get_norm(keeper.dot.get_center() - sailor.dot.get_center())
def update_title_heights(*titles):
for title in titles:
if not hasattr(title, "original_height"):
title.original_height = title.get_height()
title.set_height(min(
title.original_height,
0.8 * get_ks_distance(),
))
if len(titles) > 1:
return titles
else:
return titles[0]
initial_light_width = lights[0].get_width()
def update_lights(lights):
for k in range(-half_N, half_N + 1):
if k == 0:
continue
light = lights[k]
light = light.set_width(
(get_fraction() / start_fraction) * initial_light_width
)
point = self.get_circle_point_at_proportion(k * get_fraction())
light.move_source_to(point)
return lights
def update_braces(braces):
for brace in braces:
f1 = brace.fraction1 * (get_fraction() / start_fraction)
f2 = brace.fraction2 * (get_fraction() / start_fraction)
new_brace = self.get_circle_brace(f1, f2)
new_brace.match_style(brace)
Transform(brace, new_brace).update(1)
return braces
light_update_anim = UpdateFromFunc(lights, update_lights)
label_update_anim = UpdateFromFunc(
labels,
lambda ls: self.position_labels_outside_lights(
update_title_heights(*ls)),
)
sailor_dot_anim = UpdateFromFunc(
sailor.dot,
lambda d: d.move_to(
self.get_circle_point_at_proportion(get_fraction() / 2))
)
sailor_title_anim = UpdateFromFunc(
sailor.title,
lambda m: update_title_heights(m).next_to(
sailor.dot, RIGHT, SMALL_BUFF)
)
keeper_title_anim = UpdateFromFunc(
keeper.title,
lambda m: update_title_heights(m).next_to(
keeper.dot, RIGHT, SMALL_BUFF)
)
braces_update_anim = UpdateFromFunc(braces, update_braces)
lights[0].fade(1)
labels[0].fade(1)
all_updates = [
light_update_anim,
label_update_anim,
sailor_dot_anim,
sailor_title_anim,
keeper_title_anim,
braces_update_anim,
]
self.play(
fraction_tracker.set_value, target_fraction,
*all_updates,
run_time=run_time
)
if zoom_in_after:
self.play(
circle.scale, factor, {"about_point": circle.get_right()},
*all_updates,
run_time=1
)
self.wait()
self.play(
circle.scale, 1.0 /
factor, {"about_point": circle.get_right()},
*all_updates,
run_time=1
)
self.wait()
self.play(
fraction_tracker.set_value, start_fraction,
*all_updates,
run_time=run_time / 2
)
def get_keeper_point(self):
return self.get_circle_point_at_proportion(0)
def get_sailor_point(self):
return self.get_circle_point_at_proportion(0.5 / self.num_lighthouses)
def create_pi_creatures(self):
keeper = self.keeper = PiCreature(color=self.keeper_color).flip()
sailor = self.sailor = Sailor()
observers = self.observers = VGroup(keeper, sailor)
observers.set_height(3)
keeper.shift(4 * RIGHT + 2 * DOWN)
sailor.shift(4 * RIGHT + 2 * UP)
return VGroup(keeper, sailor)
def replace_lighthouses_with_labels(self):
lighthouse_labels = self.get_light_labels()
self.lighthouse_labels = lighthouse_labels
self.remove(self.lights[0], self.lighthouses[0])
self.play(
FadeOut(self.lighthouses[1:]),
FadeIn(lighthouse_labels[1:]),
)
def get_light_labels(self):
labels = VGroup()
for count, point in enumerate(self.get_lh_points()):
if count > self.num_lighthouses / 2:
count -= self.num_lighthouses
label = TexMobject("L_{%d}" % count)
label.scale(0.8)
labels.add(label)
self.position_labels_outside_lights(labels)
return labels
def position_labels_outside_lights(self, labels):
center = self.circle.get_center()
for light, label in zip(self.lights, labels):
point = light[0].get_center()
vect = (point - center)
norm = get_norm(vect)
buff = label.get_height()
vect *= (norm + buff) / norm
label.move_to(center + vect)
return labels
def get_keeper_lines(self, line_class=Line):
lines = self.get_distance_lines(self.get_keeper_point())
lines.set_stroke(self.keeper_color, 3)
return lines
def get_sailor_lines(self, line_class=Line):
lines = self.get_distance_lines(self.get_sailor_point())
lines.set_stroke(self.sailor_color, 3)
return lines
def get_term_rects(self, terms):
return VGroup(*[
SurroundingRectangle(term, color=WHITE)
for term in terms
])
def get_circle_brace(self, f1, f2):
line = Line(
self.get_circle_point_at_proportion(f1),
self.get_circle_point_at_proportion(f2),
)
angle = (line.get_angle() + TAU / 2) % TAU
scale_factor = 1.5
line.rotate(-angle, about_point=ORIGIN)
line.scale(scale_factor, about_point=ORIGIN)
brace = Brace(line, DOWN, buff=SMALL_BUFF)
group = VGroup(line, brace)
group.scale(1.0 / scale_factor, about_point=ORIGIN)
group.rotate(angle, about_point=ORIGIN)
# Keep track of a fraction between -0.5 and 0.5
if f1 > 0.5:
f1 -= 1
if f2 > 0.5:
f2 -= 1
brace.fraction1 = f1
brace.fraction2 = f2
return brace
def get_all_circle_braces(self):
fractions = np.linspace(0, 1, 2 * self.num_lighthouses + 1)
return VGroup(*[
self.get_circle_brace(f1, f2)
for f1, f2 in zip(fractions, fractions[1:])
])
class MentionJohnWallis(Scene):
def construct(self):
product = get_wallis_product(10)
product.to_edge(UP)
name = TextMobject("``Wallis product''")
name.scale(1.5)
name.set_color(BLUE)
name.next_to(product, DOWN, MED_LARGE_BUFF)
image = ImageMobject("John_Wallis")
image.set_height(3)
image.next_to(name, DOWN)
image_name = TextMobject("John Wallis")
image_name.next_to(image, DOWN)
infinity = TexMobject("\\infty")
infinity.set_height(1.5)
infinity.next_to(image, RIGHT, MED_LARGE_BUFF)
self.add(product)
self.wait()
self.play(Write(name))
self.play(GrowFromEdge(image, UP))
self.play(Write(image_name))
self.wait(2)
self.play(Write(infinity, run_time=3))
self.wait(2)
class HowThisArgumentRequiresCommunitingLimits(PiCreatureScene):
def construct(self):
mathy, morty = self.pi_creatures
scale_val = 0.7
factors = TexMobject(
"{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot",
"{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", "\\cdot",
"{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot",
"{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", "\\cdot",
"{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdot",
"{|L_{-3} - K|", "\\over", "|L_{-3} - S|}", "\\cdots",
)
factors.set_color_by_tex_to_color_map({
"K": BLUE,
"S": YELLOW,
})
equals = TexMobject("=")
result = TexMobject(
"{N", "{\\text{distance} \\choose \\text{between obs.}}",
"\\over", "2}"
)
top_line = VGroup(factors, equals, result)
top_line.arrange(RIGHT, buff=SMALL_BUFF)
result.shift(SMALL_BUFF * UP)
top_line.scale(scale_val)
top_line.to_edge(UP)
fractions = VGroup(*[
factors[i:i + 3]
for i in range(0, len(factors), 4)
])
fraction_limit_arrows = VGroup(*[
Vector(0.5 * DOWN).next_to(fraction, DOWN)
for fraction in fractions
])
fraction_limit_arrows.set_color(WHITE)
wallis_product = get_wallis_product(6)
fraction_limits = VGroup(*[
wallis_product[i:i + 3]
for i in range(0, 4 * 6, 4)
])
for lf, arrow in zip(fraction_limits, fraction_limit_arrows):
lf.next_to(arrow, DOWN, MED_SMALL_BUFF)
result_limit_arrow = fraction_limit_arrows[0].copy()
result_limit_arrow.next_to(result, DOWN)
result_limit_arrow.align_to(fraction_limit_arrows[0])
result_limit = wallis_product[-3:]
result_limit.next_to(result_limit_arrow, DOWN, MED_SMALL_BUFF)
lower_equals = TexMobject("=")
lower_equals.next_to(result_limit, LEFT)
mult_signs = VGroup()
for f1, f2 in zip(fraction_limits, fraction_limits[1:]):
mult_sign = TexMobject("\\times")
mult_sign.move_to(VGroup(f1, f2))
mult_signs.add(mult_sign)
cdots = TexMobject("\\cdots")
cdots.move_to(VGroup(fraction_limits[-1], lower_equals))
mult_signs.add(cdots)
# Pi creatures react
self.play(
PiCreatureSays(
mathy,
"Whoa whoa whoa \\\\ there buddy",
look_at_arg=morty.eyes,
target_mode="sassy",
),
morty.change, "guilty", mathy.eyes,
)
self.wait(2)
# Write out commutative diagram
self.play(
RemovePiCreatureBubble(
mathy,
target_mode="raise_right_hand",
look_at_arg=factors,
),
morty.change, "pondering", factors,
LaggedStartMap(FadeIn, factors),
)
self.wait()
self.play(
FadeIn(equals),
Write(result)
)
self.wait()
self.play(
LaggedStartMap(GrowArrow, fraction_limit_arrows),
LaggedStartMap(
FadeInFrom, fraction_limits,
direction=UP
),
run_time=4,
lag_ratio=0.25,
)
self.wait()
self.play(
LaggedStartMap(FadeIn, mult_signs),
FadeIn(lower_equals),
mathy.change, "sassy",
)
self.play(
GrowArrow(result_limit_arrow),
FadeInFrom(result_limit, direction=UP),
morty.change, "confused",
)
self.wait(2)
# Write general limit rule
limit_rule = TexMobject(
"\\left( \\lim_{N \\to \\infty} a_N^{(1)} \\right)",
"\\left( \\lim_{N \\to \\infty} a_N^{(2)} \\right)",
"\\cdots", "=",
"\\lim_{N \\to \\infty} \\left( a_N^{(1)} a_N^{(2)} \\cdots \\right)"
)
limit_rule.next_to(self.pi_creatures, UP)
q_marks = TexMobject("???")
q_marks.set_color(YELLOW)
limit_equals = limit_rule.get_part_by_tex("=")
q_marks.next_to(limit_equals, UP, SMALL_BUFF)
index_of_equals = limit_rule.index_of_part(limit_equals)
lhs_brace = Brace(limit_rule[:index_of_equals], UP)
rhs_brace = Brace(limit_rule[index_of_equals + 1:], UP)
self.play(
FadeInFromDown(limit_rule),
mathy.change, "angry",
morty.change, "erm",
)
self.play(GrowFromCenter(lhs_brace))
self.wait()
self.play(ReplacementTransform(lhs_brace, rhs_brace))
self.wait(2)
self.play(FadeOut(rhs_brace), Write(q_marks))
limit_rule.add(q_marks)
self.wait(2)
self.play(morty.change, "pondering")
self.play(mathy.change, "tease")
self.wait(3)
self.play(
Animation(limit_rule),
morty.change, "pondering",
mathy.change, "pondering",
)
self.wait(3)
# Write dominated convergence
mover = VGroup(
top_line, fraction_limits, fraction_limit_arrows,
mult_signs, lower_equals,
result_limit, result_limit_arrow,
)
self.play(
mover.next_to, FRAME_HEIGHT * UP / 2, UP,
limit_rule.to_edge, UP,
)
dominated_convergence = TextMobject("``Dominated convergence''")
dominated_convergence.set_color(BLUE)
dominated_convergence.next_to(limit_rule, DOWN, LARGE_BUFF)
see_blog_post = TextMobject("(See supplementary blog post)")
see_blog_post.next_to(dominated_convergence, DOWN)
self.play(
FadeInFromDown(dominated_convergence),
mathy.change, "raise_right_hand",
)
self.play(morty.change, "thinking")
self.wait()
self.play(Write(see_blog_post))
self.wait(4)
def create_pi_creatures(self):
group = VGroup(PiCreature(color=GREY), Mortimer())
group.set_height(2)
group.arrange(RIGHT, buff=4)
group.to_edge(DOWN)
return group
class NonCommunitingLimitsExample(Scene):
CONFIG = {
"num_terms_per_row": 6,
"num_rows": 5,
"x_spacing": 0.75,
"y_spacing": 1,
}
def construct(self):
rows = VGroup(*[
self.get_row(seven_index)
for seven_index in range(self.num_rows)
])
rows.add(self.get_v_dot_row())
for n, row in enumerate(rows):
row.move_to(n * self.y_spacing * DOWN)
rows.center().to_edge(UP)
rows[-1].shift(MED_SMALL_BUFF * UP)
row_rects = VGroup(*list(map(SurroundingRectangle, rows[:-1])))
row_rects.set_color(YELLOW)
columns = VGroup(*[
VGroup(*[row[0][i] for row in rows])
for i in range(len(rows[0][0]))
])
column_rects = VGroup(*list(map(SurroundingRectangle, columns)))
column_rects.set_color(BLUE)
row_arrows = VGroup(*[
TexMobject("\\rightarrow").next_to(row, RIGHT)
for row in rows[:-1]
])
row_products = VGroup(*[
Integer(7).next_to(arrow)
for arrow in row_arrows
])
row_products.set_color(YELLOW)
row_product_limit_dots = TexMobject("\\vdots")
row_product_limit_dots.next_to(row_products, DOWN)
row_product_limit = Integer(7)
row_product_limit.set_color(YELLOW)
row_product_limit.next_to(row_product_limit_dots, DOWN)
column_arrows = VGroup(*[
TexMobject("\\downarrow").next_to(part, DOWN, SMALL_BUFF)
for part in rows[-1][0]
])
column_limits = VGroup(*[
Integer(1).next_to(arrow, DOWN)
for arrow in column_arrows
])
column_limits.set_color(BLUE)
column_limit_dots = self.get_row_dots(column_limits)
column_limits_arrow = TexMobject("\\rightarrow").next_to(
column_limit_dots, RIGHT
)
product_of_limits = Integer(1).next_to(column_limits_arrow, RIGHT)
product_of_limits.set_color(BLUE)
self.add(rows)
self.wait()
self.play(LaggedStartMap(ShowCreation, row_rects))
self.wait(2)
row_products_iter = iter(row_products)
self.play(
LaggedStartMap(Write, row_arrows),
LaggedStartMap(
ReplacementTransform, rows[:-1].copy(),
lambda r: (r, next(row_products_iter))
)
)
self.wait()
self.play(Write(row_product_limit_dots))
self.play(Write(row_product_limit))
self.wait()
self.play(LaggedStartMap(FadeOut, row_rects))
self.play(LaggedStartMap(FadeIn, column_rects))
self.wait()
column_limit_iter = iter(column_limits)
self.play(
LaggedStartMap(Write, column_arrows),
LaggedStartMap(
ReplacementTransform, columns.copy(),
lambda c: (c, next(column_limit_iter))
)
)
self.wait()
self.play(Write(column_limits_arrow))
self.play(
Write(product_of_limits),
FadeOut(row_product_limit)
)
self.wait()
def get_row(self, seven_index):
terms = [1] * (self.num_terms_per_row)
if seven_index < len(terms):
terms[seven_index] = 7
row = VGroup(*list(map(Integer, terms)))
self.arrange_row(row)
dots = self.get_row_dots(row)
return VGroup(row, dots)
def get_v_dot_row(self):
row = VGroup(*[
TexMobject("\\vdots")
for x in range(self.num_terms_per_row)
])
self.arrange_row(row)
dots = self.get_row_dots(row)
dots.fade(1)
return VGroup(row, dots)
def arrange_row(self, row):
for n, part in enumerate(row):
part.move_to(n * self.x_spacing * RIGHT)
def get_row_dots(self, row):
dots = VGroup()
for p1, p2 in zip(row, row[1:]):
dots.add(TexMobject("\\cdot").move_to(VGroup(p1, p2)))
dots.add(TexMobject("\\cdots").next_to(row, RIGHT))
return dots
class DelicacyInIntermixingSeries(Scene):
def construct(self):
n_terms = 6
top_product, bottom_product = products = VGroup(*[
TexMobject(*it.chain(*[
["{%d" % (2 * x), "\\over", "%d}" % (2 * x + u), "\\cdot"]
for x in range(1, n_terms + 1)
]))
for u in (-1, 1)
])
top_product.set_color(GREEN)
bottom_product.set_color(BLUE)
top_product.to_edge(UP)
bottom_product.next_to(top_product, DOWN, LARGE_BUFF)
infinity = TexMobject("\\infty")
top_product.limit = infinity
zero = TexMobject("0")
bottom_product.limit = zero
for product in products:
cdots = TexMobject("\\cdots")
cdots.move_to(product[-1], LEFT)
cdots.match_color(product)
product.submobjects[-1] = cdots
product.parts = VGroup(*[
product[i:i + 4]
for i in range(0, len(product), 4)
])
arrow = Vector(0.75 * RIGHT)
arrow.set_color(WHITE)
arrow.next_to(product, RIGHT)
product.arrow = arrow
product.limit.next_to(arrow, RIGHT)
product.limit.match_color(product)
group = VGroup(products, infinity)
h_line = Line(LEFT, RIGHT)
h_line.stretch_to_fit_width(group.get_width() + LARGE_BUFF)
h_line.next_to(group, DOWN, aligned_edge=RIGHT)
times = TexMobject("\\times")
times.next_to(h_line, UP, aligned_edge=LEFT)
q_marks = TexMobject("?????")
q_marks.set_color_by_gradient(BLUE, YELLOW)
q_marks.scale(2)
q_marks.next_to(h_line, DOWN)
# Show initial products
self.play(
LaggedStartMap(FadeIn, top_product),
LaggedStartMap(FadeIn, bottom_product),
)
self.wait()
for product in products:
self.play(
GrowArrow(product.arrow),
FadeInFrom(product.limit, direction=LEFT)
)
self.wait()
self.play(
Write(times),
ShowCreation(h_line)
)
self.play(Write(q_marks, run_time=3))
self.wait(2)
# Show alternate interweaving
top_parts_iter = iter(top_product.parts)
bottom_parts_iter = iter(bottom_product.parts)
movers1 = VGroup()
while True:
try:
new_terms = [
next(bottom_parts_iter),
next(top_parts_iter),
next(top_parts_iter),
]
movers1.add(*new_terms)
except StopIteration:
break
new_product = VGroup()
movers1.save_state()
for mover in movers1:
mover.generate_target()
new_product.add(mover.target)
new_product.arrange(RIGHT, buff=SMALL_BUFF)
new_product.next_to(h_line, DOWN, LARGE_BUFF, aligned_edge=LEFT)
new_arrow = top_product.arrow.copy()
new_arrow.next_to(new_product, RIGHT)
ghost_top = top_product.copy().fade()
ghost_bottom = bottom_product.copy().fade()
self.add(ghost_top, top_product)
self.add(ghost_bottom, bottom_product)
new_limit = TexMobject("\\frac{\\pi}{2}", "\\sqrt{2}")
new_limit.next_to(new_arrow, RIGHT)
randy = Randolph(height=1.5)
randy.flip()
randy.to_corner(DR)
movers2 = VGroup(*it.chain(*list(zip(
top_product.parts, bottom_product.parts
))))
final_product = VGroup()
for mover in movers2:
mover.final_position = mover.copy()
if mover is movers2[-2]:
# Excessive ellipses
final_dot = mover.final_position[-1][0]
mover.final_position.submobjects[-1] = final_dot
final_product.add(mover.final_position)
final_product.arrange(RIGHT, buff=SMALL_BUFF)
final_product.move_to(new_product, RIGHT)
self.play(
FadeOut(q_marks),
LaggedStartMap(
MoveToTarget, movers1,
run_time=5,
lag_ratio=0.2,
)
)
self.play(
GrowArrow(new_arrow),
FadeInFrom(new_limit, LEFT),
bottom_product.parts[3:].fade, 1,
)
self.play(FadeIn(randy))
self.play(randy.change, "confused", new_limit)
self.wait()
self.play(Blink(randy))
self.wait()
self.play(LaggedStartMap(
Transform, movers2,
lambda m: (m, m.final_position),
run_time=3,
path_arc=TAU / 4,
))
self.play(
FadeOut(new_limit[1]),
randy.change, "pondering", new_limit
)
self.wait(2)
self.play(Blink(randy))
self.wait(2)
class JustTechnicalities(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"These are just \\\\ technicalities"
)
self.change_all_student_modes("happy")
self.play(RemovePiCreatureBubble(
self.teacher, target_mode="raise_right_hand",
))
self.look_at(self.screen)
self.wait(4)
class KeeperAndSailorForSineProduct(KeeperAndSailor):
CONFIG = {
# "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG,
"new_sailor_fraction": 0.7,
"big_circle_center": FRAME_WIDTH * LEFT / 2 + 2.6 * LEFT,
}
def construct(self):
# Rerun old animation (probably to be skipped)g
self.force_skipping()
self.place_lighthouses()
self.introduce_observers()
self.write_distance_product_fraction()
self.revert_to_original_skipping_status()
# New animations
self.replace_lighthouses_with_labels()
self.move_sailor_to_new_spot()
self.show_new_distance_product()
self.show_new_limit_of_ratio()
self.show_new_infinite_product()
def move_sailor_to_new_spot(self):
sailor = self.sailor
fraction = self.new_sailor_fraction
self.get_sailor_point = lambda: self.get_circle_point_at_proportion(
fraction / self.num_lighthouses
)
target_point = self.get_sailor_point()
brace1 = self.get_circle_brace(0, 0.5 / self.num_lighthouses)
brace2 = self.get_circle_brace(0, fraction / self.num_lighthouses)
center = self.circle.get_center()
radius = self.get_radius()
def warp_func(point):
vect = point - center
norm = get_norm(vect)
new_norm = norm + 0.5 * (radius - norm)
return center + new_norm * vect / norm
brace1.apply_function(warp_func)
brace2.apply_function(warp_func)
scale_val = 0.7
words1 = TextMobject("Instead of", "$\\frac{1}{2}$")
words1.set_color_by_tex("$", YELLOW)
words1.scale(scale_val)
words1.next_to(brace1.get_center(), LEFT)
words2 = TextMobject("Some other fraction", "$f$")
words2.set_color_by_tex("$", GREEN)
words2.scale(scale_val)
words2.next_to(brace2.get_center(), LEFT)
self.play(
GrowFromCenter(brace1),
Write(words1, run_time=1)
)
self.wait()
self.play(
sailor.dot.move_to, target_point,
sailor.dot.set_color, GREEN,
sailor.set_color, GREEN,
MaintainPositionRelativeTo(sailor, sailor.dot),
MaintainPositionRelativeTo(sailor.title, sailor),
ReplacementTransform(brace1, brace2),
ReplacementTransform(words1, words2),
run_time=1.5
)
self.fraction_label_group = VGroup(brace2, words2)
def show_new_distance_product(self):
result_fraction = self.result_fraction
N, dist, over, two = result_fraction
sailor_dp = self.distance_product_fraction[-1]
sailor_dp_rect = SurroundingRectangle(sailor_dp)
sailor_dp_rect.set_color(GREEN)
sailor_lines = self.get_distance_lines(
self.sailor.dot.get_center(), line_class=DashedLine
)
chord_f = TextMobject("Chord(", "$f$", ")", arg_separator="")
chord_f.set_color_by_tex("$f$", GREEN)
chord_f.move_to(two, UP)
two_cross = Cross(SurroundingRectangle(two))
two_group = VGroup(two, two_cross)
self.play(
ShowCreation(sailor_dp_rect),
LaggedStartMap(ShowCreation, sailor_lines),
)
self.wait()
self.play(ShowCreation(two_cross))
self.play(
two_group.next_to, chord_f, DOWN,
ReplacementTransform(
VGroup(
sailor_dp[:-6].copy(),
sailor_dp[-6:-4].copy(),
sailor_dp[-4:-2].copy(),
sailor_dp[-2:].copy(),
),
chord_f
)
)
self.wait()
self.play(LaggedStartMap(FadeOut, VGroup(
sailor_lines, sailor_dp_rect, two_group
)))
result_fraction.submobjects[-1] = chord_f
def show_new_limit_of_ratio(self):
fraction_label_group = self.fraction_label_group
fraction_brace, fraction_words = fraction_label_group
frac1 = TexMobject(
"{N", "(", "f", " \\cdot 2\\pi / N)", "\\over",
"\\text{Chord}(", "f", ")}"
)
frac2 = TexMobject(
"{f", "\\cdot", "2", "\\pi", "\\over",
"\\text{Chord}(", "f", ")}"
)
for frac in frac1, frac2:
frac.set_color_by_tex("f", GREEN, substring=False)
arrow1, arrow2 = [
Vector(0.75 * DOWN, color=WHITE)
for x in range(2)
]
group = VGroup(arrow1, frac1, arrow2, frac2)
group.arrange(DOWN)
group.next_to(self.result_fraction, DOWN)
big_group = VGroup(self.result_fraction, *group)
arrow = TexMobject("\\rightarrow")
arrow.move_to(self.distance_product_equals)
fraction_brace.generate_target()
fraction_brace.target.rotate(-10 * DEGREES)
fraction_brace.target.scale(0.65)
fraction_brace.target.align_to(ORIGIN, DOWN)
fraction_brace.target.shift(3.3 * LEFT)
fraction_words.generate_target()
fraction_words.target[0][:9].next_to(
VGroup(fraction_words.target[0][9:], fraction_words.target[1]),
UP, SMALL_BUFF
)
fraction_words.target.next_to(fraction_brace.target, LEFT, SMALL_BUFF)
self.play(LaggedStartMap(FadeIn, group))
self.grow_circle_and_N(
added_anims=[
MoveToTarget(fraction_brace),
MoveToTarget(fraction_words),
]
)
self.wait()
self.play(
big_group.next_to, self.distance_product_equals, RIGHT,
{"submobject_to_align": frac2},
UpdateFromAlphaFunc(
big_group[:-1],
lambda m, a: m.set_fill(opacity=1 - a),
),
Transform(self.distance_product_equals, arrow)
)
self.wait()
self.result_fraction = frac2
def show_new_infinite_product(self):
scale_val = 0.7
fractions = TexMobject(
"\\cdots",
"{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", "\\cdot",
"{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", "\\cdot",
"{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot",
"{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot",
"{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdots",
)
fractions.scale(scale_val)
fractions.move_to(self.observers)
fractions.to_edge(RIGHT)
fractions.set_color_by_tex_to_color_map({
"K": BLUE,
"S": GREEN,
})
keeper_lines = self.get_keeper_lines()
sailor_lines = self.get_sailor_lines()
sailor_lines.set_color(GREEN)
ratios = VGroup(*[
fractions[i:i + 3]
for i in range(1, len(fractions), 4)
])
limit_arrows = VGroup(*[
Vector(0.5 * DOWN, color=WHITE).next_to(ratio, DOWN, SMALL_BUFF)
for ratio in ratios
])
limits = VGroup(*[
TexMobject("{%d" % k, "\\over", "%d" % k, "-", "f}")
for k in (-2, -1, 1, 2, 3)
])
for limit, arrow in zip(limits, limit_arrows):
limit.set_color_by_tex("f", GREEN)
limit.scale(scale_val)
limit.next_to(arrow, DOWN, SMALL_BUFF)
dots = VGroup()
dots.add(TexMobject("\\cdots").next_to(limits, LEFT))
for l1, l2 in zip(limits, limits[1:]):
dots.add(TexMobject("\\cdot").move_to(VGroup(l1, l2)))
dots.add(TexMobject("\\cdots").next_to(limits, RIGHT))
full_limits_group = VGroup(*list(limits) + list(dots))
# brace = Brace(limits, DOWN)
product = TexMobject(
"\\prod_{k \\ne 0}", "{k", "\\over", "k", "-", "f}"
)
product.next_to(limits, DOWN, LARGE_BUFF)
product_lines = VGroup(
DashedLine(full_limits_group.get_corner(DL), product.get_corner(UL)),
DashedLine(full_limits_group.get_corner(DR), product.get_corner(UR)),
)
product_lines.set_color(YELLOW)
self.play(
LaggedStartMap(FadeIn, fractions),
*[
LaggedStartMap(
FadeIn, VGroup(*list(lines[-10:]) + list(lines[1:10])),
rate_func=there_and_back,
remover=True,
run_time=3,
lag_ratio=0.1
)
for lines in (keeper_lines, sailor_lines)
]
)
self.wait()
self.play(
LaggedStartMap(GrowArrow, limit_arrows),
LaggedStartMap(
FadeInFrom, limits,
lambda m: (m, UP),
),
LaggedStartMap(FadeIn, dots)
)
self.wait()
self.play(
# GrowFromCenter(brace),
ShowCreation(product_lines, lag_ratio=0),
FadeIn(product)
)
self.wait()
# Shift everything
result_fraction = self.result_fraction
big_group = VGroup(
fractions, limit_arrows, full_limits_group,
# brace,
product_lines,
product,
)
big_group.generate_target()
big_group.target.to_edge(UP)
equals = TexMobject("=")
equals.next_to(big_group.target[-1], LEFT)
result_fraction.generate_target()
result_fraction.target.next_to(equals, LEFT)
self.play(
MoveToTarget(big_group),
FadeIn(equals),
MoveToTarget(result_fraction),
FadeOut(self.distance_product_fraction),
FadeOut(self.distance_product_equals),
)
self.wait()
# Replace chord with sine
chord_f = result_fraction[-3:]
f_pi = VGroup(result_fraction[0], result_fraction[3])
over = result_fraction.get_part_by_tex("\\over")
dot_two = result_fraction[1:3]
two_sine_f_pi = TexMobject("2", "\\sin(", "f", "\\pi", ")")
sine_f_pi = two_sine_f_pi[1:]
two_sine_f_pi.set_color_by_tex("f", GREEN)
two_sine_f_pi.move_to(chord_f)
self.play(
FadeIn(two_sine_f_pi),
chord_f.shift, DOWN
)
self.wait()
self.play(FadeOut(chord_f))
self.wait()
self.play(
f_pi.arrange, RIGHT, {"buff": SMALL_BUFF},
f_pi.next_to, over, UP, SMALL_BUFF,
FadeOut(dot_two),
FadeOut(two_sine_f_pi[0]),
sine_f_pi.shift, SMALL_BUFF * LEFT,
)
self.wait()
# Reciprocate
pairs = VGroup()
for num, denom in zip(fractions[1::4], fractions[3::4]):
pairs.add(VGroup(num, denom))
for limit in limits:
pairs.add(VGroup(limit[0], limit[2:]))
pairs.add(
VGroup(f_pi, sine_f_pi),
VGroup(product[1], product[3:]),
)
for pair in pairs:
pair.generate_target()
pair.target[0].move_to(pair[1], UP)
pair.target[1].move_to(pair[0], DOWN)
self.play(
LaggedStartMap(
MoveToTarget, pairs,
path_arc=180 * DEGREES,
run_time=3,
),
product_lines[1].scale, 0.9, {"about_point": product_lines[1].get_start()},
product_lines[1].shift, SMALL_BUFF * UP
)
self.wait()
# Rearrange
one_minus_f_over_k = TexMobject(
"\\left(", "1", "-", "{f", "\\over", "k}", "\\right)"
)
# 0 1 2 3 4
# k / k - f
one_minus_f_over_k.set_color_by_tex("{f", GREEN)
one_minus_f_over_k.next_to(product[0], RIGHT, buff=SMALL_BUFF)
one_minus_f_over_k.shift(SMALL_BUFF * UP)
self.play(
FadeIn(one_minus_f_over_k[0]),
FadeIn(one_minus_f_over_k[-1]),
FadeOut(product_lines),
*[
ReplacementTransform(product[i], one_minus_f_over_k[j])
for i, j in [(3, 1), (4, 2), (5, 3), (2, 4), (1, 5)]
]
)
self.wait()
product = VGroup(product[0], *one_minus_f_over_k)
product.generate_target()
f_pi.generate_target()
f_pi.target.next_to(equals, RIGHT, SMALL_BUFF)
product.target.next_to(f_pi.target, RIGHT, SMALL_BUFF)
product.target.shift(SMALL_BUFF * DOWN)
self.play(
sine_f_pi.next_to, equals, LEFT, SMALL_BUFF,
FadeOut(over),
MoveToTarget(f_pi),
MoveToTarget(product),
)
self.wait()
# Show final result
rect = SurroundingRectangle(VGroup(sine_f_pi, product))
rect.set_color(BLUE)
pi_creatures = VGroup(
PiCreature(color=BLUE_E),
PiCreature(color=BLUE_C),
PiCreature(color=BLUE_D),
Mortimer()
)
pi_creatures.arrange(RIGHT, LARGE_BUFF)
pi_creatures.set_height(1)
pi_creatures.next_to(rect, DOWN)
for pi in pi_creatures:
pi.change("hooray", rect)
pi.save_state()
pi.change("plain")
pi.fade(1)
self.play(
ShowCreation(rect),
LaggedStartMap(ApplyMethod, pi_creatures, lambda m: (m.restore,))
)
for x in range(4):
self.play(Blink(random.choice(pi_creatures)))
self.wait()
class Conclusion(TeacherStudentsScene):
CONFIG = {
"camera_config": {"background_opacity": 1},
}
def construct(self):
wallis_product = get_wallis_product(6)
wallis_product.move_to(self.hold_up_spot, DOWN)
wallis_product.shift_onto_screen()
for i in range(0, len(wallis_product) - 5, 4):
color = GREEN if (i / 4) % 2 == 0 else BLUE
wallis_product[i:i + 3].set_color(color)
sine_formula = TexMobject(
"\\sin(", "f", "\\pi", ")", "=", "f", "\\pi",
"\\prod_{k \\ne 0}", "\\left(", "1", "-",
"{f", "\\over", "k}", "\\right)"
)
sine_formula.set_color_by_tex("f", GREEN)
sine_formula.set_color_by_tex("left", WHITE)
sine_formula.move_to(self.hold_up_spot, DOWN)
sine_formula.shift_onto_screen()
euler = ImageMobject("Euler")
euler.set_height(2.5)
basel_problem = TexMobject(
"\\frac{1}{1^2} + ",
"\\frac{1}{2^2} + ",
"\\frac{1}{3^2} + ",
"\\cdots",
"\\frac{\\pi^2}{6}"
)
basel_problem.move_to(self.hold_up_spot, DOWN)
implication = TexMobject("\\Rightarrow")
implication.next_to(basel_problem[0][1], LEFT)
self.play(
self.teacher.change, "raise_right_hand",
FadeInFromDown(wallis_product),
self.get_student_changes("thinking", "hooray", "surprised")
)
self.wait()
self.play(
self.teacher.change, "hooray",
FadeInFromDown(sine_formula),
wallis_product.to_edge, UP,
self.get_student_changes("pondering", "thinking", "erm")
)
self.wait(3)
self.play(
sine_formula.next_to, implication, LEFT,
{"submobject_to_align": sine_formula[-1]},
FadeIn(implication),
)
euler.next_to(sine_formula, UP)
self.play(
FadeIn(euler),
LaggedStartMap(FadeIn, basel_problem),
self.teacher.change, "happy",
self.get_student_changes("sassy", "confused", "hesitant")
)
self.wait(2)
wallis_rect = SurroundingRectangle(wallis_product)
wallis_rect.set_color(BLUE)
basel_rect = SurroundingRectangle(basel_problem)
basel_rect.set_color(YELLOW)
self.play(
ShowCreation(wallis_rect),
FadeOut(implication),
FadeOut(euler),
FadeOut(sine_formula),
)
self.play(
ShowCreation(basel_rect),
self.teacher.change, "surprised",
self.get_student_changes(*["happy"] * 3)
)
self.wait(5)
class ByLine(Scene):
def construct(self):
self.add(TextMobject("""
Written and animated by \\\\
\\quad \\\\
Sridhar Ramesh \\\\
Grant Sanderson
""").shift(2 * UP))
class SponsorUnderlay(PiCreatureScene):
CONFIG = {
"default_pi_creature_start_corner": DR,
}
def construct(self):
url = TextMobject("https://udacity.com/3b1b/")
url.to_corner(UL)
rect = ScreenRectangle(height=5.5)
rect.next_to(url, DOWN)
rect.to_edge(LEFT)
url.next_to(rect, UP)
url.save_state()
url.next_to(self.pi_creature.get_corner(UL), UP)
logo = SVGMobject(file_name="udacity")
logo.set_fill("#02b3e4")
logo.to_corner(UR)
self.add(logo)
self.play(
Write(url),
self.pi_creature.change, "raise_right_hand"
)
self.play(
url.restore,
ShowCreation(rect),
path_arc=90 * DEGREES,
)
self.wait(2)
self.change_mode("thinking")
self.wait()
self.look_at(url)
self.wait(10)
self.change_mode("happy")
self.wait(10)
self.change_mode("raise_right_hand")
self.wait(10)
self.remove(rect)
self.play(url.next_to, self.pi_creature, UL)
url_rect = SurroundingRectangle(url)
self.play(ShowCreation(url_rect))
self.play(FadeOut(url_rect))
self.wait(3)
class EndScreen(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
"Juan Benet",
"Keith Smith",
"Chloe Zhou",
"Ross Garber",
"Desmos",
"Burt Humburg",
"CrypticSwarm",
"Hoang Tung Lam",
"Sergei",
"Devin Scott",
"George John",
"Akash Kumar",
"Felix Tripier",
"Arthur Zey",
"David Kedmey",
"Ali Yahya",
"Mayank M. Mehrotra",
"Lukas Biewald",
"Yana Chernobilsky",
"Kaustuv DeBiswas",
"Yu Jun",
"dave nicponski",
"Damion Kistler",
"Jordan Scales",
"Markus Persson",
"Fred Ehrsam",
"Britt Selvitelle",
"Jonathan Wilson",
"Ryan Atallah",
"Joseph John Cox",
"Luc Ritchie",
"Cooper Jones",
"James Hughes",
"John V Wertheim",
"Chris Giddings",
"Song Gao",
"William Fritzon",
"Alexander Feldman",
# "孟子易",
"Mengzi yi",
"zheng zhang",
"Matt Langford",
"Max Mitchell",
"Richard Burgmann",
"John Griffith",
"Chris Connett",
"Steven Tomlinson",
"Jameel Syed",
"Bong Choung",
"Ignacio Freiberg",
"Zhilong Yang",
"Dan Esposito (Guardion)",
"Giovanni Filippi",
"Eric Younge",
"Prasant Jagannath",
"Cody Brocious",
"James H. Park",
"Norton Wang",
"Kevin Le",
"Tianyu Ge",
"David MacCumber",
"Oliver Steele",
"Yaw Etse",
"Dave B",
"Waleed Hamied",
"George Chiesa",
"supershabam",
"Delton Ding",
"Thomas Tarler",
"Jonathan Eppele",
"Isak Hietala",
"1stViewMaths",
"Jacob Magnuson",
"Mark Govea",
"Clark Gaebel",
"Mathias Jansson",
"David Clark",
"Michael Gardner",
"Mads Elvheim",
"Awoo",
"Dr David G. Stork",
"Ted Suzman",
"Linh Tran",
"Andrew Busey",
"John Haley",
"Ankalagon",
"Eric Lavault",
"Boris Veselinovich",
"Julian Pulgarin",
"Jeff Linse",
"Ryan Dahl",
"Robert Teed",
"Jason Hise",
"Meshal Alshammari",
"Bernd Sing",
"James Thornton",
"Mustafa Mahdi",
"Mathew Bramson",
"Jerry Ling",
# "世珉 匡",
"Shi min kuang",
"Rish Kundalia",
"Achille Brighton",
"Ripta Pasay",
]
}
class Thumbnail(DistanceProductScene):
def construct(self):
product = get_wallis_product()
product.scale(1.5)
product.move_to(2.5 * UP)
frac_lines = product.get_parts_by_tex("\\over")
frac_indices = list(map(product.index_of_part, frac_lines))
parts = VGroup(*[
product[i-1:i+2]
for i in frac_indices
])
parts[::2].set_color(WHITE)
parts[1::2].set_color(WHITE)
parts[-1].set_color(WHITE)
parts[-1].set_background_stroke(color=YELLOW, width=3)
parts[-1].scale(1.5, about_edge=LEFT)
product[-4:].next_to(product[:-4], DOWN, MED_LARGE_BUFF)
product.scale(1.2).center().to_edge(UP)
# new_proof = TextMobject("New proof")
# new_proof.scale(2.5)
# new_proof.set_color(YELLOW)
# new_proof.set_stroke(RED, 0.75)
# new_proof.next_to(product, DOWN, MED_LARGE_BUFF)
circle = self.circle = Circle(radius=8, color=YELLOW)
circle.move_to(3 * DOWN, DOWN)
bottom_dot = Dot(color=BLUE)
bottom_dot.move_to(circle.get_bottom())
observer = PiCreature(mode="pondering")
observer.set_height(0.5)
observer.next_to(bottom_dot, DOWN)
lights = VGroup()
lines = VGroup()
light_config = dict(HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG)
# light_config = dict(CHEAP_AMBIENT_LIGHT_CONFIG)
light_config["max_opacity"] = 1
step = 0.03
for frac in np.arange(step, 0.2, step):
for u in -1, 1:
light = AmbientLight(**light_config)
dot = Dot(color=BLUE)
dot.move_to(light)
light.add_to_back(dot)
light.move_to(self.get_circle_point_at_proportion(u * frac - 0.25))
lights.add(light)
line = DashedLine(dot.get_center(), circle.get_bottom())
lines.add(line)
self.add(circle)
self.add(lights)
self.add(product)
# self.add(new_proof)
self.add(bottom_dot, observer)