First draft of music and measure theory

This commit is contained in:
Grant Sanderson 2015-09-30 14:22:17 -07:00
parent 0d60cf6207
commit 42a8e166f0
7 changed files with 739 additions and 68 deletions

View file

@ -92,38 +92,44 @@ class Grid(Mobject1D):
class NumberLine(Mobject1D): class NumberLine(Mobject1D):
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"radius" : SPACE_WIDTH, "color" : "skyblue",
"numerical_radius" : SPACE_WIDTH,
"unit_length_to_spacial_width" : 1, "unit_length_to_spacial_width" : 1,
"tick_size" : 0.1, "tick_size" : 0.1,
"tick_frequency" : 0.5, "tick_frequency" : 0.5,
"leftmost_tick" : -int(SPACE_WIDTH),
"number_at_center" : 0, "number_at_center" : 0,
"numbers_with_elongated_ticks" : [0], "numbers_with_elongated_ticks" : [0],
"longer_tick_multiple" : 2, "longer_tick_multiple" : 2,
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
digest_config(self, NumberLine, kwargs) digest_config(self, NumberLine, kwargs)
numerical_radius = float(self.radius) / self.unit_length_to_spacial_width self.left_num = self.number_at_center - self.numerical_radius
self.left_num = self.number_at_center - numerical_radius self.right_num = self.number_at_center + self.numerical_radius
self.right_num = self.number_at_center + numerical_radius
Mobject1D.__init__(self, **kwargs) Mobject1D.__init__(self, **kwargs)
def generate_points(self): def generate_points(self):
spacial_radius = self.numerical_radius*self.unit_length_to_spacial_width
self.add_points([ self.add_points([
(b*x, 0, 0) (b*x, 0, 0)
for x in np.arange(0, self.radius, self.epsilon) for x in np.arange(0, spacial_radius, self.epsilon)
for b in [-1, 1] for b in [-1, 1]
]) ])
self.index_of_left = np.argmin(self.points[:,0]) self.index_of_left = np.argmin(self.points[:,0])
self.index_of_right = np.argmax(self.points[:,0]) self.index_of_right = np.argmax(self.points[:,0])
spacial_tick_frequency = self.tick_frequency*self.unit_length_to_spacial_width spacial_tick_frequency = self.tick_frequency*self.unit_length_to_spacial_width
self.add_points([ self.add_points([
(b*x, y, 0) (x, y, 0)
for x in np.arange(0, self.radius, spacial_tick_frequency) for num in self.get_tick_numbers()
for y in np.arange(-self.tick_size, self.tick_size, self.epsilon) for y in np.arange(-self.tick_size, self.tick_size, self.epsilon)
for b in ([1, -1] if x > 0 else [1]) for x in [self.number_to_point(num)[0]]
]) ])
for number in self.numbers_with_elongated_ticks: for number in self.numbers_with_elongated_ticks:
self.elongate_tick_at(number, self.longer_tick_multiple) self.elongate_tick_at(number, self.longer_tick_multiple)
self.number_of_points_without_numbers = self.get_num_points()
def get_tick_numbers(self):
return np.arange(self.leftmost_tick, self.right_num, self.tick_frequency)
def elongate_tick_at(self, number, multiple = 2): def elongate_tick_at(self, number, multiple = 2):
x = self.number_to_point(number)[0] x = self.number_to_point(number)[0]
@ -144,21 +150,51 @@ class NumberLine(Mobject1D):
float(number-self.left_num)/(self.right_num - self.left_num) float(number-self.left_num)/(self.right_num - self.left_num)
) )
def add_numbers(self, *numbers): def point_to_number(self, point):
return self.number_at_center + point[0]/self.unit_length_to_spacial_width
def default_numbers_to_display(self):
return self.get_tick_numbers()[::2]
def get_vertical_number_offset(self):
return 4*DOWN*self.tick_size
def get_number_mobjects(self, *numbers):
if len(numbers) == 0: if len(numbers) == 0:
numbers = range(int(self.left_num), int(self.right_num+1)) numbers = self.default_numbers_to_display()
log_spacing = int(np.log10(self.tick_frequency))
if log_spacing < 0:
num_decimal_places = 2-log_spacing
else:
num_decimal_places = 1+log_spacing
result = []
for number in numbers: for number in numbers:
mob = tex_mobject(str(number)).scale(0.5) if number < 0:
num_string = str(number)[:num_decimal_places+1]
else:
num_string = str(number)[:num_decimal_places]
mob = tex_mobject(num_string)
vert_scale = 2*self.tick_size/mob.get_height()
hori_scale = self.tick_frequency*self.unit_length_to_spacial_width/mob.get_width()
mob.scale(min(vert_scale, hori_scale))
mob.shift(self.number_to_point(number)) mob.shift(self.number_to_point(number))
mob.shift(DOWN*4*self.tick_size) mob.shift(self.get_vertical_number_offset())
self.add(mob) result.append(mob)
return self return result
def add_numbers(self, *numbers):
self.add(*self.get_number_mobjects(*numbers))
def remove_numbers(self):
self.points = self.points[:self.number_of_points_without_numbers]
self.rgbs = self.rgbs[:self.number_of_points_without_numbers]
class UnitInterval(NumberLine): class UnitInterval(NumberLine):
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"radius" : SPACE_WIDTH-1, "numerical_radius" : 0.5,
"unit_length_to_spacial_width" : 2*(SPACE_WIDTH-1), "unit_length_to_spacial_width" : 2*(SPACE_WIDTH-1),
"tick_frequency" : 0.1, "tick_frequency" : 0.1,
"leftmost_tick" : 0,
"number_at_center" : 0.5, "number_at_center" : 0.5,
"numbers_with_elongated_ticks" : [0, 1], "numbers_with_elongated_ticks" : [0, 1],
} }

