mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
1533 lines
53 KiB
Python
1533 lines
53 KiB
Python
#!/usr/bin/env python
|
|
|
|
|
|
import numpy as np
|
|
import itertools as it
|
|
from copy import deepcopy
|
|
import sys
|
|
from fractions import Fraction, gcd
|
|
|
|
from manimlib.imports import *
|
|
from .inventing_math import Underbrace
|
|
|
|
import random
|
|
|
|
MOVIE_PREFIX = "music_and_measure/"
|
|
|
|
INTERVAL_RADIUS = 6
|
|
NUM_INTERVAL_TICKS = 16
|
|
TICK_STRETCH_FACTOR = 4
|
|
INTERVAL_COLOR_PALETTE = [
|
|
"yellow",
|
|
"green",
|
|
"skyblue",
|
|
"#AD1457",
|
|
"#6A1B9A",
|
|
"#26C6DA",
|
|
"#FF8F00",
|
|
]
|
|
|
|
def rationals():
|
|
curr = Fraction(1, 2)
|
|
numerator, denominator = 1, 2
|
|
while True:
|
|
yield curr
|
|
if curr.numerator < curr.denominator - 1:
|
|
new_numerator = curr.numerator + 1
|
|
while gcd(new_numerator, curr.denominator) != 1:
|
|
new_numerator += 1
|
|
curr = Fraction(new_numerator, curr.denominator)
|
|
else:
|
|
curr = Fraction(1, curr.denominator + 1)
|
|
|
|
def fraction_mobject(fraction):
|
|
n, d = fraction.numerator, fraction.denominator
|
|
return TexMobject("\\frac{%d}{%d}"%(n, d))
|
|
|
|
def continued_fraction(int_list):
|
|
if len(int_list) == 1:
|
|
return int_list[0]
|
|
return int_list[0] + Fraction(1, continued_fraction(int_list[1:]))
|
|
|
|
def zero_to_one_interval():
|
|
interval = NumberLine(
|
|
radius = INTERVAL_RADIUS,
|
|
interval_size = 2.0*INTERVAL_RADIUS/NUM_INTERVAL_TICKS
|
|
)
|
|
interval.elongate_tick_at(-INTERVAL_RADIUS, TICK_STRETCH_FACTOR)
|
|
interval.elongate_tick_at(INTERVAL_RADIUS, TICK_STRETCH_FACTOR)
|
|
interval.add(TexMobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN))
|
|
interval.add(TexMobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN))
|
|
return interval
|
|
|
|
class LeftParen(Mobject):
|
|
def generate_points(self):
|
|
self.add(TexMobject("("))
|
|
self.center()
|
|
|
|
def get_center(self):
|
|
return Mobject.get_center(self) + 0.04*LEFT
|
|
|
|
class RightParen(Mobject):
|
|
def generate_points(self):
|
|
self.add(TexMobject(")"))
|
|
self.center()
|
|
|
|
def get_center(self):
|
|
return Mobject.get_center(self) + 0.04*RIGHT
|
|
|
|
|
|
class OpenInterval(Mobject):
|
|
def __init__(self, center_point = ORIGIN, width = 2, **kwargs):
|
|
digest_config(self, kwargs, locals())
|
|
left = LeftParen().shift(LEFT*width/2)
|
|
right = RightParen().shift(RIGHT*width/2)
|
|
Mobject.__init__(self, left, right, **kwargs)
|
|
# scale_factor = width / 2.0
|
|
# self.stretch(scale_factor, 0)
|
|
# self.stretch(0.5+0.5*scale_factor, 1)
|
|
self.shift(center_point)
|
|
|
|
class Piano(ImageMobject):
|
|
CONFIG = {
|
|
"stroke_width" : 1,
|
|
"invert" : False,
|
|
"scale_factorue" : 0.5
|
|
}
|
|
def __init__(self, **kwargs):
|
|
digest_config(self, kwargs)
|
|
ImageMobject.__init__(self, "piano_keyboard")
|
|
jump = self.get_width()/24
|
|
self.center()
|
|
self.half_note_jump = self.get_width()/24
|
|
self.ivory_jump = self.get_width()/14
|
|
|
|
def split(self):
|
|
left = self.get_left()[0]
|
|
keys = []
|
|
for count in range(14):
|
|
key = Mobject(
|
|
color = "white",
|
|
stroke_width = 1
|
|
)
|
|
x0 = left + count*self.ivory_jump
|
|
x1 = x0 + self.ivory_jump
|
|
key.add_points(
|
|
self.points[
|
|
(self.points[:,0] > x0)*(self.points[:,0] < x1)
|
|
]
|
|
)
|
|
keys.append(key)
|
|
return keys
|
|
|
|
|
|
class Vibrate(Animation):
|
|
CONFIG = {
|
|
"num_periods" : 1,
|
|
"overtones" : 4,
|
|
"amplitude" : 0.5,
|
|
"radius" : INTERVAL_RADIUS,
|
|
"center" : ORIGIN,
|
|
"color" : "white",
|
|
"run_time" : 3.0,
|
|
"rate_func" : None
|
|
}
|
|
def __init__(self, **kwargs):
|
|
digest_config(self, kwargs)
|
|
def func(x, t):
|
|
return sum([
|
|
(self.amplitude/((k+1)**2.5))*np.sin(2*mult*t)*np.sin(k*mult*x)
|
|
for k in range(self.overtones)
|
|
for mult in [(self.num_periods+k)*np.pi]
|
|
])
|
|
self.func = func
|
|
Animation.__init__(self, Mobject1D(color = self.color), **kwargs)
|
|
|
|
def interpolate_mobject(self, alpha):
|
|
self.mobject.reset_points()
|
|
epsilon = self.mobject.epsilon
|
|
self.mobject.add_points([
|
|
[x*self.radius, self.func(x, alpha*self.run_time)+y, 0]
|
|
for x in np.arange(-1, 1, epsilon/self.radius)
|
|
for y in epsilon*np.arange(3)
|
|
])
|
|
self.mobject.shift(self.center)
|
|
|
|
|
|
class IntervalScene(NumberLineScene):
|
|
def construct(self):
|
|
self.number_line = UnitInterval()
|
|
self.displayed_numbers = [0, 1]
|
|
self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers)
|
|
self.add(self.number_line, *self.number_mobs)
|
|
|
|
def show_all_fractions(self,
|
|
num_fractions = 27,
|
|
pause_time = 1.0,
|
|
remove_as_you_go = True):
|
|
shrink = not remove_as_you_go
|
|
for fraction, count in zip(rationals(), list(range(num_fractions))):
|
|
frac_mob, tick = self.add_fraction(fraction, shrink)
|
|
self.wait(pause_time)
|
|
if remove_as_you_go:
|
|
self.remove(frac_mob, tick)
|
|
|
|
def add_fraction(self, fraction, shrink = False):
|
|
point = self.number_line.number_to_point(fraction)
|
|
tick_rad = self.number_line.tick_size*TICK_STRETCH_FACTOR
|
|
frac_mob = fraction_mobject(fraction)
|
|
if shrink:
|
|
scale_factor = 2.0/fraction.denominator
|
|
frac_mob.scale(scale_factor)
|
|
tick_rad *= scale_factor
|
|
frac_mob.shift(point + frac_mob.get_height()*UP)
|
|
tick = Line(point + DOWN*tick_rad, point + UP*tick_rad)
|
|
tick.set_color("yellow")
|
|
self.add(frac_mob, tick)
|
|
return frac_mob, tick
|
|
|
|
def add_fraction_ticks(self, num_fractions = 1000, run_time = 0):
|
|
long_tick_size = self.number_line.tick_size*TICK_STRETCH_FACTOR
|
|
all_ticks = []
|
|
for frac, count in zip(rationals(), list(range(num_fractions))):
|
|
point = self.number_line.number_to_point(frac)
|
|
tick_rad = 2.0*long_tick_size/frac.denominator
|
|
tick = Line(point+tick_rad*DOWN, point+tick_rad*UP)
|
|
tick.set_color("yellow")
|
|
all_ticks.append(tick)
|
|
all_ticks = Mobject(*all_ticks)
|
|
if run_time > 0:
|
|
self.play(ShowCreation(all_ticks))
|
|
else:
|
|
self.add(all_ticks)
|
|
return all_ticks
|
|
|
|
|
|
def cover_fractions(self,
|
|
epsilon = 0.3,
|
|
num_fractions = 10,
|
|
run_time_per_interval = 0.5):
|
|
intervals = []
|
|
lines = []
|
|
num_intervals = 0
|
|
all_rationals = rationals()
|
|
count = 0
|
|
while True:
|
|
fraction = next(all_rationals)
|
|
count += 1
|
|
if num_intervals >= num_fractions:
|
|
break
|
|
if fraction < self.number_line.left_num or fraction > self.number_line.right_num:
|
|
continue
|
|
num_intervals += 1
|
|
interval, line = self.add_open_interval(
|
|
fraction,
|
|
epsilon / min(2**count, 2**30),
|
|
run_time = run_time_per_interval
|
|
)
|
|
intervals.append(interval)
|
|
lines.append(line)
|
|
return intervals, lines
|
|
|
|
def add_open_interval(self, num, width, color = None, run_time = 0):
|
|
spatial_width = width*self.number_line.unit_length_to_spatial_width
|
|
center_point = self.number_line.number_to_point(num)
|
|
open_interval = OpenInterval(center_point, spatial_width)
|
|
color = color or "yellow"
|
|
interval_line = Line(
|
|
center_point+spatial_width*LEFT/2,
|
|
center_point+spatial_width*RIGHT/2
|
|
)
|
|
interval_line.do_in_place(interval_line.sort_points, get_norm)
|
|
interval_line.set_color(color)
|
|
if run_time > 0:
|
|
squished_interval = deepcopy(open_interval).stretch_to_fit_width(0)
|
|
self.play(
|
|
Transform(squished_interval, open_interval),
|
|
ShowCreation(interval_line),
|
|
run_time = run_time
|
|
)
|
|
self.remove(squished_interval)
|
|
self.add(open_interval, interval_line)
|
|
return open_interval, interval_line
|
|
|
|
|
|
class TwoChallenges(Scene):
|
|
def construct(self):
|
|
two_challenges = TextMobject("Two Challenges", size = "\\Huge").to_edge(UP)
|
|
one, two = list(map(TextMobject, ["1.", "2."]))
|
|
one.shift(UP).to_edge(LEFT)
|
|
two.shift(DOWN).to_edge(LEFT)
|
|
notes = ImageMobject("musical_notes").scale(0.3)
|
|
notes.next_to(one)
|
|
notes.set_color("blue")
|
|
measure = TextMobject("Measure Theory").next_to(two)
|
|
probability = TextMobject("Probability")
|
|
probability.next_to(measure).shift(DOWN+RIGHT)
|
|
integration = TexMobject("\\int")
|
|
integration.next_to(measure).shift(UP+RIGHT)
|
|
arrow_to_prob = Arrow(measure, probability)
|
|
arrow_to_int = Arrow(measure, integration)
|
|
for arrow in arrow_to_prob, arrow_to_int:
|
|
arrow.set_color("yellow")
|
|
|
|
|
|
self.add(two_challenges)
|
|
self.wait()
|
|
self.add(one, notes)
|
|
self.wait()
|
|
self.add(two, measure)
|
|
self.wait()
|
|
self.play(ShowCreation(arrow_to_int))
|
|
self.add(integration)
|
|
self.wait()
|
|
self.play(ShowCreation(arrow_to_prob))
|
|
self.add(probability)
|
|
self.wait()
|
|
|
|
class MeasureTheoryToHarmony(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.cover_fractions()
|
|
self.wait()
|
|
all_mobs = Mobject(*self.mobjects)
|
|
all_mobs.sort_points()
|
|
self.clear()
|
|
radius = self.interval.radius
|
|
line = Line(radius*LEFT, radius*RIGHT).set_color("white")
|
|
self.play(DelayByOrder(Transform(all_mobs, line)))
|
|
self.clear()
|
|
self.play(Vibrate(rate_func = smooth))
|
|
self.clear()
|
|
self.add(line)
|
|
self.wait()
|
|
|
|
|
|
class ChallengeOne(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Challenge #1").to_edge(UP)
|
|
start_color = Color("blue")
|
|
colors = start_color.range_to("white", 6)
|
|
self.bottom_vibration = Vibrate(
|
|
num_periods = 1, run_time = 3.0,
|
|
center = DOWN, color = start_color
|
|
)
|
|
top_vibrations = [
|
|
Vibrate(
|
|
num_periods = freq, run_time = 3.0,
|
|
center = 2*UP, color = next(colors)
|
|
)
|
|
for freq in [1, 2, 5.0/3, 4.0/3, 2]
|
|
]
|
|
freq_220 = TextMobject("220 Hz")
|
|
freq_r220 = TextMobject("$r\\times$220 Hz")
|
|
freq_330 = TextMobject("1.5$\\times$220 Hz")
|
|
freq_sqrt2 = TextMobject("$\\sqrt{2}\\times$220 Hz")
|
|
freq_220.shift(1.5*DOWN)
|
|
for freq in freq_r220, freq_330, freq_sqrt2:
|
|
freq.shift(1.5*UP)
|
|
r_constraint = TexMobject("(1<r<2)", size = "\\large")
|
|
|
|
self.add(title)
|
|
self.wait()
|
|
self.vibrate(1)
|
|
self.add(freq_220)
|
|
self.vibrate(1)
|
|
self.add(r_constraint)
|
|
self.vibrate(1)
|
|
self.add(freq_r220)
|
|
self.vibrate(2, top_vibrations[1])
|
|
self.remove(freq_r220, r_constraint)
|
|
self.add(freq_330)
|
|
self.vibrate(2, top_vibrations[2])
|
|
self.remove(freq_330)
|
|
self.add(freq_sqrt2)
|
|
self.vibrate(1, top_vibrations[3])
|
|
self.remove(freq_sqrt2)
|
|
self.continuously_vary_frequency(top_vibrations[0], top_vibrations[4])
|
|
|
|
def vibrate(self, num_repeats, *top_vibrations):
|
|
anims = [self.bottom_vibration] + list(top_vibrations)
|
|
for count in range(num_repeats):
|
|
self.play(*anims)
|
|
self.remove(*[a.mobject for a in anims])
|
|
|
|
def continuously_vary_frequency(self, top_vib_1, top_vib_2):
|
|
number_line = NumberLine(interval_size = 1).add_numbers()
|
|
|
|
one, two = 2*number_line.interval_size*RIGHT, 4*number_line.interval_size*RIGHT
|
|
arrow1 = Arrow(one+UP, one)
|
|
arrow2 = Arrow(two+UP, two)
|
|
r1 = TexMobject("r").next_to(arrow1, UP)
|
|
r2 = TexMobject("r").next_to(arrow2, UP)
|
|
kwargs = {
|
|
"run_time" : 5.0,
|
|
"rate_func" : there_and_back
|
|
}
|
|
run_time = 3.0
|
|
vibrations = [self.bottom_vibration, top_vib_1, top_vib_2]
|
|
|
|
self.add(number_line, r1, arrow1)
|
|
self.play(bottom_vibration, top_vib_1)
|
|
for vib in vibrations:
|
|
vib.set_run_time(kwargs["run_time"])
|
|
self.play(
|
|
self.bottom_vibration,
|
|
Transform(arrow1, arrow2, **kwargs),
|
|
Transform(r1, r2, **kwargs),
|
|
TransformAnimations(top_vib_1, top_vib_2, **kwargs)
|
|
)
|
|
for vib in vibrations:
|
|
vib.set_run_time(3.0)
|
|
self.play(bottom_vibration, top_vib_1)
|
|
|
|
class JustByAnalyzingTheNumber(Scene):
|
|
def construct(self):
|
|
nums = [
|
|
1.5,
|
|
np.sqrt(2),
|
|
2**(5/12.),
|
|
4/3.,
|
|
1.2020569031595942,
|
|
]
|
|
last = None
|
|
r_equals = TexMobject("r=").shift(2*LEFT)
|
|
self.add(r_equals)
|
|
for num in nums:
|
|
mob = TexMobject(str(num)).next_to(r_equals)
|
|
mob.set_color()
|
|
mob.sort_points()
|
|
if last:
|
|
self.play(DelayByOrder(Transform(last, mob, run_time = 0.5)))
|
|
self.remove(last)
|
|
self.add(mob)
|
|
else:
|
|
self.add(mob)
|
|
self.wait()
|
|
last = mob
|
|
|
|
class QuestionAndAnswer(Scene):
|
|
def construct(self):
|
|
Q = TextMobject("Q:").shift(UP).to_edge(LEFT)
|
|
A = TextMobject("A:").shift(DOWN).to_edge(LEFT)
|
|
string1 = Vibrate(center = 3*UP, color = "blue")
|
|
string2 = Vibrate(num_periods = 2, center = 3.5*UP, color = "green")
|
|
twotwenty = TexMobject("220").scale(0.25).next_to(string1.mobject, LEFT)
|
|
r220 = TexMobject("r\\times220").scale(0.25).next_to(string2.mobject, LEFT)
|
|
question = TextMobject(
|
|
"For what values of $r$ will the frequencies 220~Hz and \
|
|
$r\\times$220~Hz sound nice together?"
|
|
).next_to(Q)
|
|
answer = TextMobject([
|
|
"When $r$ is",
|
|
"sufficiently close to",
|
|
"a rational number"
|
|
], size = "\\small").scale(1.5)
|
|
answer.next_to(A)
|
|
correction1 = TextMobject(
|
|
"with sufficiently low denominator",
|
|
size = "\\small"
|
|
).scale(1.5)
|
|
correction1.set_color("yellow")
|
|
correction1.next_to(A).shift(2*answer.get_height()*DOWN)
|
|
answer = answer.split()
|
|
answer[1].set_color("green")
|
|
temp_answer_end = deepcopy(answer[-1]).next_to(answer[0], buff = 0.2)
|
|
|
|
self.add(Q, A, question, twotwenty, r220)
|
|
self.play(string1, string2)
|
|
self.add(answer[0], temp_answer_end)
|
|
self.play(string1, string2)
|
|
self.play(ShimmerIn(correction1), string1, string2)
|
|
self.play(string1, string2, run_time = 3.0)
|
|
self.play(
|
|
Transform(Point(answer[1].get_left()), answer[1]),
|
|
Transform(temp_answer_end, answer[2]),
|
|
string1, string2
|
|
)
|
|
self.play(string1, string2, run_time = 3.0)
|
|
|
|
class PlaySimpleRatio(Scene):
|
|
args_list = [
|
|
(Fraction(3, 2), "green"),
|
|
(Fraction(4, 3), "purple"),
|
|
(Fraction(8, 5), "skyblue"),
|
|
(Fraction(211, 198), "orange"),
|
|
(Fraction(1093, 826), "red"),
|
|
((np.exp(np.pi)-np.pi)/15, "#7e008e")
|
|
]
|
|
@staticmethod
|
|
def args_to_string(fraction, color):
|
|
return str(fraction).replace("/", "_to_")
|
|
|
|
def construct(self, fraction, color):
|
|
string1 = Vibrate(
|
|
num_periods = 1, run_time = 5.0,
|
|
center = DOWN, color = "blue"
|
|
)
|
|
string2 = Vibrate(
|
|
num_periods = fraction, run_time = 5.0,
|
|
center = 2*UP, color = color
|
|
)
|
|
if isinstance(fraction, Fraction):
|
|
mob = fraction_mobject(fraction).shift(0.5*UP)
|
|
else:
|
|
mob = TexMobject("\\frac{e^\\pi - \\pi}{15} \\approx \\frac{4}{3}")
|
|
mob.shift(0.5*UP)
|
|
self.add(mob)
|
|
self.play(string1, string2)
|
|
|
|
class LongSine(Mobject1D):
|
|
def generate_points(self):
|
|
self.add_points([
|
|
(x, np.sin(2*np.pi*x), 0)
|
|
for x in np.arange(0, 100, self.epsilon/10)
|
|
])
|
|
|
|
class DecomposeMusicalNote(Scene):
|
|
def construct(self):
|
|
line = Line(FRAME_Y_RADIUS*DOWN, FRAME_Y_RADIUS*UP)
|
|
sine = LongSine()
|
|
kwargs = {
|
|
"run_time" : 4.0,
|
|
"rate_func" : None
|
|
}
|
|
words = TextMobject("Imagine 220 per second...")
|
|
words.shift(2*UP)
|
|
|
|
self.add(line)
|
|
self.play(ApplyMethod(sine.shift, 4*LEFT, **kwargs))
|
|
self.add(words)
|
|
kwargs["rate_func"] = rush_into
|
|
self.play(ApplyMethod(sine.shift, 80*LEFT, **kwargs))
|
|
kwargs["rate_func"] = None
|
|
kwargs["run_time"] = 1.0
|
|
sine.to_edge(LEFT, buff = 0)
|
|
for x in range(5):
|
|
self.play(ApplyMethod(sine.shift, 85*LEFT, **kwargs))
|
|
|
|
class DecomposeTwoFrequencies(Scene):
|
|
def construct(self):
|
|
line = Line(FRAME_Y_RADIUS*DOWN, FRAME_Y_RADIUS*UP)
|
|
sine1 = LongSine().shift(2*UP).set_color("yellow")
|
|
sine2 = LongSine().shift(DOWN).set_color("lightgreen")
|
|
sine1.stretch(2.0/3, 0)
|
|
comp = Mobject(sine1, sine2)
|
|
|
|
self.add(line)
|
|
self.play(ApplyMethod(
|
|
comp.shift, 15*LEFT,
|
|
run_time = 7.5,
|
|
rate_func=linear
|
|
))
|
|
|
|
|
|
class MostRationalsSoundBad(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Most rational numbers sound bad!"))
|
|
|
|
class FlashOnXProximity(Animation):
|
|
def __init__(self, mobject, x_val, *close_mobjects, **kwargs):
|
|
self.x_val = x_val
|
|
self.close_mobjects = close_mobjects
|
|
Animation.__init__(self, mobject, **kwargs)
|
|
|
|
def interpolate_mobject(self, alpha):
|
|
for mob in self.close_mobjects:
|
|
if np.min(np.abs(mob.points[:,0] - self.x_val)) < 0.1:
|
|
self.mobject.set_color()
|
|
return
|
|
self.mobject.to_original_color()
|
|
|
|
class PatternInFrequencies(Scene):
|
|
args_list = [
|
|
(3, 2, "green"),
|
|
(4, 3, "purple"),
|
|
(8, 5, "skyblue"),
|
|
(35, 43, "red"),
|
|
]
|
|
@staticmethod
|
|
def args_to_string(num1, num2, color):
|
|
return "%d_to_%d"%(num1, num2)
|
|
|
|
def construct(self, num1, num2, color):
|
|
big_line = Line(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN)
|
|
big_line.set_color("white").shift(2*LEFT)
|
|
line_template = Line(UP, DOWN)
|
|
line_template.shift(2*UP+2*LEFT)
|
|
setup_width = FRAME_WIDTH
|
|
num_top_lines = int(setup_width)
|
|
num_bot_lines = int(setup_width*num1/num2)
|
|
top_lines = Mobject(*[
|
|
deepcopy(line_template).shift(k*(float(num1)/num2)*RIGHT)
|
|
for k in range(num_top_lines)
|
|
])
|
|
line_template.shift(4*DOWN)
|
|
bottom_lines = Mobject(*[
|
|
deepcopy(line_template).shift(k*RIGHT)
|
|
for k in range(num_bot_lines)
|
|
])
|
|
bottom_lines.set_color("blue")
|
|
top_lines.set_color(color)
|
|
kwargs = {
|
|
"run_time" : 10,
|
|
"rate_func" : None
|
|
}
|
|
|
|
self.add(big_line)
|
|
self.add(TexMobject("%d:%d"%(num1, num2)))
|
|
fracs = (
|
|
1.0/(num_top_lines-1),
|
|
1.0/(num_bot_lines-1)
|
|
)
|
|
anims = [
|
|
ApplyMethod(mob.shift, setup_width*LEFT, **kwargs)
|
|
for mob in (top_lines, bottom_lines)
|
|
]
|
|
anim_mobs = [anim.mobject for anim in anims]
|
|
self.play(
|
|
FlashOnXProximity(big_line, -2, *anim_mobs, **kwargs),
|
|
*anims
|
|
)
|
|
|
|
|
|
class CompareFractionComplexity(Scene):
|
|
def construct(self):
|
|
fractions = []
|
|
for num, den in [(4, 3), (1093,826)]:
|
|
top = TexMobject("%d \\over"%num)
|
|
bottom = TexMobject(str(den)).next_to(top, DOWN, buff = 0.3)
|
|
fractions.append(Mobject(top, bottom))
|
|
frac0 = fractions[0].shift(3*LEFT).split()
|
|
frac1 = fractions[1].shift(3*RIGHT).split()
|
|
arrow1 = Arrow(UP, ORIGIN).next_to(frac0[0], UP)
|
|
arrow2 = Arrow(UP, ORIGIN).next_to(frac1[0], UP)
|
|
simple = TextMobject("Simple").next_to(arrow1, UP)
|
|
simple.set_color("green")
|
|
complicated = TextMobject("Complicated").next_to(arrow2, UP)
|
|
complicated.set_color("red")
|
|
indicates = TextMobject("Indicates complexity").shift(2*DOWN)
|
|
arrow3 = Arrow(indicates.get_top(), frac0[1])
|
|
arrow4 = Arrow(indicates.get_top(), frac1[1])
|
|
|
|
self.add(*frac0 + frac1)
|
|
self.wait()
|
|
self.add(simple, complicated)
|
|
self.play(*[
|
|
ShowCreation(arrow)
|
|
for arrow in (arrow1, arrow2)
|
|
])
|
|
self.wait()
|
|
self.play(*[
|
|
DelayByOrder(ApplyMethod(frac[1].set_color, "yellow"))
|
|
for frac in (frac0, frac1)
|
|
])
|
|
self.play(
|
|
FadeIn(indicates),
|
|
ShowCreation(arrow3),
|
|
ShowCreation(arrow4)
|
|
)
|
|
self.wait()
|
|
|
|
class IrrationalGang(Scene):
|
|
def construct(self):
|
|
randy = Randolph()
|
|
randy.mouth.set_color(randy.DEFAULT_COLOR)
|
|
randy.sync_parts()
|
|
sqrt13 = TexMobject("\\sqrt{13}").shift(2*LEFT)
|
|
sqrt13.set_color("green")
|
|
zeta3 = TexMobject("\\zeta(3)").shift(2*RIGHT)
|
|
zeta3.set_color("grey")
|
|
eyes = Mobject(*randy.eyes)
|
|
eyes.scale(0.5)
|
|
sqrt13.add(eyes.next_to(sqrt13, UP, buff = 0).shift(0.25*RIGHT))
|
|
eyes.scale(0.5)
|
|
zeta3.add(eyes.next_to(zeta3, UP, buff = 0).shift(0.3*LEFT+0.08*DOWN))
|
|
speech_bubble = SpeechBubble()
|
|
speech_bubble.pin_to(randy)
|
|
speech_bubble.write("We want to play too!")
|
|
|
|
self.add(randy, sqrt13, zeta3, speech_bubble, speech_bubble.content)
|
|
self.play(BlinkPiCreature(randy))
|
|
|
|
|
|
class ConstructPiano(Scene):
|
|
def construct(self):
|
|
piano = Piano()
|
|
keys = piano.split()
|
|
anims = []
|
|
askew = deepcopy(keys[-1])
|
|
keys[-1].rotate_in_place(np.pi/5)
|
|
for key in keys:
|
|
key.stroke_width = 1
|
|
key_copy = deepcopy(key).to_corner(DOWN+LEFT)
|
|
key_copy.scale_in_place(0.25)
|
|
key_copy.shift(1.8*random.random()*FRAME_X_RADIUS*RIGHT)
|
|
key_copy.shift(1.8*random.random()*FRAME_Y_RADIUS*UP)
|
|
key_copy.rotate(2*np.pi*random.random())
|
|
anims.append(Transform(key_copy, key))
|
|
self.play(*anims, run_time = 3.0)
|
|
self.wait()
|
|
self.play(Transform(anims[-1].mobject, askew))
|
|
self.wait()
|
|
|
|
|
|
class PianoTuning(Scene):
|
|
def construct(self):
|
|
piano = self.piano = Piano()
|
|
jump = piano.half_note_jump
|
|
semicircle = Circle().filter_out(lambda p : p[1] < 0)
|
|
semicircle.scale(jump/semicircle.get_width())
|
|
semicircles = Mobject(*[
|
|
deepcopy(semicircle).shift(jump*k*RIGHT)
|
|
for k in range(23)
|
|
])
|
|
semicircles.set_color("white")
|
|
semicircles.next_to(piano, UP, buff = 0)
|
|
semicircles.shift(0.05*RIGHT)
|
|
semicircles.sort_points(lambda p : p[0])
|
|
first_jump = semicircles.split()[0]
|
|
twelfth_root = TexMobject("2^{\\left(\\frac{1}{12}\\right)}")
|
|
twelfth_root.scale(0.75).next_to(first_jump, UP, buff = 1.5)
|
|
line = Line(twelfth_root, first_jump).set_color("grey")
|
|
self.keys = piano.split()
|
|
self.semicircles = semicircles.split()
|
|
|
|
self.add(piano)
|
|
self.wait()
|
|
self.play(ShowCreation(first_jump))
|
|
self.play(
|
|
ShowCreation(line),
|
|
FadeIn(twelfth_root)
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(semicircles, rate_func=linear))
|
|
self.wait()
|
|
|
|
self.show_interval(5, 7)
|
|
self.show_interval(4, 5)
|
|
|
|
def show_interval(self, interval, half_steps):
|
|
whole_notes_to_base = 5
|
|
half_notes_to_base = 9
|
|
|
|
self.clear()
|
|
self.add(self.piano)
|
|
colors = list(Color("blue").range_to("yellow", 7))
|
|
low = self.keys[whole_notes_to_base]
|
|
high = self.keys[whole_notes_to_base + interval - 1]
|
|
u_brace = Underbrace(low.get_bottom(), high.get_bottom())
|
|
u_brace.set_color("yellow")
|
|
ratio = TexMobject("2^{\\left(\\frac{%d}{12}\\right)}"%half_steps)
|
|
ratio.next_to(u_brace, DOWN, buff = 0.2)
|
|
semicircles = self.semicircles[half_notes_to_base:half_notes_to_base+half_steps]
|
|
product = TexMobject(
|
|
["\\left(2^{\\left(\\frac{1}{12}\\right)}\\right)"]*half_steps,
|
|
size = "\\small"
|
|
).next_to(self.piano, UP, buff = 1.0)
|
|
approximate_form = TexMobject("\\approx"+str(2**(float(half_steps)/12)))
|
|
approximate_form.scale(0.75)
|
|
approximate_form.next_to(ratio)
|
|
if interval == 5:
|
|
num_den = (3, 2)
|
|
elif interval == 4:
|
|
num_den = (4, 3)
|
|
should_be = TextMobject("Should be $\\frac{%d}{%d}$"%num_den)
|
|
should_be.next_to(u_brace, DOWN)
|
|
|
|
self.play(ApplyMethod(low.set_color, colors[0]))
|
|
self.play(
|
|
ApplyMethod(high.set_color, colors[interval]),
|
|
Transform(Point(u_brace.get_left()), u_brace),
|
|
)
|
|
self.wait()
|
|
self.play(ShimmerIn(should_be))
|
|
self.wait()
|
|
self.remove(should_be)
|
|
terms = product.split()
|
|
for term, semicircle in zip(terms, semicircles):
|
|
self.add(term, semicircle)
|
|
self.wait(0.25)
|
|
self.wait()
|
|
product.sort_points(lambda p : p[1])
|
|
self.play(DelayByOrder(Transform(product, ratio)))
|
|
self.wait()
|
|
self.play(ShimmerIn(approximate_form))
|
|
self.wait()
|
|
|
|
class PowersOfTwelfthRoot(Scene):
|
|
def construct(self):
|
|
max_height = FRAME_Y_RADIUS-0.5
|
|
min_height = -max_height
|
|
num_terms = 11
|
|
mob_list = []
|
|
fraction_map = {
|
|
1 : Fraction(16, 15),
|
|
2 : Fraction(9, 8),
|
|
3 : Fraction(6, 5),
|
|
4 : Fraction(5, 4),
|
|
5 : Fraction(4, 3),
|
|
7 : Fraction(3, 2),
|
|
8 : Fraction(8, 5),
|
|
9 : Fraction(5, 3),
|
|
10 : Fraction(16, 9),
|
|
}
|
|
approx = TexMobject("\\approx").scale(0.5)
|
|
curr_height = max_height*UP
|
|
spacing = UP*(max_height-min_height)/(len(fraction_map)-1.0)
|
|
for i in range(1, num_terms+1):
|
|
if i not in fraction_map:
|
|
continue
|
|
term = TexMobject("2^{\\left(\\frac{%d}{12}\\right)}"%i)
|
|
term.shift(curr_height)
|
|
curr_height -= spacing
|
|
term.shift(4*LEFT)
|
|
value = 2**(i/12.0)
|
|
approx_form = TexMobject(str(value)[:10])
|
|
approx_copy = deepcopy(approx).next_to(term)
|
|
approx_form.scale(0.5).next_to(approx_copy)
|
|
words = TextMobject("is close to")
|
|
words.scale(approx_form.get_height()/words.get_height())
|
|
words.next_to(approx_form)
|
|
frac = fraction_map[i]
|
|
frac_mob = TexMobject("%d/%d"%(frac.numerator, frac.denominator))
|
|
frac_mob.scale(0.5).next_to(words)
|
|
percent_error = abs(100*((value - frac) / frac))
|
|
error_string = TextMobject([
|
|
"with", str(percent_error)[:4] + "\\%", "error"
|
|
])
|
|
error_string = error_string.split()
|
|
error_string[1].set_color()
|
|
error_string = Mobject(*error_string)
|
|
error_string.scale(approx_form.get_height()/error_string.get_height())
|
|
error_string.next_to(frac_mob)
|
|
|
|
mob_list.append(Mobject(*[
|
|
term, approx_copy, approx_form, words, frac_mob, error_string
|
|
]))
|
|
self.play(ShimmerIn(Mobject(*mob_list), run_time = 3.0))
|
|
|
|
class InterestingQuestion(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Interesting Question:", size = "\\Huge")
|
|
words.scale(2.0)
|
|
self.add(words)
|
|
|
|
|
|
class SupposeThereIsASavant(Scene):
|
|
def construct(self):
|
|
words = "Suppose there is a musical savant " + \
|
|
"who finds pleasure in all pairs of " + \
|
|
"notes whose frequencies have a rational ratio"
|
|
words = words.split(" ")
|
|
word_mobs = TextMobject(words).split()
|
|
word_mobs[4].set_color()
|
|
word_mobs[5].set_color()
|
|
for word, word_mob in zip(words, word_mobs):
|
|
self.add(word_mob)
|
|
self.wait(0.1*len(word))
|
|
|
|
class AllValuesBetween1And2(NumberLineScene):
|
|
def construct(self):
|
|
NumberLineScene.construct(self)
|
|
irrational = 1.2020569031595942
|
|
cont_frac = [1, 4, 1, 18, 1, 1, 1, 4, 1, 9, 9, 2, 1, 1, 1, 2]
|
|
one, two, irr = list(map(
|
|
self.number_line.number_to_point,
|
|
[1, 2, irrational]
|
|
))
|
|
top_arrow = Arrow(one+UP, one)
|
|
bot_arrow = Arrow(irr+2*DOWN, irr)
|
|
r = TexMobject("r").next_to(top_arrow, UP)
|
|
irr_mob = TexMobject(str(irrational)+"\\dots").next_to(bot_arrow, DOWN)
|
|
|
|
approximations = [
|
|
continued_fraction(cont_frac[:k])
|
|
for k in range(1, len(cont_frac))[:8]
|
|
]
|
|
|
|
kwargs = {
|
|
"run_time" : 3.0,
|
|
"rate_func" : there_and_back
|
|
}
|
|
self.play(*[
|
|
ApplyMethod(mob.shift, RIGHT, **kwargs)
|
|
for mob in (r, top_arrow)
|
|
])
|
|
self.wait()
|
|
self.remove(r, top_arrow)
|
|
self.play(
|
|
ShimmerIn(irr_mob),
|
|
ShowCreation(bot_arrow)
|
|
)
|
|
self.wait()
|
|
self.add(irr_mob, bot_arrow)
|
|
|
|
frac_mob = Point(top_arrow.get_top())
|
|
max_num_zooms = 4
|
|
num_zooms = 0
|
|
for approx in approximations:
|
|
point = self.number_line.number_to_point(approx)
|
|
new_arrow = Arrow(point+UP, point)
|
|
mob = fraction_mobject(approx).next_to(new_arrow, UP)
|
|
self.play(
|
|
Transform(top_arrow, new_arrow),
|
|
Transform(frac_mob, mob),
|
|
run_time = 0.5
|
|
)
|
|
self.wait(0.5)
|
|
points = list(map(self.number_line.number_to_point, [approx, irrational]))
|
|
distance = get_norm(points[1]-points[0])
|
|
if distance < 0.3*FRAME_X_RADIUS and num_zooms < max_num_zooms:
|
|
num_zooms += 1
|
|
new_distance = 0.75*FRAME_X_RADIUS
|
|
self.zoom_in_on(irrational, new_distance/distance)
|
|
for mob in irr_mob, bot_arrow:
|
|
mob.shift(mob.get_center()[0]*LEFT)
|
|
self.wait(0.5)
|
|
|
|
|
|
class ChallengeTwo(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Challenge #2"))
|
|
|
|
class CoveringSetsWithOpenIntervals(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
dots = Mobject(*[
|
|
Dot().shift(self.number_line.number_to_point(num)+UP)
|
|
for num in set([0.2, 0.25, 0.45, 0.6, 0.65])
|
|
])
|
|
theorems = [
|
|
TextMobject(words).shift(UP)
|
|
for words in [
|
|
"Heine-Borel Theorem",
|
|
"Lebesgue's Number Lemma",
|
|
"Vitali Covering Lemma",
|
|
"Besicovitch Covering Theorem",
|
|
"$\\vdots$"
|
|
]
|
|
]
|
|
|
|
self.add(dots)
|
|
self.play(DelayByOrder(ApplyMethod(dots.shift, DOWN, run_time = 2)))
|
|
self.wait()
|
|
for center in 0.225, 0.475, 0.625:
|
|
self.add_open_interval(center, 0.1, run_time = 1.0)
|
|
self.wait()
|
|
for x in range(2*len(theorems)):
|
|
self.play(*[
|
|
ApplyMethod(th.shift, UP, rate_func=linear)
|
|
for th in theorems[:x+1]
|
|
])
|
|
|
|
class DefineOpenInterval(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
open_interval, line = self.add_open_interval(0.5, 0.75, run_time = 1.0)
|
|
left, right = open_interval.get_left(), open_interval.get_right()
|
|
a, less_than1, x, less_than2, b = \
|
|
TexMobject(["a", "<", "x", "<", "b"]).shift(UP).split()
|
|
left_arrow = Arrow(a.get_corner(DOWN+LEFT), left)
|
|
right_arrow = Arrow(b.get_corner(DOWN+RIGHT), right)
|
|
|
|
self.play(*[ShimmerIn(mob) for mob in (a, less_than1, x)])
|
|
self.play(ShowCreation(left_arrow))
|
|
self.wait()
|
|
self.play(*[ShimmerIn(mob) for mob in (less_than2, b)])
|
|
self.play(ShowCreation(right_arrow))
|
|
self.wait()
|
|
|
|
|
|
class ShowAllFractions(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.show_all_fractions(
|
|
num_fractions = 100,
|
|
remove_as_you_go = False,
|
|
pause_time = 0.3
|
|
)
|
|
self.wait()
|
|
self.play(*[
|
|
CounterclockwiseTransform(mob, Point(mob.get_center()))
|
|
for mob in self.mobjects
|
|
if isinstance(mob, ImageMobject)
|
|
])
|
|
self.wait()
|
|
|
|
class NaiveFractionCover(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.add_fraction_ticks(100)
|
|
self.add_fraction_ticks(run_time = 5.0)
|
|
last_interval = None
|
|
centers = np.arange(0, 1.1, .1)
|
|
random.shuffle(centers)
|
|
for num, count in zip(centers, it.count()):
|
|
if count == 0:
|
|
kwargs = {
|
|
"run_time" : 1.0,
|
|
"rate_func" : rush_into
|
|
}
|
|
elif count == 10:
|
|
kwargs = {
|
|
"run_time" : 1.0,
|
|
"rate_func" : rush_from
|
|
}
|
|
else:
|
|
kwargs = {
|
|
"run_time" : 0.25,
|
|
"rate_func" : None
|
|
}
|
|
open_interval, line = self.add_open_interval(num, 0.1)
|
|
open_interval.shift(UP)
|
|
self.remove(line)
|
|
anims = [ApplyMethod(open_interval.shift, DOWN, **kwargs)]
|
|
last_interval = open_interval
|
|
self.play(*anims)
|
|
self.wait()
|
|
|
|
class CoverFractionsWithWholeInterval(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.add_fraction_ticks()
|
|
self.wait()
|
|
self.add_open_interval(0.5, 1, color = "red", run_time = 2.0)
|
|
self.wait()
|
|
|
|
class SumOfIntervalsMustBeLessThan1(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.add_fraction_ticks()
|
|
anims = []
|
|
last_plus = Point((FRAME_X_RADIUS-0.5)*LEFT+2*UP)
|
|
for num in np.arange(0, 1.1, .1):
|
|
open_interval, line = self.add_open_interval(num, 0.1)
|
|
self.remove(line)
|
|
int_copy = deepcopy(open_interval)
|
|
int_copy.scale(0.6).next_to(last_plus, buff = 0.1)
|
|
last_plus = TexMobject("+").scale(0.3)
|
|
last_plus.next_to(int_copy, buff = 0.1)
|
|
anims.append(Transform(open_interval, int_copy))
|
|
if num < 1.0:
|
|
anims.append(FadeIn(last_plus))
|
|
less_than1 = TexMobject("<1").scale(0.5)
|
|
less_than1.next_to(int_copy)
|
|
dots = TexMobject("\\dots").replace(int_copy)
|
|
words = TextMobject("Use infinitely many intervals")
|
|
words.shift(UP)
|
|
|
|
self.wait()
|
|
self.play(*anims)
|
|
self.play(ShimmerIn(less_than1))
|
|
self.wait()
|
|
self.play(Transform(anims[-1].mobject, dots))
|
|
self.play(ShimmerIn(words))
|
|
self.wait()
|
|
|
|
class RationalsAreDense(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
words = TextMobject(["Rationals are", "\\emph{dense}", "in the reals"])
|
|
words.shift(2*UP)
|
|
words = words.split()
|
|
words[1].set_color()
|
|
|
|
self.add(words[0])
|
|
self.play(ShimmerIn(words[1]))
|
|
self.add(words[2])
|
|
self.wait()
|
|
ticks = self.add_fraction_ticks(run_time = 5.0)
|
|
self.wait()
|
|
for center, width in [(0.5, 0.1), (0.2, 0.05), (0.7, 0.01)]:
|
|
oi, line = self.add_open_interval(center, width, run_time = 1)
|
|
self.remove(line)
|
|
self.wait()
|
|
for compound in oi, ticks:
|
|
self.remove(compound)
|
|
self.add(*compound.split())
|
|
self.zoom_in_on(0.7, 100)
|
|
self.play(ShowCreation(ticks, run_time = 2.0))
|
|
self.wait()
|
|
|
|
|
|
class SurelyItsImpossible(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Surely it's impossible!"))
|
|
|
|
class HowCanYouNotCoverEntireInterval(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
small_words = TextMobject("""
|
|
In case you are wondering, it is indeed true
|
|
that if you cover all real numbers between 0
|
|
and 1 with a set of open intervals, the sum
|
|
of the lengths of those intervals must be at
|
|
least 1. While intuitive, this is not actually
|
|
as obvious as it might seem, just try to prove
|
|
it for yourself!
|
|
""")
|
|
small_words.scale(0.5).to_corner(UP+RIGHT)
|
|
big_words = TextMobject("""
|
|
Covering all numbers from 0 to 1 \\emph{will}
|
|
force the sum of the lengths of your intervals
|
|
to be at least 1.
|
|
""")
|
|
big_words.next_to(self.number_line, DOWN, buff = 0.5)
|
|
|
|
ticks = self.add_fraction_ticks()
|
|
left = self.number_line.number_to_point(0)
|
|
right = self.number_line.number_to_point(1)
|
|
full_line = Line(left, right)
|
|
full_line.set_color("red")
|
|
# dot = Dot().shift(left).set_color("red")
|
|
# point = Point(left).set_color("red")
|
|
intervals = []
|
|
for num in np.arange(0, 1.1, .1):
|
|
open_interval, line = self.add_open_interval(num, 0.1)
|
|
self.remove(line)
|
|
intervals.append(open_interval)
|
|
self.wait()
|
|
self.remove(ticks)
|
|
self.play(*[
|
|
Transform(tick, full_line)
|
|
for tick in ticks.split()
|
|
])
|
|
self.play(ShimmerIn(big_words))
|
|
self.wait()
|
|
# self.play(DelayByOrder(FadeToColor(full_line, "red")))
|
|
self.play(ShimmerIn(small_words))
|
|
self.wait()
|
|
|
|
class PauseNow(Scene):
|
|
def construct(self):
|
|
top_words = TextMobject("Try for yourself!").scale(2).shift(3*UP)
|
|
bot_words = TextMobject("""
|
|
If you've never seen this before, you will get
|
|
the most out of the solution and the intuitions
|
|
I illustrate only after pulling out a pencil and
|
|
paper and taking a wack at it yourself.
|
|
""").next_to(top_words, DOWN, buff = 0.5)
|
|
self.add(top_words, bot_words)
|
|
|
|
class StepsToSolution(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
self.spacing = 0.7
|
|
steps = list(map(TextMobject, [
|
|
"Enumerate all rationals in (0, 1)",
|
|
"Assign one interval to each rational",
|
|
"Choose sum of the form $\\mathlarger{\\sum}_{n=1}^\\infty a_n = 1$",
|
|
"Pick any $\\epsilon$ such that $0 < \\epsilon < 1$",
|
|
"Stretch the $n$th interval to have length $\\epsilon/2^n$",
|
|
]))
|
|
for step in steps:
|
|
step.shift(DOWN)
|
|
for step in steps[2:]:
|
|
step.shift(DOWN)
|
|
for step, count in zip(steps, it.count()):
|
|
self.add(step)
|
|
self.wait()
|
|
if count == 0:
|
|
self.enumerate_rationals()
|
|
elif count == 1:
|
|
self.assign_intervals_to_rationals()
|
|
elif count == 2:
|
|
self.add_terms()
|
|
elif count == 3:
|
|
self.multiply_by_epsilon()
|
|
elif count == 4:
|
|
self.stretch_intervals()
|
|
self.remove(step)
|
|
|
|
def enumerate_rationals(self):
|
|
ticks = self.add_fraction_ticks(run_time = 2.0)
|
|
anims = []
|
|
commas = Mobject()
|
|
denom_to_mobs = {}
|
|
for frac, count in zip(rationals(), list(range(1,28))):
|
|
mob, tick = self.add_fraction(frac, shrink = True)
|
|
self.wait(0.1)
|
|
self.remove(tick)
|
|
if frac.denominator not in denom_to_mobs:
|
|
denom_to_mobs[frac.denominator] = []
|
|
denom_to_mobs[frac.denominator].append(mob)
|
|
mob_copy = deepcopy(mob).center()
|
|
mob_copy.shift((2.4-mob_copy.get_bottom()[1])*UP)
|
|
mob_copy.shift((-FRAME_X_RADIUS+self.spacing*count)*RIGHT)
|
|
comma = TextMobject(",").next_to(mob_copy, buff = 0.1, aligned_edge = DOWN)
|
|
anims.append(Transform(mob, mob_copy))
|
|
commas.add(comma)
|
|
anims.append(ShimmerIn(commas))
|
|
new_ticks = []
|
|
for tick, count in zip(ticks.split(), it.count(1)):
|
|
tick_copy = deepcopy(tick).center().shift(1.6*UP)
|
|
tick_copy.shift((-FRAME_X_RADIUS+self.spacing*count)*RIGHT)
|
|
new_ticks.append(tick_copy)
|
|
new_ticks = Mobject(*new_ticks)
|
|
anims.append(DelayByOrder(Transform(ticks, new_ticks)))
|
|
self.wait()
|
|
self.play(*anims)
|
|
self.wait()
|
|
for denom in range(2, 10):
|
|
for mob in denom_to_mobs[denom]:
|
|
mob.set_color("green")
|
|
self.wait()
|
|
for mob in denom_to_mobs[denom]:
|
|
mob.to_original_color()
|
|
self.ticks = ticks.split()[:20]
|
|
|
|
def assign_intervals_to_rationals(self):
|
|
anims = []
|
|
for tick in self.ticks:
|
|
interval = OpenInterval(tick.get_center(), self.spacing)
|
|
interval.scale_in_place(0.5)
|
|
squished = deepcopy(interval).stretch_to_fit_width(0)
|
|
anims.append(Transform(squished, interval))
|
|
self.play(*anims)
|
|
self.show_frame()
|
|
self.wait()
|
|
to_remove = [self.number_line] + self.number_mobs
|
|
self.play(*[
|
|
ApplyMethod(mob.shift, FRAME_WIDTH*RIGHT)
|
|
for mob in to_remove
|
|
])
|
|
self.remove(*to_remove)
|
|
self.wait()
|
|
|
|
self.intervals = [a.mobject for a in anims]
|
|
kwargs = {
|
|
"run_time" : 2.0,
|
|
"rate_func" : there_and_back
|
|
}
|
|
self.play(*[
|
|
ApplyMethod(mob.scale_in_place, 0.5*random.random(), **kwargs)
|
|
for mob in self.intervals
|
|
])
|
|
self.wait()
|
|
|
|
def add_terms(self):
|
|
self.ones = []
|
|
scale_factor = 0.6
|
|
plus = None
|
|
for count in range(1, 10):
|
|
frac_bottom = TexMobject("\\over %d"%(2**count))
|
|
frac_bottom.scale(scale_factor)
|
|
one = TexMobject("1").scale(scale_factor)
|
|
one.next_to(frac_bottom, UP, buff = 0.1)
|
|
compound = Mobject(frac_bottom, one)
|
|
if plus:
|
|
compound.next_to(plus)
|
|
else:
|
|
compound.to_edge(LEFT)
|
|
plus = TexMobject("+").scale(scale_factor)
|
|
plus.next_to(compound)
|
|
frac_bottom, one = compound.split()
|
|
self.ones.append(one)
|
|
self.add(frac_bottom, one, plus)
|
|
self.wait(0.2)
|
|
dots = TexMobject("\\dots").scale(scale_factor).next_to(plus)
|
|
arrow = Arrow(ORIGIN, RIGHT).next_to(dots)
|
|
one = TexMobject("1").next_to(arrow)
|
|
self.ones.append(one)
|
|
self.play(*[ShowCreation(mob) for mob in (dots, arrow, one)])
|
|
self.wait()
|
|
|
|
def multiply_by_epsilon(self):
|
|
self.play(*[
|
|
CounterclockwiseTransform(
|
|
one,
|
|
TexMobject("\\epsilon").replace(one)
|
|
)
|
|
for one in self.ones
|
|
])
|
|
self.wait()
|
|
|
|
def stretch_intervals(self):
|
|
for interval, count in zip(self.intervals, it.count(1)):
|
|
self.play(
|
|
ApplyMethod(interval.scale_in_place, 1.0/(count**2)),
|
|
run_time = 1.0/count
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class OurSumCanBeArbitrarilySmall(Scene):
|
|
def construct(self):
|
|
step_size = 0.01
|
|
epsilon = TexMobject("\\epsilon")
|
|
equals = TexMobject("=").next_to(epsilon)
|
|
self.add(epsilon, equals)
|
|
for num in np.arange(1, 0, -step_size):
|
|
parts = list(map(TexMobject, str(num)))
|
|
parts[0].next_to(equals)
|
|
for i in range(1, len(parts)):
|
|
parts[i].next_to(parts[i-1], buff = 0.1, aligned_edge = DOWN)
|
|
self.add(*parts)
|
|
self.wait(0.05)
|
|
self.remove(*parts)
|
|
self.wait()
|
|
self.clear()
|
|
words = TextMobject([
|
|
"Not only can the sum be $< 1$,\\\\",
|
|
"it can be \\emph{arbitrarily small} !"
|
|
]).split()
|
|
self.add(words[0])
|
|
self.wait()
|
|
self.play(ShimmerIn(words[1].set_color()))
|
|
self.wait()
|
|
|
|
class ProofDoesNotEqualIntuition(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Proof $\\ne$ Intuition"))
|
|
|
|
class StillFeelsCounterintuitive(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
ticks = self.add_fraction_ticks(run_time = 1.0)
|
|
epsilon, equals, num = list(map(TexMobject, ["\\epsilon", "=", "0.3"]))
|
|
epsilon.shift(2*UP)
|
|
equals.next_to(epsilon)
|
|
num.next_to(equals)
|
|
self.add(epsilon, equals, num)
|
|
self.cover_fractions()
|
|
self.remove(ticks)
|
|
self.add(*ticks.split())
|
|
self.zoom_in_on(np.sqrt(2)/2, 100)
|
|
self.play(ShowCreation(ticks))
|
|
self.wait()
|
|
|
|
class VisualIntuition(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Visual Intuition:"))
|
|
|
|
class SideNote(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("(Brief Sidenote)"))
|
|
|
|
class TroubleDrawingSmallInterval(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
interval, line = self.add_open_interval(0.5, 0.5)
|
|
big = Mobject(interval, line)
|
|
small_int, small_line = self.add_open_interval(0.5, 0.01)
|
|
small = Mobject(small_int, line.scale_in_place(0.01/0.5))
|
|
shrunk = deepcopy(big).scale_in_place(0.01/0.5)
|
|
self.clear()
|
|
IntervalScene.construct(self)
|
|
words = TextMobject("This tiny stretch")
|
|
words.shift(2*UP+2*LEFT)
|
|
arrow = Arrow(words, line)
|
|
|
|
for target in shrunk, small:
|
|
mob = deepcopy(big)
|
|
self.play(Transform(
|
|
mob, target,
|
|
run_time = 2.0
|
|
))
|
|
self.wait()
|
|
self.play(Transform(mob, big))
|
|
self.wait()
|
|
self.remove(mob)
|
|
self.play(Transform(big, small))
|
|
self.play(ShimmerIn(words), ShowCreation(arrow))
|
|
self.play(Transform(
|
|
line, deepcopy(line).scale(10).shift(DOWN),
|
|
run_time = 2.0,
|
|
rate_func = there_and_back
|
|
))
|
|
self.wait()
|
|
|
|
class WhatDoesItLookLikeToBeOutside(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject(
|
|
"What does it look like for a number to be outside a dense set of intervals?"
|
|
))
|
|
|
|
class ZoomInOnSqrt2Over2(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
epsilon, equals, num = list(map(TexMobject, ["\\epsilon", "=", "0.3"]))
|
|
equals.next_to(epsilon)
|
|
num.next_to(equals)
|
|
self.add(Mobject(epsilon, equals, num).center().shift(2*UP))
|
|
intervals, lines = self.cover_fractions()
|
|
self.remove(*lines)
|
|
irr = TexMobject("\\frac{\\sqrt{2}}{2}")
|
|
point = self.number_line.number_to_point(np.sqrt(2)/2)
|
|
arrow = Arrow(point+UP, point)
|
|
irr.next_to(arrow, UP)
|
|
self.play(ShimmerIn(irr), ShowCreation(arrow))
|
|
for count in range(4):
|
|
self.remove(*intervals)
|
|
self.remove(*lines)
|
|
self.zoom_in_on(np.sqrt(2)/2, 20)
|
|
for mob in irr, arrow:
|
|
mob.shift(mob.get_center()[0]*LEFT)
|
|
intervals, lines = self.cover_fractions()
|
|
|
|
class NotCoveredMeansCacophonous(Scene):
|
|
def construct(self):
|
|
statement1 = TextMobject("$\\frac{\\sqrt{2}}{2}$ is not covered")
|
|
implies = TexMobject("\\Rightarrow")
|
|
statement2 = TextMobject("Rationals which are close to $\\frac{\\sqrt{2}}{2}$ must have large denominators")
|
|
statement1.to_edge(LEFT)
|
|
implies.next_to(statement1)
|
|
statement2.next_to(implies)
|
|
implies.sort_points()
|
|
|
|
self.add(statement1)
|
|
self.wait()
|
|
self.play(ShowCreation(implies))
|
|
self.play(ShimmerIn(statement2))
|
|
self.wait()
|
|
|
|
class ShiftSetupByOne(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
new_interval = UnitInterval(
|
|
number_at_center = 1.5,
|
|
leftmost_tick = 1,
|
|
numbers_with_elongated_ticks = [1, 2],
|
|
)
|
|
new_interval.add_numbers(1, 2)
|
|
side_shift_val = self.number_line.number_to_point(1)
|
|
side_shift_val -= new_interval.number_to_point(1)
|
|
new_interval.shift(side_shift_val)
|
|
self.add(new_interval)
|
|
self.number_line.add_numbers(0)
|
|
self.remove(*self.number_mobs)
|
|
epsilon_mob = TexMobject("\\epsilon = 0.01").to_edge(UP)
|
|
self.add(epsilon_mob)
|
|
fraction_ticks = self.add_fraction_ticks()
|
|
self.remove(fraction_ticks)
|
|
intervals, lines = self.cover_fractions(
|
|
epsilon = 0.01,
|
|
num_fractions = 150,
|
|
run_time_per_interval = 0,
|
|
)
|
|
self.remove(*intervals+lines)
|
|
for interval, frac in zip(intervals, rationals()):
|
|
interval.scale_in_place(2.0/frac.denominator)
|
|
|
|
|
|
for interval in intervals[:10]:
|
|
squished = deepcopy(interval).stretch_to_fit_width(0)
|
|
self.play(Transform(squished, interval), run_time = 0.2)
|
|
self.remove(squished)
|
|
self.add(interval)
|
|
for interval in intervals[10:50]:
|
|
self.add(interval)
|
|
self.wait(0.1)
|
|
for interval in intervals[50:]:
|
|
self.add(interval)
|
|
self.wait()
|
|
mobs_shifts = [
|
|
(intervals, UP),
|
|
([self.number_line, new_interval], side_shift_val*LEFT),
|
|
(intervals, DOWN)
|
|
]
|
|
for mobs, shift_val in mobs_shifts:
|
|
self.play(*[
|
|
ApplyMethod(mob.shift, shift_val)
|
|
for mob in mobs
|
|
])
|
|
self.number_line = new_interval
|
|
self.wait()
|
|
words = TextMobject(
|
|
"Almost all the covered numbers are harmonious!",
|
|
size = "\\small"
|
|
).shift(2*UP)
|
|
self.play(ShimmerIn(words))
|
|
self.wait()
|
|
for num in [7, 5]:
|
|
point = self.number_line.number_to_point(2**(num/12.))
|
|
arrow = Arrow(point+DOWN, point)
|
|
mob = TexMobject(
|
|
"2^{\\left(\\frac{%d}{12}\\right)}"%num
|
|
).next_to(arrow, DOWN)
|
|
self.play(ShimmerIn(mob), ShowCreation(arrow))
|
|
self.wait()
|
|
self.remove(mob, arrow)
|
|
self.remove(words)
|
|
words = TextMobject(
|
|
"Cacophonous covered numbers:",
|
|
size = "\\small"
|
|
)
|
|
words.shift(2*UP)
|
|
answer1 = TextMobject("Complicated rationals,", size = "\\small")
|
|
answer2 = TextMobject(
|
|
"real numbers \\emph{very very very} close to them",
|
|
size = "\\small"
|
|
)
|
|
compound = Mobject(answer1, answer2.next_to(answer1))
|
|
compound.next_to(words, DOWN)
|
|
answer1, answer2 = compound.split()
|
|
|
|
self.add(words)
|
|
self.wait()
|
|
self.remove(*intervals)
|
|
self.add(answer1)
|
|
self.play(ShowCreation(fraction_ticks, run_time = 5.0))
|
|
self.add(answer2)
|
|
self.wait()
|
|
self.remove(words, answer1, answer2)
|
|
words = TextMobject([
|
|
"To a", "savant,", "harmonious numbers could be ",
|
|
"\\emph{precisely}", "those 1\\% covered by the intervals"
|
|
]).shift(2*UP)
|
|
words = words.split()
|
|
words[1].set_color()
|
|
words[3].set_color()
|
|
self.add(*words)
|
|
self.play(ShowCreation(
|
|
Mobject(*intervals),
|
|
run_time = 5.0
|
|
))
|
|
self.wait()
|
|
|
|
class FinalEquivalence(IntervalScene):
|
|
def construct(self):
|
|
IntervalScene.construct(self)
|
|
ticks = self.add_fraction_ticks()
|
|
intervals, lines = self.cover_fractions(
|
|
epsilon = 0.01,
|
|
num_fractions = 150,
|
|
run_time_per_interval = 0,
|
|
)
|
|
for interval, frac in zip(intervals, rationals()):
|
|
interval.scale_in_place(2.0/frac.denominator)
|
|
self.remove(*intervals+lines)
|
|
intervals = Mobject(*intervals)
|
|
arrow = TexMobject("\\Leftrightarrow")
|
|
top_words = TextMobject("Harmonious numbers are rare,")
|
|
bot_words = TextMobject("even for the savant")
|
|
bot_words.set_color().next_to(top_words, DOWN)
|
|
words = Mobject(top_words, bot_words)
|
|
words.next_to(arrow)
|
|
|
|
self.play(
|
|
ShowCreation(ticks),
|
|
Transform(
|
|
deepcopy(intervals).stretch_to_fit_height(0),
|
|
intervals
|
|
)
|
|
)
|
|
everything = Mobject(*self.mobjects)
|
|
self.clear()
|
|
self.play(Transform(
|
|
everything,
|
|
deepcopy(everything).scale(0.5).to_edge(LEFT)
|
|
))
|
|
self.add(arrow)
|
|
self.play(ShimmerIn(words))
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
command_line_create_scene(MOVIE_PREFIX)
|
|
|
|
|