Messy pile of brachistochrone work

This commit is contained in:
Grant Sanderson 2016-02-21 15:46:12 -08:00
parent 9262c395ce
commit 19e3c7f849
6 changed files with 627 additions and 424 deletions

View file

@ -153,29 +153,6 @@ class PathSlidingScene(Scene):
"gravity" : 3,
"delta_t" : 0.05
}
def get_time_slices(self, points):
dt_list = np.zeros(len(points))
ds_list = np.apply_along_axis(
np.linalg.norm,
1,
points[1:]-points[:-1]
)
delta_y_list = np.abs(points[0, 1] - points[1:,1])
delta_y_list += 0.001*(delta_y_list == 0)
v_list = self.gravity*np.sqrt(delta_y_list)
dt_list[1:] = ds_list / v_list
return np.cumsum(dt_list)
def adjust_mobject_to_index(self, mobject, index, points):
point_a, point_b = points[index-1], points[index]
while np.all(point_a == point_b):
index += 1
point_b = points[index]
theta = angle_of_vector(point_b - point_a)
mobject.rotate(theta)
mobject.shift(points[index])
return mobject
def slide(self, mobject, path, roll = False):
points = path.points
time_slices = self.get_time_slices(points)
@ -208,6 +185,29 @@ class PathSlidingScene(Scene):
self.add(self.slider)
self.dither()
def get_time_slices(self, points):
dt_list = np.zeros(len(points))
ds_list = np.apply_along_axis(
np.linalg.norm,
1,
points[1:]-points[:-1]
)
delta_y_list = np.abs(points[0, 1] - points[1:,1])
delta_y_list += 0.001*(delta_y_list == 0)
v_list = self.gravity*np.sqrt(delta_y_list)
dt_list[1:] = ds_list / v_list
return np.cumsum(dt_list)
def adjust_mobject_to_index(self, mobject, index, points):
point_a, point_b = points[index-1], points[index]
while np.all(point_a == point_b):
index += 1
point_b = points[index]
theta = angle_of_vector(point_b - point_a)
mobject.rotate(theta)
mobject.shift(points[index])
return mobject
def write_time(self, time):
if hasattr(self, "time_mob"):
self.remove(self.time_mob)
@ -223,6 +223,16 @@ class PathSlidingScene(Scene):
theta = arc_length / radius
mobject.rotate_in_place(-theta)
def add_cycloid_end_points(self):
cycloid = Cycloid()
point_a = Dot(cycloid.points[0])
point_b = Dot(cycloid.points[-1])
A = TexMobject("A").next_to(point_a, LEFT)
B = TexMobject("B").next_to(point_b, RIGHT)
self.add(point_a, point_b, A, B)
digest_locals(self)
class TryManyPaths(PathSlidingScene):
def construct(self):
randy = Randolph()
@ -271,17 +281,17 @@ class TryManyPaths(PathSlidingScene):
Line(DOWN, DOWN+3*RIGHT)
).ingest_sub_mobjects().highlight(GREEN)
paths = [
Line(7*LEFT, 7*RIGHT, color = RED_D),
LoopTheLoop(),
FunctionGraph(
lambda x : 0.05*(x**2)+0.1*np.sin(2*x)
),
Arc(
angle = np.pi/2,
radius = 3,
start_angle = 4
),
LoopTheLoop(),
Line(7*LEFT, 7*RIGHT, color = RED_D),
sharp_corner,
FunctionGraph(
lambda x : 0.05*(x**2)+0.1*np.sin(2*x)
),
FunctionGraph(
lambda x : x**2,
x_min = -3,
@ -294,40 +304,60 @@ class TryManyPaths(PathSlidingScene):
return paths + [cycloid]
def align_paths(self, paths, target_path):
def path_displacement(path):
return path.points[-1]-path.points[0]
target = path_displacement(target_path)
start = target_path.points[0]
end = target_path.point[-1]
for path in paths:
vect = path_displacement(path)
path.scale(np.linalg.norm(target)/np.linalg.norm(vect))
path.rotate(
angle_of_vector(target) - \
angle_of_vector(vect)
)
path.shift(target_path.points[0]-path.points[0])
path.position_endpoints_on(start, end)
class RollingRandolph(PathSlidingScene):
def construct(self):
cycloid = Cycloid()
point_a = Dot(cycloid.points[0])
point_b = Dot(cycloid.points[-1])
A = TexMobject("A").next_to(point_a, LEFT)
B = TexMobject("B").next_to(point_b, RIGHT)
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
self.add_cycloid_end_points()
self.slide(randy, self.cycloid, roll = True)
self.add(point_a, point_b, A, B, cycloid)
self.slide(randy, cycloid, roll = True)
class NotTheCircle(PathSlidingScene):
def construct(self):
self.add_cycloid_end_points()
start = self.point_a.get_center()
end = self.point_b.get_center()
angle = 2*np.pi/3
path = Arc(angle, radius = 3)
path.gradient_highlight(RED_D, WHITE)
radius = Line(ORIGIN, path.points[0])
randy = Randolph()
randy.scale(RANDY_SCALE_VAL)
randy.shift(-randy.get_bottom())
randy_copy = randy.copy()
words = TextMobject("Circular paths are good, \\\\ but still not the best")
words.shift(UP)
self.play(
ShowCreation(path),
ApplyMethod(
radius.rotate,
angle,
path_func = path_along_arc(angle)
)
)
self.play(FadeOut(radius))
self.play(
ApplyMethod(
path.position_endpoints_on, start, end,
path_func = path_along_arc(-angle)
),
run_time = 3
)
self.adjust_mobject_to_index(randy_copy, 1, path.points)
self.play(FadeIn(randy_copy))
self.remove(randy_copy)
self.slide(randy, path)
self.play(ShimmerIn(words))
self.dither()

View file

@ -1,5 +1,3 @@
#!/usr/bin/env python
import numpy as np
import itertools as it
import operator as op
@ -17,16 +15,17 @@ from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import \
MobjectFromRegion, ImageMobject, MobjectFromPixelArray
from mobject.tex_mobject import TextMobject, TexMobject
from animation.transform import \
Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\
FadeIn, FadeOut, GrowFromCenter
FadeIn, FadeOut, GrowFromCenter, ShimmerIn, ApplyMethod
from animation.simple_animations import \
ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder
from animation.playground import TurnInsideOut, Vibrate
from topics.geometry import \
Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point
from topics.characters import Randolph, Mathematician
from topics.characters import Randolph, Mathematician, ThoughtBubble
from topics.functions import ParametricFunction
from topics.number_line import NumberPlane
from region import Region, region_from_polygon_vertices
@ -134,7 +133,7 @@ class TracePicture(Scene):
("Steven_Strogatz",),
("Pierre_de_Fermat",),
("Galileo_Galilei",),
("Jakob_Bernoulli",),
("Jacob_Bernoulli",),
("Johann_Bernoulli2",),
]
@ -191,16 +190,110 @@ class TracePicture(Scene):
class JohannThinksHeIsBetter(Scene):
def construct(self):
names = [
"Johann_Bernoulli2",
"Jacob_Bernoulli",
"Gottfried_Wilhelm_von_Leibniz",
]
guys = [
ImageMobject(name, invert = False)
for name in names
]
johann = guys[0]
johann.scale(0.8)
pensive_johann = johann.copy()
pensive_johann.scale(0.25)
pensive_johann.to_corner(DOWN+LEFT)
comparitive_johann = johann.copy()
template = Square(side_length = 2)
comparitive_johann.replace(template)
comparitive_johann.shift(UP+LEFT)
greater_than = TexMobject(">")
greater_than.next_to(comparitive_johann)
for guy, name in zip(guys, names)[1:]:
guy.replace(template)
guy.next_to(greater_than)
name_mob = TextMobject(name.replace("_", " "))
name_mob.scale(0.5)
name_mob.next_to(guy, DOWN)
guy.name_mob = name_mob
guy.sort_points(lambda p : np.dot(p, DOWN+RIGHT))
bubble = ThoughtBubble(initial_width = 12)
bubble.stretch_to_fit_height(6)
bubble.ingest_sub_mobjects()
bubble.pin_to(pensive_johann)
bubble.shift(DOWN)
point = Point(johann.get_corner(UP+RIGHT))
self.add(johann)
self.dither()
self.play(
Transform(johann, pensive_johann),
Transform(point, bubble),
run_time = 2
)
self.remove(point)
self.add(bubble)
weakling = guys[1]
self.play(
FadeIn(comparitive_johann),
ShowCreation(greater_than),
FadeIn(weakling)
)
self.dither(2)
for guy in guys[2:]:
self.play(
DelayByOrder(Transform(weakling, guy)),
ShimmerIn(guy.name_mob)
)
self.dither(3)
self.remove(guy.name_mob)
class NewtonVsJohann(Scene):
def construct(self):
newton, johann = [
ImageMobject(name, invert = False).scale(0.5)
for name in "Newton", "Johann_Bernoulli2"
]
greater_than = TexMobject(">")
newton.next_to(greater_than, RIGHT)
johann.next_to(greater_than, LEFT)
self.add(johann, greater_than, newton)
for i in range(2):
kwargs = {
"path_func" : counterclockwise_path(),
"run_time" : 2
}
self.play(
ApplyMethod(newton.replace, johann, **kwargs),
ApplyMethod(johann.replace, newton, **kwargs),
)
self.dither()
class JohannThinksOfFermat(Scene):
def construct(self):
johann, fermat = [
ImageMobject(name, invert = False)
for name in "Johann_Bernoulli2", "Pierre_de_Fermat"
]
johann.scale(0.2)
johann.to_corner(DOWN+LEFT)
bubble = ThoughtBubble(initial_width = 12)
bubble.stretch_to_fit_height(6)
bubble.pin_to(johann)
bubble.shift(DOWN)
bubble.add_content(fermat)
fermat.scale_in_place(0.4)
self.add(johann, bubble)
self.dither()
self.play(FadeIn(fermat))
self.dither()

View file

@ -4,7 +4,7 @@ import itertools as it
from helpers import *
from mobject.tex_mobject import TexMobject, TextMobject, Brace
from mobject import Mobject
from mobject import Mobject, Mobject1D
from mobject.image_mobject import \
MobjectFromRegion, ImageMobject, MobjectFromPixelArray
from topics.three_dimensions import Stars
@ -237,7 +237,7 @@ class ShowMultiplePathsThroughLens(ShowMultiplePathsScene):
self.end_point = 2*RIGHT
def get_paths(self):
alphas = [0.2, 0.45, 0.55, 0.8]
alphas = [0.25, 0.4, 0.58, 0.75]
lower_right, upper_right, upper_left, lower_left = map(
self.lens.point_from_proportion, alphas
)
@ -306,6 +306,219 @@ class ShowMultiplePathsInGlass(ShowMultiplePathsScene):
]
class MultilayeredGlass(PhotonScene):
DEFAULT_CONFIG = {
"num_discrete_layers" : 5,
"num_variables" : 3,
"top_color" : BLUE_E,
"bottom_color" : BLUE_A,
}
def construct(self):
self.cycloid = Cycloid(end_theta = np.pi)
self.top = self.cycloid.get_top()[1]
self.bottom = self.cycloid.get_bottom()[1]-1
self.generate_layer_regions()
self.generate_discrete_path()
photon_run = self.photon_run_along_path(
self.augmented_path,
run_time = 1,
rate_func = rush_into
)
# self.continuous_to_smooth()
self.paint_layers()
self.show_layer_variables()
self.play(photon_run)
self.play(ShowCreation(self.discrete_path))
self.isolate_bend_points()
# self.dither()
def continuous_to_smooth(self):
continuous = self.get_continuous_background()
layers = Mobject(*[
MobjectFromRegion(region, color)
for region, color in zip(
self.layer_regions, self.layer_colors
)
])
layers.ingest_sub_mobjects()
self.play(FadeIn(continuous))
self.play(Transform(continuous, layers))
self.remove(continuous)
self.paint_layers()
self.dither()
def paint_layers(self):
# for region, color in zip(self.layer_regions, self.layer_colors):
# self.highlight_region(region, color)
for top, color in zip(self.layer_tops, self.layer_colors):
self.add(Line(
SPACE_WIDTH*LEFT+top*UP, SPACE_WIDTH*RIGHT+top*UP,
color = color
))
def get_continuous_background(self):
glass = MobjectFromRegion(Region(
lambda x, y : (y < self.top) & (y > self.bottom)
))
glass.gradient_highlight(self.top_color, self.bottom_color)
glass.scale_in_place(0.99)
return glass
def generate_layer_info(self):
self.layer_thickness = float(self.top-self.bottom)/self.num_discrete_layers
self.layer_tops = np.arange(
self.top, self.bottom, -self.layer_thickness
)
top_rgb, bottom_rgb = [
np.array(Color(color).get_rgb())
for color in self.top_color, self.bottom_color
]
epsilon = 1./(self.num_discrete_layers-1)
self.layer_colors = [
Color(rgb = interpolate(top_rgb, bottom_rgb, alpha))
for alpha in np.arange(0, 1+epsilon, epsilon)
]
def generate_layer_regions(self):
self.generate_layer_info()
self.layer_regions = [
Region(lambda x, y : (y < top) & (y > top-self.layer_thickness))
for top in self.layer_tops
]
def generate_discrete_path(self):
points = self.cycloid.points
indices = [
np.argmin(np.abs(points[:, 1]-top))
for top in self.layer_tops
]
self.bend_points = points[indices[1:-1]]
self.discrete_path = Mobject1D(color = YELLOW)
for start, end in zip(indices, indices[1:]):
self.discrete_path.add_line(
points[start], points[end]
)
self.augmented_path = self.discrete_path.copy()
self.augmented_path.add_line(
points[end], SPACE_WIDTH*RIGHT+(self.layer_tops[-1]-1)*UP
)
def show_layer_variables(self):
layer_top_pairs = zip(
self.layer_tops[:self.num_variables],
self.layer_tops[1:]
)
v_equations = []
start_ys = []
end_ys = []
center_paths = []
braces = []
for (top1, top2), x in zip(layer_top_pairs, it.count(1)):
eq_mob = TexMobject(
["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"],
size = "\\Large"
)
midpoint = UP*(top1+top2)/2
eq_mob.shift(midpoint)
v_eq = eq_mob.split()
center_paths.append(Line(
midpoint+SPACE_WIDTH*LEFT,
midpoint+SPACE_WIDTH*RIGHT
))
brace_endpoints = Mobject(
Point(self.top*UP+x*RIGHT),
Point(top2*UP+x*RIGHT)
)
brace = Brace(brace_endpoints, RIGHT)
start_y = TexMobject("y_%d"%x, size = "\\Large")
end_y = start_y.copy()
start_y.next_to(brace, RIGHT)
end_y.shift(v_eq[-1].get_center())
end_y.shift(0.2*RIGHT)
v_equations.append(v_eq)
start_ys.append(start_y)
end_ys.append(end_y)
braces.append(brace)
for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]):
photon_run = self.photon_run_along_path(
path,
rate_func = None
)
self.play(
ShimmerIn(v_eq[0]),
photon_run,
run_time = time
)
self.dither()
for start_y, brace in zip(start_ys, braces):
start_y.highlight(BLACK)
self.add(start_y)
self.play(GrowFromCenter(brace))
self.dither()
quads = zip(v_equations, start_ys, end_ys, braces)
self.equations = []
for v_eq, start_y, end_y, brace in quads:
self.remove(brace)
self.play(
ShowCreation(v_eq[1]),
ShowCreation(v_eq[2]),
Transform(start_y, end_y)
)
v_eq.append(start_y)
self.equations.append(Mobject(*v_eq))
def isolate_bend_points(self):
little_square = Square(side_length = 4, color = WHITE)
little_square.scale(0.25)
little_square.shift(self.bend_points[0])
big_square = little_square.copy()
big_square.scale(4)
big_square.to_corner(UP+RIGHT)
first_time = True
for bend_point in self.bend_points:
if first_time:
self.play(ShowCreation(little_square))
first_time = False
else:
self.remove(lines, big_square)
self.play(ApplyMethod(
little_square.shift,
bend_point - little_square.get_center()
))
lines = self.lines_connecting_squares(little_square, big_square)
self.play(
ShowCreation(lines),
ShowCreation(big_square)
)
self.dither(2)
def lines_connecting_squares(self, square1, square2):
return Mobject(*[
Line(
square1.get_corner(vect),
square2.get_corner(vect),
)
for vect in [UP+LEFT, DOWN+LEFT]
]).highlight(square1.get_color())
class MultilayeredGlassZoomIn(Scene):
def construct(self, layer_number):
pass

