3b1b-manim/active_projects/alt_calc.py

3296 lines
104 KiB
Python

from big_ol_pile_of_manim_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_anchors": 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.main_line.insert_n_anchor_points(
self.num_inserted_number_line_anchors
)
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.main_line.insert_n_anchor_points(
self.num_inserted_number_line_anchors
)
return AnimationGroup(
self.get_mapping_animation(
func, input_line_copy.main_line,
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.main_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.scale_to_fit_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_anchor_points(self.num_inserted_number_line_anchors)
mini_line.match_style(self.input_line.main_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 coordiantes
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(LaggedStart(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.scale_to_fit_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(LaggedStart(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.scale_to_fit_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(LaggedStart(
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.scale_to_fit_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.scale_to_fit_width(min(
group.get_width(),
self.image_frame_width - 2 * MED_SMALL_BUFF
))
group.scale_to_fit_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 dx} = 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.scale_to_fit_height(0.4)
t_tracker = ValueTracker(0)
group = VGroup(spring, weight)
group.continual_animations = [
ContinualUpdateFromTimeFunc(
t_tracker,
lambda tracker, dt: tracker.set_value(
tracker.get_value() + dt
)
),
ContinualUpdateFromFunc(
spring,
lambda s: s.stretch_to_fit_height(
1.5 + 0.5 * np.cos(3 * t_tracker.get_value()),
about_edge=UP
)
),
ContinualUpdateFromFunc(
weight,
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.scale_to_fit_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.scale_to_fit_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 = [
NormalAnimationAsContinualAnimation(Write(rects)),
NormalAnimationAsContinualAnimation(ShowCreation(graph)),
NormalAnimationAsContinualAnimation(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": 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=2, 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=8,
rate_func=there_and_back,
run_time=5,
added_anims=[
Write(deriv_text),
VFadeIn(ss_group, run_time=2),
]
)
self.play(FadeIn(integral_text))
self.play(
LaggedStart(
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_submobjects(DOWN)
words.to_edge(UP)
return LaggedStart(
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 = ScreenRectangle(height=self.screen_height)
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 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)
time_tracker = self.time_tracker = ValueTracker(0)
self.add(ContinualGrowValue(time_tracker))
vectors = self.get_vectors()
self.add(ContinualUpdateFromFunc(
vectors,
lambda vs: self.update_vectors(vs)
))
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 = np.linalg.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)
others = VGroup(
TextMobject("Multivariable calculus"),
TextMobject("Complex analysis"),
TextMobject("Differential geometry"),
TextMobject("$\\vdots$")
)
others.arrange_submobjects(
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(
LaggedStart(ShowCreation, lines),
LaggedStart(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.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)
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.scale_to_fit_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 = ContinualUpdateFromFunc(
input_triangle, lambda m: m.move_to(get_x_point(), UP)
)
output_triangle_update = ContinualUpdateFromFunc(
output_triangle, lambda m: m.move_to(get_y_point(), RIGHT)
)
x_label = TexMobject("x")
x_label_update = ContinualUpdateFromFunc(
x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF)
)
output_label = TexMobject("f(x)")
output_label_update = ContinualUpdateFromFunc(
output_label, lambda m: m.next_to(
output_triangle, LEFT, SMALL_BUFF)
)
v_line = get_v_line()
v_line_update = ContinualUpdateFromFunc(
v_line, lambda vl: Transform(vl, get_v_line()).update(1)
)
h_line = get_h_line()
h_line_update = ContinualUpdateFromFunc(
h_line, lambda hl: Transform(hl, get_h_line()).update(1)
)
graph_dot = Dot(color=YELLOW)
graph_dot_update = ContinualUpdateFromFunc(
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 = ContinualUpdateFromFunc(
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 = ContinualUpdateFromFunc(
deriv_label, position_deriv_label
)
slope_decimal = DecimalNumber(slope_line.get_slope())
slope_decimal.match_color(slope_line)
slope_decimal_update = ContinualChangingDecimal(
slope_decimal, lambda dt: slope_line.get_slope(),
position_update_func=lambda m: m.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,
slope_decimal_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 = 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 = 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)
self.play(
LaggedStart(GrowFromCenter, sample_dots, run_time=1),
LaggedStart(GrowArrow, arrows)
)
self.add(sample_dot_ghosts)
self.apply_function(
self.func, sample_dots=sample_dots
)
self.wait()
self.play(LaggedStart(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 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(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(
LaggedStart(DrawBorderThenFill, sample_dots),
LaggedStart(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,
"use_rectangular_stem": False,
}
la = Arrow(DOWN, UP, **kwargs)
ra = Arrow(UP, DOWN, **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
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()
sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
self.apply_function(
self.func,
apply_function_to_number_line=False,
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 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()
self.play(LaggedStart(DrawBorderThenFill, sample_dots))
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 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()
# TODO, move this
# self.plug_func_into_self()
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(0.5 * 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(
LaggedStart(
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_submobjects(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(*range(-6, 0, step) + range(step, 7, step))
self.y_axis.label_direction = RIGHT
self.y_axis.add_numbers(*range(-2, 0, step) + 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,
submobject_mode="lagged_start",
run_time=2
)
line.continual_anim = CycleAnimation(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.plug_func_into_self()
def obviously_not(self):
morty, friend = self.get_pi_creatures()
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, "Of course 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(LaggedStart(
ShowCreationThenDestruction, plus_rects,
run_time=4,
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 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" % value)
value = func(value)
value_labels.add(value_label)
lines.arrange_submobjects(
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 == 3:
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)
class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication):
def construct(self):
self.force_skipping()
self.ask_question()
self.obviously_not()
self.revert_to_original_skipping_status()
self.plug_func_into_self(
value=(1 - np.sqrt(5)) / 2,
value_str="-1/\\varphi"
)
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_submobjects(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(LaggedStart(GrowFromCenter, line[6]))
self.wait()
self.play(FadeIn(line[7]))
self.play(
GrowFromCenter(braces[2]),
FadeIn(brace_texts[2]),
)
self.wait()
class ShowPhiAsFixedPoint(ShowRepeatedApplication):
CONFIG = {
"title_color": WHITE,
}
def construct(self):
self.add_func_title()
self.show_fixed_point_formulas()
def show_fixed_point_formulas(self):
var_formula = TexMobject(
"{1 \\over", "\\varphi}", "=", "\\varphi", "-", "1",
)
var_formula.set_color_by_tex("\\varphi", YELLOW)
var_formula.move_to(UP)
alt_var_formula = TexMobject(
"1", "+", "{1 \\over", "\\varphi}", "=", "\\varphi",
)
alt_var_formula.set_color_by_tex("\\varphi", YELLOW)
alt_var_formula.move_to(var_formula)
num_formula = TexMobject(
"{1 \\over 1.618\\dots} = 0.618\\dots",
tex_to_color_map={
"1.618\\dots": YELLOW,
"0.618\\dots": YELLOW,
"=": WHITE,
}
)
num_formula.next_to(var_formula, DOWN, LARGE_BUFF)
fixed_phi_formula = VGroup(
TexMobject("f(", "\\varphi", ")"),
TexMobject("="),
TexMobject("\\quad", "\\varphi", "\\quad"),
)
fixed_phi_formula.arrange_submobjects(RIGHT)
for mob in fixed_phi_formula:
mob.set_color_by_tex("\\varphi", YELLOW)
fixed_phi_formula.next_to(alt_var_formula, UP, LARGE_BUFF)
self.add(num_formula)
self.wait()
self.play(Write(var_formula))
self.wait()
self.play(*[
ReplacementTransform(
part,
alt_var_formula.get_part_by_tex(
tex_string if tex_string is not "-" else "+",
substring=False
),
path_arc=90 * DEGREES,
run_time=2
)
for part in var_formula
for tex_string in [part.get_tex_string()]
])
self.wait()
self.play(
ReplacementTransform(
self.title[:2].copy(),
fixed_phi_formula[:2],
path_arc=90 * DEGREES
),
)
self.play(
ReplacementTransform(
fixed_phi_formula[0][1].copy(),
fixed_phi_formula[2][1],
path_arc=-180 * DEGREES
)
)
self.wait()
group = VGroup(
self.title, fixed_phi_formula,
alt_var_formula,
num_formula
)
self.play(
group.arrange_submobjects, DOWN,
{"buff": LARGE_BUFF, "aligned_edge": LEFT},
group.to_corner, UL
)
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(FadeInAndShiftFromDirection(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.main_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)
point_func = self.number_func_to_point_func(self.func)
arrows = VGroup(*[
Arrow(
c, point_func(c), buff=SMALL_BUFF,
rectangular_stem_width=0.02,
tip_length=0.15
)
for c in map(Mobject.get_center, sample_dots)
if np.linalg.norm(c - input_zero_point) > 0.3
])
arrows.set_fill(WHITE, 0.75)
arrows.set_stroke(BLACK, 0.5)
self.play(LaggedStart(
FadeInAndShiftFromDirection, sample_dots,
lambda m: (m, UP)
))
self.play(LaggedStart(GrowArrow, arrows))
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))
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)
self.play(
LaggedStart(
ApplyMethod, self.all_arrows,
lambda m: (m.set_fill, {"opacity": 0.1})
),
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.scale_to_fit_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()
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(*filter(
lambda a: np.linalg.norm(a.get_start() - point) < 0.75,
self.all_arrows
)).copy()
arrows.set_fill(PINK, 1)
arrows.second_anim = LaggedStart(
ApplyMethod, arrows,
lambda m: (m.set_fill, YELLOW, 1),
rate_func=there_and_back_with_pause,
lag_ratio=0.7,
run_time=2,
)
arrows.anim = AnimationGroup(*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()
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 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_submobjects(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(LaggedStart(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,
LaggedStart(FadeOut, self.other_topics),
LaggedStart(FadeOut, self.lines),
FadeOut(self.calculus),
*[
ApplyMethod(creature.change, "pondering")
for creature in creatures
]
)
self.wait(4)
class ComplexAnalysisOverlay(Scene):
def construct(self):
words = TextMobject("Complex analysis")
words.scale(1.25)
words.to_edge(UP)
words.add_background_rectangle()
self.add(words)
self.wait()
class CompelxAnalyticFluidFlow(ComplexTransformationScene, MovingCameraScene):
CONFIG = {
"num_anchors_to_add_per_line": 200,
"plane_config": {"y_radius": 8}
}
def setup(self):
MovingCameraScene.setup(self)
ComplexTransformationScene.setup(self)
def construct(self):
self.camera.frame.shift(2 * UP)
self.camera.frame.scale(0.5, about_point=ORIGIN)
plane = NumberPlane(
x_radius=15,
y_radius=25,
y_unit_size=0.5,
secondary_line_ratio=0,
)
plane.next_to(ORIGIN, UP, buff=0.001)
horizontal_lines = VGroup(*filter(
lambda l: np.abs(l.get_center()[0]) < 0.1,
list(plane.main_lines) + [plane.axes[0]]
))
plane.set_stroke(MAROON_B, width=2)
horizontal_lines.set_stroke(BLUE, width=2)
for line in horizontal_lines:
# To lag the paths of the droplets
line.scale(1 + random.random())
self.prepare_for_transformation(plane)
self.add_transformable_mobjects(plane)
self.background.set_stroke(width=2)
for label in self.background.coordinate_labels:
label.set_stroke(width=0)
label.scale(0.75, about_edge=UR)
words = TextMobject("Flow near \\\\", "a wall")
words.scale(0.75)
words.add_background_rectangle_to_submobjects()
words.next_to(0.75 * UP, LEFT, MED_LARGE_BUFF)
equation = TexMobject("z \\rightarrow z^{1/2}")
equation.scale(0.75)
equation.add_background_rectangle()
equation.next_to(words, UP)
self.apply_complex_function(
lambda x: x**(1. / 2),
added_anims=[Write(equation)]
)
self.play(Write(words, run_time=1))
dots = VGroup()
num_dots_per_line = 50
for x in range(num_dots_per_line):
for line in horizontal_lines:
dot = Dot(radius=0.025)
opacity = 1.0 - x / float(num_dots_per_line)
dot.set_fill(opacity=opacity)
dot.path = line
dots.add(dot)
dots.set_color_by_gradient(BLUE_B, BLUE_D)
self.play(LaggedStart(
MoveAlongPath, dots,
lambda d: (d, d.path),
run_time=3,
lag_ratio=0.9
))