3b1b-manim/scripts/music_and_measure.py
2015-09-30 14:22:17 -07:00

1275 lines
45 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 animation import *
from mobject import *
from constants import *
from region import *
from scene import Scene, NumberLineScene
from script_wrapper import command_line_create_scene
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 tex_mobject("\\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(tex_mobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN))
interval.add(tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN))
return interval
class LeftParen(Mobject):
def generate_points(self):
self.add(tex_mobject("("))
self.center()
def get_center(self):
return Mobject.get_center(self) + 0.04*LEFT
class RightParen(Mobject):
def generate_points(self):
self.add(tex_mobject(")"))
self.center()
def get_center(self):
return Mobject.get_center(self) + 0.04*RIGHT
class OpenInterval(CompoundMobject):
def __init__(self, center_point = ORIGIN, width = 2, **kwargs):
digest_config(self, OpenInterval, kwargs, locals())
left = LeftParen().shift(LEFT*width/2)
right = RightParen().shift(RIGHT*width/2)
CompoundMobject.__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):
DEFAULT_CONFIG = {
"should_buffer_points" : False,
"invert" : False,
"scale_value" : 0.5
}
def __init__(self, **kwargs):
digest_config(self, Piano, 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")
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 VibratingString(Animation):
DEFAULT_CONFIG = {
"num_periods" : 1,
"overtones" : 4,
"amplitude" : 0.5,
"radius" : INTERVAL_RADIUS,
"center" : ORIGIN,
"color" : "white",
"run_time" : 3.0,
"alpha_func" : None
}
def __init__(self, **kwargs):
digest_config(self, VibratingString, 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 update_mobject(self, alpha):
self.mobject.init_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(), range(num_fractions)):
frac_mob, tick = self.add_fraction(fraction, shrink)
self.dither(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.highlight("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(), 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.highlight("yellow")
all_ticks.append(tick)
all_ticks = CompoundMobject(*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 = all_rationals.next()
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):
spacial_width = width*self.number_line.unit_length_to_spacial_width
center_point = self.number_line.number_to_point(num)
open_interval = OpenInterval(center_point, spacial_width)
color = color or "yellow"
interval_line = Line(
center_point+spacial_width*LEFT/2,
center_point+spacial_width*RIGHT/2
)
interval_line.do_in_place(interval_line.sort_points, np.linalg.norm)
interval_line.highlight(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 = text_mobject("Two Challenges", size = "\\Huge").to_edge(UP)
one, two = map(text_mobject, ["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.highlight("blue")
measure = text_mobject("Measure Theory").next_to(two)
probability = text_mobject("Probability")
probability.next_to(measure).shift(DOWN+RIGHT)
integration = tex_mobject("\\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.highlight("yellow")
self.add(two_challenges)
self.dither()
self.add(one, notes)
self.dither()
self.add(two, measure)
self.dither()
self.play(ShowCreation(arrow_to_int))
self.add(integration)
self.dither()
self.play(ShowCreation(arrow_to_prob))
self.add(probability)
self.dither()
class MeasureTheoryToHarmony(IntervalScene):
def construct(self):
IntervalScene.construct(self)
self.cover_fractions()
self.dither()
all_mobs = CompoundMobject(*self.mobjects)
all_mobs.sort_points()
self.clear()
radius = self.interval.radius
line = Line(radius*LEFT, radius*RIGHT).highlight("white")
self.play(DelayByOrder(Transform(all_mobs, line)))
self.clear()
self.play(VibratingString(alpha_func = smooth))
self.clear()
self.add(line)
self.dither()
class ChallengeOne(Scene):
def construct(self):
title = text_mobject("Challenge #1").to_edge(UP)
start_color = Color("blue")
colors = start_color.range_to("white", 6)
self.bottom_vibration = VibratingString(
num_periods = 1, run_time = 3.0,
center = DOWN, color = start_color
)
top_vibrations = [
VibratingString(
num_periods = freq, run_time = 3.0,
center = 2*UP, color = colors.next()
)
for freq in [1, 2, 5.0/3, 4.0/3, 2]
]
freq_220 = text_mobject("220 Hz")
freq_r220 = text_mobject("$r\\times$220 Hz")
freq_330 = text_mobject("1.5$\\times$220 Hz")
freq_sqrt2 = text_mobject("$\\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 = tex_mobject("(1<r<2)", size = "\\large")
self.add(title)
self.dither()
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 = tex_mobject("r").next_to(arrow1, UP)
r2 = tex_mobject("r").next_to(arrow2, UP)
kwargs = {
"run_time" : 5.0,
"alpha_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 QuestionAndAnswer(Scene):
def construct(self):
Q = text_mobject("Q:").shift(UP).to_edge(LEFT)
A = text_mobject("A:").shift(DOWN).to_edge(LEFT)
string1 = VibratingString(center = 3*UP, color = "blue")
string2 = VibratingString(num_periods = 2, center = 3.5*UP, color = "green")
twotwenty = tex_mobject("220").scale(0.25).next_to(string1.mobject, LEFT)
r220 = tex_mobject("r\\times220").scale(0.25).next_to(string2.mobject, LEFT)
question = text_mobject(
"For what values of $r$ will the frequencies 220~Hz and \
$r\\times$220~Hz sound nice together?"
).next_to(Q)
answer = text_mobject([
"When $r$ is",
"sufficiently close to",
"a rational number"
], size = "\\small").scale(1.5)
answer.next_to(A)
correction1 = text_mobject(
"with sufficiently low denominator",
size = "\\small"
).scale(1.5)
correction1.highlight("yellow")
correction1.next_to(A).shift(2*answer.get_height()*DOWN)
answer = answer.split()
answer[1].highlight("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"),
]
@staticmethod
def args_to_string(fraction, color):
return str(fraction).replace("/", "_to_")
def construct(self, fraction, color):
string1 = VibratingString(
num_periods = 1, run_time = 5.0,
center = DOWN, color = "blue"
)
string2 = VibratingString(
num_periods = fraction, run_time = 5.0,
center = 2*UP, color = color
)
self.add(fraction_mobject(fraction).shift(0.5*UP))
self.play(string1, string2)
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 update_mobject(self, alpha):
for mob in self.close_mobjects:
if np.min(np.abs(mob.points[:,0] - self.x_val)) < 0.1:
self.mobject.highlight()
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(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
big_line.highlight("white").shift(2*LEFT)
line_template = Line(UP, DOWN)
line_template.shift(2*UP+2*LEFT)
setup_width = 2*SPACE_WIDTH
num_top_lines = int(setup_width)
num_bot_lines = int(setup_width*num1/num2)
top_lines = CompoundMobject(*[
deepcopy(line_template).shift(k*(float(num1)/num2)*RIGHT)
for k in range(num_top_lines)
])
line_template.shift(4*DOWN)
bottom_lines = CompoundMobject(*[
deepcopy(line_template).shift(k*RIGHT)
for k in range(num_bot_lines)
])
bottom_lines.highlight("blue")
top_lines.highlight(color)
kwargs = {
"run_time" : 10,
"alpha_func" : None
}
self.add(big_line)
self.add(tex_mobject("%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 = tex_mobject("%d \\over"%num)
bottom = tex_mobject(str(den)).next_to(top, DOWN, buff = 0.3)
fractions.append(CompoundMobject(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 = text_mobject("Simple").next_to(arrow1, UP)
simple.highlight("green")
complicated = text_mobject("Complicated").next_to(arrow2, UP)
complicated.highlight("red")
indicates = text_mobject("Indicates complexity").shift(2*DOWN)
arrow3 = Arrow(indicates.get_top(), frac0[1])
arrow4 = Arrow(indicates.get_top(), frac1[1])
self.add(*frac0 + frac1)
self.dither()
self.add(simple, complicated)
self.play(*[
ShowCreation(arrow)
for arrow in arrow1, arrow2
])
self.dither()
self.play(*[
DelayByOrder(ApplyMethod(frac[1].highlight, "yellow"))
for frac in frac0, frac1
])
self.play(
FadeIn(indicates),
ShowCreation(arrow3),
ShowCreation(arrow4)
)
self.dither()
class IrrationalGang(Scene):
def construct(self):
randy = Randolph()
randy.mouth.highlight(randy.DEFAULT_COLOR)
randy.sync_parts()
sqrt13 = tex_mobject("\\sqrt{13}").shift(2*LEFT)
sqrt13.highlight("green")
zeta3 = tex_mobject("\\zeta(3)").shift(2*RIGHT)
zeta3.highlight("grey")
eyes = CompoundMobject(*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 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 = CompoundMobject(*[
deepcopy(semicircle).shift(jump*k*RIGHT)
for k in range(23)
])
semicircles.highlight("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 = tex_mobject("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).highlight("grey")
self.keys = piano.split()
self.semicircles = semicircles.split()
self.add(piano)
self.dither()
self.play(ShowCreation(first_jump))
self.play(
ShowCreation(line),
FadeIn(twelfth_root)
)
self.dither()
self.play(ShowCreation(semicircles, alpha_func = None))
self.dither()
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.highlight("yellow")
ratio = tex_mobject("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 = tex_mobject(
["\\left(2^{\\left(\\frac{1}{12}\\right)}\\right)"]*half_steps,
size = "\\small"
).next_to(self.piano, UP, buff = 1.0)
approximate_form = tex_mobject("\\approx"+str(2**(float(half_steps)/12)))
approximate_form.scale(0.75)
approximate_form.next_to(ratio)
self.play(ApplyMethod(low.highlight, colors[0]))
self.play(
ApplyMethod(high.highlight, colors[interval]),
Transform(Point(u_brace.get_left()), u_brace),
)
terms = product.split()
for term, semicircle in zip(terms, semicircles):
self.add(term, semicircle)
self.dither(0.25)
self.dither()
product.sort_points(lambda p : p[1])
self.play(DelayByOrder(Transform(product, ratio)))
self.dither()
self.play(ShimmerIn(approximate_form))
self.dither()
class PowersOfTwelfthRoot(Scene):
def construct(self):
max_height = SPACE_HEIGHT-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 = tex_mobject("\\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 = tex_mobject("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 = tex_mobject(str(value)[:10])
approx_copy = deepcopy(approx).next_to(term)
approx_form.scale(0.5).next_to(approx_copy)
words = text_mobject("is close to")
words.scale(approx_form.get_height()/words.get_height())
words.next_to(approx_form)
frac = fraction_map[i]
frac_mob = tex_mobject("%d/%d"%(frac.numerator, frac.denominator))
frac_mob.scale(0.5).next_to(words)
percent_error = abs(100*((value - frac) / frac))
error_string = text_mobject([
"with", str(percent_error)[:4] + "\\%", "error"
])
error_string = error_string.split()
error_string[1].highlight()
error_string = CompoundMobject(*error_string)
error_string.scale(approx_form.get_height()/error_string.get_height())
error_string.next_to(frac_mob)
mob_list.append(CompoundMobject(*[
term, approx_copy, approx_form, words, frac_mob, error_string
]))
self.play(ShimmerIn(CompoundMobject(*mob_list), run_time = 3.0))
class SupposeThereIsASavant(Scene):
def construct(self):
words = "Suppose there is a musical savant " + \
"who find pleasure in all pairs of " + \
"notes whose frequencies have a rational ratio"
words = text_mobject(words.split(" ")).split()
words[4].highlight()
words[5].highlight()
for word in words:
self.add(word)
self.dither(0.2)
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 = map(
self.number_line.number_to_point,
[1, 2, irrational]
)
top_arrow = Arrow(one+UP, one)
bot_arrow = Arrow(irr+2*DOWN, irr)
r = tex_mobject("r").next_to(top_arrow, UP)
irr_mob = tex_mobject(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,
"alpha_func" : there_and_back
}
self.play(*[
ApplyMethod(mob.shift, RIGHT, **kwargs)
for mob in r, top_arrow
])
self.dither()
self.remove(r, top_arrow)
self.play(
ShimmerIn(irr_mob),
ShowCreation(bot_arrow)
)
self.dither()
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.dither(0.5)
points = map(self.number_line.number_to_point, [approx, irrational])
distance = np.linalg.norm(points[1]-points[0])
if distance < 0.3*SPACE_WIDTH and num_zooms < max_num_zooms:
num_zooms += 1
new_distance = 0.75*SPACE_WIDTH
self.zoom_in_on(irrational, new_distance/distance)
for mob in irr_mob, bot_arrow:
mob.shift(mob.get_center()[0]*LEFT)
self.dither(0.5)
class CoveringSetsWithOpenIntervals(IntervalScene):
def construct(self):
IntervalScene.construct(self)
dots = CompoundMobject(*[
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 = [
text_mobject(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.dither()
for center in 0.225, 0.475, 0.625:
self.add_open_interval(center, 0.1, run_time = 1.0)
self.dither()
for x in range(2*len(theorems)):
self.play(*[
ApplyMethod(th.shift, UP, alpha_func = None)
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.05, run_time = 1.0)
left, right = open_interval.get_left(), open_interval.get_right()
a, less_than1, x, less_than2, b = \
tex_mobject(["a", "<", "x", "<", "b"]).shift(UP).split()
left_arrow = Arrow(a, left)
right_arrow = Arrow(b, right)
self.play(*[ShimmerIn(mob) for mob in a, less_than1, x])
self.play(ShowCreation(left_arrow))
self.dither()
self.play(*[ShimmerIn(mob) for mob in less_than2, b])
self.play(ShowCreation(right_arrow))
self.dither()
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.dither()
self.play(*[
CounterclockwiseTransform(mob, Point(mob.get_center()))
for mob in self.mobjects
if isinstance(mob, ImageMobject)
])
self.dither()
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,
"alpha_func" : rush_into
}
elif count == 10:
kwargs = {
"run_time" : 1.0,
"alpha_func" : rush_from
}
else:
kwargs = {
"run_time" : 0.25,
"alpha_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.dither()
class CoverFractionsWithWholeInterval(IntervalScene):
def construct(self):
IntervalScene.construct(self)
self.add_fraction_ticks()
self.dither()
self.add_open_interval(0.5, 1, color = "red", run_time = 2.0)
self.dither()
class SumOfIntervalsMustBeLessThan1(IntervalScene):
def construct(self):
IntervalScene.construct(self)
self.add_fraction_ticks()
anims = []
last_plus = Point((SPACE_WIDTH-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 = tex_mobject("+").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 = tex_mobject("<1").scale(0.5)
less_than1.next_to(int_copy)
dots = tex_mobject("\\dots").replace(int_copy)
words = text_mobject("Use infinitely many intervals")
words.shift(UP)
self.dither()
self.play(*anims)
self.play(ShimmerIn(less_than1))
self.dither()
self.play(Transform(anims[-1].mobject, dots))
self.play(ShimmerIn(words))
self.dither()
class RationalsAreDense(IntervalScene):
def construct(self):
IntervalScene.construct(self)
words = text_mobject(["Rationals are", "\\emph{dense}", "in the reals"])
words.shift(2*UP)
words = words.split()
words[1].highlight()
self.add(words[0])
self.play(ShimmerIn(words[1]))
self.add(words[2])
self.dither()
ticks = self.add_fraction_ticks(run_time = 5.0)
self.dither()
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.dither()
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.dither()
class HowCanYouNotCoverEntireInterval(IntervalScene):
def construct(self):
IntervalScene.construct(self)
words = text_mobject("""
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!
""")
words.scale(0.5).to_corner(UP+RIGHT)
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.highlight("red")
# dot = Dot().shift(left).highlight("red")
# point = Point(left).highlight("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.dither()
self.remove(ticks)
self.play(*[
Transform(tick, full_line)
for tick in ticks.split()
])
# self.play(DelayByOrder(FadeToColor(full_line, "red")))
self.play(ShimmerIn(words))
self.dither()
class StepsToSolution(IntervalScene):
def construct(self):
IntervalScene.construct(self)
self.spacing = 0.7
steps = map(text_mobject, [
"Enumerate all rationals in (0, 1)",
"Assign one interval to each rational",
"Choose sum of the form $\\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, count in zip(steps, it.count()):
self.add(step)
self.dither()
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(), range(1,28)):
mob, tick = self.add_fraction(frac, shrink = True)
self.dither(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((-SPACE_WIDTH+self.spacing*count)*RIGHT)
comma = text_mobject(",").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((-SPACE_WIDTH+self.spacing*count)*RIGHT)
new_ticks.append(tick_copy)
new_ticks = CompoundMobject(*new_ticks)
anims.append(DelayByOrder(Transform(ticks, new_ticks)))
self.dither()
self.play(*anims)
self.dither()
for denom in range(2, 10):
for mob in denom_to_mobs[denom]:
mob.highlight("green")
self.dither()
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/2)
squished = deepcopy(interval).stretch_to_fit_width(0)
anims.append(Transform(squished, interval))
self.play(*anims)
self.dither()
self.intervals = [a.mobject for a in anims]
kwargs = {
"run_time" : 2.0,
"alpha_func" : there_and_back
}
self.play(*[
ApplyMethod(mob.scale_in_place, 0.5*random.random(), **kwargs)
for mob in self.intervals
])
self.dither()
def add_terms(self):
self.ones = []
scale_val = 0.3
for count in range(1, 10):
frac_bottom = tex_mobject("\\over %d"%(2**count))
frac_bottom.scale(scale_val)
frac_bottom.shift(0.5*UP + (-SPACE_WIDTH+self.spacing*count)*RIGHT)
one = tex_mobject("1").scale(scale_val)
one.next_to(frac_bottom, UP, buff = 0.1)
self.ones.append(one)
plus = tex_mobject("+").scale(scale_val)
plus.next_to(frac_bottom, buff = self.spacing/4)
self.add(frac_bottom, one, plus)
self.dither(0.2)
dots = tex_mobject("\\dots").scale(scale_val).next_to(plus)
arrow = Arrow(ORIGIN, RIGHT).next_to(dots)
one = tex_mobject("1").next_to(arrow)
self.ones.append(one)
self.play(*[ShowCreation(mob) for mob in dots, arrow, one])
self.dither()
def multiply_by_epsilon(self):
self.play(*[
CounterclockwiseTransform(
one,
tex_mobject("\\epsilon").replace(one)
)
for one in self.ones
])
self.dither()
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.dither()
class OurSumCanBeArbitrarilySmall(Scene):
def construct(self):
step_size = 0.01
epsilon = tex_mobject("\\epsilon")
equals = tex_mobject("=").next_to(epsilon)
self.add(epsilon, equals)
for num in np.arange(1, 0, -step_size):
parts = map(tex_mobject, 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.dither(0.05)
self.remove(*parts)
self.dither()
self.clear()
words = text_mobject([
"Not only can the sum be $< 1$,\\\\",
"it can be \\emph{arbitrarily small} !"
]).split()
self.add(words[0])
self.dither()
self.play(ShimmerIn(words[1].highlight()))
self.dither()
class StillFeelsCounterintuitive(IntervalScene):
def construct(self):
IntervalScene.construct(self)
ticks = self.add_fraction_ticks(run_time = 1.0)
epsilon, equals, num = map(tex_mobject, ["\\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.dither()
class ZoomInOnSqrt2Over2(IntervalScene):
def construct(self):
IntervalScene.construct(self)
epsilon, equals, num = map(tex_mobject, ["\\epsilon", "=", "0.3"])
equals.next_to(epsilon)
num.next_to(equals)
self.add(CompoundMobject(epsilon, equals, num).center().shift(2*UP))
intervals, lines = self.cover_fractions()
self.remove(*lines)
irr = tex_mobject("\\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 = text_mobject("$\\frac{\\sqrt{2}}{2}$ is not covered")
implies = tex_mobject("\\Rightarrow")
statement2 = text_mobject("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.dither()
self.play(ShowCreation(implies))
self.play(ShimmerIn(statement2))
self.dither()
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 = tex_mobject("\\epsilon = 0.01").to_edge(UP)
self.add(epsilon_mob)
fraction_ticks = self.add_fraction_ticks()
self.remove(fraction_ticks)
all_intervals, all_lines = self.cover_fractions(
epsilon = 0.01,
num_fractions = 150,
run_time_per_interval = 0,
)
self.remove(*all_intervals+all_lines)
intervals, lines = self.cover_fractions(epsilon = 0.01)
self.dither()
mobs_shifts = [
(intervals+lines, UP),
([self.number_line, new_interval], side_shift_val*LEFT),
(intervals+lines, 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.dither()
words = text_mobject(
"Almost all the covered numbers are harmonious!",
size = "\\small"
).shift(2*UP)
self.play(ShimmerIn(words))
self.dither()
for num in [7, 5]:
point = self.number_line.number_to_point(2**(num/12.))
arrow = Arrow(point+DOWN, point)
mob = tex_mobject(
"2^{\\left(\\frac{%d}{12}\\right)}"%num
).next_to(arrow, DOWN)
self.play(ShimmerIn(mob), ShowCreation(arrow))
self.dither()
self.remove(mob, arrow)
self.remove(words)
words = text_mobject(
"Cacophonous covered numbers:",
size = "\\small"
)
words.shift(2*UP)
answer1 = text_mobject("Complicated rationals,", size = "\\small")
answer2 = text_mobject(
"real numbers \\emph{very very very} close to them",
size = "\\small"
)
compound = CompoundMobject(answer1, answer2.next_to(answer1))
compound.next_to(words, DOWN)
answer1, answer2 = compound.split()
self.add(words)
self.dither()
self.remove(*intervals+lines)
self.add(answer1)
self.play(ShowCreation(fraction_ticks, run_time = 5.0))
self.add(answer2)
self.dither()
self.remove(words, answer1, answer2)
words = text_mobject([
"To a", "savant,", "harmonious numbers could be ",
"\\emph{precisely}", "those 1\\% covered by the intervals"
]).shift(2*UP)
words = words.split()
words[1].highlight()
words[3].highlight()
self.add(*words)
for interval, frac in zip(all_intervals, rationals()):
interval.scale_in_place(2.0/frac.denominator)
self.add(interval)
self.dither(0.1)
self.dither()
if __name__ == "__main__":
command_line_create_scene(MOVIE_PREFIX)