View file

@ -1,5 +1,6 @@
import numpy as np
import itertools as it
import os
from helpers import *
@ -26,13 +27,96 @@ from topics.functions import ParametricFunction, FunctionGraph
from topics.number_line import NumberPlane
from region import Region, region_from_polygon_vertices
from scene import Scene
from generate_logo import LogoGeneration
from brachistochrone.drawing_images import sort_by_color
class Intro(Scene):
def construct(self):
logo = ImageMobject("LogoGeneration", invert = False)
name_mob = TextMobject("3Blue1Brown").center()
name_mob.highlight("grey")
name_mob.shift(2*DOWN)
self.add(name_mob, logo)
new_text = TextMobject(["with ", "Steven Strogatz"])
new_text.next_to(name_mob, DOWN)
self.play(*[
ShimmerIn(part)
for part in new_text.split()
])
self.dither()
with_word, steve = new_text.split()
steve_copy = steve.copy().center().to_edge(UP)
# logo.sort_points(lambda p : -np.linalg.norm(p))
sort_by_color(logo)
self.play(
Transform(steve, steve_copy),
DelayByOrder(Transform(logo, Point())),
FadeOut(with_word),
FadeOut(name_mob),
run_time = 3
)
class IntroduceSteve(Scene):
def construct(self):
name = TextMobject("Steven Strogatz")
name.to_edge(UP)
contributions = TextMobject("Frequent Contributions")
contributions.scale(0.5).to_edge(RIGHT).shift(2*UP)
books_word = TextMobject("Books")
books_word.scale(0.5).to_edge(LEFT).shift(2*UP)
radio_lab, sci_fri, cornell, book2, book3, book4 = [
ImageMobject(filename, invert = False, filter_color = WHITE)
for filename in [
"radio_lab",
"science_friday",
"cornell",
"strogatz_book2",
"strogatz_book3",
"strogatz_book4",
]
]
book1 = ImageMobject("strogatz_book1", invert = False)
nyt = ImageMobject("new_york_times")
logos = [radio_lab, nyt, sci_fri]
books = [book1, book2, book3, book4]
sample_size = Square(side_length = 2)
last = contributions
for image in logos:
image.replace(sample_size)
image.next_to(last, DOWN)
last = image
sci_fri.scale_in_place(0.9)
shift_val = 0
sample_size.scale(0.75)
for book in books:
book.replace(sample_size)
book.next_to(books_word, DOWN)
book.shift(shift_val*(RIGHT+DOWN))
shift_val += 0.5
sample_size.scale(2)
cornell.replace(sample_size)
cornell.next_to(name, DOWN)
Mobject(*logos+books+[cornell]).show()
self.add(name)
self.play(FadeIn(cornell))
self.play(ShimmerIn(contributions))
for logo in logos:
self.play(FadeIn(logo))
self.play(ShimmerIn(books_word))
for book in books:
book.shift(5*LEFT)
self.play(ApplyMethod(book.shift, 5*RIGHT))
self.dither()
class DisectBrachistochroneWord(Scene):
def construct(self):
word = TextMobject(
["Bra", "chis", "to", "chrone"]
)
word = TextMobject(["Bra", "chis", "to", "chrone"])
original_word = word.copy()
dots = []
for part in word.split():
@ -52,13 +136,29 @@ class DisectBrachistochroneWord(Scene):
time = TextMobject("Time")
time.next_to(overbrace2, UP)
time.highlight(YELLOW)
chrono_example = TextMobject("As in ``Chronological''")
chrono_example = TextMobject("""
As in ``Chronological'' \\\\
or ``Synchronize''
""")
chrono_example.scale(0.5)
chrono_example.to_edge(RIGHT)
chrono_example.shift(2*UP)
chrono_example.highlight(BLUE_D)
chrono_arrow = Arrow(word.split()[-1], chrono_example)
chrono_arrow.highlight(BLUE_D)
chrono_arrow = Arrow(
word.get_right(),
chrono_example.get_bottom(),
color = BLUE_D
)
brachy_example = TextMobject("As in . . . brachydactyly?")
brachy_example.scale(0.5)
brachy_example.to_edge(LEFT)
brachy_example.shift(2*DOWN)
brachy_example.highlight(GREEN)
brachy_arrow = Arrow(
word.get_left(),
brachy_example.get_top(),
color = GREEN
)
pronunciation = TextMobject(["/br", "e", "kist","e","kr$\\bar{o}$n/"])
pronunciation.split()[1].rotate_in_place(np.pi)
@ -94,9 +194,119 @@ class DisectBrachistochroneWord(Scene):
self.dither()
self.play(ShimmerIn(shortest))
self.play(ShimmerIn(time))
self.dither()
for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]:
self.play(
ShowCreation(chrono_arrow),
ShimmerIn(chrono_example)
ShowCreation(ar),
ShimmerIn(ex)
)
self.dither()
class FermatsPrincipleStatement(Scene):
def construct(self):
words = TextMobject([
"Fermat's principle:",
"""
If a beam of light travels
from point $A$ to $B$, it does so along the
fastest path possible.
"""
])
words.split()[0].highlight(BLUE)
everything = MobjectFromRegion(Region())
everything.scale(0.9)
angles = np.apply_along_axis(
angle_of_vector, 1, everything.points
)
norms = np.apply_along_axis(
np.linalg.norm, 1, everything.points
)
norms -= np.min(norms)
norms /= np.max(norms)
alphas = 0.25 + 0.75 * norms * (1 + np.sin(12*angles))/2
everything.rgbs = alphas.repeat(3).reshape((len(alphas), 3))
Mobject(everything, words).show()
everything.sort_points(np.linalg.norm)
self.add(words)
self.play(
DelayByOrder(FadeIn(everything, run_time = 3)),
Animation(words)
)
self.play(
ApplyMethod(everything.highlight, WHITE),
)
self.dither()
class VideoProgression(Scene):
def construct(self):
all_topics = [
TextMobject(word, size = "\\Huge")
for word in [
"Brachistochrone",
"Light through \\\\ multilayered glass",
"Light from \\\\ air into glass",
"Lifeguard problem",
"Springs and rod",
"Snell's Law",
"Cycloid",
"Mark Levi's cleverness",
]
]
brachy, multi_glass, bi_glass, lifeguard, springs, \
snell, cycloid, levi = all_topics
positions = [
(multi_glass, brachy, DOWN, "both"),
(bi_glass, multi_glass, LEFT, "to"),
(lifeguard, bi_glass, UP, "both"),
(springs, bi_glass, DOWN, "both"),
(snell, springs, RIGHT, "from"),
(cycloid, multi_glass, RIGHT, "from"),
(cycloid, snell, UP+RIGHT, "from"),
(levi, cycloid, UP, "to"),
]
arrows = []
for mob1, mob2, direction, arrow_type in positions:
hasarrow = hasattr(mob1, "arrow")
if not hasarrow:
mob1.next_to(mob2, direction, buff = 3)
arrow = Arrow(mob1, mob2)
if arrow_type in ["to", "from"]:
arrow.highlight(GREEN)
if arrow_type == "from":
arrow.rotate_in_place(np.pi)
elif arrow_type == "both":
arrow.highlight(BLUE_D)
arrow.add(arrow.copy().rotate_in_place(np.pi))
arrows.append(arrow)
if hasarrow:
mob1.arrow.add(arrow)
else:
mob1.arrow = arrow
everything = Mobject(*all_topics+arrows)
everything.scale(0.7)
everything.center()
everything.to_edge(UP)
everything.show()
self.add(brachy)
for mob in all_topics[1:]:
self.play(ApplyMethod(mob.highlight, YELLOW))
self.play(ShowCreation(mob.arrow))
self.dither()
mob.highlight(WHITE)