View file

@ -16,7 +16,8 @@ class ImageMobject(Mobject):
"invert" : True, "invert" : True,
"use_cache" : True, "use_cache" : True,
"should_buffer_points" : False, "should_buffer_points" : False,
"scale_value" : 1.0 "scale_value" : 1.0,
"should_center" : True
} }
def __init__(self, image_file, **kwargs): def __init__(self, image_file, **kwargs):
digest_config(self, ImageMobject, kwargs, locals()) digest_config(self, ImageMobject, kwargs, locals())
@ -35,7 +36,8 @@ class ImageMobject(Mobject):
if os.path.exists(path): if os.path.exists(path):
self.generate_points_from_file(path) self.generate_points_from_file(path)
self.scale(self.scale_value) self.scale(self.scale_value)
self.center() if self.should_center:
self.center()
return return
raise IOError("File not Found") raise IOError("File not Found")
@ -135,12 +137,19 @@ def tex_mobject(expression,
size = "\\large" size = "\\large"
#Todo, make this more sophisticated. #Todo, make this more sophisticated.
image_files = tex_to_image(expression, size, template_tex_file) image_files = tex_to_image(expression, size, template_tex_file)
config = {
"should_buffer_points" : True,
"should_center" : False,
}
if isinstance(image_files, list): if isinstance(image_files, list):
#TODO, is checking listiness really the best here? #TODO, is checking listiness really the best here?
result = CompoundMobject(*map(ImageMobject, image_files)) result = CompoundMobject(*[
ImageMobject(f, **config)
for f in image_files
])
else: else:
result = ImageMobject(image_files, should_buffer_points = True) result = ImageMobject(image_files, **config)
return result.highlight("white") return result.center().highlight("white")

View file

@ -16,7 +16,7 @@ class Point(Mobject):
Mobject.__init__(self, **kwargs) Mobject.__init__(self, **kwargs)
def generate_points(self): def generate_points(self):
self.add_points(self.location) self.add_points([self.location])
class Dot(Mobject1D): #Use 1D density, even though 2D class Dot(Mobject1D): #Use 1D density, even though 2D

View file

@ -10,16 +10,18 @@ from animation import *
from mobject import * from mobject import *
from constants import * from constants import *
from region import * from region import *
from scene import Scene from scene import Scene, NumberLineScene
from script_wrapper import command_line_create_scene from script_wrapper import command_line_create_scene
class SampleScene(Scene): class SampleScene(NumberLineScene):
def construct(self): def construct(self):
square = Square() NumberLineScene.construct(self)
self.add(square) arrow = Arrow(2*RIGHT+UP, 2*RIGHT)
self.dither() self.add(arrow)
self.play(Flash(square)) self.dither(2)
self.zoom_in_on(2.4, zoom_factor = 10)
self.dither(2)
if __name__ == "__main__": if __name__ == "__main__":
command_line_create_scene() command_line_create_scene()

View file

@ -3,4 +3,5 @@ from sub_scenes import *
from arithmetic_scenes import * from arithmetic_scenes import *
from counting_scene import * from counting_scene import *
from pascals_triangle import * from pascals_triangle import *
from scene_from_video import * from scene_from_video import *
from number_line import *

View file

@ -12,4 +12,67 @@ from helpers import *
class NumberLineScene(Scene): class NumberLineScene(Scene):
def construct(self): def construct(self):
pass self.number_line = NumberLine()
self.displayed_numbers = self.number_line.default_numbers_to_display()
self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers)
self.add(self.number_line, *self.number_mobs)
def zoom_in_on(self, number, zoom_factor, run_time = 2.0):
unit_length_to_spacial_width = self.number_line.unit_length_to_spacial_width*zoom_factor
radius = SPACE_WIDTH/unit_length_to_spacial_width
tick_frequency = 10**(np.floor(np.log10(radius)))
left_tick = tick_frequency*(np.ceil((number-radius)/tick_frequency))
new_number_line = NumberLine(
numerical_radius = radius,
unit_length_to_spacial_width = unit_length_to_spacial_width,
tick_frequency = tick_frequency,
leftmost_tick = left_tick,
number_at_center = number
)
new_displayed_numbers = new_number_line.default_numbers_to_display()
new_number_mobs = new_number_line.get_number_mobjects(*new_displayed_numbers)
transforms = []
additional_mobjects = []
squished_new_line = deepcopy(new_number_line)
squished_new_line.scale(1.0/zoom_factor)
squished_new_line.shift(self.number_line.number_to_point(number))
squished_new_line.points[:,1] = self.number_line.number_to_point(0)[1]
transforms.append(Transform(squished_new_line, new_number_line))
for mob, num in zip(new_number_mobs, new_displayed_numbers):
point = Point(self.number_line.number_to_point(num))
point.shift(new_number_line.get_vertical_number_offset())
transforms.append(Transform(point, mob))
for mob in self.mobjects:
if mob == self.number_line:
new_mob = deepcopy(mob)
new_mob.shift(-self.number_line.number_to_point(number))
new_mob.stretch(zoom_factor, 0)
transforms.append(Transform(mob, new_mob))
continue
mob_center = mob.get_center()
number_under_center = self.number_line.point_to_number(mob_center)
new_point = new_number_line.number_to_point(number_under_center)
new_point += mob_center[1]*UP
if mob in self.number_mobs:
transforms.append(Transform(mob, Point(new_point)))
else:
transforms.append(ApplyMethod(mob.shift, new_point - mob_center))
additional_mobjects.append(mob)
line_to_hide_pixelation = Line(
self.number_line.get_left(),
self.number_line.get_right(),
color = self.number_line.get_color()
)
self.add(line_to_hide_pixelation)
self.play(*transforms, run_time = run_time)
self.clear()
self.number_line = new_number_line
self.displayed_numbers = new_displayed_numbers
self.number_mobs = new_number_mobs
self.add(self.number_line, *self.number_mobs)
self.add(*additional_mobjects)

