mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
First draft of music and measure theory
This commit is contained in:
parent
0d60cf6207
commit
42a8e166f0
7 changed files with 739 additions and 68 deletions
|
@ -92,38 +92,44 @@ class Grid(Mobject1D):
|
|||
|
||||
class NumberLine(Mobject1D):
|
||||
DEFAULT_CONFIG = {
|
||||
"radius" : SPACE_WIDTH,
|
||||
"color" : "skyblue",
|
||||
"numerical_radius" : SPACE_WIDTH,
|
||||
"unit_length_to_spacial_width" : 1,
|
||||
"tick_size" : 0.1,
|
||||
"tick_frequency" : 0.5,
|
||||
"leftmost_tick" : -int(SPACE_WIDTH),
|
||||
"number_at_center" : 0,
|
||||
"numbers_with_elongated_ticks" : [0],
|
||||
"longer_tick_multiple" : 2,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, NumberLine, kwargs)
|
||||
numerical_radius = float(self.radius) / self.unit_length_to_spacial_width
|
||||
self.left_num = self.number_at_center - numerical_radius
|
||||
self.right_num = self.number_at_center + numerical_radius
|
||||
self.left_num = self.number_at_center - self.numerical_radius
|
||||
self.right_num = self.number_at_center + self.numerical_radius
|
||||
Mobject1D.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
spacial_radius = self.numerical_radius*self.unit_length_to_spacial_width
|
||||
self.add_points([
|
||||
(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]
|
||||
])
|
||||
self.index_of_left = np.argmin(self.points[:,0])
|
||||
self.index_of_right = np.argmax(self.points[:,0])
|
||||
spacial_tick_frequency = self.tick_frequency*self.unit_length_to_spacial_width
|
||||
self.add_points([
|
||||
(b*x, y, 0)
|
||||
for x in np.arange(0, self.radius, spacial_tick_frequency)
|
||||
(x, y, 0)
|
||||
for num in self.get_tick_numbers()
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
)
|
||||
|
||||
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:
|
||||
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:
|
||||
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(DOWN*4*self.tick_size)
|
||||
self.add(mob)
|
||||
return self
|
||||
mob.shift(self.get_vertical_number_offset())
|
||||
result.append(mob)
|
||||
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):
|
||||
DEFAULT_CONFIG = {
|
||||
"radius" : SPACE_WIDTH-1,
|
||||
"numerical_radius" : 0.5,
|
||||
"unit_length_to_spacial_width" : 2*(SPACE_WIDTH-1),
|
||||
"tick_frequency" : 0.1,
|
||||
"leftmost_tick" : 0,
|
||||
"number_at_center" : 0.5,
|
||||
"numbers_with_elongated_ticks" : [0, 1],
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ class ImageMobject(Mobject):
|
|||
"invert" : True,
|
||||
"use_cache" : True,
|
||||
"should_buffer_points" : False,
|
||||
"scale_value" : 1.0
|
||||
"scale_value" : 1.0,
|
||||
"should_center" : True
|
||||
}
|
||||
def __init__(self, image_file, **kwargs):
|
||||
digest_config(self, ImageMobject, kwargs, locals())
|
||||
|
@ -35,7 +36,8 @@ class ImageMobject(Mobject):
|
|||
if os.path.exists(path):
|
||||
self.generate_points_from_file(path)
|
||||
self.scale(self.scale_value)
|
||||
self.center()
|
||||
if self.should_center:
|
||||
self.center()
|
||||
return
|
||||
raise IOError("File not Found")
|
||||
|
||||
|
@ -135,12 +137,19 @@ def tex_mobject(expression,
|
|||
size = "\\large"
|
||||
#Todo, make this more sophisticated.
|
||||
image_files = tex_to_image(expression, size, template_tex_file)
|
||||
config = {
|
||||
"should_buffer_points" : True,
|
||||
"should_center" : False,
|
||||
}
|
||||
if isinstance(image_files, list):
|
||||
#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:
|
||||
result = ImageMobject(image_files, should_buffer_points = True)
|
||||
return result.highlight("white")
|
||||
result = ImageMobject(image_files, **config)
|
||||
return result.center().highlight("white")
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Point(Mobject):
|
|||
Mobject.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
self.add_points(self.location)
|
||||
self.add_points([self.location])
|
||||
|
||||
|
||||
class Dot(Mobject1D): #Use 1D density, even though 2D
|
||||
|
|
|
@ -10,16 +10,18 @@ from animation import *
|
|||
from mobject import *
|
||||
from constants import *
|
||||
from region import *
|
||||
from scene import Scene
|
||||
from scene import Scene, NumberLineScene
|
||||
from script_wrapper import command_line_create_scene
|
||||
|
||||
|
||||
class SampleScene(Scene):
|
||||
class SampleScene(NumberLineScene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.add(square)
|
||||
self.dither()
|
||||
self.play(Flash(square))
|
||||
NumberLineScene.construct(self)
|
||||
arrow = Arrow(2*RIGHT+UP, 2*RIGHT)
|
||||
self.add(arrow)
|
||||
self.dither(2)
|
||||
self.zoom_in_on(2.4, zoom_factor = 10)
|
||||
self.dither(2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
command_line_create_scene()
|
|
@ -3,4 +3,5 @@ from sub_scenes import *
|
|||
from arithmetic_scenes import *
|
||||
from counting_scene import *
|
||||
from pascals_triangle import *
|
||||
from scene_from_video import *
|
||||
from scene_from_video import *
|
||||
from number_line import *
|
|
@ -12,4 +12,67 @@ from helpers import *
|
|||
|
||||
class NumberLineScene(Scene):
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from animation import *
|
|||
from mobject import *
|
||||
from constants import *
|
||||
from region import *
|
||||
from scene import Scene
|
||||
from scene import Scene, NumberLineScene
|
||||
from script_wrapper import command_line_create_scene
|
||||
from inventing_math import underbrace
|
||||
|
||||
|
@ -64,15 +64,32 @@ def zero_to_one_interval():
|
|||
interval.add(tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN))
|
||||
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):
|
||||
digest_config(self, OpenInterval, kwargs, locals())
|
||||
Mobject.__init__(self, **kwargs)
|
||||
self.add(tex_mobject("(").shift(LEFT))
|
||||
self.add(tex_mobject(")").shift(RIGHT))
|
||||
scale_factor = width / 2.0
|
||||
self.stretch(scale_factor, 0)
|
||||
self.stretch(0.5+0.5*scale_factor, 1)
|
||||
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):
|
||||
|
@ -138,10 +155,12 @@ class VibratingString(Animation):
|
|||
self.mobject.shift(self.center)
|
||||
|
||||
|
||||
class IntervalScene(Scene):
|
||||
class IntervalScene(NumberLineScene):
|
||||
def construct(self):
|
||||
self.interval = UnitInterval()
|
||||
self.add(self.interval)
|
||||
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,
|
||||
|
@ -155,8 +174,8 @@ class IntervalScene(Scene):
|
|||
self.remove(frac_mob, tick)
|
||||
|
||||
def add_fraction(self, fraction, shrink = False):
|
||||
point = self.num_to_point(fraction)
|
||||
tick_rad = self.interval.tick_size*TICK_STRETCH_FACTOR
|
||||
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
|
||||
|
@ -168,27 +187,60 @@ class IntervalScene(Scene):
|
|||
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 = 9,
|
||||
num_fractions = 10,
|
||||
run_time_per_interval = 0.5):
|
||||
for fraction, count in zip(rationals(), range(num_fractions)):
|
||||
self.add_open_interval(
|
||||
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 / 2**(count+1),
|
||||
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):
|
||||
width *= self.interval.unit_length_to_spacial_width
|
||||
center_point = self.interval.number_to_point(num)
|
||||
open_interval = OpenInterval(center_point, width)
|
||||
if color:
|
||||
open_interval.highlight(color)
|
||||
interval_line = Line(open_interval.get_left(), open_interval.get_right())
|
||||
interval_line.scale_in_place(0.9)#Silliness
|
||||
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("yellow")
|
||||
interval_line.highlight(color)
|
||||
if run_time > 0:
|
||||
squished_interval = deepcopy(open_interval).stretch_to_fit_width(0)
|
||||
self.play(
|
||||
|
@ -647,55 +699,123 @@ class PowersOfTwelfthRoot(Scene):
|
|||
self.play(ShimmerIn(CompoundMobject(*mob_list), run_time = 3.0))
|
||||
|
||||
|
||||
class AllValuesBetween1And2(Scene):
|
||||
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]
|
||||
number_line = NumberLine(interval_size = 1).add_numbers()
|
||||
one, two = 2*RIGHT, 4*RIGHT
|
||||
one, two, irr = map(
|
||||
self.number_line.number_to_point,
|
||||
[1, 2, irrational]
|
||||
)
|
||||
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)
|
||||
irr_mob = tex_mobject(str(irrational)).next_to(bot_arrow, DOWN)
|
||||
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))
|
||||
for k in range(1, len(cont_frac))[:8]
|
||||
]
|
||||
approx_mobs = [fraction_mobject(a) for a in approximations]
|
||||
|
||||
self.add(number_line)
|
||||
kwargs = {
|
||||
"run_time" : 3.0,
|
||||
"alpha_func" : there_and_back
|
||||
}
|
||||
self.play(*[
|
||||
ApplyMethod(mob.shift, 2*RIGHT, **kwargs)
|
||||
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 = 2*float(approx)*RIGHT
|
||||
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.2
|
||||
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 SampleIntervalScene(IntervalScene):
|
||||
class CoveringSetsWithOpenIntervals(IntervalScene):
|
||||
def 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()
|
||||
|
||||
|
||||
|
@ -707,6 +827,446 @@ class ShowAllFractions(IntervalScene):
|
|||
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__":
|
||||
|
|
Loading…
Add table
Reference in a new issue