mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
3775 lines
118 KiB
Python
3775 lines
118 KiB
Python
# -*- coding: utf-8 -*-
|
|
from manimlib.imports import *
|
|
|
|
|
|
def apply_function_to_center(point_func, mobject):
|
|
mobject.apply_function_to_position(point_func)
|
|
|
|
|
|
def apply_function_to_submobjects(point_func, mobject):
|
|
mobject.apply_function_to_submobject_positions(point_func)
|
|
|
|
|
|
def apply_function_to_points(point_func, mobject):
|
|
mobject.apply_function(point_func)
|
|
|
|
|
|
def get_nested_one_plus_one_over_x(n_terms, bottom_term="x"):
|
|
tex = "1+ {1 \\over" * n_terms + bottom_term + "}" * n_terms
|
|
return TexMobject(tex, substrings_to_isolate=["1", "\\over", bottom_term])
|
|
|
|
|
|
def get_phi_continued_fraction(n_terms):
|
|
return get_nested_one_plus_one_over_x(n_terms, bottom_term="1+\\cdots")
|
|
|
|
|
|
def get_nested_f(n_terms, arg="x"):
|
|
terms = ["f("] * n_terms + [arg] + [")"] * n_terms
|
|
return TexMobject(*terms)
|
|
|
|
|
|
# Scene types
|
|
|
|
class NumberlineTransformationScene(ZoomedScene):
|
|
CONFIG = {
|
|
"input_line_zero_point": 0.5 * UP + (FRAME_X_RADIUS - 1) * LEFT,
|
|
"output_line_zero_point": 2 * DOWN + (FRAME_X_RADIUS - 1) * LEFT,
|
|
"number_line_config": {
|
|
"include_numbers": True,
|
|
"x_min": -3.5,
|
|
"x_max": 3.5,
|
|
"unit_size": 2,
|
|
},
|
|
# These would override number_line_config
|
|
"input_line_config": {
|
|
"color": BLUE,
|
|
},
|
|
"output_line_config": {},
|
|
"num_inserted_number_line_curves": 20,
|
|
"default_delta_x": 0.1,
|
|
"default_sample_dot_radius": 0.07,
|
|
"default_sample_dot_colors": [RED, YELLOW],
|
|
"default_mapping_animation_config": {
|
|
"run_time": 3,
|
|
# "path_arc": 30 * DEGREES,
|
|
},
|
|
"local_coordinate_num_decimal_places": 2,
|
|
"zoom_factor": 0.1,
|
|
"zoomed_display_height": 2.5,
|
|
"zoomed_display_corner_buff": MED_SMALL_BUFF,
|
|
"mini_line_scale_factor": 2,
|
|
"default_coordinate_value_dx": 0.05,
|
|
"zoomed_camera_background_rectangle_fill_opacity": 1.0,
|
|
}
|
|
|
|
def setup(self):
|
|
ZoomedScene.setup(self)
|
|
self.setup_number_lines()
|
|
self.setup_titles()
|
|
self.setup_zoomed_camera_background_rectangle()
|
|
|
|
def setup_number_lines(self):
|
|
number_lines = self.number_lines = VGroup()
|
|
added_configs = (self.input_line_config, self.output_line_config)
|
|
zero_opints = (self.input_line_zero_point, self.output_line_zero_point)
|
|
for added_config, zero_point in zip(added_configs, zero_opints):
|
|
full_config = dict(self.number_line_config)
|
|
full_config.update(added_config)
|
|
number_line = NumberLine(**full_config)
|
|
number_line.insert_n_curves(
|
|
self.num_inserted_number_line_curves
|
|
)
|
|
number_line.shift(zero_point - number_line.number_to_point(0))
|
|
number_lines.add(number_line)
|
|
self.input_line, self.output_line = number_lines
|
|
|
|
self.add(number_lines)
|
|
|
|
def setup_titles(self):
|
|
input_title, output_title = self.titles = VGroup(*[
|
|
TextMobject(word)
|
|
for word in ("Inputs", "Outputs")
|
|
])
|
|
vects = [UP, DOWN]
|
|
for title, line, vect in zip(self.titles, self.number_lines, vects):
|
|
title.next_to(line, vect, aligned_edge=LEFT)
|
|
title.shift_onto_screen()
|
|
|
|
self.add(self.titles)
|
|
|
|
def setup_zoomed_camera_background_rectangle(self):
|
|
frame = self.zoomed_camera.frame
|
|
frame.next_to(self.camera.frame, UL)
|
|
self.zoomed_camera_background_rectangle = BackgroundRectangle(
|
|
frame, fill_opacity=self.zoomed_camera_background_rectangle_fill_opacity
|
|
)
|
|
self.zoomed_camera_background_rectangle_anim = UpdateFromFunc(
|
|
self.zoomed_camera_background_rectangle,
|
|
lambda m: m.replace(frame, stretch=True)
|
|
)
|
|
self.zoomed_camera_background_rectangle_group = VGroup(
|
|
self.zoomed_camera_background_rectangle,
|
|
)
|
|
|
|
def get_sample_input_points(self, x_min=None, x_max=None, delta_x=None):
|
|
x_min = x_min or self.input_line.x_min
|
|
x_max = x_max or self.input_line.x_max
|
|
delta_x = delta_x or self.default_delta_x
|
|
return [
|
|
self.get_input_point(x)
|
|
for x in np.arange(x_min, x_max + delta_x, delta_x)
|
|
]
|
|
|
|
def get_sample_dots(self, x_min=None, x_max=None,
|
|
delta_x=None, dot_radius=None, colors=None):
|
|
dot_radius = dot_radius or self.default_sample_dot_radius
|
|
colors = colors or self.default_sample_dot_colors
|
|
|
|
dots = VGroup(*[
|
|
Dot(point, radius=dot_radius)
|
|
for point in self.get_sample_input_points(x_min, x_max, delta_x)
|
|
])
|
|
dots.set_color_by_gradient(*colors)
|
|
return dots
|
|
|
|
def get_local_sample_dots(self, x, sample_radius=None, **kwargs):
|
|
zoom_factor = self.get_zoom_factor()
|
|
delta_x = kwargs.get("delta_x", self.default_delta_x * zoom_factor)
|
|
dot_radius = kwargs.get("dot_radius", self.default_sample_dot_radius * zoom_factor)
|
|
|
|
if sample_radius is None:
|
|
unrounded_radius = self.zoomed_camera.frame.get_width() / 2
|
|
sample_radius = int(unrounded_radius / delta_x) * delta_x
|
|
config = {
|
|
"x_min": x - sample_radius,
|
|
"x_max": x + sample_radius,
|
|
"delta_x": delta_x,
|
|
"dot_radius": dot_radius,
|
|
}
|
|
config.update(kwargs)
|
|
return self.get_sample_dots(**config)
|
|
|
|
def add_sample_dot_ghosts(self, sample_dots, fade_factor=0.5):
|
|
self.sample_dot_ghosts = sample_dots.copy()
|
|
self.sample_dot_ghosts.fade(fade_factor)
|
|
self.add(self.sample_dot_ghosts, sample_dots)
|
|
|
|
def get_local_coordinate_values(self, x, dx=None, n_neighbors=1):
|
|
dx = dx or self.default_coordinate_value_dx
|
|
return [
|
|
x + n * dx
|
|
for n in range(-n_neighbors, n_neighbors + 1)
|
|
]
|
|
|
|
# Mapping animations
|
|
def get_mapping_animation(self, func, mobject,
|
|
how_to_apply_func=apply_function_to_center,
|
|
**kwargs):
|
|
anim_config = dict(self.default_mapping_animation_config)
|
|
anim_config.update(kwargs)
|
|
|
|
point_func = self.number_func_to_point_func(func)
|
|
|
|
mobject.generate_target(use_deepcopy=True)
|
|
how_to_apply_func(point_func, mobject.target)
|
|
return MoveToTarget(mobject, **anim_config)
|
|
|
|
def get_line_mapping_animation(self, func, **kwargs):
|
|
input_line_copy = self.input_line.deepcopy()
|
|
self.moving_input_line = input_line_copy
|
|
input_line_copy.remove(input_line_copy.numbers)
|
|
# input_line_copy.set_stroke(width=2)
|
|
input_line_copy.insert_n_curves(
|
|
self.num_inserted_number_line_curves
|
|
)
|
|
return AnimationGroup(
|
|
self.get_mapping_animation(
|
|
func, input_line_copy,
|
|
apply_function_to_points
|
|
),
|
|
self.get_mapping_animation(
|
|
func, input_line_copy.tick_marks,
|
|
apply_function_to_submobjects
|
|
),
|
|
)
|
|
|
|
def get_sample_dots_mapping_animation(self, func, dots, **kwargs):
|
|
return self.get_mapping_animation(
|
|
func, dots, how_to_apply_func=apply_function_to_submobjects
|
|
)
|
|
|
|
def get_zoomed_camera_frame_mapping_animation(self, func, x=None, **kwargs):
|
|
frame = self.zoomed_camera.frame
|
|
if x is None:
|
|
point = frame.get_center()
|
|
else:
|
|
point = self.get_input_point(x)
|
|
point_mob = VectorizedPoint(point)
|
|
return AnimationGroup(
|
|
self.get_mapping_animation(func, point_mob),
|
|
UpdateFromFunc(frame, lambda m: m.move_to(point_mob)),
|
|
)
|
|
|
|
def apply_function(self, func,
|
|
apply_function_to_number_line=True,
|
|
sample_dots=None,
|
|
local_sample_dots=None,
|
|
target_coordinate_values=None,
|
|
added_anims=None,
|
|
**kwargs
|
|
):
|
|
zcbr_group = self.zoomed_camera_background_rectangle_group
|
|
zcbr_anim = self.zoomed_camera_background_rectangle_anim
|
|
frame = self.zoomed_camera.frame
|
|
|
|
anims = []
|
|
if apply_function_to_number_line:
|
|
anims.append(self.get_line_mapping_animation(func))
|
|
if hasattr(self, "mini_line"): # Test for if mini_line is in self?
|
|
anims.append(self.get_mapping_animation(
|
|
func, self.mini_line,
|
|
how_to_apply_func=apply_function_to_center
|
|
))
|
|
if sample_dots:
|
|
anims.append(
|
|
self.get_sample_dots_mapping_animation(func, sample_dots)
|
|
)
|
|
if self.zoom_activated:
|
|
zoom_anim = self.get_zoomed_camera_frame_mapping_animation(func)
|
|
anims.append(zoom_anim)
|
|
anims.append(zcbr_anim)
|
|
|
|
zoom_anim.update(1)
|
|
target_mini_line = Line(frame.get_left(), frame.get_right())
|
|
target_mini_line.scale(self.mini_line_scale_factor)
|
|
target_mini_line.match_style(self.output_line)
|
|
zoom_anim.update(0)
|
|
zcbr_group.submobjects.insert(1, target_mini_line)
|
|
if target_coordinate_values:
|
|
coordinates = self.get_local_coordinates(
|
|
self.output_line,
|
|
*target_coordinate_values
|
|
)
|
|
anims.append(FadeIn(coordinates))
|
|
zcbr_group.add(coordinates)
|
|
self.local_target_coordinates = coordinates
|
|
if local_sample_dots:
|
|
anims.append(
|
|
self.get_sample_dots_mapping_animation(func, local_sample_dots)
|
|
)
|
|
zcbr_group.add(local_sample_dots)
|
|
if added_anims:
|
|
anims += added_anims
|
|
anims.append(Animation(zcbr_group))
|
|
|
|
self.play(*anims, **kwargs)
|
|
|
|
# Zooming
|
|
def zoom_in_on_input(self, x,
|
|
local_sample_dots=None,
|
|
local_coordinate_values=None,
|
|
pop_out=True,
|
|
first_added_anims=None,
|
|
first_anim_kwargs=None,
|
|
second_added_anims=None,
|
|
second_anim_kwargs=None,
|
|
zoom_factor=None,
|
|
):
|
|
|
|
first_added_anims = first_added_anims or []
|
|
first_anim_kwargs = first_anim_kwargs or {}
|
|
second_added_anims = second_added_anims or []
|
|
second_anim_kwargs = second_anim_kwargs or {}
|
|
input_point = self.get_input_point(x)
|
|
|
|
# Decide how to move camera frame into place
|
|
frame = self.zoomed_camera.frame
|
|
frame.generate_target()
|
|
frame.target.move_to(input_point)
|
|
if zoom_factor:
|
|
frame.target.set_height(
|
|
self.zoomed_display.get_height() * zoom_factor
|
|
)
|
|
movement = MoveToTarget(frame)
|
|
zcbr = self.zoomed_camera_background_rectangle
|
|
zcbr_group = self.zoomed_camera_background_rectangle_group
|
|
zcbr_anim = self.zoomed_camera_background_rectangle_anim
|
|
anims = []
|
|
if self.zoom_activated:
|
|
anims.append(movement)
|
|
anims.append(zcbr_anim)
|
|
else:
|
|
movement.update(1)
|
|
zcbr_anim.update(1)
|
|
anims.append(self.get_zoom_in_animation())
|
|
anims.append(FadeIn(zcbr))
|
|
|
|
# Make sure frame is in final place
|
|
for anim in anims:
|
|
anim.update(1)
|
|
|
|
# Add miniature number_line
|
|
mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
|
|
mini_line.scale(self.mini_line_scale_factor)
|
|
mini_line.insert_n_curves(self.num_inserted_number_line_curves)
|
|
mini_line.match_style(self.input_line)
|
|
mini_line_copy = mini_line.copy()
|
|
zcbr_group.add(mini_line_copy, mini_line)
|
|
anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]
|
|
|
|
# Add tiny coordinates
|
|
if local_coordinate_values is None:
|
|
local_coordinate_values = [x]
|
|
local_coordinates = self.get_local_coordinates(
|
|
self.input_line,
|
|
*local_coordinate_values
|
|
)
|
|
anims.append(FadeIn(local_coordinates))
|
|
zcbr_group.add(local_coordinates)
|
|
self.local_coordinates = local_coordinates
|
|
|
|
# Add tiny dots
|
|
if local_sample_dots is not None:
|
|
anims.append(LaggedStartMap(GrowFromCenter, local_sample_dots))
|
|
zcbr_group.add(local_sample_dots)
|
|
|
|
if first_added_anims:
|
|
anims += first_added_anims
|
|
|
|
anims.append(Animation(zcbr_group))
|
|
if not pop_out:
|
|
self.activate_zooming(animate=False)
|
|
self.play(*anims, **first_anim_kwargs)
|
|
|
|
if not self.zoom_activated and pop_out:
|
|
self.activate_zooming(animate=False)
|
|
added_anims = second_added_anims or []
|
|
self.play(
|
|
self.get_zoomed_display_pop_out_animation(),
|
|
*added_anims,
|
|
**second_anim_kwargs
|
|
)
|
|
|
|
def get_local_coordinates(self, line, *x_values, **kwargs):
|
|
num_decimal_places = kwargs.get(
|
|
"num_decimal_places", self.local_coordinate_num_decimal_places
|
|
)
|
|
result = VGroup()
|
|
result.tick_marks = VGroup()
|
|
result.numbers = VGroup()
|
|
result.add(result.tick_marks, result.numbers)
|
|
for x in x_values:
|
|
tick_mark = Line(UP, DOWN)
|
|
tick_mark.set_height(
|
|
0.15 * self.zoomed_camera.frame.get_height()
|
|
)
|
|
tick_mark.move_to(line.number_to_point(x))
|
|
result.tick_marks.add(tick_mark)
|
|
|
|
number = DecimalNumber(x, num_decimal_places=num_decimal_places)
|
|
number.scale(self.get_zoom_factor())
|
|
number.scale(0.5) # To make it seem small
|
|
number.next_to(tick_mark, DOWN, buff=0.5 * number.get_height())
|
|
result.numbers.add(number)
|
|
return result
|
|
|
|
def get_mobjects_in_zoomed_camera(self, mobjects):
|
|
frame = self.zoomed_camera.frame
|
|
x_min = frame.get_left()[0]
|
|
x_max = frame.get_right()[0]
|
|
y_min = frame.get_bottom()[1]
|
|
y_max = frame.get_top()[1]
|
|
result = VGroup()
|
|
for mob in mobjects:
|
|
for point in mob.get_all_points():
|
|
if (x_min < point[0] < x_max) and (y_min < point[1] < y_max):
|
|
result.add(mob)
|
|
break
|
|
return result
|
|
|
|
# Helpers
|
|
def get_input_point(self, x):
|
|
return self.input_line.number_to_point(x)
|
|
|
|
def get_output_point(self, fx):
|
|
return self.output_line.number_to_point(fx)
|
|
|
|
def number_func_to_point_func(self, number_func):
|
|
input_line, output_line = self.number_lines
|
|
|
|
def point_func(point):
|
|
input_number = input_line.point_to_number(point)
|
|
output_number = number_func(input_number)
|
|
return output_line.number_to_point(output_number)
|
|
return point_func
|
|
|
|
|
|
class ExampleNumberlineTransformationScene(NumberlineTransformationScene):
|
|
CONFIG = {
|
|
"number_line_config": {
|
|
"x_min": 0,
|
|
"x_max": 5,
|
|
"unit_size": 2.0
|
|
},
|
|
"output_line_config": {
|
|
"x_max": 20,
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
func = lambda x: x**2
|
|
x = 3
|
|
dx = 0.05
|
|
|
|
sample_dots = self.get_sample_dots()
|
|
local_sample_dots = self.get_local_sample_dots(x)
|
|
|
|
self.play(LaggedStartMap(GrowFromCenter, sample_dots))
|
|
self.zoom_in_on_input(
|
|
x,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=[x - dx, x, x + dx],
|
|
)
|
|
self.wait()
|
|
self.apply_function(
|
|
func,
|
|
sample_dots=sample_dots,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=[func(x) - dx, func(x), func(x) + dx],
|
|
)
|
|
self.wait()
|
|
|
|
|
|
# Scenes
|
|
|
|
|
|
class WriteOpeningWords(Scene):
|
|
def construct(self):
|
|
raw_string1 = "Dear calculus student,"
|
|
raw_string2 = "You're about to go through your first course. Like " + \
|
|
"any new topic, it will take some hard work to understand,"
|
|
words1, words2 = [
|
|
TextMobject("\\Large", *rs.split(" "))
|
|
for rs in (raw_string1, raw_string2)
|
|
]
|
|
words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF)
|
|
words = VGroup(*it.chain(words1, words2))
|
|
words.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
|
|
words.to_edge(UP)
|
|
|
|
letter_wait = 0.05
|
|
word_wait = 2 * letter_wait
|
|
comma_wait = 5 * letter_wait
|
|
for word in words:
|
|
self.play(LaggedStartMap(
|
|
FadeIn, word,
|
|
run_time=len(word) * letter_wait,
|
|
lag_ratio=1.5 / len(word)
|
|
))
|
|
self.wait(word_wait)
|
|
if word.get_tex_string()[-1] == ",":
|
|
self.wait(comma_wait)
|
|
|
|
|
|
class StartingCalc101(PiCreatureScene):
|
|
CONFIG = {
|
|
"camera_config": {"background_opacity": 1},
|
|
"image_frame_width": 3.5,
|
|
"image_frame_height": 2.5,
|
|
}
|
|
|
|
def construct(self):
|
|
self.show_you()
|
|
self.show_images()
|
|
self.show_mystery_topic()
|
|
|
|
def show_you(self):
|
|
randy = self.pi_creature
|
|
title = self.title = Title("Calculus 101")
|
|
you = TextMobject("You")
|
|
arrow = Vector(DL, color=WHITE)
|
|
arrow.next_to(randy, UR)
|
|
you.next_to(arrow.get_start(), UP)
|
|
|
|
self.play(
|
|
Write(you),
|
|
GrowArrow(arrow),
|
|
randy.change, "erm", title
|
|
)
|
|
self.wait()
|
|
self.play(Write(title, run_time=1))
|
|
self.play(FadeOut(VGroup(arrow, you)))
|
|
|
|
def show_images(self):
|
|
randy = self.pi_creature
|
|
images = self.get_all_images()
|
|
modes = [
|
|
"pondering", # hard_work_image
|
|
"pondering", # neat_example_image
|
|
"hesitant", # not_so_neat_example_image
|
|
"hesitant", # physics_image
|
|
"horrified", # piles_of_formulas_image
|
|
"horrified", # getting_stuck_image
|
|
"thinking", # aha_image
|
|
"thinking", # graphical_intuition_image
|
|
]
|
|
|
|
for i, image, mode in zip(it.count(), images, modes):
|
|
anims = []
|
|
if hasattr(image, "fade_in_anim"):
|
|
anims.append(image.fade_in_anim)
|
|
anims.append(FadeIn(image.frame))
|
|
else:
|
|
anims.append(FadeIn(image))
|
|
|
|
if i >= 3:
|
|
image_to_fade_out = images[i - 3]
|
|
if hasattr(image_to_fade_out, "fade_out_anim"):
|
|
anims.append(image_to_fade_out.fade_out_anim)
|
|
else:
|
|
anims.append(FadeOut(image_to_fade_out))
|
|
|
|
if hasattr(image, "continual_animations"):
|
|
self.add(*image.continual_animations)
|
|
|
|
anims.append(ApplyMethod(randy.change, mode))
|
|
self.play(*anims)
|
|
self.wait()
|
|
if i >= 3:
|
|
if hasattr(image_to_fade_out, "continual_animations"):
|
|
self.remove(*image_to_fade_out.continual_animations)
|
|
self.remove(image_to_fade_out.frame)
|
|
self.wait(3)
|
|
|
|
self.remaining_images = images[-3:]
|
|
|
|
def show_mystery_topic(self):
|
|
images = self.remaining_images
|
|
randy = self.pi_creature
|
|
|
|
mystery_box = Rectangle(
|
|
width=self.image_frame_width,
|
|
height=self.image_frame_height,
|
|
stroke_color=YELLOW,
|
|
fill_color=DARK_GREY,
|
|
fill_opacity=0.5,
|
|
)
|
|
mystery_box.scale(1.5)
|
|
mystery_box.next_to(self.title, DOWN, MED_LARGE_BUFF)
|
|
|
|
rects = images[-1].rects.copy()
|
|
rects.center()
|
|
rects.set_height(FRAME_HEIGHT - 1)
|
|
# image = rects.get_image()
|
|
open_cv_image = cv2.imread(get_full_raster_image_path("alt_calc_hidden_image"))
|
|
blurry_iamge = cv2.blur(open_cv_image, (50, 50))
|
|
array = np.array(blurry_iamge)[:, :, ::-1]
|
|
im_mob = ImageMobject(array)
|
|
im_mob.replace(mystery_box, stretch=True)
|
|
mystery_box.add(im_mob)
|
|
|
|
q_marks = TexMobject("???").scale(3)
|
|
q_marks.space_out_submobjects(1.5)
|
|
q_marks.set_stroke(BLACK, 1)
|
|
q_marks.move_to(mystery_box)
|
|
mystery_box.add(q_marks)
|
|
|
|
for image in images:
|
|
if hasattr(image, "continual_animations"):
|
|
self.remove(*image.continual_animations)
|
|
self.play(
|
|
image.shift, DOWN,
|
|
image.fade, 1,
|
|
randy.change, "erm",
|
|
run_time=1.5
|
|
)
|
|
self.remove(image)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFromDown(mystery_box),
|
|
randy.change, "confused"
|
|
)
|
|
self.wait(5)
|
|
|
|
# Helpers
|
|
|
|
def get_all_images(self):
|
|
# Images matched to narration's introductory list
|
|
images = VGroup(
|
|
self.get_hard_work_image(),
|
|
self.get_neat_example_image(),
|
|
self.get_not_so_neat_example_image(),
|
|
self.get_physics_image(),
|
|
self.get_piles_of_formulas_image(),
|
|
self.get_getting_stuck_image(),
|
|
self.get_aha_image(),
|
|
self.get_graphical_intuition_image(),
|
|
)
|
|
colors = color_gradient([BLUE, YELLOW], len(images))
|
|
for i, image, color in zip(it.count(), images, colors):
|
|
self.adjust_size(image)
|
|
frame = Rectangle(
|
|
width=self.image_frame_width,
|
|
height=self.image_frame_height,
|
|
color=color,
|
|
stroke_width=2,
|
|
)
|
|
frame.move_to(image)
|
|
image.frame = frame
|
|
image.add(frame)
|
|
image.next_to(self.title, DOWN)
|
|
alt_i = (i % 3) - 1
|
|
vect = (self.image_frame_width + LARGE_BUFF) * RIGHT
|
|
image.shift(alt_i * vect)
|
|
return images
|
|
|
|
def adjust_size(self, group):
|
|
group.set_width(min(
|
|
group.get_width(),
|
|
self.image_frame_width - 2 * MED_SMALL_BUFF
|
|
))
|
|
group.set_height(min(
|
|
group.get_height(),
|
|
self.image_frame_height - 2 * MED_SMALL_BUFF
|
|
))
|
|
return group
|
|
|
|
def get_hard_work_image(self):
|
|
new_randy = self.pi_creature.copy()
|
|
new_randy.change_mode("telepath")
|
|
bubble = new_randy.get_bubble(height=3.5, width=4)
|
|
bubble.add_content(TexMobject("\\frac{d}{dx}(\\sin(\\sqrt{x}))"))
|
|
bubble.add(bubble.content) # Remove?
|
|
|
|
return VGroup(new_randy, bubble)
|
|
|
|
def get_neat_example_image(self):
|
|
filled_circle = Circle(
|
|
stroke_width=0,
|
|
fill_color=BLUE_E,
|
|
fill_opacity=1
|
|
)
|
|
area = TexMobject("\\pi r^2")
|
|
area.move_to(filled_circle)
|
|
unfilled_circle = Circle(
|
|
stroke_width=3,
|
|
stroke_color=YELLOW,
|
|
fill_opacity=0,
|
|
)
|
|
unfilled_circle.next_to(filled_circle, RIGHT)
|
|
circles = VGroup(filled_circle, unfilled_circle)
|
|
circumference = TexMobject("2\\pi r")
|
|
circumference.move_to(unfilled_circle)
|
|
equation = TexMobject(
|
|
"{d (\\pi r^2) \\over dr} = 2\\pi r",
|
|
tex_to_color_map={
|
|
"\\pi r^2": BLUE_D,
|
|
"2\\pi r": YELLOW,
|
|
}
|
|
)
|
|
equation.next_to(circles, UP)
|
|
|
|
return VGroup(
|
|
filled_circle, area,
|
|
unfilled_circle, circumference,
|
|
equation
|
|
)
|
|
|
|
def get_not_so_neat_example_image(self):
|
|
return TexMobject("\\int x \\cos(x) \\, dx")
|
|
|
|
def get_physics_image(self):
|
|
t_max = 6.5
|
|
r = 0.2
|
|
spring = ParametricFunction(
|
|
lambda t: op.add(
|
|
r * (np.sin(TAU * t) * RIGHT + np.cos(TAU * t) * UP),
|
|
t * DOWN,
|
|
),
|
|
t_min=0, t_max=t_max,
|
|
color=WHITE,
|
|
stroke_width=2,
|
|
)
|
|
spring.color_using_background_image("grey_gradient")
|
|
|
|
weight = Square()
|
|
weight.set_stroke(width=0)
|
|
weight.set_fill(opacity=1)
|
|
weight.color_using_background_image("grey_gradient")
|
|
weight.set_height(0.4)
|
|
|
|
t_tracker = ValueTracker(0)
|
|
group = VGroup(spring, weight)
|
|
group.continual_animations = [
|
|
t_tracker.add_udpater(
|
|
lambda tracker, dt: tracker.set_value(
|
|
tracker.get_value() + dt
|
|
)
|
|
),
|
|
spring.add_updater(
|
|
lambda s: s.stretch_to_fit_height(
|
|
1.5 + 0.5 * np.cos(3 * t_tracker.get_value()),
|
|
about_edge=UP
|
|
)
|
|
),
|
|
weight.add_updater(
|
|
lambda w: w.move_to(spring.points[-1])
|
|
)
|
|
]
|
|
|
|
def update_group_style(alpha):
|
|
spring.set_stroke(width=2 * alpha)
|
|
weight.set_fill(opacity=alpha)
|
|
|
|
group.fade_in_anim = UpdateFromAlphaFunc(
|
|
group,
|
|
lambda g, a: update_group_style(a)
|
|
)
|
|
group.fade_out_anim = UpdateFromAlphaFunc(
|
|
group,
|
|
lambda g, a: update_group_style(1 - a)
|
|
)
|
|
return group
|
|
|
|
def get_piles_of_formulas_image(self):
|
|
return TexMobject("(f/g)' = \\frac{gf' - fg'}{g^2}")
|
|
|
|
def get_getting_stuck_image(self):
|
|
creature = self.pi_creature.copy()
|
|
creature.change_mode("angry")
|
|
equation = TexMobject("\\frac{d}{dx}(x^x)")
|
|
equation.set_height(creature.get_height() / 2)
|
|
equation.next_to(creature, RIGHT, aligned_edge=UP)
|
|
creature.look_at(equation)
|
|
return VGroup(creature, equation)
|
|
|
|
def get_aha_image(self):
|
|
creature = self.pi_creature.copy()
|
|
creature.change_mode("hooray")
|
|
from old_projects.eoc.chapter3 import NudgeSideLengthOfCube
|
|
scene = NudgeSideLengthOfCube(
|
|
end_at_animation_number=7,
|
|
skip_animations=True
|
|
)
|
|
group = VGroup(
|
|
scene.cube, scene.faces,
|
|
scene.bars, scene.corner_cube,
|
|
)
|
|
group.set_height(0.75 * creature.get_height())
|
|
group.next_to(creature, RIGHT)
|
|
creature.look_at(group)
|
|
return VGroup(creature, group)
|
|
|
|
def get_graphical_intuition_image(self):
|
|
gs = GraphScene()
|
|
gs.setup_axes()
|
|
graph = gs.get_graph(
|
|
lambda x: 0.2 * (x - 3) * (x - 5) * (x - 6) + 4,
|
|
x_min=2, x_max=8,
|
|
)
|
|
rects = gs.get_riemann_rectangles(
|
|
graph, x_min=2, x_max=8,
|
|
stroke_width=0.5,
|
|
dx=0.25
|
|
)
|
|
gs.add(graph, rects, gs.axes)
|
|
group = VGroup(*gs.mobjects)
|
|
self.adjust_size(group)
|
|
group.next_to(self.title, DOWN, MED_LARGE_BUFF)
|
|
group.rects = rects
|
|
group.continual_animations = [
|
|
turn_animation_into_updater(Write(rects)),
|
|
turn_animation_into_updater(ShowCreation(graph)),
|
|
turn_animation_into_updater(FadeIn(gs.axes)),
|
|
]
|
|
self.adjust_size(group)
|
|
return group
|
|
|
|
|
|
class GraphicalIntuitions(GraphScene):
|
|
CONFIG = {
|
|
"func": lambda x: 0.1 * (x - 2) * (x - 5) * (x - 7) + 4,
|
|
"x_labeled_nums": list(range(1, 10)),
|
|
}
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
axes = self.axes
|
|
graph = self.get_graph(self.func)
|
|
|
|
ss_group = self.get_secant_slope_group(
|
|
x=8, graph=graph, dx=0.01,
|
|
secant_line_length=6,
|
|
secant_line_color=RED,
|
|
)
|
|
rects = self.get_riemann_rectangles(
|
|
graph, x_min=2, x_max=8, dx=0.01, stroke_width=0
|
|
)
|
|
|
|
deriv_text = TextMobject(
|
|
"Derivative $\\rightarrow$ slope",
|
|
tex_to_color_map={"slope": ss_group.secant_line.get_color()}
|
|
)
|
|
deriv_text.to_edge(UP)
|
|
integral_text = TextMobject(
|
|
"Integral $\\rightarrow$ area",
|
|
tex_to_color_map={"area": rects[0].get_color()}
|
|
)
|
|
integral_text.next_to(deriv_text, DOWN)
|
|
|
|
self.play(
|
|
Succession(Write(axes), ShowCreation(graph, run_time=2)),
|
|
self.get_graph_words_anim(),
|
|
)
|
|
self.animate_secant_slope_group_change(
|
|
ss_group,
|
|
target_x=2,
|
|
rate_func=smooth,
|
|
run_time=2.5,
|
|
added_anims=[
|
|
Write(deriv_text),
|
|
VFadeIn(ss_group, run_time=2),
|
|
]
|
|
)
|
|
self.play(FadeIn(integral_text))
|
|
self.play(
|
|
LaggedStartMap(
|
|
GrowFromEdge, rects,
|
|
lambda r: (r, DOWN)
|
|
),
|
|
Animation(axes),
|
|
Animation(graph),
|
|
)
|
|
self.wait()
|
|
|
|
def get_graph_words_anim(self):
|
|
words = VGroup(
|
|
TextMobject("Graphs,"),
|
|
TextMobject("graphs,"),
|
|
TextMobject("non-stop graphs"),
|
|
TextMobject("all day"),
|
|
TextMobject("every day"),
|
|
TextMobject("as if to visualize is to graph"),
|
|
)
|
|
for word in words:
|
|
word.add_background_rectangle()
|
|
words.arrange(DOWN)
|
|
words.to_edge(UP)
|
|
return LaggedStartMap(
|
|
FadeIn, words,
|
|
rate_func=there_and_back,
|
|
run_time=len(words) - 1,
|
|
lag_ratio=0.6,
|
|
remover=True
|
|
)
|
|
|
|
|
|
class Wrapper(Scene):
|
|
CONFIG = {
|
|
"title": "",
|
|
"title_kwargs": {},
|
|
"screen_height": 6,
|
|
"wait_time": 2,
|
|
}
|
|
|
|
def construct(self):
|
|
rect = self.rect = ScreenRectangle(height=self.screen_height)
|
|
title = self.title = TextMobject(self.title, **self.title_kwargs)
|
|
title.to_edge(UP)
|
|
rect.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait(self.wait_time)
|
|
|
|
|
|
class DomainColoringWrapper(Wrapper):
|
|
CONFIG = {
|
|
"title": "Complex $\\rightarrow$ Complex",
|
|
}
|
|
|
|
|
|
class R2ToR2Wrapper(Wrapper):
|
|
CONFIG = {"title": "$\\mathds{R}^2 \\rightarrow \\mathds{R}^2$"}
|
|
|
|
|
|
class ExampleMultivariableFunction(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors": False,
|
|
"show_coordinates": True,
|
|
}
|
|
|
|
def construct(self):
|
|
def example_function(point):
|
|
x, y, z = point
|
|
return np.array([
|
|
x + np.sin(y),
|
|
y + np.sin(x),
|
|
0
|
|
])
|
|
self.wait()
|
|
self.apply_nonlinear_transformation(example_function, run_time=5)
|
|
self.wait()
|
|
|
|
|
|
class ChangingVectorFieldWrapper(Wrapper):
|
|
CONFIG = {"title": "$(x, y, t) \\rightarrow (x', y')$"}
|
|
|
|
|
|
class ChangingVectorField(Scene):
|
|
CONFIG = {
|
|
"wait_time": 30,
|
|
}
|
|
|
|
def construct(self):
|
|
plane = self.plane = NumberPlane()
|
|
plane.set_stroke(width=2)
|
|
plane.add_coordinates()
|
|
self.add(plane)
|
|
|
|
# Obviously a silly thing to do, but I'm sweeping
|
|
# through trying to make sure old scenes don't
|
|
# completely break in spots which used to have
|
|
# Continual animations
|
|
time_tracker = self.time_tracker = ValueTracker(0)
|
|
time_tracker.add_updater(
|
|
lambda t: t.set_value(self.get_time())
|
|
)
|
|
|
|
vectors = self.get_vectors()
|
|
vectors.add_updater(self.update_vectors)
|
|
self.add(vectors)
|
|
self.wait(self.wait_time)
|
|
|
|
def get_vectors(self):
|
|
vectors = VGroup()
|
|
x_max = int(np.ceil(FRAME_WIDTH))
|
|
y_max = int(np.ceil(FRAME_HEIGHT))
|
|
step = 0.5
|
|
for x in np.arange(-x_max, x_max + 1, step):
|
|
for y in np.arange(-y_max, y_max + 1, step):
|
|
point = x * RIGHT + y * UP
|
|
vectors.add(Vector(RIGHT).shift(point))
|
|
vectors.set_color_by_gradient(YELLOW, RED)
|
|
return vectors
|
|
|
|
def update_vectors(self, vectors):
|
|
time = self.time_tracker.get_value()
|
|
for vector in vectors:
|
|
point = vector.get_start()
|
|
out_point = self.func(point, time)
|
|
norm = get_norm(out_point)
|
|
if norm == 0:
|
|
out_point = RIGHT # Fake it
|
|
vector.set_fill(opacity=0)
|
|
else:
|
|
out_point *= 0.5
|
|
color = interpolate_color(BLUE, RED, norm / np.sqrt(8))
|
|
vector.set_fill(color, opacity=1)
|
|
vector.set_stroke(BLACK, width=1)
|
|
new_x, new_y = out_point[:2]
|
|
vector.put_start_and_end_on(
|
|
point, point + new_x * RIGHT + new_y * UP
|
|
)
|
|
|
|
def func(self, point, time):
|
|
x, y, z = point
|
|
return np.array([
|
|
np.sin(time + 0.5 * x + y),
|
|
np.cos(time + 0.2 * x * y + 0.7),
|
|
0
|
|
])
|
|
|
|
|
|
class MoreTopics(Scene):
|
|
def construct(self):
|
|
calculus = TextMobject("Calculus")
|
|
calculus.next_to(LEFT, LEFT)
|
|
calculus.set_color(YELLOW)
|
|
calculus.add_background_rectangle()
|
|
others = VGroup(
|
|
TextMobject("Multivariable calculus"),
|
|
TextMobject("Complex analysis"),
|
|
TextMobject("Differential geometry"),
|
|
TextMobject("$\\vdots$")
|
|
)
|
|
for word in others:
|
|
word.add_background_rectangle()
|
|
others.arrange(
|
|
DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT,
|
|
)
|
|
others.next_to(RIGHT, RIGHT)
|
|
lines = VGroup(*[
|
|
Line(calculus.get_right(), word.get_left(), buff=MED_SMALL_BUFF)
|
|
for word in others
|
|
])
|
|
|
|
rect = FullScreenFadeRectangle(fill_opacity=0.7)
|
|
self.add(rect)
|
|
|
|
self.add(calculus)
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, lines),
|
|
LaggedStartMap(Write, others),
|
|
)
|
|
self.wait()
|
|
|
|
self.calculus = calculus
|
|
self.lines = lines
|
|
self.full_screen_rect = rect
|
|
self.other_topics = others
|
|
|
|
|
|
class TransformationalViewWrapper(Wrapper):
|
|
CONFIG = {
|
|
"title": "Transformational view"
|
|
}
|
|
|
|
|
|
class SetTheStage(TeacherStudentsScene):
|
|
def construct(self):
|
|
ordinary = TextMobject("Ordinary visual")
|
|
transformational = TextMobject("Transformational visual")
|
|
for word in ordinary, transformational:
|
|
word.move_to(self.hold_up_spot, DOWN)
|
|
word.shift_onto_screen()
|
|
|
|
self.screen.scale(1.25, about_edge=UL)
|
|
self.add(self.screen)
|
|
self.teacher_holds_up(
|
|
ordinary,
|
|
added_anims=[self.get_student_changes(*3 * ["sassy"])]
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ordinary.shift, UP,
|
|
FadeInFromDown(transformational),
|
|
self.teacher.change, "hooray",
|
|
self.get_student_changes(*3 * ["erm"])
|
|
)
|
|
self.wait(3)
|
|
self.change_all_student_modes("pondering", look_at_arg=self.screen)
|
|
|
|
|
|
class StandardDerivativeVisual(GraphScene):
|
|
CONFIG = {
|
|
"y_max": 8,
|
|
"y_axis_height": 5,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_function_graph()
|
|
self.show_slope_of_graph()
|
|
self.encourage_not_to_think_of_slope_as_definition()
|
|
self.show_sensitivity()
|
|
|
|
def add_title(self):
|
|
title = self.title = TextMobject("Standard derivative visual")
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT)
|
|
h_line.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
|
|
h_line.next_to(title, DOWN)
|
|
|
|
self.add(title, h_line)
|
|
|
|
def show_function_graph(self):
|
|
self.setup_axes()
|
|
|
|
def func(x):
|
|
x -= 5
|
|
return 0.1 * (x + 3) * (x - 3) * x + 3
|
|
graph = self.get_graph(func)
|
|
graph_label = self.get_graph_label(graph, x_val=9.5)
|
|
|
|
input_tracker = ValueTracker(4)
|
|
|
|
def get_x_value():
|
|
return input_tracker.get_value()
|
|
|
|
def get_y_value():
|
|
return graph.underlying_function(get_x_value())
|
|
|
|
def get_x_point():
|
|
return self.coords_to_point(get_x_value(), 0)
|
|
|
|
def get_y_point():
|
|
return self.coords_to_point(0, get_y_value())
|
|
|
|
def get_graph_point():
|
|
return self.coords_to_point(get_x_value(), get_y_value())
|
|
|
|
def get_v_line():
|
|
return DashedLine(get_x_point(), get_graph_point(), stroke_width=2)
|
|
|
|
def get_h_line():
|
|
return DashedLine(get_graph_point(), get_y_point(), stroke_width=2)
|
|
|
|
input_triangle = RegularPolygon(n=3, start_angle=TAU / 4)
|
|
output_triangle = RegularPolygon(n=3, start_angle=0)
|
|
for triangle in input_triangle, output_triangle:
|
|
triangle.set_fill(WHITE, 1)
|
|
triangle.set_stroke(width=0)
|
|
triangle.scale(0.1)
|
|
|
|
input_triangle_update = input_tracker.add_updater(
|
|
lambda m: m.move_to(get_x_point(), UP)
|
|
)
|
|
output_triangle_update = output_triangle.add_updater(
|
|
lambda m: m.move_to(get_y_point(), RIGHT)
|
|
)
|
|
|
|
x_label = TexMobject("x")
|
|
x_label_update = Mobject.add_updater(
|
|
x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF)
|
|
)
|
|
|
|
output_label = TexMobject("f(x)")
|
|
output_label_update = Mobject.add_updater(
|
|
output_label, lambda m: m.next_to(
|
|
output_triangle, LEFT, SMALL_BUFF)
|
|
)
|
|
|
|
v_line = get_v_line()
|
|
v_line_update = Mobject.add_updater(
|
|
v_line, lambda vl: Transform(vl, get_v_line()).update(1)
|
|
)
|
|
|
|
h_line = get_h_line()
|
|
h_line_update = Mobject.add_updater(
|
|
h_line, lambda hl: Transform(hl, get_h_line()).update(1)
|
|
)
|
|
|
|
graph_dot = Dot(color=YELLOW)
|
|
graph_dot_update = Mobject.add_updater(
|
|
graph_dot, lambda m: m.move_to(get_graph_point())
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(graph),
|
|
Write(graph_label),
|
|
)
|
|
self.play(
|
|
DrawBorderThenFill(input_triangle, run_time=1),
|
|
Write(x_label),
|
|
ShowCreation(v_line),
|
|
GrowFromCenter(graph_dot),
|
|
)
|
|
self.add_foreground_mobject(graph_dot)
|
|
self.play(
|
|
ShowCreation(h_line),
|
|
Write(output_label),
|
|
DrawBorderThenFill(output_triangle, run_time=1)
|
|
)
|
|
self.add(
|
|
input_triangle_update,
|
|
x_label_update,
|
|
graph_dot_update,
|
|
v_line_update,
|
|
h_line_update,
|
|
output_triangle_update,
|
|
output_label_update,
|
|
)
|
|
self.play(
|
|
input_tracker.set_value, 8,
|
|
run_time=6,
|
|
rate_func=there_and_back
|
|
)
|
|
|
|
self.input_tracker = input_tracker
|
|
self.graph = graph
|
|
|
|
def show_slope_of_graph(self):
|
|
input_tracker = self.input_tracker
|
|
deriv_input_tracker = ValueTracker(input_tracker.get_value())
|
|
|
|
# Slope line
|
|
def get_slope_line():
|
|
return self.get_secant_slope_group(
|
|
x=deriv_input_tracker.get_value(),
|
|
graph=self.graph,
|
|
dx=0.01,
|
|
secant_line_length=4
|
|
).secant_line
|
|
|
|
slope_line = get_slope_line()
|
|
slope_line_update = Mobject.add_updater(
|
|
slope_line, lambda sg: Transform(sg, get_slope_line()).update(1)
|
|
)
|
|
|
|
def position_deriv_label(deriv_label):
|
|
deriv_label.next_to(slope_line, UP)
|
|
return deriv_label
|
|
deriv_label = TexMobject(
|
|
"\\frac{df}{dx}(x) =", "\\text{Slope}", "="
|
|
)
|
|
deriv_label.get_part_by_tex("Slope").match_color(slope_line)
|
|
deriv_label_update = Mobject.add_updater(
|
|
deriv_label, position_deriv_label
|
|
)
|
|
|
|
slope_decimal = DecimalNumber(slope_line.get_slope())
|
|
slope_decimal.match_color(slope_line)
|
|
slope_decimal.add_updater(
|
|
lambda d: d.set_value(slope_line.get_slope())
|
|
)
|
|
slope_decimal.add_upater(
|
|
lambda d: d.next_to(
|
|
deriv_label, RIGHT, SMALL_BUFF
|
|
).shift(0.2 * SMALL_BUFF * DOWN)
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(slope_line),
|
|
Write(deriv_label),
|
|
Write(slope_decimal),
|
|
run_time=1
|
|
)
|
|
self.wait()
|
|
self.add(
|
|
slope_line_update,
|
|
# deriv_label_update,
|
|
)
|
|
for x in 9, 2, 4:
|
|
self.play(
|
|
input_tracker.set_value, x,
|
|
deriv_input_tracker.set_value, x,
|
|
run_time=3
|
|
)
|
|
self.wait()
|
|
|
|
self.deriv_input_tracker = deriv_input_tracker
|
|
|
|
def encourage_not_to_think_of_slope_as_definition(self):
|
|
morty = Mortimer(height=2)
|
|
morty.to_corner(DR)
|
|
|
|
self.play(FadeIn(morty))
|
|
self.play(PiCreatureSays(
|
|
morty, "Don't think of \\\\ this as the definition",
|
|
bubble_kwargs={"height": 2, "width": 4}
|
|
))
|
|
self.play(Blink(morty))
|
|
self.wait()
|
|
self.play(
|
|
RemovePiCreatureBubble(morty),
|
|
UpdateFromAlphaFunc(
|
|
morty, lambda m, a: m.set_fill(opacity=1 - a),
|
|
remover=True
|
|
)
|
|
)
|
|
|
|
def show_sensitivity(self):
|
|
input_tracker = self.input_tracker
|
|
deriv_input_tracker = self.deriv_input_tracker
|
|
|
|
self.wiggle_input()
|
|
for x in 9, 7, 2:
|
|
self.play(
|
|
input_tracker.set_value, x,
|
|
deriv_input_tracker.set_value, x,
|
|
run_time=3
|
|
)
|
|
self.wiggle_input()
|
|
|
|
###
|
|
def wiggle_input(self, dx=0.5, run_time=3):
|
|
input_tracker = self.input_tracker
|
|
|
|
x = input_tracker.get_value()
|
|
x_min = x - dx
|
|
x_max = x + dx
|
|
y, y_min, y_max = list(map(
|
|
self.graph.underlying_function,
|
|
[x, x_min, x_max]
|
|
))
|
|
x_line = Line(
|
|
self.coords_to_point(x_min, 0),
|
|
self.coords_to_point(x_max, 0),
|
|
)
|
|
y_line = Line(
|
|
self.coords_to_point(0, y_min),
|
|
self.coords_to_point(0, y_max),
|
|
)
|
|
|
|
x_rect, y_rect = rects = VGroup(Rectangle(), Rectangle())
|
|
rects.set_stroke(width=0)
|
|
rects.set_fill(YELLOW, 0.5)
|
|
x_rect.match_width(x_line)
|
|
x_rect.stretch_to_fit_height(0.25)
|
|
x_rect.move_to(x_line)
|
|
y_rect.match_height(y_line)
|
|
y_rect.stretch_to_fit_width(0.25)
|
|
y_rect.move_to(y_line)
|
|
|
|
self.play(
|
|
ApplyMethod(
|
|
input_tracker.set_value, input_tracker.get_value() + dx,
|
|
rate_func=lambda t: wiggle(t, 6)
|
|
),
|
|
FadeIn(
|
|
rects,
|
|
rate_func=squish_rate_func(smooth, 0, 0.33),
|
|
remover=True,
|
|
),
|
|
run_time=run_time,
|
|
)
|
|
self.play(FadeOut(rects))
|
|
|
|
|
|
class EoCWrapper(Scene):
|
|
def construct(self):
|
|
title = Title("Essence of calculus")
|
|
self.play(Write(title))
|
|
self.wait()
|
|
|
|
|
|
class IntroduceTransformationView(NumberlineTransformationScene):
|
|
CONFIG = {
|
|
"func": lambda x: 0.5 * np.sin(2 * x) + x,
|
|
"number_line_config": {
|
|
"x_min": 0,
|
|
"x_max": 6,
|
|
"unit_size": 2.0
|
|
},
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_animation_preview()
|
|
self.indicate_indicate_point_densities()
|
|
self.show_zoomed_transformation()
|
|
|
|
def add_title(self):
|
|
title = self.title = TextMobject("$f(x)$ as a transformation")
|
|
title.to_edge(UP)
|
|
self.add(title)
|
|
|
|
def show_animation_preview(self):
|
|
input_points = self.get_sample_input_points()
|
|
output_points = list(map(
|
|
self.number_func_to_point_func(self.func),
|
|
input_points
|
|
))
|
|
sample_dots = self.get_sample_dots()
|
|
sample_dot_ghosts = sample_dots.copy().fade(0.5)
|
|
arrows = VGroup(*[
|
|
Arrow(ip, op, buff=MED_SMALL_BUFF)
|
|
for ip, op in zip(input_points, output_points)
|
|
])
|
|
arrows = arrows[1::3]
|
|
arrows.set_stroke(BLACK, 1)
|
|
|
|
for sd in sample_dots:
|
|
sd.save_state()
|
|
sd.scale(2)
|
|
sd.fade(1)
|
|
self.play(LaggedStartMap(
|
|
ApplyMethod, sample_dots,
|
|
lambda sd: (sd.restore,),
|
|
run_time=2
|
|
))
|
|
self.play(LaggedStartMap(
|
|
GrowArrow, arrows,
|
|
run_time=6,
|
|
lag_ratio=0.3,
|
|
))
|
|
self.add(sample_dot_ghosts)
|
|
self.apply_function(
|
|
self.func, sample_dots=sample_dots,
|
|
run_time=3
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStartMap(FadeOut, arrows, run_time=1))
|
|
|
|
self.sample_dots = sample_dots
|
|
self.sample_dot_ghosts = sample_dot_ghosts
|
|
|
|
def indicate_indicate_point_densities(self):
|
|
lower_brace = Brace(Line(LEFT, RIGHT), UP)
|
|
upper_brace = lower_brace.copy()
|
|
input_tracker = ValueTracker(0.5)
|
|
dx = 0.5
|
|
|
|
def update_upper_brace(brace):
|
|
x = input_tracker.get_value()
|
|
line = Line(
|
|
self.get_input_point(x),
|
|
self.get_input_point(x + dx),
|
|
)
|
|
brace.match_width(line, stretch=True)
|
|
brace.next_to(line, UP, buff=SMALL_BUFF)
|
|
return brace
|
|
|
|
def update_lower_brace(brace):
|
|
x = input_tracker.get_value()
|
|
line = Line(
|
|
self.get_output_point(self.func(x)),
|
|
self.get_output_point(self.func(x + dx)),
|
|
)
|
|
brace.match_width(line, stretch=True)
|
|
brace.next_to(line, UP, buff=SMALL_BUFF)
|
|
return brace
|
|
|
|
lower_brace_anim = UpdateFromFunc(lower_brace, update_lower_brace)
|
|
upper_brace_anim = UpdateFromFunc(upper_brace, update_upper_brace)
|
|
|
|
new_title = TextMobject(
|
|
"$\\frac{df}{dx}(x)$ measures stretch/squishing"
|
|
)
|
|
new_title.move_to(self.title, UP)
|
|
|
|
stretch_factor = DecimalNumber(0, color=YELLOW)
|
|
stretch_factor_anim = ChangingDecimal(
|
|
stretch_factor, lambda a: lower_brace.get_width() / upper_brace.get_width(),
|
|
position_update_func=lambda m: m.next_to(lower_brace, UP, SMALL_BUFF)
|
|
)
|
|
|
|
self.play(
|
|
GrowFromCenter(upper_brace),
|
|
FadeOut(self.title),
|
|
# FadeIn(new_title)
|
|
Write(new_title, run_time=2)
|
|
)
|
|
self.title = new_title
|
|
self.play(
|
|
ReplacementTransform(upper_brace.copy(), lower_brace),
|
|
GrowFromPoint(stretch_factor, upper_brace.get_center())
|
|
)
|
|
self.play(
|
|
input_tracker.set_value, self.input_line.x_max - dx,
|
|
lower_brace_anim,
|
|
upper_brace_anim,
|
|
stretch_factor_anim,
|
|
run_time=8,
|
|
rate_func=bezier([0, 0, 1, 1])
|
|
)
|
|
self.wait()
|
|
|
|
new_sample_dots = self.get_sample_dots()
|
|
self.play(
|
|
FadeOut(VGroup(
|
|
upper_brace, lower_brace, stretch_factor,
|
|
self.sample_dots, self.moving_input_line,
|
|
)),
|
|
FadeIn(new_sample_dots),
|
|
)
|
|
self.sample_dots = new_sample_dots
|
|
|
|
def show_zoomed_transformation(self):
|
|
x = 2.75
|
|
local_sample_dots = self.get_local_sample_dots(x)
|
|
|
|
self.zoom_in_on_input(
|
|
x,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=self.get_local_coordinate_values(x),
|
|
)
|
|
self.wait()
|
|
self.apply_function(
|
|
self.func,
|
|
sample_dots=self.sample_dots,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=self.get_local_coordinate_values(self.func(x))
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class ExamplePlease(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says("Example?", student_index=0)
|
|
self.teacher_holds_up(TexMobject("f(x) = x^2").scale(1.5))
|
|
self.wait(2)
|
|
|
|
|
|
class TalkThroughXSquaredExample(IntroduceTransformationView):
|
|
CONFIG = {
|
|
"func": lambda x: x**2,
|
|
"number_line_config": {
|
|
"x_min": 0,
|
|
"x_max": 5,
|
|
"unit_size": 1.25,
|
|
},
|
|
"output_line_config": {
|
|
"x_max": 25,
|
|
},
|
|
"default_delta_x": 0.2
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_specific_points_mapping()
|
|
|
|
def add_title(self):
|
|
title = self.title = TextMobject("$f(x) = x^2$")
|
|
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
self.add(title)
|
|
|
|
def show_specific_points_mapping(self):
|
|
# First, just show integers as examples
|
|
int_dots = self.get_sample_dots(1, 6, 1)
|
|
int_dot_ghosts = int_dots.copy().fade(0.5)
|
|
int_arrows = VGroup(*[
|
|
Arrow(
|
|
# num.get_bottom(),
|
|
self.get_input_point(x),
|
|
self.get_output_point(self.func(x)),
|
|
buff=MED_SMALL_BUFF
|
|
)
|
|
for x, num in zip(list(range(1, 6)), self.input_line.numbers[1:])
|
|
])
|
|
point_func = self.number_func_to_point_func(self.func)
|
|
|
|
numbers = self.input_line.numbers
|
|
numbers.next_to(self.input_line, UP, SMALL_BUFF)
|
|
self.titles[0].next_to(numbers, UP, MED_SMALL_BUFF, LEFT)
|
|
# map(TexMobject.add_background_rectangle, numbers)
|
|
# self.add_foreground_mobject(numbers)
|
|
|
|
for dot, dot_ghost, arrow in zip(int_dots, int_dot_ghosts, int_arrows):
|
|
arrow.match_color(dot)
|
|
self.play(DrawBorderThenFill(dot, run_time=1))
|
|
self.add(dot_ghost)
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
dot.apply_function_to_position, point_func
|
|
)
|
|
self.wait()
|
|
|
|
# Show more sample_dots
|
|
sample_dots = self.get_sample_dots()
|
|
sample_dot_ghosts = sample_dots.copy().fade(0.5)
|
|
|
|
self.play(
|
|
LaggedStartMap(DrawBorderThenFill, sample_dots),
|
|
LaggedStartMap(FadeOut, int_arrows),
|
|
)
|
|
self.remove(int_dot_ghosts)
|
|
self.add(sample_dot_ghosts)
|
|
self.apply_function(self.func, sample_dots=sample_dots)
|
|
self.remove(int_dots)
|
|
self.wait()
|
|
|
|
self.sample_dots = sample_dots
|
|
self.sample_dot_ghosts = sample_dot_ghosts
|
|
|
|
def get_stretch_words(self, factor, color=RED, less_than_one=False):
|
|
factor_str = "$%s$" % str(factor)
|
|
result = TextMobject(
|
|
"Scale \\\\ by", factor_str,
|
|
tex_to_color_map={factor_str: color}
|
|
)
|
|
result.scale(0.7)
|
|
la, ra = TexMobject("\\leftarrow \\rightarrow")
|
|
if less_than_one:
|
|
la, ra = ra, la
|
|
if factor < 0:
|
|
kwargs = {
|
|
"path_arc": np.pi,
|
|
}
|
|
la = Arrow(UP, DOWN, **kwargs)
|
|
ra = Arrow(DOWN, UP, **kwargs)
|
|
for arrow in la, ra:
|
|
arrow.pointwise_become_partial(arrow, 0, 0.9)
|
|
arrow.tip.scale(2)
|
|
VGroup(la, ra).match_height(result)
|
|
la.next_to(result, LEFT)
|
|
ra.next_to(result, RIGHT)
|
|
result.add(la, ra)
|
|
result.next_to(
|
|
self.zoomed_display.get_top(), DOWN, SMALL_BUFF
|
|
)
|
|
return result
|
|
|
|
def get_deriv_equation(self, x, rhs, color=RED):
|
|
deriv_equation = self.deriv_equation = TexMobject(
|
|
"\\frac{df}{dx}(", str(x), ")", "=", str(rhs),
|
|
tex_to_color_map={str(x): color, str(rhs): color}
|
|
)
|
|
deriv_equation.next_to(self.title, DOWN, MED_LARGE_BUFF)
|
|
return deriv_equation
|
|
|
|
|
|
class ZoomInOnXSquaredNearOne(TalkThroughXSquaredExample):
|
|
def setup(self):
|
|
TalkThroughXSquaredExample.setup(self)
|
|
self.force_skipping()
|
|
self.add_title()
|
|
self.show_specific_points_mapping()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
def construct(self):
|
|
zoom_words = TextMobject("Zoomed view \\\\ near 1")
|
|
zoom_words.next_to(self.zoomed_display, DOWN)
|
|
# zoom_words.shift_onto_screen()
|
|
|
|
x = 1
|
|
local_sample_dots = self.get_local_sample_dots(x)
|
|
local_coords = self.get_local_coordinate_values(x, dx=0.1)
|
|
|
|
zcbr_anim = self.zoomed_camera_background_rectangle_anim
|
|
zcbr_group = self.zoomed_camera_background_rectangle_group
|
|
frame = self.zoomed_camera.frame
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
|
|
self.zoom_in_on_input(x, local_sample_dots, local_coords)
|
|
self.play(FadeIn(zoom_words))
|
|
self.wait()
|
|
local_sample_dots.save_state()
|
|
frame.save_state()
|
|
self.mini_line.save_state()
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=local_coords
|
|
)
|
|
self.remove(sample_dot_ghost_copies)
|
|
self.wait()
|
|
|
|
# Go back
|
|
self.play(
|
|
frame.restore,
|
|
self.mini_line.restore,
|
|
local_sample_dots.restore,
|
|
zcbr_anim,
|
|
Animation(zcbr_group)
|
|
)
|
|
self.wait()
|
|
|
|
# Zoom in even more
|
|
extra_zoom_factor = 0.3
|
|
one_group = VGroup(
|
|
self.local_coordinates.tick_marks[1],
|
|
self.local_coordinates.numbers[1],
|
|
)
|
|
all_other_coordinates = VGroup(
|
|
self.local_coordinates.tick_marks[::2],
|
|
self.local_coordinates.numbers[::2],
|
|
self.local_target_coordinates,
|
|
)
|
|
self.play(frame.scale, extra_zoom_factor)
|
|
new_local_sample_dots = self.get_local_sample_dots(x, delta_x=0.005)
|
|
new_coordinate_values = self.get_local_coordinate_values(x, dx=0.02)
|
|
new_local_coordinates = self.get_local_coordinates(
|
|
self.input_line, *new_coordinate_values
|
|
)
|
|
|
|
self.play(
|
|
Write(new_local_coordinates),
|
|
Write(new_local_sample_dots),
|
|
one_group.scale, extra_zoom_factor, {"about_point": self.get_input_point(1)},
|
|
FadeOut(all_other_coordinates),
|
|
*[
|
|
ApplyMethod(dot.scale, extra_zoom_factor)
|
|
for dot in local_sample_dots
|
|
]
|
|
)
|
|
self.remove(one_group, local_sample_dots)
|
|
zcbr_group.remove(
|
|
self.local_coordinates, self.local_target_coordinates,
|
|
local_sample_dots
|
|
)
|
|
|
|
# Transform new zoomed view
|
|
stretch_by_two_words = self.get_stretch_words(2)
|
|
self.add_foreground_mobject(stretch_by_two_words)
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=new_local_sample_dots,
|
|
target_coordinate_values=new_coordinate_values,
|
|
added_anims=[FadeIn(stretch_by_two_words)]
|
|
)
|
|
self.remove(sample_dot_ghost_copies)
|
|
self.wait()
|
|
|
|
# Write derivative
|
|
deriv_equation = self.get_deriv_equation(1, 2, color=RED)
|
|
self.play(Write(deriv_equation))
|
|
self.wait()
|
|
|
|
|
|
class ZoomInOnXSquaredNearThree(ZoomInOnXSquaredNearOne):
|
|
CONFIG = {
|
|
"zoomed_display_width": 4,
|
|
}
|
|
|
|
def construct(self):
|
|
zoom_words = TextMobject("Zoomed view \\\\ near 3")
|
|
zoom_words.next_to(self.zoomed_display, DOWN)
|
|
|
|
x = 3
|
|
local_sample_dots = self.get_local_sample_dots(x)
|
|
local_coordinate_values = self.get_local_coordinate_values(x, dx=0.1)
|
|
target_coordinate_values = self.get_local_coordinate_values(self.func(x), dx=0.1)
|
|
|
|
color = self.sample_dots[len(self.sample_dots) / 2].get_color()
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
stretch_words = self.get_stretch_words(2 * x, color)
|
|
deriv_equation = self.get_deriv_equation(x, 2 * x, color)
|
|
|
|
self.add(deriv_equation)
|
|
self.zoom_in_on_input(
|
|
x,
|
|
pop_out=False,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values
|
|
)
|
|
self.play(Write(zoom_words, run_time=1))
|
|
self.wait()
|
|
self.add_foreground_mobject(stretch_words)
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=target_coordinate_values,
|
|
added_anims=[Write(stretch_words)]
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class ZoomInOnXSquaredNearOneFourth(ZoomInOnXSquaredNearOne):
|
|
CONFIG = {
|
|
"zoom_factor": 0.01,
|
|
"local_coordinate_num_decimal_places": 4,
|
|
"zoomed_display_width": 4,
|
|
"default_delta_x": 0.25,
|
|
}
|
|
|
|
def construct(self):
|
|
# Much copy-pasting from previous scenes. Not great, but
|
|
# the fastest way to get the ease-of-tweaking I'd like.
|
|
zoom_words = TextMobject("Zoomed view \\\\ near $1/4$")
|
|
zoom_words.next_to(self.zoomed_display, DOWN)
|
|
|
|
x = 0.25
|
|
local_sample_dots = self.get_local_sample_dots(
|
|
x, sample_radius=2.5 * self.zoomed_camera.frame.get_width(),
|
|
)
|
|
local_coordinate_values = self.get_local_coordinate_values(
|
|
x, dx=0.01,
|
|
)
|
|
target_coordinate_values = self.get_local_coordinate_values(
|
|
self.func(x), dx=0.01,
|
|
)
|
|
|
|
color = RED
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
stretch_words = self.get_stretch_words("1/2", color, less_than_one=True)
|
|
deriv_equation = self.get_deriv_equation("1/4", "1/2", color)
|
|
|
|
one_fourth_point = self.get_input_point(x)
|
|
one_fourth_arrow = Vector(0.5 * UP, color=WHITE)
|
|
one_fourth_arrow.stem.stretch(0.75, 0)
|
|
one_fourth_arrow.tip.scale(0.75, about_edge=DOWN)
|
|
one_fourth_arrow.next_to(one_fourth_point, DOWN, SMALL_BUFF)
|
|
one_fourth_label = TexMobject("0.25")
|
|
one_fourth_label.match_height(self.input_line.numbers)
|
|
one_fourth_label.next_to(one_fourth_arrow, DOWN, SMALL_BUFF)
|
|
|
|
self.add(deriv_equation)
|
|
self.zoom_in_on_input(
|
|
x,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values,
|
|
pop_out=False,
|
|
first_added_anims=[
|
|
FadeIn(one_fourth_label),
|
|
GrowArrow(one_fourth_arrow),
|
|
]
|
|
)
|
|
self.play(Write(zoom_words, run_time=1))
|
|
self.wait()
|
|
self.add_foreground_mobject(stretch_words)
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=target_coordinate_values,
|
|
added_anims=[Write(stretch_words)]
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class ZoomInOnXSquaredNearZero(ZoomInOnXSquaredNearOne):
|
|
CONFIG = {
|
|
"zoom_factor": 0.1,
|
|
"zoomed_display_width": 4,
|
|
"scale_by_term": "???",
|
|
}
|
|
|
|
def construct(self):
|
|
zoom_words = TextMobject(
|
|
"Zoomed %sx \\\\ near 0" % "{:,}".format(int(1.0 / self.zoom_factor))
|
|
)
|
|
zoom_words.next_to(self.zoomed_display, DOWN)
|
|
|
|
x = 0
|
|
local_sample_dots = self.get_local_sample_dots(
|
|
x, sample_radius=2 * self.zoomed_camera.frame.get_width()
|
|
)
|
|
local_coordinate_values = self.get_local_coordinate_values(
|
|
x, dx=self.zoom_factor
|
|
)
|
|
# target_coordinate_values = self.get_local_coordinate_values(
|
|
# self.func(x), dx=self.zoom_factor
|
|
# )
|
|
|
|
color = self.sample_dots[len(self.sample_dots) / 2].get_color()
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
stretch_words = self.get_stretch_words(
|
|
self.scale_by_term, color, less_than_one=True
|
|
)
|
|
deriv_equation = self.get_deriv_equation(x, 2 * x, color)
|
|
|
|
self.add(deriv_equation)
|
|
self.zoom_in_on_input(
|
|
x,
|
|
pop_out=False,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values
|
|
)
|
|
self.play(Write(zoom_words, run_time=1))
|
|
self.wait()
|
|
self.add_foreground_mobject(stretch_words)
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=local_sample_dots,
|
|
# target_coordinate_values=target_coordinate_values,
|
|
added_anims=[
|
|
Write(stretch_words),
|
|
MaintainPositionRelativeTo(
|
|
self.local_coordinates,
|
|
self.zoomed_camera.frame
|
|
)
|
|
]
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero):
|
|
def construct(self):
|
|
x = 0
|
|
color = self.sample_dots[len(self.sample_dots) / 2].get_color()
|
|
deriv_equation = self.get_deriv_equation(x, 2 * x, color)
|
|
self.add(deriv_equation)
|
|
|
|
frame = self.zoomed_camera.frame
|
|
zoomed_display_height = self.zoomed_display.get_height()
|
|
|
|
last_sample_dots = VGroup()
|
|
last_coords = VGroup()
|
|
last_zoom_words = None
|
|
for factor in 0.1, 0.01, 0.001, 0.0001:
|
|
frame.save_state()
|
|
frame.set_height(factor * zoomed_display_height)
|
|
self.local_coordinate_num_decimal_places = int(-np.log10(factor))
|
|
zoom_words = TextMobject(
|
|
"Zoomed", "{:,}x \\\\".format(int(1.0 / factor)),
|
|
"near 0",
|
|
)
|
|
zoom_words.next_to(self.zoomed_display, DOWN)
|
|
|
|
sample_dots = self.get_local_sample_dots(x)
|
|
coords = self.get_local_coordinate_values(x, dx=factor)
|
|
frame.restore()
|
|
|
|
added_anims = [
|
|
ApplyMethod(last_sample_dots.fade, 1),
|
|
ApplyMethod(last_coords.fade, 1),
|
|
]
|
|
if last_zoom_words is not None:
|
|
added_anims.append(ReplacementTransform(
|
|
last_zoom_words, zoom_words
|
|
))
|
|
else:
|
|
added_anims.append(FadeIn(zoom_words))
|
|
self.zoom_in_on_input(
|
|
x,
|
|
local_sample_dots=sample_dots,
|
|
local_coordinate_values=coords,
|
|
pop_out=False,
|
|
zoom_factor=factor,
|
|
first_added_anims=added_anims
|
|
)
|
|
self.wait()
|
|
last_sample_dots = sample_dots
|
|
last_coords = self.local_coordinates
|
|
last_zoom_words = zoom_words
|
|
|
|
|
|
class ZoomInOnXSquared100xZero(ZoomInOnXSquaredNearZero):
|
|
CONFIG = {
|
|
"zoom_factor": 0.01
|
|
}
|
|
|
|
|
|
class ZoomInOnXSquared1000xZero(ZoomInOnXSquaredNearZero):
|
|
CONFIG = {
|
|
"zoom_factor": 0.001,
|
|
"local_coordinate_num_decimal_places": 3,
|
|
}
|
|
|
|
|
|
class ZoomInOnXSquared10000xZero(ZoomInOnXSquaredNearZero):
|
|
CONFIG = {
|
|
"zoom_factor": 0.0001,
|
|
"local_coordinate_num_decimal_places": 4,
|
|
"scale_by_term": "0",
|
|
}
|
|
|
|
|
|
class XSquaredForNegativeInput(TalkThroughXSquaredExample):
|
|
CONFIG = {
|
|
"input_line_config": {
|
|
"x_min": -4,
|
|
"x_max": 4,
|
|
},
|
|
"input_line_zero_point": 0.5 * UP + 0 * LEFT,
|
|
"output_line_config": {},
|
|
"default_mapping_animation_config": {
|
|
"path_arc": 30 * DEGREES
|
|
},
|
|
"zoomed_display_width": 4,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_full_transformation()
|
|
self.zoom_in_on_example()
|
|
|
|
def show_full_transformation(self):
|
|
sample_dots = self.get_sample_dots(
|
|
x_min=-4.005,
|
|
delta_x=0.05,
|
|
dot_radius=0.05
|
|
)
|
|
sample_dots.set_fill(opacity=0.8)
|
|
|
|
self.play(LaggedStartMap(DrawBorderThenFill, sample_dots))
|
|
self.play(LaggedStartMap(
|
|
ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1],
|
|
lambda mob: (
|
|
lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK),
|
|
mob,
|
|
),
|
|
rate_func=there_and_back,
|
|
))
|
|
self.add_sample_dot_ghosts(sample_dots)
|
|
self.apply_function(self.func, sample_dots=sample_dots)
|
|
self.wait()
|
|
|
|
def zoom_in_on_example(self):
|
|
x = -2
|
|
|
|
local_sample_dots = self.get_local_sample_dots(x)
|
|
local_coordinate_values = self.get_local_coordinate_values(
|
|
x, dx=0.1
|
|
)
|
|
target_coordinate_values = self.get_local_coordinate_values(
|
|
self.func(x), dx=0.1
|
|
)
|
|
deriv_equation = self.get_deriv_equation(x, 2 * x, color=BLUE)
|
|
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
|
|
scale_words = self.get_stretch_words(-4, color=BLUE)
|
|
|
|
self.zoom_in_on_input(
|
|
x,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values,
|
|
)
|
|
self.wait()
|
|
self.play(Write(deriv_equation))
|
|
self.add_foreground_mobject(scale_words)
|
|
self.play(Write(scale_words))
|
|
self.apply_function(
|
|
self.func,
|
|
sample_dots=sample_dot_ghost_copies,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=target_coordinate_values
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class FeelsALittleCramped(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Kind of cramped,\\\\ isn't it?",
|
|
target_mode="sassy"
|
|
)
|
|
self.wait()
|
|
self.teacher_says(
|
|
"Sure, but think \\\\ locally"
|
|
)
|
|
self.change_all_student_modes("pondering", look_at_arg=self.screen)
|
|
self.wait(3)
|
|
|
|
|
|
class HowDoesThisSolveProblems(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Is this...useful?",
|
|
target_mode="confused"
|
|
)
|
|
self.change_student_modes("maybe", "confused", "sassy")
|
|
self.play(self.teacher.change, "happy")
|
|
self.wait(3)
|
|
|
|
|
|
class IntroduceContinuedFractionPuzzle(PiCreatureScene):
|
|
CONFIG = {
|
|
"remove_initial_rhs": True,
|
|
}
|
|
|
|
def construct(self):
|
|
self.ask_question()
|
|
self.set_equal_to_x()
|
|
|
|
def create_pi_creatures(self):
|
|
morty = Mortimer(height=2)
|
|
morty.to_corner(DR)
|
|
|
|
friend = PiCreature(color=GREEN, height=2)
|
|
friend.to_edge(DOWN)
|
|
friend.shift(LEFT)
|
|
|
|
group = VGroup(morty, friend)
|
|
group.shift(2 * LEFT)
|
|
|
|
return morty, friend
|
|
|
|
def ask_question(self):
|
|
morty, friend = self.pi_creatures
|
|
frac = get_phi_continued_fraction(9)
|
|
frac.scale(0.8)
|
|
rhs = DecimalNumber(
|
|
(1 - np.sqrt(5)) / 2.0,
|
|
num_decimal_places=5,
|
|
show_ellipsis=True,
|
|
)
|
|
rhs.set_color(YELLOW)
|
|
equals = TexMobject("=")
|
|
equals.next_to(frac.get_part_by_tex("\\over"), RIGHT)
|
|
rhs.next_to(equals, RIGHT)
|
|
group = VGroup(frac, equals, rhs)
|
|
group.scale(1.5)
|
|
group.to_corner(UR)
|
|
|
|
self.play(
|
|
LaggedStartMap(
|
|
Write, frac,
|
|
run_time=15,
|
|
lag_ratio=0.15,
|
|
),
|
|
FadeInFromDown(equals),
|
|
FadeInFromDown(rhs),
|
|
PiCreatureSays(
|
|
friend, "Would this be valid? \\\\ If not, why not?",
|
|
target_mode="confused",
|
|
look_at_arg=frac,
|
|
bubble_kwargs={
|
|
"direction": RIGHT,
|
|
"width": 4,
|
|
"height": 3,
|
|
}
|
|
),
|
|
morty.change, "pondering",
|
|
)
|
|
self.wait()
|
|
|
|
anims = [
|
|
RemovePiCreatureBubble(
|
|
friend, target_mode="pondering",
|
|
look_at_arg=frac
|
|
),
|
|
]
|
|
if self.remove_initial_rhs:
|
|
anims += [
|
|
Animation(frac),
|
|
FadeOut(equals),
|
|
rhs.scale, 0.5,
|
|
rhs.to_corner, DL,
|
|
]
|
|
self.play(*anims)
|
|
|
|
self.neg_one_over_phi = rhs
|
|
self.equals = equals
|
|
self.frac = frac
|
|
|
|
def set_equal_to_x(self):
|
|
frac = self.frac
|
|
morty, friend = self.get_pi_creatures()
|
|
|
|
inner_frac = frac[4:]
|
|
inner_frac_rect = SurroundingRectangle(
|
|
inner_frac, stroke_width=2, buff=0.5 * SMALL_BUFF
|
|
)
|
|
inner_frac_group = VGroup(inner_frac, inner_frac_rect)
|
|
|
|
equals = TexMobject("=")
|
|
equals.next_to(frac[3], RIGHT)
|
|
x, new_x = [TexMobject("x") for i in range(2)]
|
|
xs = VGroup(x, new_x)
|
|
xs.set_color(YELLOW)
|
|
xs.scale(1.3)
|
|
x.next_to(equals, RIGHT)
|
|
new_x.next_to(frac[3], DOWN, 2 * SMALL_BUFF)
|
|
|
|
fixed_point_words = VGroup(
|
|
TextMobject("Fixed point of"),
|
|
TexMobject(
|
|
"f(x) = 1 + \\frac{1}{x}",
|
|
tex_to_color_map={"x": YELLOW}
|
|
)
|
|
)
|
|
fixed_point_words.arrange(DOWN)
|
|
|
|
self.play(Write(x), Write(equals))
|
|
self.wait()
|
|
self.play(ShowCreation(inner_frac_rect))
|
|
self.wait()
|
|
self.play(
|
|
inner_frac_group.scale, 0.75,
|
|
inner_frac_group.center,
|
|
inner_frac_group.to_edge, LEFT,
|
|
ReplacementTransform(
|
|
x.copy(), new_x,
|
|
path_arc=-90 * DEGREES
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
frac[3].stretch, 0.1, 0, {"about_edge": RIGHT},
|
|
MaintainPositionRelativeTo(
|
|
VGroup(frac[2], new_x), frac[3]
|
|
),
|
|
UpdateFromFunc(
|
|
frac[:2], lambda m: m.next_to(frac[3], LEFT)
|
|
)
|
|
)
|
|
self.wait()
|
|
fixed_point_words.next_to(VGroup(frac[0], xs), DOWN, LARGE_BUFF)
|
|
self.play(
|
|
Write(fixed_point_words),
|
|
morty.change, "hooray",
|
|
friend.change, "happy"
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class GraphOnePlusOneOverX(GraphScene):
|
|
CONFIG = {
|
|
"x_min": -6,
|
|
"x_max": 6,
|
|
"x_axis_width": 12,
|
|
"y_min": -4,
|
|
"y_max": 5,
|
|
"y_axis_height": 8,
|
|
"y_axis_label": None,
|
|
"graph_origin": 0.5 * DOWN,
|
|
"num_graph_anchor_points": 100,
|
|
"func_graph_color": GREEN,
|
|
"identity_graph_color": BLUE,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_title()
|
|
self.setup_axes()
|
|
self.draw_graphs()
|
|
self.show_solutions()
|
|
|
|
def add_title(self):
|
|
title = self.title = TexMobject(
|
|
"\\text{Solve: }", "1 + \\frac{1}{x}", "=", "x",
|
|
)
|
|
title.set_color_by_tex("x", self.identity_graph_color, substring=False)
|
|
title.set_color_by_tex("frac", self.func_graph_color)
|
|
title.to_corner(UL)
|
|
self.add(title)
|
|
|
|
def setup_axes(self):
|
|
GraphScene.setup_axes(self)
|
|
step = 2
|
|
self.x_axis.add_numbers(*list(range(-6, 0, step)) + list(range(step, 7, step)))
|
|
self.y_axis.label_direction = RIGHT
|
|
self.y_axis.add_numbers(*list(range(-2, 0, step)) + list(range(step, 4, step)))
|
|
|
|
def draw_graphs(self, animate=True):
|
|
lower_func_graph, upper_func_graph = func_graph = VGroup(*[
|
|
self.get_graph(
|
|
lambda x: 1.0 + 1.0 / x,
|
|
x_min=x_min,
|
|
x_max=x_max,
|
|
color=self.func_graph_color,
|
|
)
|
|
for x_min, x_max in [(-10, -0.1), (0.1, 10)]
|
|
])
|
|
func_graph.label = self.get_graph_label(
|
|
upper_func_graph, "y = 1 + \\frac{1}{x}",
|
|
x_val=6, direction=UP,
|
|
)
|
|
|
|
identity_graph = self.get_graph(
|
|
lambda x: x, color=self.identity_graph_color
|
|
)
|
|
identity_graph.label = self.get_graph_label(
|
|
identity_graph, "y = x",
|
|
x_val=3, direction=UL, buff=SMALL_BUFF
|
|
)
|
|
|
|
if animate:
|
|
for graph in func_graph, identity_graph:
|
|
self.play(
|
|
ShowCreation(graph),
|
|
Write(graph.label),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
else:
|
|
self.add(
|
|
func_graph, func_graph.label,
|
|
identity_graph, identity_graph.label,
|
|
)
|
|
|
|
self.func_graph = func_graph
|
|
self.identity_graph = identity_graph
|
|
|
|
def show_solutions(self):
|
|
phi = 0.5 * (1 + np.sqrt(5))
|
|
phi_bro = 0.5 * (1 - np.sqrt(5))
|
|
|
|
lines = VGroup()
|
|
for num in phi, phi_bro:
|
|
line = DashedLine(
|
|
self.coords_to_point(num, 0),
|
|
self.coords_to_point(num, num),
|
|
color=WHITE
|
|
)
|
|
line_copy = line.copy()
|
|
line_copy.set_color(YELLOW)
|
|
line.fade(0.5)
|
|
line_anim = ShowCreationThenDestruction(
|
|
line_copy,
|
|
lag_ratio=0.5,
|
|
run_time=2
|
|
)
|
|
cycle_animation(line_anim)
|
|
lines.add(line)
|
|
|
|
phi_line, phi_bro_line = lines
|
|
|
|
decimal_kwargs = {
|
|
"num_decimal_places": 3,
|
|
"show_ellipsis": True,
|
|
"color": YELLOW,
|
|
}
|
|
arrow_kwargs = {
|
|
"buff": SMALL_BUFF,
|
|
"color": WHITE,
|
|
"tip_length": 0.15,
|
|
"rectangular_stem_width": 0.025,
|
|
}
|
|
|
|
phi_decimal = DecimalNumber(phi, **decimal_kwargs)
|
|
phi_decimal.next_to(phi_line, DOWN, LARGE_BUFF)
|
|
phi_arrow = Arrow(
|
|
phi_decimal[:4].get_top(), phi_line.get_bottom(),
|
|
**arrow_kwargs
|
|
)
|
|
phi_label = TexMobject("=", "\\varphi")
|
|
phi_label.next_to(phi_decimal, RIGHT)
|
|
phi_label.set_color_by_tex("\\varphi", YELLOW)
|
|
|
|
phi_bro_decimal = DecimalNumber(phi_bro, **decimal_kwargs)
|
|
phi_bro_decimal.next_to(phi_bro_line, UP, LARGE_BUFF)
|
|
phi_bro_decimal.shift(0.5 * LEFT)
|
|
phi_bro_arrow = Arrow(
|
|
phi_bro_decimal[:6].get_bottom(), phi_bro_line.get_top(),
|
|
**arrow_kwargs
|
|
)
|
|
|
|
brother_words = TextMobject(
|
|
"$\\varphi$'s little brother",
|
|
tex_to_color_map={"$\\varphi$": YELLOW},
|
|
arg_separator=""
|
|
)
|
|
brother_words.next_to(
|
|
phi_bro_decimal[-2], UP, buff=MED_SMALL_BUFF,
|
|
aligned_edge=RIGHT
|
|
)
|
|
|
|
self.add(phi_line.continual_anim)
|
|
self.play(ShowCreation(phi_line))
|
|
self.play(
|
|
Write(phi_decimal),
|
|
GrowArrow(phi_arrow),
|
|
)
|
|
self.play(Write(phi_label))
|
|
self.wait(3)
|
|
self.add(phi_bro_line.continual_anim)
|
|
self.play(ShowCreation(phi_bro_line))
|
|
self.play(
|
|
Write(phi_bro_decimal),
|
|
GrowArrow(phi_bro_arrow),
|
|
)
|
|
self.wait(4)
|
|
self.play(Write(brother_words))
|
|
self.wait(8)
|
|
|
|
|
|
class ThinkAboutWithRepeatedApplication(IntroduceContinuedFractionPuzzle):
|
|
CONFIG = {
|
|
"remove_initial_rhs": False,
|
|
}
|
|
|
|
def construct(self):
|
|
self.force_skipping()
|
|
self.ask_question()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.obviously_not()
|
|
self.ask_about_fraction()
|
|
self.plug_func_into_self()
|
|
|
|
def obviously_not(self):
|
|
morty, friend = self.get_pi_creatures()
|
|
friend.change_mode("confused")
|
|
randy = Randolph()
|
|
randy.match_height(morty)
|
|
randy.to_corner(DL)
|
|
|
|
frac = self.frac
|
|
rhs = self.neg_one_over_phi
|
|
plusses = frac[1::4]
|
|
plus_rects = VGroup(*[
|
|
SurroundingRectangle(plus, buff=0) for plus in plusses
|
|
])
|
|
plus_rects.set_color(PINK)
|
|
|
|
self.play(FadeIn(randy))
|
|
self.play(
|
|
PiCreatureSays(
|
|
randy, "Obviously not!",
|
|
bubble_kwargs={"width": 3, "height": 2},
|
|
target_mode="angry",
|
|
run_time=1,
|
|
),
|
|
morty.change, "guilty",
|
|
friend.change, "hesitant"
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Animation(frac),
|
|
RemovePiCreatureBubble(randy, target_mode="sassy"),
|
|
morty.change, "confused",
|
|
friend.change, "confused",
|
|
)
|
|
self.play(LaggedStartMap(
|
|
ShowCreationThenDestruction, plus_rects,
|
|
run_time=2,
|
|
lag_ratio=0.35,
|
|
))
|
|
self.play(WiggleOutThenIn(rhs))
|
|
self.wait(2)
|
|
self.play(
|
|
frac.scale, 0.7,
|
|
frac.to_corner, UL,
|
|
FadeOut(self.equals),
|
|
rhs.scale, 0.5,
|
|
rhs.center,
|
|
rhs.to_edge, LEFT,
|
|
FadeOut(randy),
|
|
morty.change, "pondering",
|
|
friend.change, "pondering",
|
|
)
|
|
|
|
def ask_about_fraction(self):
|
|
frac = self.frac
|
|
arrow = Vector(LEFT, color=RED)
|
|
arrow.next_to(frac, RIGHT)
|
|
question = TextMobject("What does this \\\\ actually mean?")
|
|
question.set_color(RED)
|
|
question.next_to(arrow, RIGHT)
|
|
|
|
self.play(
|
|
LaggedStartMap(FadeIn, question, run_time=1),
|
|
GrowArrow(arrow),
|
|
LaggedStartMap(
|
|
ApplyMethod, frac,
|
|
lambda m: (m.set_color, RED),
|
|
rate_func=there_and_back,
|
|
lag_ratio=0.2,
|
|
run_time=2
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(question), FadeOut(arrow))
|
|
|
|
def plug_func_into_self(self, value=1, value_str="1"):
|
|
morty, friend = self.pi_creatures
|
|
|
|
def func(x):
|
|
return 1 + 1.0 / x
|
|
|
|
lines = VGroup()
|
|
value_labels = VGroup()
|
|
for n_terms in range(5):
|
|
lhs = get_nested_f(n_terms, arg="c")
|
|
equals = TexMobject("=")
|
|
rhs = get_nested_one_plus_one_over_x(n_terms, bottom_term=value_str)
|
|
equals.next_to(rhs[0], LEFT)
|
|
lhs.next_to(equals, LEFT)
|
|
lines.add(VGroup(lhs, equals, rhs))
|
|
|
|
value_label = TexMobject("= %.3f\\dots" % value)
|
|
value = func(value)
|
|
value_labels.add(value_label)
|
|
|
|
lines.arrange(
|
|
DOWN, buff=MED_LARGE_BUFF,
|
|
)
|
|
VGroup(lines, value_labels).scale(0.8)
|
|
lines.to_corner(UR)
|
|
buff = MED_LARGE_BUFF + MED_SMALL_BUFF + value_labels.get_width()
|
|
lines.to_edge(RIGHT, buff=buff)
|
|
for line, value_label in zip(lines, value_labels):
|
|
value_label.move_to(line[1]).to_edge(RIGHT)
|
|
|
|
top_line = lines[0]
|
|
colors = [WHITE] + color_gradient([YELLOW, RED, PINK], len(lines) - 1)
|
|
for n in range(1, len(lines)):
|
|
color = colors[n]
|
|
lines[n][0].set_color(color)
|
|
lines[n][0][1:-1].match_style(lines[n - 1][0])
|
|
lines[n][2].set_color(color)
|
|
lines[n][2][4:].match_style(lines[n - 1][2])
|
|
|
|
arrow = Vector(0.5 * DOWN, color=WHITE)
|
|
arrow.next_to(value_labels[-1], DOWN)
|
|
q_marks = TexMobject("???")
|
|
q_marks.next_to(arrow, DOWN)
|
|
|
|
self.play(
|
|
FadeInFromDown(top_line),
|
|
FadeInFromDown(value_labels[0])
|
|
)
|
|
for n in range(1, len(lines)):
|
|
new_line = lines[n]
|
|
last_line = lines[n - 1]
|
|
value_label = value_labels[n]
|
|
mover, target = [
|
|
VGroup(
|
|
line[0][0],
|
|
line[0][-1],
|
|
line[1],
|
|
line[2][:4],
|
|
)
|
|
for line in (lines[1], new_line)
|
|
]
|
|
anims = [ReplacementTransform(
|
|
mover.copy().fade(1), target, path_arc=30 * DEGREES
|
|
)]
|
|
if n == 4:
|
|
morty.generate_target()
|
|
morty.target.change("horrified")
|
|
morty.target.shift(2.5 * DOWN)
|
|
anims.append(MoveToTarget(morty, remover=True))
|
|
self.play(*anims)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(
|
|
last_line[0].copy(), new_line[0][1:-1]
|
|
),
|
|
ReplacementTransform(
|
|
last_line[2].copy(), new_line[2][4:]
|
|
),
|
|
)
|
|
self.play(FadeIn(value_label))
|
|
self.wait()
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
Write(q_marks),
|
|
friend.change, "confused"
|
|
)
|
|
self.wait(3)
|
|
|
|
self.top_line = VGroup(lines[0], value_labels[0])
|
|
|
|
|
|
class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication):
|
|
CONFIG = {
|
|
"value": (1 - np.sqrt(5)) / 2,
|
|
"value_str": "-1/\\varphi",
|
|
}
|
|
|
|
def construct(self):
|
|
self.force_skipping()
|
|
self.ask_question()
|
|
self.obviously_not()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.plug_func_into_self(
|
|
value=self.value,
|
|
value_str=self.value_str
|
|
)
|
|
|
|
|
|
class RepeatedApplicationWithNegativeSeed(RepeatedApplicationWithPhiBro, MovingCameraScene):
|
|
CONFIG = {
|
|
"value": -0.65,
|
|
"value_str": "-0.65"
|
|
}
|
|
|
|
def setup(self):
|
|
MovingCameraScene.setup(self)
|
|
RepeatedApplicationWithPhiBro.setup(self)
|
|
|
|
def construct(self):
|
|
RepeatedApplicationWithPhiBro.construct(self)
|
|
|
|
rect = SurroundingRectangle(self.top_line)
|
|
question = TextMobject("What about a negative seed?")
|
|
question.match_color(rect)
|
|
question.next_to(rect, UP)
|
|
self.play(ShowCreation(rect))
|
|
self.play(
|
|
Write(question),
|
|
self.camera.frame.set_height, FRAME_HEIGHT + 1.5
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(question),
|
|
FadeOut(rect),
|
|
self.camera.frame.set_height, FRAME_HEIGHT
|
|
)
|
|
|
|
|
|
class ShowRepeatedApplication(Scene):
|
|
CONFIG = {
|
|
"title_color": YELLOW,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_func_title()
|
|
self.show_repeated_iteration()
|
|
|
|
def add_func_title(self):
|
|
title = self.title = VGroup(
|
|
TexMobject("f(", "x", ")"),
|
|
TexMobject("="),
|
|
get_nested_one_plus_one_over_x(1)
|
|
)
|
|
title.arrange(RIGHT)
|
|
title.to_corner(UL)
|
|
title.set_color(self.title_color)
|
|
|
|
self.add(title)
|
|
|
|
def show_repeated_iteration(self):
|
|
line = VGroup()
|
|
decimal_kwargs = {
|
|
"num_decimal_places": 3,
|
|
"show_ellipsis": True,
|
|
}
|
|
phi = (1 + np.sqrt(5)) / 2
|
|
|
|
def func(x):
|
|
return 1.0 + 1.0 / x
|
|
|
|
initial_term = DecimalNumber(2.71828, **decimal_kwargs)
|
|
line.add(initial_term)
|
|
last_term = initial_term
|
|
|
|
def get_arrow():
|
|
arrow = TexMobject("\\rightarrow")
|
|
arrow.stretch(1.5, 0)
|
|
arrow.next_to(line[-1], RIGHT)
|
|
tex = TexMobject("f(x)")
|
|
tex.set_color(YELLOW)
|
|
tex.match_width(arrow)
|
|
tex.next_to(arrow, UP, SMALL_BUFF)
|
|
return VGroup(arrow, tex)
|
|
|
|
for x in range(2):
|
|
arrow = get_arrow()
|
|
line.add(arrow)
|
|
|
|
new_term = DecimalNumber(
|
|
func(last_term.number),
|
|
**decimal_kwargs
|
|
)
|
|
new_term.next_to(arrow[0], RIGHT)
|
|
last_term = new_term
|
|
line.add(new_term)
|
|
|
|
line.add(get_arrow())
|
|
line.add(TexMobject("\\dots\\dots").next_to(line[-1][0], RIGHT))
|
|
num_phi_mob = DecimalNumber(phi, **decimal_kwargs)
|
|
line.add(num_phi_mob.next_to(line[-1], RIGHT))
|
|
line.move_to(DOWN)
|
|
|
|
rects = VGroup(*[
|
|
SurroundingRectangle(mob)
|
|
for mob in (line[0], line[1:-1], line[-1])
|
|
])
|
|
rects.set_stroke(BLUE, 2)
|
|
|
|
braces = VGroup(*[
|
|
Brace(rect, DOWN, buff=SMALL_BUFF)
|
|
for rect in rects
|
|
])
|
|
braces.set_color_by_gradient(GREEN, YELLOW)
|
|
brace_texts = VGroup(*[
|
|
brace.get_text(text).scale(0.75, about_edge=UP)
|
|
for brace, text in zip(braces, [
|
|
"Arbitrary \\\\ starting \\\\ value",
|
|
"Repeatedly apply $f(x)$",
|
|
"$\\varphi$ \\\\ ``Golden ratio''"
|
|
])
|
|
])
|
|
var_phi_mob = brace_texts[2][0]
|
|
var_phi_mob.scale(2, about_edge=UP).set_color(YELLOW)
|
|
brace_texts[2][1:].next_to(var_phi_mob, DOWN, MED_SMALL_BUFF)
|
|
|
|
# Animations
|
|
self.add(line[0])
|
|
self.play(
|
|
GrowFromCenter(braces[0]),
|
|
Write(brace_texts[0])
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromEdge(line[1], LEFT),
|
|
FadeIn(braces[1]),
|
|
FadeIn(brace_texts[1]),
|
|
)
|
|
self.play(ReplacementTransform(line[0].copy(), line[2]))
|
|
self.wait()
|
|
|
|
self.play(GrowFromEdge(line[3], LEFT))
|
|
self.play(ReplacementTransform(line[2].copy(), line[4]))
|
|
self.wait()
|
|
|
|
self.play(GrowFromEdge(line[5], LEFT))
|
|
self.play(LaggedStartMap(GrowFromCenter, line[6]))
|
|
self.wait()
|
|
|
|
self.play(FadeIn(line[7]))
|
|
self.play(
|
|
GrowFromCenter(braces[2]),
|
|
FadeIn(brace_texts[2]),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class NumericalPlayFromOne(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class NumericalPlayFromTau(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class NumericalPlayFromNegPhi(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class NumericalPlayOnNumberLineFromOne(Scene):
|
|
CONFIG = {
|
|
"starting_value": 1,
|
|
"n_jumps": 10,
|
|
"func": lambda x: 1 + 1.0 / x,
|
|
"number_line_config": {
|
|
"x_min": 0,
|
|
"x_max": 2,
|
|
"unit_size": 6,
|
|
"tick_frequency": 0.25,
|
|
"numbers_with_elongated_ticks": [0, 1, 2]
|
|
}
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_number_line()
|
|
self.add_phi_label()
|
|
self.add_title()
|
|
self.bounce_around()
|
|
|
|
def add_number_line(self):
|
|
number_line = NumberLine(**self.number_line_config)
|
|
number_line.move_to(2 * DOWN)
|
|
number_line.add_numbers()
|
|
|
|
self.add(number_line)
|
|
self.number_line = number_line
|
|
|
|
def add_phi_label(self):
|
|
number_line = self.number_line
|
|
phi_point = number_line.number_to_point(
|
|
(1 + np.sqrt(5)) / 2
|
|
)
|
|
phi_dot = Dot(phi_point, color=YELLOW)
|
|
arrow = Vector(DL)
|
|
arrow.next_to(phi_point, UR, SMALL_BUFF)
|
|
phi_label = TexMobject("\\varphi = 1.618\\dots")
|
|
phi_label.set_color(YELLOW)
|
|
phi_label.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
|
|
self.add(phi_dot, phi_label, arrow)
|
|
|
|
def add_title(self):
|
|
title = TexMobject("x \\rightarrow 1 + \\frac{1}{x}")
|
|
title.to_corner(UL)
|
|
self.add(title)
|
|
|
|
def bounce_around(self):
|
|
number_line = self.number_line
|
|
value = self.starting_value
|
|
point = number_line.number_to_point(value)
|
|
dot = Dot(point)
|
|
dot.set_fill(RED, opacity=0.8)
|
|
arrow = Vector(DR)
|
|
arrow.next_to(point, UL, buff=SMALL_BUFF)
|
|
arrow.match_color(dot)
|
|
start_here = TextMobject("Start here")
|
|
start_here.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
start_here.match_color(dot)
|
|
|
|
self.play(
|
|
FadeIn(start_here),
|
|
GrowArrow(arrow),
|
|
GrowFromPoint(dot, arrow.get_start())
|
|
)
|
|
self.play(
|
|
FadeOut(start_here),
|
|
FadeOut(arrow)
|
|
)
|
|
self.wait()
|
|
for x in range(self.n_jumps):
|
|
new_value = self.func(value)
|
|
new_point = number_line.number_to_point(new_value)
|
|
if new_value - value > 0:
|
|
path_arc = -120 * DEGREES
|
|
else:
|
|
path_arc = -120 * DEGREES
|
|
arc = Line(
|
|
point, new_point,
|
|
path_arc=path_arc,
|
|
buff=SMALL_BUFF
|
|
)
|
|
self.play(
|
|
ShowCreationThenDestruction(arc, run_time=1.5),
|
|
ApplyMethod(
|
|
dot.move_to, new_point,
|
|
path_arc=path_arc
|
|
),
|
|
)
|
|
self.wait(0.5)
|
|
|
|
value = new_value
|
|
point = new_point
|
|
|
|
|
|
class NumericalPlayOnNumberLineFromTau(NumericalPlayOnNumberLineFromOne):
|
|
CONFIG = {
|
|
"starting_value": TAU,
|
|
"number_line_config": {
|
|
"x_min": 0,
|
|
"x_max": 7,
|
|
"unit_size": 2,
|
|
}
|
|
}
|
|
|
|
|
|
class NumericalPlayOnNumberLineFromMinusPhi(NumericalPlayOnNumberLineFromOne):
|
|
CONFIG = {
|
|
"starting_value": -0.61803,
|
|
"number_line_config": {
|
|
"x_min": -3,
|
|
"x_max": 3,
|
|
"unit_size": 2,
|
|
"tick_frequency": 0.25,
|
|
},
|
|
"n_jumps": 25,
|
|
}
|
|
|
|
def add_phi_label(self):
|
|
NumericalPlayOnNumberLineFromOne.add_phi_label(self)
|
|
number_line = self.number_line
|
|
new_point = number_line.number_to_point(
|
|
(1 - np.sqrt(5)) / 2
|
|
)
|
|
arrow = Vector(DR)
|
|
arrow.next_to(new_point, UL, SMALL_BUFF)
|
|
arrow.set_color(RED)
|
|
new_label = TexMobject("-\\frac{1}{\\varphi} = -0.618\\dots")
|
|
new_label.set_color(RED)
|
|
new_label.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
new_label.shift(RIGHT)
|
|
self.add(new_label, arrow)
|
|
|
|
|
|
class RepeatedApplicationGraphically(GraphOnePlusOneOverX, PiCreatureScene):
|
|
CONFIG = {
|
|
"starting_value": 1,
|
|
"n_jumps": 5,
|
|
"n_times_to_show_identity_property": 2,
|
|
}
|
|
|
|
def setup(self):
|
|
GraphOnePlusOneOverX.setup(self)
|
|
PiCreatureScene.setup(self)
|
|
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.draw_graphs(animate=False)
|
|
self.draw_spider_web()
|
|
|
|
def create_pi_creature(self):
|
|
randy = Randolph(height=2)
|
|
randy.flip()
|
|
randy.to_corner(DR)
|
|
return randy
|
|
|
|
def get_new_randy_mode(self):
|
|
randy = self.pi_creature
|
|
if not hasattr(self, "n_mode_changes"):
|
|
self.n_mode_changes = 0
|
|
else:
|
|
self.n_mode_changes += 1
|
|
if self.n_mode_changes % 3 != 0:
|
|
return randy.get_mode()
|
|
return random.choice([
|
|
"confused",
|
|
"erm",
|
|
"maybe"
|
|
])
|
|
|
|
def draw_spider_web(self):
|
|
randy = self.pi_creature
|
|
func = self.func_graph[0].underlying_function
|
|
x_val = self.starting_value
|
|
curr_output = 0
|
|
dot = Dot(color=RED, fill_opacity=0.7)
|
|
dot.move_to(self.coords_to_point(x_val, curr_output))
|
|
|
|
self.play(FadeInFrom(dot, 2 * UR))
|
|
self.wait()
|
|
|
|
for n in range(self.n_jumps):
|
|
new_output = func(x_val)
|
|
func_graph_point = self.coords_to_point(x_val, new_output)
|
|
id_graph_point = self.coords_to_point(new_output, new_output)
|
|
v_line = DashedLine(dot.get_center(), func_graph_point)
|
|
h_line = DashedLine(func_graph_point, id_graph_point)
|
|
|
|
curr_output = new_output
|
|
x_val = new_output
|
|
|
|
for line in v_line, h_line:
|
|
line_end = line.get_end()
|
|
self.play(
|
|
ShowCreation(line),
|
|
dot.move_to, line_end,
|
|
randy.change, self.get_new_randy_mode()
|
|
)
|
|
self.wait()
|
|
|
|
if n < self.n_times_to_show_identity_property:
|
|
x_point = self.coords_to_point(new_output, 0)
|
|
y_point = self.coords_to_point(0, new_output)
|
|
lines = VGroup(*[
|
|
Line(dot.get_center(), point)
|
|
for point in (x_point, y_point)
|
|
])
|
|
lines.set_color(YELLOW)
|
|
self.play(ShowCreationThenDestruction(
|
|
lines, run_time=2
|
|
))
|
|
self.wait(0.25)
|
|
|
|
|
|
class RepeatedApplicationGraphicallyFromNegPhi(RepeatedApplicationGraphically):
|
|
CONFIG = {
|
|
"starting_value": -0.61,
|
|
"n_jumps": 13,
|
|
"n_times_to_show_identity_property": 0,
|
|
}
|
|
|
|
|
|
class LetsSwitchToTheTransformationalView(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says(
|
|
"Lose the \\\\ graphs!",
|
|
target_mode="hooray"
|
|
)
|
|
self.change_student_modes("hooray", "erm", "surprised")
|
|
self.wait(5)
|
|
|
|
|
|
class AnalyzeFunctionWithTransformations(NumberlineTransformationScene):
|
|
CONFIG = {
|
|
"input_line_zero_point": 0.5 * UP,
|
|
"output_line_zero_point": 2 * DOWN,
|
|
"func": lambda x: 1 + 1.0 / x,
|
|
"num_initial_applications": 10,
|
|
"num_repeated_local_applications": 7,
|
|
"zoomed_display_width": 3.5,
|
|
"zoomed_display_height": 2,
|
|
"default_mapping_animation_config": {},
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_function_title()
|
|
self.repeatedly_apply_function()
|
|
self.show_phi_and_phi_bro()
|
|
self.zoom_in_on_phi()
|
|
self.zoom_in_on_phi_bro()
|
|
|
|
def setup_number_lines(self):
|
|
NumberlineTransformationScene.setup_number_lines(self)
|
|
for line in self.input_line, self.output_line:
|
|
VGroup(line, line.tick_marks).set_stroke(width=2)
|
|
|
|
def add_function_title(self):
|
|
title = TexMobject("f(x)", "=", "1 +", "\\frac{1}{x}")
|
|
title.to_edge(UP)
|
|
self.add(title)
|
|
self.title = title
|
|
|
|
def repeatedly_apply_function(self):
|
|
input_zero_point = self.input_line.number_to_point(0)
|
|
output_zero_point = self.output_line.number_to_point(0)
|
|
sample_dots = self.get_sample_dots(
|
|
delta_x=0.05,
|
|
dot_radius=0.05,
|
|
x_min=-10,
|
|
x_max=10,
|
|
)
|
|
sample_dots.set_stroke(BLACK, 0.5)
|
|
sample_points = list(map(Mobject.get_center, sample_dots))
|
|
|
|
self.play(LaggedStartMap(
|
|
FadeInFrom, sample_dots,
|
|
lambda m: (m, UP)
|
|
))
|
|
self.show_arrows(sample_points)
|
|
self.wait()
|
|
for x in range(self.num_initial_applications):
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
sample_dots=sample_dots
|
|
)
|
|
self.wait()
|
|
|
|
shift_vect = input_zero_point - output_zero_point
|
|
shift_vect[0] = 0
|
|
lower_output_line = self.output_line.copy()
|
|
upper_output_line = self.output_line.copy()
|
|
lower_output_line.shift(-shift_vect)
|
|
lower_output_line.fade(1)
|
|
|
|
self.remove(self.output_line)
|
|
self.play(
|
|
ReplacementTransform(lower_output_line, self.output_line),
|
|
upper_output_line.shift, shift_vect,
|
|
upper_output_line.fade, 1,
|
|
sample_dots.shift, shift_vect,
|
|
)
|
|
self.remove(upper_output_line)
|
|
self.wait()
|
|
self.play(FadeOut(sample_dots))
|
|
|
|
def show_arrows(self, sample_points):
|
|
input_zero_point = self.input_line.number_to_point(0)
|
|
point_func = self.number_func_to_point_func(self.func)
|
|
alt_point_func = self.number_func_to_point_func(lambda x: 1.0 / x)
|
|
arrows, alt_arrows = [
|
|
VGroup(*[
|
|
Arrow(
|
|
point, func(point), buff=SMALL_BUFF,
|
|
tip_length=0.15
|
|
)
|
|
for point in sample_points
|
|
if get_norm(point - input_zero_point) > 0.3
|
|
])
|
|
for func in (point_func, alt_point_func)
|
|
]
|
|
for group in arrows, alt_arrows:
|
|
group.set_stroke(WHITE, 0.5)
|
|
group.set_color_by_gradient(RED, YELLOW)
|
|
for arrow in group:
|
|
arrow.tip.set_stroke(BLACK, 0.5)
|
|
|
|
one_plus = self.title.get_part_by_tex("1 +")
|
|
one_plus_rect = BackgroundRectangle(one_plus)
|
|
one_plus_rect.set_fill(BLACK, opacity=0.8)
|
|
|
|
self.play(LaggedStartMap(GrowArrow, arrows))
|
|
self.wait()
|
|
self.play(LaggedStartMap(
|
|
ApplyMethod, arrows,
|
|
lambda a: (a.scale, 0.7),
|
|
rate_func=there_and_back,
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
Transform(arrows, alt_arrows),
|
|
FadeIn(one_plus_rect, remover=True),
|
|
rate_func=there_and_back_with_pause,
|
|
run_time=4,
|
|
)
|
|
self.wait()
|
|
|
|
self.all_arrows = arrows
|
|
|
|
def show_phi_and_phi_bro(self):
|
|
phi = (1 + np.sqrt(5)) / 2
|
|
phi_bro = (1 - np.sqrt(5)) / 2
|
|
|
|
input_phi_point = self.input_line.number_to_point(phi)
|
|
output_phi_point = self.output_line.number_to_point(phi)
|
|
input_phi_bro_point = self.input_line.number_to_point(phi_bro)
|
|
output_phi_bro_point = self.output_line.number_to_point(phi_bro)
|
|
|
|
tick = Line(UP, DOWN)
|
|
tick.set_stroke(YELLOW, 3)
|
|
tick.match_height(self.input_line.tick_marks)
|
|
phi_tick = tick.copy().move_to(input_phi_point, DOWN)
|
|
phi_bro_tick = tick.copy().move_to(input_phi_bro_point, DOWN)
|
|
VGroup(phi_tick, phi_bro_tick).shift(SMALL_BUFF * DOWN)
|
|
|
|
phi_label = TexMobject("1.618\\dots")
|
|
phi_label.next_to(phi_tick, UP)
|
|
phi_bro_label = TexMobject("-0.618\\dots")
|
|
phi_bro_label.next_to(phi_bro_tick, UP)
|
|
VGroup(phi_label, phi_bro_label).set_color(YELLOW)
|
|
|
|
arrow_kwargs = {
|
|
"buff": SMALL_BUFF,
|
|
"rectangular_stem_width": 0.035,
|
|
"tip_length": 0.2,
|
|
}
|
|
phi_arrow = Arrow(phi_tick, output_phi_point, **arrow_kwargs)
|
|
phi_bro_arrow = Arrow(phi_bro_tick, output_phi_bro_point, **arrow_kwargs)
|
|
|
|
def fade_arrow(arrow):
|
|
# arrow.set_stroke(DARK_GREY, 0.5)
|
|
arrow.set_stroke(width=0.1)
|
|
arrow.tip.set_fill(opacity=0)
|
|
arrow.tip.set_stroke(width=0)
|
|
return arrow
|
|
|
|
self.play(
|
|
LaggedStartMap(
|
|
ApplyFunction, self.all_arrows,
|
|
lambda a: (fade_arrow, a)
|
|
),
|
|
FadeIn(phi_arrow),
|
|
FadeIn(phi_bro_arrow),
|
|
)
|
|
self.play(
|
|
Write(phi_label),
|
|
GrowFromCenter(phi_tick)
|
|
)
|
|
self.play(
|
|
Write(phi_bro_label),
|
|
GrowFromCenter(phi_bro_tick)
|
|
)
|
|
|
|
self.set_variables_as_attrs(
|
|
input_phi_point, output_phi_point,
|
|
input_phi_bro_point, output_phi_bro_point,
|
|
phi_label, phi_tick,
|
|
phi_bro_label, phi_bro_tick,
|
|
phi_arrow, phi_bro_arrow
|
|
)
|
|
|
|
def zoom_in_on_phi(self):
|
|
phi = (1 + np.sqrt(5)) / 2
|
|
# phi_point = self.get_input_point(phi)
|
|
local_sample_dots = self.get_local_sample_dots(
|
|
phi, dot_radius=0.005, sample_radius=1
|
|
)
|
|
local_coordinate_values = [1.55, 1.6, 1.65, 1.7]
|
|
|
|
# zcbr = self.zoomed_camera_background_rectangle
|
|
zcbr_group = self.zoomed_camera_background_rectangle_group
|
|
zcbr_group.add(self.phi_tick)
|
|
|
|
title = self.title
|
|
deriv_text = TexMobject(
|
|
"|", "\\frac{df}{dx}(\\varphi)", "|", "< 1",
|
|
tex_to_color_map={"\\varphi": YELLOW}
|
|
)
|
|
deriv_text.get_parts_by_tex("|").match_height(
|
|
deriv_text, stretch=True
|
|
)
|
|
deriv_text.move_to(title, UP)
|
|
approx_value = TexMobject("\\approx |%.2f|" % (-1 / phi**2))
|
|
approx_value.move_to(deriv_text)
|
|
deriv_text_lhs = deriv_text[:-1]
|
|
deriv_text_rhs = deriv_text[-1]
|
|
|
|
self.zoom_in_on_input(
|
|
phi,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values
|
|
)
|
|
self.wait()
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=local_coordinate_values,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFromDown(deriv_text_lhs),
|
|
FadeInFromDown(deriv_text_rhs),
|
|
title.to_corner, UL
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
deriv_text_lhs.next_to, approx_value, LEFT,
|
|
deriv_text_rhs.next_to, approx_value, RIGHT,
|
|
FadeIn(approx_value)
|
|
)
|
|
self.wait()
|
|
for n in range(self.num_repeated_local_applications):
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
local_sample_dots=local_sample_dots,
|
|
path_arc=60 * DEGREES,
|
|
run_time=2
|
|
)
|
|
|
|
self.deriv_text = VGroup(
|
|
deriv_text_lhs, deriv_text_rhs, approx_value
|
|
)
|
|
|
|
def zoom_in_on_phi_bro(self):
|
|
zcbr = self.zoomed_camera_background_rectangle
|
|
# zcbr_group = self.zoomed_camera_background_rectangle_group
|
|
zoomed_frame = self.zoomed_camera.frame
|
|
|
|
phi_bro = (1 - np.sqrt(5)) / 2
|
|
# phi_bro_point = self.get_input_point(phi_bro)
|
|
local_sample_dots = self.get_local_sample_dots(phi_bro)
|
|
local_coordinate_values = [-0.65, -0.6, -0.55]
|
|
|
|
deriv_text = TexMobject(
|
|
"\\left| \\frac{df}{dx}\\left(\\frac{-1}{\\varphi}\\right) \\right|",
|
|
"\\approx |%.2f|" % (-1 / (phi_bro**2)),
|
|
"> 1"
|
|
)
|
|
deriv_text.move_to(self.deriv_text, UL)
|
|
deriv_text[0][10:14].set_color(YELLOW)
|
|
|
|
self.play(
|
|
zoomed_frame.set_height, 4,
|
|
zoomed_frame.center,
|
|
self.deriv_text.fade, 1,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
zcbr.set_fill(opacity=0)
|
|
self.zoom_in_on_input(
|
|
phi_bro,
|
|
local_sample_dots=local_sample_dots,
|
|
local_coordinate_values=local_coordinate_values,
|
|
zoom_factor=self.zoom_factor,
|
|
first_anim_kwargs={"run_time": 2},
|
|
)
|
|
self.wait()
|
|
self.play(FadeInFromDown(deriv_text))
|
|
self.wait()
|
|
zcbr.set_fill(opacity=1)
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
local_sample_dots=local_sample_dots,
|
|
target_coordinate_values=local_coordinate_values,
|
|
)
|
|
self.wait()
|
|
for n in range(self.num_repeated_local_applications):
|
|
self.apply_function(
|
|
self.func,
|
|
apply_function_to_number_line=False,
|
|
local_sample_dots=local_sample_dots,
|
|
path_arc=20 * DEGREES,
|
|
run_time=2,
|
|
)
|
|
|
|
|
|
class StabilityAndInstability(AnalyzeFunctionWithTransformations):
|
|
CONFIG = {
|
|
"num_initial_applications": 0,
|
|
}
|
|
|
|
def construct(self):
|
|
self.force_skipping()
|
|
self.add_function_title()
|
|
self.repeatedly_apply_function()
|
|
self.show_phi_and_phi_bro()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.label_stability()
|
|
self.write_derivative_fact()
|
|
|
|
def label_stability(self):
|
|
self.title.to_corner(UL)
|
|
|
|
stable_label = TextMobject("Stable fixed point")
|
|
unstable_label = TextMobject("Unstable fixed point")
|
|
labels = VGroup(stable_label, unstable_label)
|
|
labels.scale(0.8)
|
|
stable_label.next_to(self.phi_label, UP, aligned_edge=ORIGIN)
|
|
unstable_label.next_to(self.phi_bro_label, UP, aligned_edge=ORIGIN)
|
|
|
|
phi_point = self.input_phi_point
|
|
phi_bro_point = self.input_phi_bro_point
|
|
|
|
arrow_groups = VGroup()
|
|
for point in phi_point, phi_bro_point:
|
|
arrows = VGroup(*[a for a in self.all_arrows if get_norm(a.get_start() - point) < 0.75]).copy()
|
|
arrows.set_fill(PINK, 1)
|
|
arrows.set_stroke(PINK, 3)
|
|
arrows.second_anim = LaggedStartMap(
|
|
ApplyMethod, arrows,
|
|
lambda m: (m.set_color, YELLOW),
|
|
rate_func=there_and_back_with_pause,
|
|
lag_ratio=0.7,
|
|
run_time=2,
|
|
)
|
|
arrows.anim = AnimationGroup(*list(map(GrowArrow, arrows)))
|
|
arrow_groups.add(arrows)
|
|
phi_arrows, phi_bro_arrows = arrow_groups
|
|
|
|
self.add_foreground_mobjects(self.phi_arrow, self.phi_bro_arrow)
|
|
self.play(
|
|
Write(stable_label),
|
|
phi_arrows.anim,
|
|
)
|
|
self.play(phi_arrows.second_anim)
|
|
self.play(
|
|
Write(unstable_label),
|
|
phi_bro_arrows.anim,
|
|
)
|
|
self.play(phi_bro_arrows.second_anim)
|
|
self.wait()
|
|
|
|
self.stable_label = stable_label
|
|
self.unstable_label = unstable_label
|
|
self.phi_arrows = phi_arrows
|
|
self.phi_bro_arrows = phi_bro_arrows
|
|
|
|
def write_derivative_fact(self):
|
|
stable_label = self.stable_label
|
|
unstable_label = self.unstable_label
|
|
labels = VGroup(stable_label, unstable_label)
|
|
phi_arrows = self.phi_arrows
|
|
phi_bro_arrows = self.phi_bro_arrows
|
|
arrow_groups = VGroup(phi_arrows, phi_bro_arrows)
|
|
|
|
deriv_labels = VGroup()
|
|
for char, label in zip("<>", labels):
|
|
deriv_label = TexMobject(
|
|
"\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|",
|
|
char, "1"
|
|
)
|
|
deriv_label.get_parts_by_tex("\\big|").match_height(
|
|
deriv_label, stretch=True
|
|
)
|
|
deriv_label.set_color_by_tex("x", YELLOW, substring=False)
|
|
deriv_label.next_to(label, UP)
|
|
deriv_labels.add(deriv_label)
|
|
|
|
dot_groups = VGroup()
|
|
for arrow_group in arrow_groups:
|
|
dot_group = VGroup()
|
|
for arrow in arrow_group:
|
|
start_point, end_point = [
|
|
line.number_to_point(line.point_to_number(p))
|
|
for line, p in [
|
|
(self.input_line, arrow.get_start()),
|
|
(self.output_line, arrow.get_end()),
|
|
]
|
|
]
|
|
dot = Dot(start_point, radius=0.05)
|
|
dot.set_color(YELLOW)
|
|
dot.generate_target()
|
|
dot.target.move_to(end_point)
|
|
dot_group.add(dot)
|
|
dot_groups.add(dot_group)
|
|
|
|
for deriv_label, dot_group in zip(deriv_labels, dot_groups):
|
|
self.play(FadeInFromDown(deriv_label))
|
|
self.play(LaggedStartMap(GrowFromCenter, dot_group))
|
|
self.play(*list(map(MoveToTarget, dot_group)), run_time=2)
|
|
self.wait()
|
|
|
|
|
|
class StaticAlgebraicObject(Scene):
|
|
def construct(self):
|
|
frac = get_phi_continued_fraction(40)
|
|
frac.set_width(FRAME_WIDTH - 1)
|
|
# frac.shift(2 * DOWN)
|
|
frac.to_edge(DOWN)
|
|
frac.set_stroke(WHITE, width=0.5)
|
|
|
|
title = TexMobject(
|
|
"\\infty \\ne \\lim",
|
|
tex_to_color_map={"\\ne": RED}
|
|
)
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
|
|
polynomial = TexMobject("x^2 - x - 1 = 0")
|
|
polynomial.move_to(title)
|
|
|
|
self.add(title)
|
|
self.play(LaggedStartMap(
|
|
GrowFromCenter, frac,
|
|
lag_ratio=0.1,
|
|
run_time=3
|
|
))
|
|
self.wait()
|
|
factor = 1.1
|
|
self.play(frac.scale, factor, run_time=0.5)
|
|
self.play(
|
|
frac.scale, 1 / factor,
|
|
frac.set_color, LIGHT_GREY,
|
|
run_time=0.5, rate_func=lambda t: t**5,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(title),
|
|
FadeIn(polynomial)
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class NotBetterThanGraphs(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Um, yeah, I'll stick \\\\ with graphs thanks",
|
|
target_mode="sassy",
|
|
)
|
|
self.play(
|
|
self.teacher.change, "guilty",
|
|
self.get_student_changes("sad", "sassy", "hesitant")
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
RemovePiCreatureBubble(self.students[1]),
|
|
self.teacher.change, "raise_right_hand"
|
|
)
|
|
self.change_all_student_modes(
|
|
"confused", look_at_arg=self.screen
|
|
)
|
|
self.wait(3)
|
|
self.teacher_says(
|
|
"You must flex those \\\\ conceptual muscles",
|
|
added_anims=[self.get_student_changes(
|
|
*3 * ["thinking"],
|
|
look_at_arg=self.teacher.eyes
|
|
)]
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class WhatComesAfterWrapper(Wrapper):
|
|
CONFIG = {"title": "Beyond the first year"}
|
|
|
|
def construct(self):
|
|
Wrapper.construct(self)
|
|
new_title = TextMobject("Next video")
|
|
new_title.set_color(BLUE)
|
|
new_title.move_to(self.title)
|
|
|
|
self.play(
|
|
FadeInFromDown(new_title),
|
|
self.title.shift, UP,
|
|
self.title.fade, 1,
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
|
|
CONFIG = {
|
|
"pi_creatures_start_on_screen": False,
|
|
}
|
|
|
|
def construct(self):
|
|
MoreTopics.construct(self)
|
|
self.show_horror()
|
|
self.zero_in_on_complex_analysis()
|
|
|
|
def create_pi_creatures(self):
|
|
creatures = VGroup(*[
|
|
PiCreature(color=color)
|
|
for color in [BLUE_E, BLUE_C, BLUE_D]
|
|
])
|
|
creatures.arrange(RIGHT, buff=LARGE_BUFF)
|
|
creatures.scale(0.5)
|
|
creatures.to_corner(DR)
|
|
return creatures
|
|
|
|
def show_horror(self):
|
|
creatures = self.get_pi_creatures()
|
|
modes = ["horrified", "tired", "horrified"]
|
|
for creature, mode in zip(creatures, modes):
|
|
creature.generate_target()
|
|
creature.target.change(mode, self.other_topics)
|
|
creatures.fade(1)
|
|
|
|
self.play(LaggedStartMap(MoveToTarget, creatures))
|
|
self.wait(2)
|
|
|
|
def zero_in_on_complex_analysis(self):
|
|
creatures = self.get_pi_creatures()
|
|
complex_analysis = self.other_topics[1]
|
|
self.other_topics.remove(complex_analysis)
|
|
|
|
self.play(
|
|
complex_analysis.scale, 1.25,
|
|
complex_analysis.center,
|
|
complex_analysis.to_edge, UP,
|
|
LaggedStartMap(FadeOut, self.other_topics),
|
|
LaggedStartMap(FadeOut, self.lines),
|
|
FadeOut(self.calculus),
|
|
*[
|
|
ApplyMethod(creature.change, "pondering")
|
|
for creature in creatures
|
|
]
|
|
)
|
|
self.wait(4)
|
|
|
|
|
|
class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene):
|
|
CONFIG = {
|
|
"show_basis_vectors": False,
|
|
"show_coordinates": True,
|
|
"zoom_factor": 0.05,
|
|
}
|
|
|
|
def setup(self):
|
|
LinearTransformationScene.setup(self)
|
|
ZoomedScene.setup(self)
|
|
|
|
def construct(self):
|
|
def example_function(point):
|
|
x, y, z = point
|
|
return np.array([
|
|
x + np.sin(y),
|
|
y + np.sin(x),
|
|
0
|
|
])
|
|
|
|
zoomed_camera = self.zoomed_camera
|
|
zoomed_display = self.zoomed_display
|
|
frame = zoomed_camera.frame
|
|
frame.move_to(3 * LEFT + 1 * UP)
|
|
frame.set_color(YELLOW)
|
|
zoomed_display.display_frame.set_color(YELLOW)
|
|
zd_rect = BackgroundRectangle(
|
|
zoomed_display,
|
|
fill_opacity=1,
|
|
buff=MED_SMALL_BUFF,
|
|
)
|
|
self.add_foreground_mobject(zd_rect)
|
|
zd_rect.anim = UpdateFromFunc(
|
|
zd_rect,
|
|
lambda rect: rect.replace(zoomed_display).scale(1.1)
|
|
)
|
|
zd_rect.next_to(FRAME_HEIGHT * UP, UP)
|
|
|
|
tiny_grid = NumberPlane(
|
|
x_radius=2,
|
|
y_radius=2,
|
|
color=BLUE_E,
|
|
secondary_color=DARK_GREY,
|
|
)
|
|
tiny_grid.replace(frame)
|
|
|
|
jacobian_words = TextMobject("Jacobian")
|
|
jacobian_words.add_background_rectangle()
|
|
jacobian_words.scale(1.5)
|
|
jacobian_words.move_to(zoomed_display, UP)
|
|
zoomed_display.next_to(jacobian_words, DOWN)
|
|
|
|
self.play(self.get_zoom_in_animation())
|
|
self.activate_zooming()
|
|
self.play(
|
|
self.get_zoomed_display_pop_out_animation(),
|
|
zd_rect.anim
|
|
)
|
|
self.play(
|
|
ShowCreation(tiny_grid),
|
|
Write(jacobian_words),
|
|
run_time=2
|
|
)
|
|
self.add_transformable_mobject(tiny_grid)
|
|
self.add_foreground_mobject(jacobian_words)
|
|
self.wait()
|
|
self.apply_nonlinear_transformation(
|
|
example_function,
|
|
added_anims=[MaintainPositionRelativeTo(
|
|
zoomed_camera.frame, tiny_grid,
|
|
)],
|
|
run_time=5
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class PrinciplesOverlay(PiCreatureScene):
|
|
CONFIG = {
|
|
"default_pi_creature_kwargs": {
|
|
"color": GREY_BROWN,
|
|
"flip_at_start": True,
|
|
},
|
|
"default_pi_creature_start_corner": DR,
|
|
}
|
|
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
q_marks = VGroup(*[TexMobject("?") for x in range(40)])
|
|
q_marks.arrange_in_grid(4, 10)
|
|
q_marks.space_out_submobjects(1.4)
|
|
for mark in q_marks:
|
|
mark.shift(
|
|
random.random() * RIGHT,
|
|
random.random() * UP,
|
|
)
|
|
mark.scale(1.5)
|
|
mark.set_stroke(BLACK, 1)
|
|
q_marks.next_to(morty, UP)
|
|
q_marks.shift_onto_screen()
|
|
q_marks.sort(
|
|
lambda p: get_norm(p - morty.get_top())
|
|
)
|
|
|
|
self.play(morty.change, "pondering")
|
|
self.wait(2)
|
|
self.play(morty.change, "raise_right_hand")
|
|
self.wait()
|
|
self.play(morty.change, "thinking")
|
|
self.wait(4)
|
|
self.play(FadeInFromDown(q_marks[0]))
|
|
self.wait(2)
|
|
self.play(LaggedStartMap(
|
|
FadeInFromDown, q_marks[1:],
|
|
run_time=3
|
|
))
|
|
self.wait(3)
|
|
|
|
|
|
class ManyInfiniteExpressions(Scene):
|
|
def construct(self):
|
|
frac = get_phi_continued_fraction(10)
|
|
frac.set_height(2)
|
|
frac.to_corner(UL)
|
|
|
|
n = 9
|
|
radical_str_parts = [
|
|
"%d + \\sqrt{" % d
|
|
for d in range(1, n + 1)
|
|
]
|
|
radical_str_parts += ["\\cdots"]
|
|
radical_str_parts += ["}"] * n
|
|
radical = TexMobject("".join(radical_str_parts))
|
|
radical.to_corner(UR)
|
|
radical.to_edge(DOWN)
|
|
radical.set_color_by_gradient(YELLOW, RED)
|
|
|
|
n = 12
|
|
power_tower = TexMobject(
|
|
*["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n
|
|
)
|
|
power_tower.to_corner(UR)
|
|
power_tower.set_color_by_gradient(BLUE, GREEN)
|
|
|
|
self.play(*[
|
|
LaggedStartMap(
|
|
GrowFromCenter, group,
|
|
lag_ratio=0.1,
|
|
run_time=8,
|
|
)
|
|
for group in (frac, radical, power_tower)
|
|
])
|
|
self.wait(2)
|
|
|
|
|
|
class HoldUpPromo(PrinciplesOverlay):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
|
|
url = TextMobject("https://brilliant.org/3b1b/")
|
|
url.to_corner(UL)
|
|
|
|
rect = ScreenRectangle(height=5.5)
|
|
rect.next_to(url, DOWN)
|
|
rect.to_edge(LEFT)
|
|
|
|
self.play(
|
|
Write(url),
|
|
self.pi_creature.change, "raise_right_hand"
|
|
)
|
|
self.play(ShowCreation(rect))
|
|
self.wait(2)
|
|
self.change_mode("thinking")
|
|
self.wait()
|
|
self.look_at(url)
|
|
self.wait(10)
|
|
self.change_mode("happy")
|
|
self.wait(10)
|
|
self.change_mode("raise_right_hand")
|
|
self.wait(10)
|
|
|
|
self.play(FadeOut(rect), FadeOut(url))
|
|
self.play(morty.change, "raise_right_hand")
|
|
self.wait()
|
|
self.play(morty.change, "hooray")
|
|
self.wait(3)
|
|
|
|
|
|
class EndScreen(PatreonEndScreen):
|
|
CONFIG = {
|
|
"specific_patrons": [
|
|
"Juan Benet",
|
|
"Keith Smith",
|
|
"Chloe Zhou",
|
|
"Desmos",
|
|
"Burt Humburg",
|
|
"CrypticSwarm",
|
|
"Andrew Sachs",
|
|
"Ho\\`ang T\\`ung L\\^am",
|
|
# "Hoàng Tùng Lâm",
|
|
"Devin Scott",
|
|
"Akash Kumar",
|
|
"Felix Tripier",
|
|
"Arthur Zey",
|
|
"David Kedmey",
|
|
"Ali Yahya",
|
|
"Mayank M. Mehrotra",
|
|
"Lukas Biewald",
|
|
"Yana Chernobilsky",
|
|
"Kaustuv DeBiswas",
|
|
"Yu Jun",
|
|
"dave nicponski",
|
|
"Damion Kistler",
|
|
"Jordan Scales",
|
|
"Markus Persson",
|
|
"Fela",
|
|
"Fred Ehrsam",
|
|
"Britt Selvitelle",
|
|
"Jonathan Wilson",
|
|
"Ryan Atallah",
|
|
"Joseph John Cox",
|
|
"Luc Ritchie",
|
|
"Matt Roveto",
|
|
"Jamie Warner",
|
|
"Marek Cirkos",
|
|
"Magister Mugit",
|
|
"Stevie Metke",
|
|
"Cooper Jones",
|
|
"James Hughes",
|
|
"John V Wertheim",
|
|
"Chris Giddings",
|
|
"Song Gao",
|
|
"Alexander Feldman",
|
|
"Matt Langford",
|
|
"Max Mitchell",
|
|
"Richard Burgmann",
|
|
"John Griffith",
|
|
"Chris Connett",
|
|
"Steven Tomlinson",
|
|
"Jameel Syed",
|
|
"Bong Choung",
|
|
"Ignacio Freiberg",
|
|
"Zhilong Yang",
|
|
"Giovanni Filippi",
|
|
"Eric Younge",
|
|
"Prasant Jagannath",
|
|
"Cody Brocious",
|
|
"James H. Park",
|
|
"Norton Wang",
|
|
"Kevin Le",
|
|
"Tianyu Ge",
|
|
"David MacCumber",
|
|
"Oliver Steele",
|
|
"Yaw Etse",
|
|
"Dave B",
|
|
"Waleed Hamied",
|
|
"George Chiesa",
|
|
"supershabam",
|
|
"Delton Ding",
|
|
"Thomas Tarler",
|
|
"Isak Hietala",
|
|
"1st ViewMaths",
|
|
"Jacob Magnuson",
|
|
"Mark Govea",
|
|
"Clark Gaebel",
|
|
"Mathias Jansson",
|
|
"David Clark",
|
|
"Michael Gardner",
|
|
"Mads Elvheim",
|
|
"Awoo",
|
|
"Dr . David G. Stork",
|
|
"Ted Suzman",
|
|
"Linh Tran",
|
|
"Andrew Busey",
|
|
"John Haley",
|
|
"Ankalagon",
|
|
"Eric Lavault",
|
|
"Boris Veselinovich",
|
|
"Julian Pulgarin",
|
|
"Jeff Linse",
|
|
"Robert Teed",
|
|
"Jason Hise",
|
|
"Meshal Alshammari",
|
|
"Bernd Sing",
|
|
"James Thornton",
|
|
"Mustafa Mahdi",
|
|
"Mathew Bramson",
|
|
"Jerry Ling",
|
|
"Sh\\`im\\'in Ku\\=ang",
|
|
"Rish Kundalia",
|
|
"Achille Brighton",
|
|
"Ripta Pasay",
|
|
]
|
|
}
|
|
|
|
|
|
# class Thumbnail(GraphicalIntuitions):
|
|
class Thumbnail(AnalyzeFunctionWithTransformations):
|
|
CONFIG = {
|
|
"x_axis_width": 12,
|
|
"graph_origin": 1.5 * DOWN + 4 * LEFT,
|
|
"num_initial_applications": 1,
|
|
"input_line_zero_point": 2 * UP,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_function_title()
|
|
self.title.fade(1)
|
|
self.titles.fade(1)
|
|
self.repeatedly_apply_function()
|
|
self.all_arrows.set_stroke(width=1)
|
|
|
|
full_rect = FullScreenFadeRectangle()
|
|
cross = Cross(full_rect)
|
|
cross.set_stroke(width=40)
|
|
|
|
# self.add(cross)
|