View file

@ -10,7 +10,7 @@ from animation import *
from mobject import * from mobject import *
from constants import * from constants import *
from region import * from region import *
from scene import Scene from scene import Scene, NumberLineScene
from script_wrapper import command_line_create_scene from script_wrapper import command_line_create_scene
from inventing_math import underbrace from inventing_math import underbrace
@ -64,15 +64,32 @@ def zero_to_one_interval():
interval.add(tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN)) interval.add(tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN))
return interval return interval
class OpenInterval(Mobject): 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): def __init__(self, center_point = ORIGIN, width = 2, **kwargs):
digest_config(self, OpenInterval, kwargs, locals()) digest_config(self, OpenInterval, kwargs, locals())
Mobject.__init__(self, **kwargs) left = LeftParen().shift(LEFT*width/2)
self.add(tex_mobject("(").shift(LEFT)) right = RightParen().shift(RIGHT*width/2)
self.add(tex_mobject(")").shift(RIGHT)) CompoundMobject.__init__(self, left, right, **kwargs)
scale_factor = width / 2.0 # scale_factor = width / 2.0
self.stretch(scale_factor, 0) # self.stretch(scale_factor, 0)
self.stretch(0.5+0.5*scale_factor, 1) # self.stretch(0.5+0.5*scale_factor, 1)
self.shift(center_point) self.shift(center_point)
class Piano(ImageMobject): class Piano(ImageMobject):
@ -138,10 +155,12 @@ class VibratingString(Animation):
self.mobject.shift(self.center) self.mobject.shift(self.center)
class IntervalScene(Scene): class IntervalScene(NumberLineScene):
def construct(self): def construct(self):
self.interval = UnitInterval() self.number_line = UnitInterval()
self.add(self.interval) 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, def show_all_fractions(self,
num_fractions = 27, num_fractions = 27,
@ -155,8 +174,8 @@ class IntervalScene(Scene):
self.remove(frac_mob, tick) self.remove(frac_mob, tick)
def add_fraction(self, fraction, shrink = False): def add_fraction(self, fraction, shrink = False):
point = self.num_to_point(fraction) point = self.number_line.number_to_point(fraction)
tick_rad = self.interval.tick_size*TICK_STRETCH_FACTOR tick_rad = self.number_line.tick_size*TICK_STRETCH_FACTOR
frac_mob = fraction_mobject(fraction) frac_mob = fraction_mobject(fraction)
if shrink: if shrink:
scale_factor = 2.0/fraction.denominator scale_factor = 2.0/fraction.denominator
@ -168,27 +187,60 @@ class IntervalScene(Scene):
self.add(frac_mob, tick) self.add(frac_mob, tick)
return 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, def cover_fractions(self,
epsilon = 0.3, epsilon = 0.3,
num_fractions = 9, num_fractions = 10,
run_time_per_interval = 0.5): run_time_per_interval = 0.5):
for fraction, count in zip(rationals(), range(num_fractions)): intervals = []
self.add_open_interval( 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, fraction,
epsilon / 2**(count+1), epsilon / min(2**count, 2**30),
run_time = run_time_per_interval 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): def add_open_interval(self, num, width, color = None, run_time = 0):
width *= self.interval.unit_length_to_spacial_width spacial_width = width*self.number_line.unit_length_to_spacial_width
center_point = self.interval.number_to_point(num) center_point = self.number_line.number_to_point(num)
open_interval = OpenInterval(center_point, width) open_interval = OpenInterval(center_point, spacial_width)
if color: color = color or "yellow"
open_interval.highlight(color) interval_line = Line(
interval_line = Line(open_interval.get_left(), open_interval.get_right()) center_point+spacial_width*LEFT/2,
interval_line.scale_in_place(0.9)#Silliness center_point+spacial_width*RIGHT/2
)
interval_line.do_in_place(interval_line.sort_points, np.linalg.norm) interval_line.do_in_place(interval_line.sort_points, np.linalg.norm)
interval_line.highlight("yellow") interval_line.highlight(color)
if run_time > 0: if run_time > 0:
squished_interval = deepcopy(open_interval).stretch_to_fit_width(0) squished_interval = deepcopy(open_interval).stretch_to_fit_width(0)
self.play( self.play(
@ -647,55 +699,123 @@ class PowersOfTwelfthRoot(Scene):
self.play(ShimmerIn(CompoundMobject(*mob_list), run_time = 3.0)) self.play(ShimmerIn(CompoundMobject(*mob_list), run_time = 3.0))
class AllValuesBetween1And2(Scene): class SupposeThereIsASavant(Scene):
def construct(self): 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 irrational = 1.2020569031595942
cont_frac = [1, 4, 1, 18, 1, 1, 1, 4, 1, 9, 9, 2, 1, 1, 1, 2] cont_frac = [1, 4, 1, 18, 1, 1, 1, 4, 1, 9, 9, 2, 1, 1, 1, 2]
number_line = NumberLine(interval_size = 1).add_numbers() one, two, irr = map(
one, two = 2*RIGHT, 4*RIGHT self.number_line.number_to_point,
[1, 2, irrational]
)
top_arrow = Arrow(one+UP, one) top_arrow = Arrow(one+UP, one)
bot_arrow = Arrow(2*irrational*RIGHT+DOWN, 2*irrational*RIGHT) bot_arrow = Arrow(irr+2*DOWN, irr)
r = tex_mobject("r").next_to(top_arrow, UP) r = tex_mobject("r").next_to(top_arrow, UP)
irr_mob = tex_mobject(str(irrational)).next_to(bot_arrow, DOWN) irr_mob = tex_mobject(str(irrational)+"\\dots").next_to(bot_arrow, DOWN)
approximations = [ approximations = [
continued_fraction(cont_frac[:k]) continued_fraction(cont_frac[:k])
for k in range(1, len(cont_frac)) for k in range(1, len(cont_frac))[:8]
] ]
approx_mobs = [fraction_mobject(a) for a in approximations]
self.add(number_line)
kwargs = { kwargs = {
"run_time" : 3.0, "run_time" : 3.0,
"alpha_func" : there_and_back "alpha_func" : there_and_back
} }
self.play(*[ self.play(*[
ApplyMethod(mob.shift, 2*RIGHT, **kwargs) ApplyMethod(mob.shift, RIGHT, **kwargs)
for mob in r, top_arrow for mob in r, top_arrow
]) ])
self.dither()
self.remove(r, top_arrow) self.remove(r, top_arrow)
self.play( self.play(
ShimmerIn(irr_mob), ShimmerIn(irr_mob),
ShowCreation(bot_arrow) ShowCreation(bot_arrow)
) )
self.dither()
self.add(irr_mob, bot_arrow)
frac_mob = Point(top_arrow.get_top()) frac_mob = Point(top_arrow.get_top())
max_num_zooms = 4
num_zooms = 0
for approx in approximations: for approx in approximations:
point = 2*float(approx)*RIGHT point = self.number_line.number_to_point(approx)
new_arrow = Arrow(point+UP, point) new_arrow = Arrow(point+UP, point)
mob = fraction_mobject(approx).next_to(new_arrow, UP) mob = fraction_mobject(approx).next_to(new_arrow, UP)
self.play( self.play(
Transform(top_arrow, new_arrow), Transform(top_arrow, new_arrow),
Transform(frac_mob, mob), Transform(frac_mob, mob),
run_time = 0.2 run_time = 0.5
) )
self.dither(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):
class SampleIntervalScene(IntervalScene):
def construct(self): def construct(self):
IntervalScene.construct(self) IntervalScene.construct(self)
self.cover_fractions() 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() self.dither()
@ -707,6 +827,446 @@ class ShowAllFractions(IntervalScene):
remove_as_you_go = False, remove_as_you_go = False,
pause_time = 0.3 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__": if __name__ == "__main__":