View file

@ -31,6 +31,7 @@ class ImageMobject(Mobject):
os.path.join(IMAGE_DIR, image_file),
os.path.join(IMAGE_DIR, image_file + ".jpg"),
os.path.join(IMAGE_DIR, image_file + ".png"),
os.path.join(IMAGE_DIR, image_file + ".gif"),
]
for path in possible_paths:
if os.path.exists(path):

View file

@ -1,344 +0,0 @@
from topics import *
from animation import *
def half_plane():
plane = NumberPlane(
x_radius = SPACE_WIDTH/2,
x_unit_to_spatial_width = 0.5,
y_unit_to_spatial_height = 0.5,
x_faded_line_frequency = 0,
y_faded_line_frequency = 0,
density = 4*DEFAULT_POINT_DENSITY_1D,
)
plane.add_coordinates(
x_vals = range(-6, 7, 2),
y_vals = range(-6, 7, 2)
)
return plane
class SingleVariableFunction(Scene):
args_list = [
(lambda x : x**2 - 3, "ShiftedSquare", True),
(lambda x : x**2 - 3, "ShiftedSquare", False),
]
@staticmethod
def args_to_string(func, name, separate_lines):
return name + ("SeparateLines" if separate_lines else "")
def construct(self, func, name, separate_lines):
base_line = NumberLine(color = "grey")
moving_line = NumberLine(
tick_frequency = 1,
density = 3*DEFAULT_POINT_DENSITY_1D
)
base_line.add_numbers()
def point_function((x, y, z)):
return (func(x), y, z)
target = moving_line.copy().apply_function(point_function)
transform_config = {
"run_time" : 3,
"path_func" : path_along_arc(np.pi/4)
}
if separate_lines:
numbers = moving_line.get_number_mobjects(*range(-7, 7))
negative_numbers = []
for number in numbers:
number.highlight(GREEN_E)
number.shift(-2*moving_line.get_vertical_number_offset())
center = number.get_center()
target_num = number.copy()
target_num.shift(point_function(center) - center)
target.add(target_num)
if center[0] < -0.5:
negative_numbers.append(number)
moving_line.add(*numbers)
base_line.shift(DOWN)
target.shift(DOWN)
moving_line.shift(UP)
self.add(base_line, moving_line)
self.dither(3)
self.play(Transform(moving_line, target, **transform_config))
if separate_lines:
self.play(*[
ApplyMethod(mob.shift, 0.4*UP)
for mob in negative_numbers
])
self.dither(3)
class LineToPlaneFunction(Scene):
args_list = [
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", []),
(lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", [
("0", "(1, 0)", 0),
("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2),
("\\pi", "(-1, 0)", np.pi),
])
]
@staticmethod
def args_to_string(func, name, numbers_to_follow):
return name + ("FollowingNumbers" if numbers_to_follow else "")
def construct(self, func, name, numbers_to_follow):
line = NumberLine(
unit_length_to_spatial_width = 0.5,
tick_frequency = 1,
number_at_center = 6,
numerical_radius = 6,
numbers_with_elongated_ticks = [0, 12],
density = 3*DEFAULT_POINT_DENSITY_1D
)
line.to_edge(LEFT)
line_copy = line.copy()
line.add_numbers(*range(0, 14, 2))
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
plane = half_plane()
plane.sub_mobjects = []
plane.filter_out(
lambda (x, y, z) : abs(x) > 0.1 and abs(y) > 0.1
)
plane.shift(0.5*SPACE_WIDTH*RIGHT)
self.add(line, divider, plane)
def point_function(point):
x, y = func(line.point_to_number(point))
return plane.num_pair_to_point((x, y))
target = line_copy.copy().apply_function(point_function)
target.highlight()
anim_config = {"run_time" : 3}
anims = [Transform(line_copy, target, **anim_config)]
colors = iter([BLUE_B, GREEN_D, RED_D])
for input_tex, output_tex, number in numbers_to_follow:
center = line.number_to_point(number)
dot = Dot(center, color = colors.next())
anims.append(ApplyMethod(
dot.shift,
point_function(center) - center,
**anim_config
))
label = TexMobject(input_tex)
label.shift(center + 2*UP)
arrow = Arrow(label, dot)
self.add(label)
self.play(ShowCreation(arrow), ShowCreation(dot))
self.dither()
self.remove(arrow, label)
self.dither(2)
self.play(*anims)
self.dither()
for input_tex, output_tex, number in numbers_to_follow:
point = plane.num_pair_to_point(func(number))
label = TexMobject(output_tex)
side_shift = LEFT if number == np.pi else RIGHT
label.shift(point, 2*UP, side_shift)
arrow = Arrow(label, point)
self.add(label)
self.play(ShowCreation(arrow))
self.dither(2)
self.remove(arrow, label)
class PlaneToPlaneFunctionSeparatePlanes(Scene):
args_list = [
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
]
@staticmethod
def args_to_string(func, name):
return name
def construct(self, func, name):
shift_factor = 0.55
in_plane = half_plane().shift(shift_factor*SPACE_WIDTH*LEFT)
out_plane = half_plane().shift(shift_factor*SPACE_WIDTH*RIGHT)
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
self.add(in_plane, out_plane, divider)
plane_copy = in_plane.copy()
plane_copy.sub_mobjects = []
def point_function(point):
result = np.array(func((point*2 + 2*shift_factor*SPACE_WIDTH*RIGHT)[:2]))
result = np.append(result/2, [0])
return result + shift_factor*SPACE_WIDTH*RIGHT
target = plane_copy.copy().apply_function(point_function)
target.highlight(GREEN_B)
anim_config = {"run_time" : 5}
self.dither()
self.play(Transform(plane_copy, target, **anim_config))
self.dither()
class PlaneToPlaneFunction(Scene):
args_list = [
(lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic")
]
@staticmethod
def args_to_string(func, name):
return name
def construct(self, func, name):
plane = NumberPlane()
background = NumberPlane(color = "grey")
background.add_coordinates()
anim_config = {"run_time" : 3}
def point_function(point):
return np.append(func(point[:2]), [0])
self.add(background, plane)
self.dither(2)
self.play(ApplyPointwiseFunction(point_function, plane, **anim_config))
self.dither(3)
class PlaneToLineFunction(Scene):
args_list = [
(lambda (x, y) : x**2 + y**2, "Bowl"),
]
@staticmethod
def args_to_string(func, name):
return name
def construct(self, func, name):
line = NumberLine(
color = GREEN,
unit_length_to_spatial_width = 0.5,
tick_frequency = 1,
number_at_center = 6,
numerical_radius = 6,
numbers_with_elongated_ticks = [0, 12],
).to_edge(RIGHT)
line.add_numbers()
plane = half_plane().to_edge(LEFT, buff = 0)
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
line_left = line.number_to_point(0)
def point_function(point):
shifter = 0.5*SPACE_WIDTH*RIGHT
return func((point+shifter)[:2])*RIGHT + line_left
self.add(line, plane, divider)
self.dither()
plane.sub_mobjects = []
self.play(ApplyPointwiseFunction(point_function, plane))
self.dither()
class PlaneToSpaceFunction(Scene):
args_list = [
(lambda (x, y) : (x*x, x*y, y*y), "Quadratic"),
]
@staticmethod
def args_to_string(func, name):
return name
def construct(self, func, name):
plane = half_plane().shift(0.5*SPACE_WIDTH*LEFT)
divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN)
axes = XYZAxes()
axes.filter_out(lambda p : np.linalg.norm(p) > 3)
rot_kwargs = {
"run_time" : 3,
"radians" : 0.3*np.pi,
"axis" : [0.1, 1, 0.1],
}
axes.to_edge(RIGHT).shift(DOWN)
dampening_factor = 0.1
def point_function((x, y, z)):
return dampening_factor*np.array(func((x, y)))
target = NumberPlane().apply_function(point_function)
target.highlight("yellow")
target.shift(axes.get_center())
self.add(plane, divider, axes)
self.play(Rotating(axes, **rot_kwargs))
target.rotate_in_place(rot_kwargs["radians"])
self.play(
TransformAnimations(
Animation(plane.copy()),
Rotating(target, **rot_kwargs),
rate_func = smooth
),
Rotating(axes, **rot_kwargs)
)
axes.add(target)
self.clear()
self.add(plane, divider, axes)
self.play(Rotating(axes, **rot_kwargs))
self.clear()
for i in range(5):
self.play(Rotating(axes, **rot_kwargs))
class SpaceToSpaceFunction(Scene):
args_list = [
(lambda (x, y, z) : (y*z, x*z, x*y), "Quadratic"),
]
@staticmethod
def args_to_string(func, name):
return name
def construct(self, func, name):
space = SpaceGrid()
rot_kwargs = {
"run_time" : 10,
"radians" : 2*np.pi/5,
"axis" : [0.1, 1, 0.1],
"in_place" : False,
}
axes = XYZAxes()
target = space.copy().apply_function(func)
self.play(
TransformAnimations(
Rotating(space, **rot_kwargs),
Rotating(target, **rot_kwargs),
rate_func = squish_rate_func(smooth, 0.3, 0.7)
),
Rotating(axes, **rot_kwargs)
)
axes.add(space)
self.play(Rotating(axes, **rot_kwargs))