3b1b-videos/_2023/clt/integral.py
2023-04-03 08:46:40 -07:00

1305 lines
No EOL
44 KiB
Python

from manim_imports_ext import *
from _2022.convolutions.continuous import *
from _2023.clt.main import *
class LastTime(VideoWrapper):
wait_time = 8
title = "Normal Distribution"
class AltBuildUpGaussian(BuildUpGaussian):
pass
class BellCurveArea(InteractiveScene):
def construct(self):
# Setup
axes = NumberPlane(
(-4, 4), (0, 1.5, 0.5),
width=14, height=5,
background_line_style=dict(
stroke_color=GREY_C,
stroke_width=2,
stroke_opacity=0.5
)
)
axes.x_axis.add_numbers(font_size=24)
axes.y_axis.add_numbers(num_decimal_places=1, excluding=[0])
axes.to_edge(DOWN)
graph = axes.get_graph(lambda x: np.exp(-x**2))
graph.set_stroke(BLUE, 3)
t2c = {"x": BLUE}
graph_label = Tex("e^{-x^2}", font_size=72, t2c=t2c)
graph_label.next_to(graph.pfp(0.6), UR)
self.add(axes)
self.play(ShowCreation(graph))
self.play(Write(graph_label))
self.wait()
# Show integral
integral = Tex(R"\int_{-\infty}^\infty e^{-x^2} dx", t2c=t2c)
integral.to_edge(UP)
self.play(graph.animate.set_fill(BLUE, 0.5))
self.wait()
self.play(
Write(integral[R"\int_{-\infty}^\infty"]),
FadeTransform(graph_label.copy(), integral["e^{-x^2}"])
)
self.play(TransformFromCopy(integral["x"][0], integral["dx"]))
self.wait()
# Show rectangles
colors = (BLUE_E, BLUE_D, TEAL_D, TEAL_E)
rects = axes.get_riemann_rectangles(graph, dx=0.2, colors=colors)
rects.set_stroke(WHITE, 1)
rects.set_fill(opacity=0.75)
rect = rects[len(rects) // 2 - 2].copy()
rect.set_opacity(1)
graph_label.set_backstroke(width=5)
brace = Brace(rect, UP, SMALL_BUFF)
brace.set_backstroke(width=3)
dx_label = brace.get_tex("dx", buff=SMALL_BUFF)
dx_label["x"].set_color(BLUE)
axes.generate_target()
axes.target.y_axis.numbers.set_opacity(0)
self.play(
FadeIn(rects, lag_ratio=0.1, run_time=3),
graph.animate.set_fill(opacity=0).set_anim_args(time_span=(1, 2)),
graph_label.animate.shift(SMALL_BUFF * UR).set_anim_args(time_span=(1, 2)),
)
self.wait()
self.play(
rects.animate.set_opacity(0.1),
MoveToTarget(axes),
FadeIn(rect),
)
self.wait()
self.play(graph_label.animate.set_height(0.5).next_to(rect, LEFT, SMALL_BUFF))
self.play(FlashAround(integral["e^{-x^2}"], time_width=1, run_time=1.5))
self.wait()
self.play(
GrowFromCenter(brace),
FadeIn(dx_label, 0.5 * UP),
)
self.play(FlashAround(integral["dx"], time_width=1, run_time=1.5))
self.wait()
# Show addition
rects.set_fill(opacity=0.8)
rects.set_stroke(WHITE, 1)
self.play(
graph_label.animate.set_height(0.7).next_to(graph.pfp(0.4), UL),
rects.animate.set_opacity(0.75),
FadeOut(rect)
)
self.wait()
self.play(
LaggedStart(*(
r.animate.shift(0.25 * UP).set_color(YELLOW).set_anim_args(rate_func=there_and_back)
for r in rects
), run_time=5, lag_ratio=0.1),
LaggedStart(
FlashAround(integral[2:4], time_width=1),
FlashAround(integral[1], time_width=1),
lag_ratio=0.25,
run_time=5,
)
)
self.wait()
# Thinner rectangles
for dx in [0.1, 0.075, 0.05, 0.03, 0.02, 0.01, 0.005]:
new_rects = axes.get_riemann_rectangles(graph, dx=dx, colors=colors)
new_rects.set_stroke(WHITE, 1)
new_rects.set_fill(opacity=0.7)
self.play(
Transform(rects, new_rects),
brace.animate.set_width(dx * axes.x_axis.get_unit_size(), about_edge=LEFT),
MaintainPositionRelativeTo(dx_label, brace),
)
self.add(graph)
self.play(
FadeOut(brace), FadeOut(dx_label),
ShowCreation(graph),
)
# Indefinite integral
frame = self.frame
equals = Tex("=")
equals.move_to(integral)
equals.shift(0.5 * UP)
answer_box = SurroundingRectangle(integral["e^{-x^2} dx"])
answer_box.next_to(equals, RIGHT)
answer_box.set_stroke(TEAL, 2)
answer_box.set_fill(GREY_E, 1)
q_marks = Tex("???")
q_marks.set_height(0.6 * answer_box.get_height())
q_marks.move_to(answer_box)
answer_box.add(q_marks)
self.play(
frame.animate.set_height(9, about_edge=DOWN),
integral.animate.next_to(equals, LEFT),
FadeIn(equals),
Write(answer_box),
)
integral.save_state()
integral.generate_target()
integral.target[1:4].stretch(0, 0, about_edge=RIGHT).set_opacity(0)
integral.target[0].move_to(integral[:4], RIGHT)
self.play(MoveToTarget(integral))
# Arrows
int_box = SurroundingRectangle(integral["e^{-x^2} dx"])
int_box.set_stroke(BLUE, 2)
arc = -0.5 * PI
low_arrow = Arrow(answer_box.get_bottom(), int_box.get_bottom(), path_arc=arc)
top_arrow = Arrow(int_box.get_top(), answer_box.get_top(), path_arc=arc)
low_words = Text("Derivative", font_size=30)
low_words.next_to(low_arrow, DOWN, MED_SMALL_BUFF)
top_words = Text("Antiderivative", font_size=30)
top_words.next_to(top_arrow, UP, MED_SMALL_BUFF)
self.play(
ShowCreation(low_arrow),
FadeIn(low_words, 0.5 * LEFT),
FadeTransform(answer_box.copy(), int_box, path_arc=arc)
)
self.wait()
self.play(
ShowCreation(top_arrow),
FadeIn(top_words, 0.5 * RIGHT),
)
self.wait()
# Impossible
impossible = Text("Impossible!", font_size=72, color=RED)
impossible.next_to(answer_box, RIGHT)
functions = VGroup(
Tex(R"a_n x^n + \cdots a_1 x + a_0", t2c=t2c),
Tex(R"\sin(x), \cos(x), \tan(x)", t2c=t2c),
Tex(R"b^x", t2c=t2c),
Tex(R"\vdots")
)
functions.arrange(DOWN, MED_LARGE_BUFF, aligned_edge=LEFT)
functions.set_height(2.5)
functions.next_to(impossible, RIGHT, buff=LARGE_BUFF)
self.play(FadeIn(impossible, scale=0.5, rate_func=rush_into))
self.wait()
self.play(
LaggedStartMap(FadeIn, functions, shift=DOWN, lag_ratio=0.5),
frame.animate.shift(4 * RIGHT),
run_time=3
)
self.wait()
class AntiDerivative(InteractiveScene):
def construct(self):
# Add both planes
x_min, x_max = (-3, 3)
planes = VGroup(*(
NumberPlane(
(x_min, x_max), (0, 2),
width=5.5, height=2.75,
background_line_style=dict(stroke_color=GREY, stroke_width=1, stroke_opacity=1.0),
faded_line_ratio=3,
)
for x in range(2)
))
planes.arrange(DOWN, buff=LARGE_BUFF)
planes.to_corner(UL)
self.add(planes)
# Titles
titles = VGroup(
Tex("f(x) = e^{-x^2}", font_size=66),
Tex(R"F(x) = \int_0^x e^{-t^2} dt"),
)
for title, plane in zip(titles, planes):
title.next_to(plane, RIGHT)
ad_word = Text("Antiderivative")
ad_word.next_to(titles[1], UP, MED_LARGE_BUFF)
VGroup(ad_word, titles[1]).match_y(planes[1])
self.add(titles)
self.add(ad_word)
# High graph
x_tracker = ValueTracker(0)
get_x = x_tracker.get_value
high_graph = planes[0].get_graph(lambda x: np.exp(-x**2))
high_graph.set_stroke(BLUE, 3)
high_area = high_graph.copy()
def update_area(area: VMobject):
x = get_x()
area.become(high_graph)
area.set_stroke(width=0)
area.set_fill(BLUE, 0.5)
area.pointwise_become_partial(
high_graph, 0, inverse_interpolate(x_min, x_max, x)
)
area.add_line_to(planes[0].c2p(x, 0))
area.add_line_to(planes[0].c2p(x_min, 0))
return area
high_area.add_updater(update_area)
self.add(high_graph, high_area)
# Low graph
dist = scipy.stats.norm(0, 1)
low_graph = planes[1].get_graph(lambda x: math.sqrt(PI) * dist.cdf(x))
low_graph.set_stroke(YELLOW, 2)
low_dot = GlowDot()
low_dot.add_updater(lambda m: m.move_to(planes[1].i2gp(get_x(), low_graph)))
low_line = always_redraw(lambda: DashedLine(
planes[1].c2p(get_x(), 0), planes[1].i2gp(get_x(), low_graph),
).set_stroke(WHITE, 2))
self.add(low_graph, low_dot, low_line)
# Animations
for value in [1.5, -2, -1, 1, -0.5, 0.5, 3.0, -1.5]:
self.play(x_tracker.animate.set_value(value), run_time=3)
self.wait()
class UsualFunctionTypes(InteractiveScene):
def construct(self):
t2c = {"x": YELLOW}
functions = VGroup(
Tex(R"a_n x^n + \cdots a_1 x + a_0", t2c=t2c),
Tex(R"\sin(x), \cos(x), \arctan(x)", t2c=t2c),
Tex(R"b^x, \log(x), \cosh(x)", t2c=t2c),
Tex(R"\vdots")
)
functions.arrange(DOWN, MED_LARGE_BUFF, aligned_edge=LEFT)
functions.set_height(2.5)
functions.to_edge(RIGHT, buff=0.6)
box = SurroundingRectangle(functions, buff=MED_LARGE_BUFF)
box.set_stroke(RED, 2)
words = Text("Cannot be expressed \n in terms of these:")
words.next_to(box, UP)
words.set_fill(RED)
self.play(
FadeIn(words, lag_ratio=0.1),
FadeIn(box),
LaggedStartMap(FadeIn, functions, shift=DOWN, lag_ratio=0.5, run_time=3),
)
self.wait()
class GaussianIntegral(ThreeDScene, InteractiveScene):
def func(self, x, y):
return np.exp(-x**2 - y**2)
def get_axes(
self,
x_range=(-3, 3),
y_range=(-3, 3),
z_range=(0, 1.5, 0.5),
width=8,
height=8,
depth=3,
center=0.5 * IN,
include_plane=False
):
axes = ThreeDAxes(
x_range, y_range, z_range,
width=width, height=height, depth=depth
)
axes.set_stroke(GREY_C)
if include_plane:
plane = NumberPlane(
x_range, y_range,
width=width, height=height,
background_line_style=dict(
stroke_color=GREY_C,
stroke_width=1,
),
)
plane.faded_lines.set_stroke(opacity=0.5)
plane.shift(0.01 * IN)
axes.plane = plane
axes.add(plane)
x, y, z = axis_labels = VGroup(*map(Tex, "xyz"))
axis_labels.use_winding_fill(False)
x.next_to(axes.x_axis, RIGHT)
y.next_to(axes.y_axis, UP)
z.rotate(90 * DEGREES, RIGHT)
z.next_to(axes.z_axis, OUT)
axes.labels = axis_labels
axes.add(axis_labels)
axes.shift(center - axes.c2p(0, 0, 0))
axes.set_flat_stroke(False)
return axes
def get_gaussian_graph(
self,
axes,
color=interpolate_color(BLUE_E, BLACK, 0.6),
opacity=1.0,
shading=(0.2, 0.2, 0.4),
):
graph = axes.get_graph(self.func)
graph.set_color(color)
graph.set_opacity(opacity)
graph.set_shading(*shading)
return graph
def get_dynamic_cylinder(self, axes, r_init=1):
cylinder = self.get_cylinder(axes, r_init)
r_tracker = ValueTracker(r_init)
cylinder.add_updater(lambda m: self.set_cylinder_r(
m, axes, r_tracker.get_value()
))
return cylinder, r_tracker
def get_cylinder(
self, axes, r,
color=BLUE_E,
opacity=1
):
cylinder = Cylinder(color=color, opacity=opacity)
self.set_cylinder_r(cylinder, axes, r)
return cylinder
def set_cylinder_r(self, cylinder, axes, r):
r = max(r, 1e-5)
cylinder.set_width(2 * r * axes.x_axis.get_unit_size())
cylinder.set_depth(
self.func(r, 0) * axes.z_axis.get_unit_size(),
stretch=True
)
cylinder.move_to(axes.c2p(0, 0, 0), IN)
return cylinder
def get_thick_cylinder(self, cylinder, delta_r):
radius = 0.5 * cylinder.get_width()
outer_cylinder = cylinder.copy()
factor = (radius + delta_r) / radius
outer_cylinder.stretch(factor, 0)
outer_cylinder.stretch(factor, 1)
annulus = ParametricSurface(
lambda u, v: (radius + u * delta_r) * np.array([math.cos(v), math.sin(v), 0]),
u_range=(0, 1),
v_range=(0, TAU),
)
annulus.match_color(cylinder)
annulus.move_to(cylinder, OUT)
result = Group(cylinder.copy(), annulus, outer_cylinder)
result.clear_updaters()
return result
def get_x_slice(self, axes, y, x_range=(-3, 3.1, 0.1)):
xs = np.arange(*x_range)
ys = np.ones(len(xs)) * y
points = axes.c2p(xs, ys, self.func(xs, y))
graph = VMobject().set_points_smoothly(points)
graph.use_winding_fill(False)
return graph
def get_dynamic_slice(
self,
axes,
stroke_color=BLUE,
stroke_width=2,
fill_color=BLUE_E,
fill_opacity=0.5,
):
y_tracker = ValueTracker(0)
get_y = y_tracker.get_value
z_unit = axes.z_axis.get_unit_size()
x_slice = self.get_x_slice(axes, 0)
x_slice.set_stroke(stroke_color, stroke_width)
x_slice.set_fill(fill_color, fill_opacity)
x_slice.add_updater(
lambda m: m.set_depth(self.func(0, get_y()) * z_unit, stretch=True)
)
x_slice.add_updater(lambda m: m.move_to(axes.c2p(0, get_y(), 0), IN))
return x_slice, y_tracker
class CylinderSlices(GaussianIntegral):
def construct(self):
# Setup
frame = self.frame
axes = self.get_axes()
graph = self.get_gaussian_graph(axes)
graph.set_opacity(0.8)
graph.always_sort_to_camera(self.camera)
graph_mesh = SurfaceMesh(graph, resolution=(21, 21))
graph_mesh.set_stroke(WHITE, 0.5, opacity=0.25)
graph_mesh.set_flat_stroke(False)
self.add(axes)
# Animate in by rotating e^{-x^2}
bell_halves = Group(*(
axes.get_parametric_surface(
lambda r, theta: np.array(
[r * np.cos(theta), r * np.sin(theta), np.exp(-r**2)
]),
u_range=(0, 3),
v_range=v_range,
)
for v_range in [(0, PI), (PI, TAU)]
))
for half in bell_halves:
half.match_style(graph)
half.set_opacity(0.5)
bell2d = self.get_x_slice(axes, 0)
bell2d.set_stroke(TEAL, 3)
kw = dict(t2c={"x": BLUE, "y": YELLOW})
label2d, label3d = func_labels = VGroup(
Tex("f_1(x) = e^{-x^2}", **kw),
Tex("f_2(x, y) = e^{-(x^2 + y^2)}", **kw),
)
for label in func_labels:
label.fix_in_frame()
label.move_to(4 * LEFT + 2 * UP)
axes.save_state()
frame.reorient(0, 90)
frame.move_to(OUT + 2 * UP)
axes.y_axis.set_opacity(0)
axes.labels.set_opacity(0)
self.play(
ShowCreation(bell2d),
Write(label2d)
)
self.wait()
self.play(
ShowCreation(bell_halves[0]),
ShowCreation(bell_halves[1]),
Rotate(bell2d, PI, axis=OUT, about_point=axes.c2p(0, 0, 0)),
frame.animate.move_to(ORIGIN).reorient(-20, 70),
Restore(axes),
TransformMatchingTex(label2d.copy(), label3d, time_span=(0, 2)),
label2d.animate.next_to(label3d, UP, MED_LARGE_BUFF, LEFT),
run_time=6
)
self.wait()
self.play(
FadeOut(bell_halves, 0.01 * IN),
FadeOut(bell2d, 0.1 * IN),
FadeIn(graph, 0.01 * IN),
)
self.play(Write(graph_mesh, stroke_width=1, lag_ratio=0.01))
self.wait()
# Rotate the frame
self.play(
frame.animate.set_theta(20 * DEGREES),
rate_func=there_and_back,
run_time=30,
)
# Reposition to 2d view
frame.save_state()
graph_mesh.save_state()
func_labels.use_winding_fill(False)
self.play(
frame.animate.reorient(0, 0).set_height(10).move_to(1.5 * LEFT).set_field_of_view(1 * DEGREES),
graph.animate.set_opacity(0.25),
func_labels.animate.scale(0.75).to_corner(UL),
graph_mesh.animate.set_stroke(width=1),
run_time=3,
)
# Explain meaning of r
x, y = (1.5, 0.75)
dot = Dot(axes.c2p(x, y), fill_color=RED)
dot.set_stroke(WHITE, 0.5)
coords = Tex("(x, y)", font_size=36)
coords.next_to(dot, UR, SMALL_BUFF)
x_line = Line(axes.get_origin(), axes.c2p(x, 0, 0))
y_line = Line(axes.c2p(x, 0, 0), axes.c2p(x, y, 0))
r_line = Line(axes.c2p(x, y, 0), axes.get_origin())
x_line.set_stroke(BLUE, 3)
y_line.set_stroke(YELLOW, 3)
r_line.set_stroke(RED, 3)
lines = VGroup(x_line, y_line, r_line)
labels = VGroup(*map(Tex, "xyr"))
for label, line in zip(labels, lines):
label.match_color(line)
label.scale(0.85)
label.next_to(line.get_center(), rotate_vector(line.get_vector(), -90 * DEGREES), SMALL_BUFF)
self.add(dot, coords, set_depth_test=False)
self.play(
FadeIn(dot, scale=0.5),
FadeIn(coords),
)
for line, label in zip(lines, labels):
self.add(line, label, dot, set_depth_test=False)
self.play(
ShowCreation(line),
Write(label),
)
# Plug in r
r_label_rect = SurroundingRectangle(labels[2], buff=SMALL_BUFF)
r_label_rect.set_stroke(RED, 2)
arrow = Arrow(r_label_rect, axes.c2p(-3, 3, 0) + 3.2 * LEFT + 0.25 * UP, path_arc=45 * DEGREES)
arrow.set_stroke(RED)
self.always_depth_test = False
self.play(ShowCreation(r_label_rect))
self.play(ShowCreation(arrow))
self.wait()
# Show Pythagorean equations
r_func = Tex("= e^{-r^2}", t2c={"r": RED})
r_func.match_height(label2d["= e^{-x^2}"])
r_func.next_to(label3d, RIGHT, MED_SMALL_BUFF, UP)
r_func.fix_in_frame()
r_rect = SurroundingRectangle(r_func["r^2"], buff=0.025)
xy_rect = SurroundingRectangle(label3d["x^2 + y^2"], buff=0.025)
VGroup(r_rect, xy_rect).set_stroke(TEAL, 1)
VGroup(r_rect, xy_rect).fix_in_frame()
pythag = Tex("x^2 + y^2 = r^2", t2c={"x": BLUE, "y": YELLOW, "r": RED})
pythag.next_to(label3d, DOWN, buff=2.0, aligned_edge=LEFT)
pythag.fix_in_frame()
self.play(
FadeTransform(label2d["= e^{-x^2}"].copy(), r_func),
FadeOut(arrow, scale=0.8, shift=DR + RIGHT),
FadeOut(r_label_rect)
)
self.wait()
line_copies = lines.copy()
self.add(*line_copies, set_depth_test=False)
self.play(
*(
VShowPassingFlash(line.insert_n_curves(20).set_stroke(width=8), time_width=1.5)
for line in line_copies
),
*map(ShowCreation, lines)
)
self.play(
FadeTransform(r_func["r^2"][0].copy(), pythag["r^2"]),
FadeTransform(label3d["x^2 + y^2"][0].copy(), pythag["x^2 + y^2"]),
Write(pythag["="]),
)
self.wait()
self.wait()
self.play(ShowCreation(xy_rect))
self.wait()
self.play(Transform(xy_rect, r_rect))
self.play(FadeOut(xy_rect))
functions = VGroup(label2d, label3d, r_func)
functions.fix_in_frame()
# Emphasize rotational symmetry
self.always_depth_test = True
x_label, y_label, r_label = labels
self.play(
*map(FadeOut, [x_line, y_line, x_label, y_label, pythag])
)
def get_circle(point, z_shift=0.02):
origin = axes.c2p(0, 0, 0)
point[2] = origin[2]
radius = get_norm(point - origin)
circle = Circle(radius=radius, n_components=96)
x = axes.x_axis.p2n(point)
y = axes.y_axis.p2n(point)
circle.move_to(axes.c2p(0, 0, self.func(x, y) + z_shift))
circle.set_stroke(RED, 2)
circle.rotate(np.arctan2(y, x))
circle.set_flat_stroke(False)
return circle
r_label.add_updater(lambda m: m.next_to(r_line.get_center(), UL, SMALL_BUFF))
dot.add_updater(lambda m: m.move_to(r_line.get_start()))
coords.add_updater(lambda m: m.next_to(dot, UR, SMALL_BUFF))
circle = get_circle(r_line.get_start())
self.play(
Rotate(r_line, TAU, about_point=axes.get_origin()),
ShowCreation(circle),
frame.animate.reorient(30, 60).move_to(ORIGIN).set_height(8).set_field_of_view(45 * DEGREES),
Restore(graph_mesh),
run_time=7,
)
self.wait()
self.play(
r_line.animate.scale(0.1, about_point=axes.get_origin()),
UpdateFromFunc(circle, lambda c: c.replace(get_circle(r_line.get_start()))),
rate_func=there_and_back,
run_time=8,
)
self.wait()
self.play(*map(FadeOut, [r_line, dot, r_label, coords, circle]))
# Dynamic cylinder
cylinder, r_tracker = self.get_dynamic_cylinder(axes)
delta_r = 0.1
cylinders = Group(*(
self.get_cylinder(axes, r, opacity=0.5)
for r in np.arange(0, 3, delta_r)
))
r_tracker.set_value(0)
self.add(cylinder, cylinders, graph, graph_mesh)
self.play(
graph.animate.set_opacity(0.1).set_anim_args(time_span=(0, 2)),
FadeIn(cylinders, lag_ratio=0.9),
r_tracker.animate.set_value(3).set_anim_args(
rate_func=linear,
time_span=(0.5, 10),
),
frame.animate.reorient(-15, 75).set_height(5.5),
run_time=10,
)
self.wait()
# Isolate one particular cylinder
self.play(
r_tracker.animate.set_value(0.7),
cylinders.animate.set_opacity(0.1),
frame.animate.reorient(-27, 71),
run_time=3,
)
# Unwrap cylinder
axes.labels[2].set_opacity(0)
R = cylinder.get_width() / 2
rect = Square3D(resolution=cylinder.resolution)
rect.set_width(TAU * R)
rect.set_height(cylinder.get_depth(), stretch=True)
rect.match_color(cylinder)
rect_top = Line(rect.get_corner(UL), rect.get_corner(UR))
rect_top.set_stroke(RED, 3)
rect_side = Line(rect.get_corner(DL), rect.get_corner(UL))
rect_side.set_stroke(PINK, 3)
VGroup(rect_top, rect_side).set_flat_stroke(False)
rect_group = Group(rect, rect_top, rect_side)
rect_group.apply_matrix(frame.get_orientation().as_matrix())
rect_group.next_to(cylinder, [1, 0, 1], LARGE_BUFF)
eq_kw = dict(
font_size=35,
t2c={"{r}": RED},
)
area_eq1 = TexText("Area = (Circumference)(Height)", **eq_kw)
area_eq2 = TexText(R"Area = $2 \pi {r} \cdot e^{-{r}^2}$", **eq_kw)
for eq in area_eq1, area_eq2:
eq.fix_in_frame()
eq.to_corner(UL)
area_eq1.shift(area_eq2[0].get_center() - area_eq1[0].get_center())
self.add(functions)
functions.fix_in_frame()
functions.deactivate_depth_test()
functions.use_winding_fill(False)
self.play(
FadeIn(area_eq1, DOWN),
functions.animate.shift(1.5 * DOWN).scale(0.7, about_edge=DL).set_fill(opacity=0.75)
)
self.wait()
pre_rect = cylinder.copy()
pre_rect.clear_updaters()
self.add(pre_rect, graph)
self.play(
pre_rect.animate.scale(0.95).next_to(cylinder, OUT, buff=1.0),
frame.animate.set_height(7).move_to([1.0, 0.15, 1.0]),
run_time=2,
)
self.play(ReplacementTransform(pre_rect, rect), run_time=2)
self.wait()
# Show cylinder area
circle = get_circle(cylinder.get_points()[0], z_shift=0)
height_line = Line(cylinder.get_corner(IN + DOWN), cylinder.get_corner(OUT + DOWN))
height_line.set_stroke(PINK, 3)
height_line.set_flat_stroke(False)
circ_brace = Brace(area_eq2[R"2 \pi {r}"], DOWN, SMALL_BUFF)
height_brace = Brace(area_eq2[R"e^{-{r}^2}"], DOWN, SMALL_BUFF)
VGroup(circ_brace, height_brace).fix_in_frame()
circ_word = area_eq1["(Circumference)"]
height_word = area_eq1["(Height)"]
self.add(circle, set_depth_test=False)
self.play(
ShowCreation(circle),
ShowCreation(rect_top),
)
self.play(
FadeIn(circ_brace),
circ_word.animate.scale(0.75).next_to(circ_brace, DOWN, SMALL_BUFF),
Write(area_eq2[R"2 \pi {r}"]),
height_word.animate.next_to(area_eq2[R"2 \pi {r}"], RIGHT)
)
self.wait()
self.add(height_line, set_depth_test=False)
self.play(
FadeOut(circle),
FadeOut(rect_top),
ShowCreation(rect_side),
ShowCreation(height_line),
)
self.play(
FadeIn(height_brace),
height_word.animate.scale(0.75).next_to(height_brace, DOWN, SMALL_BUFF, aligned_edge=LEFT),
circ_word.animate.align_to(circ_brace, RIGHT),
FadeInFromPoint(area_eq2[R"\cdot e^{-{r}^2}"], r_func[1:].get_center()),
)
self.remove(area_eq1)
self.add(area_eq2, circ_word, height_word)
self.wait()
self.play(
frame.animate.center().reorient(-15, 66).set_height(4).set_anim_args(run_time=15),
*map(FadeOut, [rect, rect_side, height_line]),
)
# Show thickness
volume_word = Text("Volume", **eq_kw)
volume_word.fix_in_frame()
volume_word.move_to(area_eq2, DL)
area_part = area_eq2[R"= $2 \pi {r} \cdot e^{-{r}^2}$"]
annotations = VGroup(circ_brace, height_brace, circ_word, height_word)
dr_tex = Tex("d{r}", **eq_kw)
dr_tex.fix_in_frame()
thick_cylinder = self.get_thick_cylinder(cylinder, delta_r * axes.x_axis.get_unit_size())
thin_cylinder = self.get_thick_cylinder(cylinder, 0.1 * delta_r * axes.x_axis.get_unit_size())
_, annulus, outer_cylinder = thick_cylinder
dr_brace = Brace(
Line(axes.get_origin(), axes.c2p(delta_r, 0, 0)), UP
)
dr_brace.stretch(0.5, 1)
brace_label = dr_brace.get_tex("d{r}", buff=SMALL_BUFF)
brace_label["r"].set_color(RED)
brace_label.scale(0.35, about_edge=DOWN)
dr_brace.add(brace_label)
dr_brace.rotate(90 * DEGREES, RIGHT)
dr_brace.move_to(thick_cylinder.get_corner(OUT + LEFT), IN + LEFT)
self.remove(cylinder)
self.add(thin_cylinder, cylinders, graph, graph_mesh)
self.play(Transform(thin_cylinder, thick_cylinder))
self.add(dr_brace, set_depth_test=False)
self.play(Write(dr_brace))
self.wait()
self.play(
LaggedStartMap(FadeOut, annotations, shift=DOWN, run_time=1),
FadeOut(area_eq2["Area"], DOWN),
FadeIn(volume_word, DOWN),
area_part.animate.next_to(volume_word, RIGHT, SMALL_BUFF, DOWN),
)
dr_tex.next_to(area_part, RIGHT, SMALL_BUFF, DOWN)
self.play(FadeIn(dr_tex))
self.wait()
# Show all cylinders
integrand = VGroup(*area_part[0][1:], *dr_tex)
integrand.fix_in_frame()
integral = Tex(R"\int_0^\infty", **eq_kw)
integral.fix_in_frame()
integral.move_to(volume_word, LEFT)
thick_cylinders = Group(*(
self.get_thick_cylinder(cyl, delta_r * axes.x_axis.get_unit_size())
for cyl in cylinders
))
thick_cylinders.set_opacity(0.8)
thick_cylinders.set_shading(0.25, 0.25, 0.25)
small_dr = 0.02
thin_cylinders = Group(*(
self.get_thick_cylinder(self.get_cylinder(axes, r), small_dr)
for r in np.arange(0, 5, small_dr)
))
thin_cylinders.set_opacity(0.5)
self.play(
FadeOut(volume_word, LEFT),
FadeOut(area_part[0][0], LEFT),
FadeIn(integral, LEFT),
integrand.animate.next_to(integral, RIGHT, buff=0),
)
self.add(thick_cylinders, cylinders, graph, graph_mesh)
self.play(ShowIncreasingSubsets(thick_cylinders, run_time=8))
self.play(FadeOut(thick_cylinders, 0.1 * IN))
self.wait()
self.add(dr_brace[:-1], dr_brace[-1], set_depth_test=False)
self.play(
Transform(thin_cylinder, thin_cylinders[int(np.round(r_tracker.get_value() / small_dr))]),
dr_brace[:-1].animate.stretch(small_dr / delta_r, 0, about_edge=RIGHT),
UpdateFromFunc(dr_brace[-1], lambda m: m.next_to(dr_brace[:-1], OUT, SMALL_BUFF)),
run_time=3,
)
self.add(thin_cylinders, cylinders, graph, graph_mesh)
self.add(dr_brace)
dr_brace.deactivate_depth_test()
self.play(
ShowIncreasingSubsets(thin_cylinders),
frame.animate.reorient(20, 70).set_height(8).move_to(OUT),
FadeOut(dr_brace, time_span=(0, 2)),
FadeOut(thin_cylinder, 2 * IN, time_span=(0, 2)),
FadeOut(functions, time_span=(0, 2)),
FadeOut(integral, time_span=(0, 2)),
FadeOut(integrand, time_span=(0, 2)),
run_time=20,
)
self.wait()
# Ambient rotation
t0 = self.time
frame.add_updater(lambda m: m.reorient(20 * math.cos(0.1 * (self.time - t0))))
self.wait(30)
class CylinderIntegral(InteractiveScene):
def construct(self):
# Set up equations
kw = dict(
font_size=48,
t2c={
"{r}": RED,
"{0}": RED,
R"{\infty}": RED,
}
)
exprs = VGroup(
Tex(R"\int_0^\infty 2\pi {r} \cdot e^{-{r}^2} \, d{r}", **kw),
Tex(R"\pi \int_0^\infty 2 {r} \cdot e^{-{r}^2}\, d{r}", **kw),
Tex(R"= \pi \left[ -e^{-{\infty}^2} - \left(-e^{-{0}^2} \right) \right]", **kw),
Tex(R"= \pi", **kw),
)
exprs[1:].arrange(RIGHT, buff=SMALL_BUFF)
exprs[1:].to_corner(UL)
exprs[0].move_to(exprs[1], LEFT)
# Factor out
self.add(exprs[0])
self.wait()
self.play(TransformMatchingTex(*exprs[:2], run_time=1, path_arc=30 * DEGREES))
self.wait()
# Show antiderivative
integrand = exprs[1][R"2 {r} \cdot e^{-{r}^2}"]
integrand_rect = SurroundingRectangle(integrand, buff=SMALL_BUFF)
integrand_rect.set_stroke(YELLOW, 2)
anti_derivative = Tex(R"-e^{-{r}^2}", **kw)
anti_derivative.next_to(integrand_rect, DOWN, buff=1.5)
arrow = Arrow(anti_derivative, integrand_rect)
arrow.set_color(YELLOW)
arrow_label = Tex(R"d \over d{r}", **kw)
arrow_label.scale(0.75)
arrow_label.next_to(arrow, RIGHT, MED_SMALL_BUFF)
self.play(
ShowCreation(integrand_rect),
GrowArrow(arrow),
FadeIn(arrow_label, UP),
)
self.wait()
self.play(TransformMatchingShapes(integrand.copy(), anti_derivative))
self.wait()
# Evaluate
self.play(
Write(exprs[2]["="]),
TransformFromCopy(exprs[1][R"\pi"], exprs[2][R"\pi"]),
TransformFromCopy(exprs[1][R"\int"], exprs[2][R"["]),
TransformFromCopy(exprs[1][R"\int"], exprs[2][R"]"]),
)
self.wait()
self.play(LaggedStart(
FadeTransform(anti_derivative.copy(), exprs[2][R"-e^{-{\infty}^2}"]),
FadeIn(VGroup(*exprs[2][R"- \left("], exprs[2][R"\right)"])),
FadeTransform(anti_derivative.copy(), exprs[2][R"-e^{-{0}^2}"]),
lag_ratio=0.7
))
self.play(
TransformMatchingShapes(
VGroup(
*exprs[1][R"\pi \int_0^\infty"],
*anti_derivative
).copy(),
exprs[2]
)
)
self.wait()
self.play(TransformMatchingTex(exprs[2].copy(), exprs[3]))
self.wait()
# Simplify
rects = VGroup(
SurroundingRectangle(exprs[2][R"-e^{-{\infty}^2}"]),
SurroundingRectangle(exprs[2][R"-e^{-{0}^2}"]),
)
rects.set_stroke(TEAL, 1)
values = VGroup(*map(Integer, [0, -1]))
for value, rect in zip(values, rects):
value.next_to(rect, DOWN)
value.match_color(rect)
zero, one = values
self.play(
TransformFromCopy(integrand_rect, rects[0]),
integrand_rect.animate.set_stroke(YELLOW, 1, 0.5)
)
self.play(FadeIn(zero, 0.5 * DOWN))
self.wait()
self.play(TransformFromCopy(*rects))
self.play(FadeIn(one, 0.5 * DOWN))
self.wait()
self.play(Write(exprs[3]))
self.wait()
# Highlight answer
answer = exprs[3][R"\pi"]
self.play(
LaggedStartMap(FadeOut, VGroup(
integrand_rect, arrow, arrow_label, anti_derivative,
*rects, *values
)),
answer.animate.scale(2, about_edge=LEFT)
)
self.play(FlashAround(answer, run_time=2, time_width=1.5, color=TEAL))
self.wait()
class CartesianSlices(GaussianIntegral):
def construct(self):
# Setup
frame = self.frame
axes = self.get_axes()
graph = self.get_gaussian_graph(axes)
graph_mesh = SurfaceMesh(graph, resolution=(21, 21))
graph_mesh.set_stroke(WHITE, 0.5, opacity=0.25)
graph_mesh.set_flat_stroke(False)
self.add(axes, graph, graph_mesh)
# Dynamic slice
x_slice, y_tracker = self.get_dynamic_slice(axes)
y_unit = axes.y_axis.get_unit_size()
graph.add_updater(lambda m: m.set_clip_plane(UP, -y_tracker.get_value() * y_unit))
x_max = axes.x_range[1]
y_tracker.set_value(x_max)
self.add(x_slice)
self.play(
y_tracker.animate.set_value(-x_max),
run_time=5,
rate_func=linear,
)
self.wait()
# Show many slices
def get_x_slices(dx=0.25):
original_y_value = y_tracker.get_value()
x_slices = VGroup()
x_min, x_max = axes.x_range[:2]
for y in np.arange(x_max, x_min, -dx):
y_tracker.set_value(y)
x_slice.update()
x_slices.add(x_slice.copy().clear_updaters())
x_slices.use_winding_fill(False)
x_slices.deactivate_depth_test()
x_slices.set_stroke(BLUE, 2, 0.5)
x_slices.set_flat_stroke(False)
y_tracker.set_value(original_y_value)
return x_slices
x_slices = get_x_slices(dx=0.25)
self.add(x_slice, x_slices, graph, graph_mesh)
self.play(
FadeOut(graph, time_span=(0, 1)),
FadeOut(x_slice, time_span=(0, 1)),
FadeIn(x_slices, 0.1 * OUT, lag_ratio=0.1),
axes.labels[2].animate.set_opacity(0),
frame.animate.reorient(-80),
run_time=4
)
self.play(
frame.animate.reorient(-100),
run_time=3,
)
self.wait()
y_tracker.set_value(-x_max)
self.add(x_slice, x_slices, graph, graph_mesh)
self.play(
FadeOut(x_slices, 0.1 * IN, time_span=(0, 2.5)),
FadeIn(graph, time_span=(0, 2.5)),
VFadeIn(x_slice),
frame.animate.reorient(-15).set_height(6),
y_tracker.animate.set_value(0),
run_time=5,
)
# Discuss area of each slice
tex_kw = dict(
font_size=42,
t2c={"x": BLUE, "y": YELLOW}
)
get_y = y_tracker.get_value
x_slice_label = Tex("0.00 e^{-x^2}", **tex_kw)
coef = x_slice_label.make_number_changable("0.00")
coef.set_color(YELLOW)
coef.add_updater(lambda m: m.set_value(math.exp(-get_y()**2)).rotate(90 * DEGREES, RIGHT))
x_term = x_slice_label[1:]
brace = Brace(coef, UP, MED_SMALL_BUFF)
y_term = Tex("e^{-y^2}", **tex_kw)
y_term.next_to(brace, UP, SMALL_BUFF)
y0_label = Tex("y = 0", **tex_kw)
y0_label.rotate(90 * DEGREES, RIGHT)
y0_label.next_to(x_slice.pfp(0.35), OUT + LEFT)
x_slice_label.add(brace, y_term)
x_slice_label.rotate(90 * DEGREES, RIGHT)
x_slice_label.add_updater(lambda m: m.next_to(x_slice.pfp(0.6), OUT + RIGHT))
x_slice_label.save_state()
y_term.next_to(x_term, LEFT, SMALL_BUFF, aligned_edge=DOWN)
brace.scale(0, about_edge=IN)
coef.scale(0, about_edge=IN)
swap = Swap(x_term, y_term)
swap.begin()
swap.update(1)
func_label = Tex(R"e^{-(x^2 + y^2)}", **tex_kw)
func_label.rotate(90 * DEGREES, RIGHT)
func_label.next_to(x_slice_label, OUT, MED_LARGE_BUFF)
fx0 = Tex(R"e^{-(x^2 + 0^2)} = e^{-x^2}", **tex_kw)
fx0.rotate(90 * DEGREES, RIGHT)
fx0.next_to(func_label, IN, MED_LARGE_BUFF, aligned_edge=LEFT)
fx0["0"].set_color(YELLOW)
self.always_depth_test = False
self.play(
*(
VShowPassingFlash(mob, time_width=1.5, run_time=3)
for mob in [
x_slice.copy().set_stroke(YELLOW, 8).set_fill(opacity=0).shift(0.02 * OUT),
Line(*axes.x_axis.get_start_and_end()).set_stroke(YELLOW, 8).insert_n_curves(40),
]
),
Write(y0_label)
)
self.wait()
self.play(FadeIn(func_label))
self.wait()
self.play(TransformMatchingTex(func_label.copy(), fx0, lag_ratio=0.025))
self.wait()
self.play(FadeOut(fx0, RIGHT, rate_func=running_start))
self.play(TransformMatchingShapes(func_label.copy(), x_slice_label))
self.wait()
self.play(Swap(x_term, y_term, path_arc=0.5 * PI))
self.play(
Restore(x_slice_label),
FadeOut(func_label, OUT),
)
self.wait()
# Note the area
def get_area_label():
area_label = TexText(R"Area = $0.00 \cdot C$", font_size=30)
area_label["C"].set_color(RED)
num = area_label.make_number_changable("0.00")
num.set_value(coef.get_value())
area_label.rotate(90 * DEGREES, RIGHT)
area_label.move_to(interpolate(x_slice.get_zenith(), x_slice.get_nadir(), 0.66))
area_label.shift(0.1 * DOWN)
return area_label
self.play(FadeIn(get_area_label(), run_time=3, rate_func=there_and_back_with_pause))
# Move the slice
y0_slice_copy = x_slice.copy()
y0_slice_copy.clear_updaters()
y0_slice_copy.set_fill(opacity=0)
self.play(FadeOut(y0_label))
for value in [-0.5, -0.75, -1]:
self.play(y_tracker.animate.set_value(value), run_time=3)
slice_copy = y0_slice_copy.copy().set_opacity(0)
area_label = get_area_label()
self.play(FadeIn(area_label))
self.play(slice_copy.animate.match_y(x_slice).set_stroke(YELLOW, 3, 1))
self.wait(0.25)
self.play(slice_copy.animate.match_depth(x_slice, stretch=True, about_edge=IN).set_opacity(0))
self.wait()
self.play(FadeOut(area_label))
# Go back to finer slices
x_slices = get_x_slices(dx=0.1)
y_tracker.set_value(-1)
self.add(x_slices, graph, graph_mesh)
self.play(
FadeIn(x_slices, 0.1 * OUT, lag_ratio=0.1, run_time=4),
FadeOut(graph),
FadeOut(x_slice, time_span=(3, 4)),
FadeOut(x_slice_label, time_span=(3, 4)),
frame.animate.reorient(-83, 72, 0).set_height(8).center().set_anim_args(run_time=5)
)
# Ambient rotation
t0 = self.time
theta0 = frame.get_theta()
frame.clear_updaters()
frame.add_updater(lambda m: m.set_theta(
theta0 + -0.2 * math.sin(0.1 * (self.time - t0))
))
self.wait(10)
# Show slice width
mid_index = len(x_slices) // 2 - 3
line = Line(x_slices[mid_index].get_zenith(), x_slices[mid_index + 1].get_zenith())
brace = Brace(Line().set_width(line.get_length()), UP)
brace.stretch(0.5, 1)
brace.add(brace.get_tex("dy", buff=SMALL_BUFF).scale(0.75, about_edge=DOWN))
brace.rotate(90 * DEGREES, RIGHT)
brace.rotate(90 * DEGREES, IN)
brace.next_to(line, OUT, buff=0)
brace.use_winding_fill(False)
self.play(FadeIn(brace))
self.wait(60)
class CartesianSliceOverlay(InteractiveScene):
def construct(self):
# Show integral
kw = dict(
t2c={"{x}": BLUE, "{y}": YELLOW, "C": RED},
font_size=48,
)
integral1 = Tex(R"\int_{-\infty}^\infty C \cdot e^{-{y}^2} d{y}", **kw)
integral2 = Tex(R"= C \int_{-\infty}^\infty e^{-{y}^2}d{y}", **kw)
rhs = Tex(R"= C^2", **kw)
top_eq = VGroup(integral1, integral2, rhs)
top_eq.arrange(RIGHT, buff=MED_SMALL_BUFF)
top_eq.to_corner(UL)
for part in top_eq:
part.shift((integral1[0].get_y() - part[0].get_y()) * UP)
self.play(FadeIn(integral1))
self.wait()
# Spell out meanings of each part
area_part = integral1[R"C \cdot e^{-{y}^2}"]
volume_part = integral1[R"C \cdot e^{-{y}^2} d{y}"]
area_rect = SurroundingRectangle(area_part, buff=0.05)
volume_rect = SurroundingRectangle(volume_part, buff=0.05)
rects = VGroup(area_rect, volume_rect)
rects.set_stroke(TEAL, 1)
rects.set_fill(TEAL, 0.25)
area_word = Text("Area of a slice")
volume_word = Text("Volume of a slice")
words = VGroup(area_word, volume_word)
arrows = VGroup()
for word, rect in zip(words, rects):
word.next_to(rect, DOWN, LARGE_BUFF, LEFT)
arrows.add(Arrow(rect, word))
self.add(area_rect, integral1)
self.play(
FadeIn(area_rect),
GrowArrow(arrows[0]),
FadeIn(area_word)
)
self.wait()
self.play(
Transform(*rects),
Transform(*arrows),
TransformMatchingStrings(area_word, volume_word, run_time=1),
)
self.wait()
self.play(*map(FadeOut, [area_rect, arrows[0], volume_word]))
self.wait()
# Show meaning of C
sub_int = Tex(R"C = \int_{-\infty}^\infty e^{-{x}^2} d{x}", **kw)
sub_int.next_to(top_eq, DOWN, LARGE_BUFF, aligned_edge=LEFT)
box = SurroundingRectangle(sub_int)
box.set_stroke(RED, 2)
arrow = Arrow(integral1["C"], box, stroke_color=RED)
for mob in box, sub_int:
mob.save_state()
mob.replace(integral1["C"], stretch=True)
mob.set_opacity(0)
self.play(
ShowCreation(arrow),
Restore(box),
Restore(sub_int),
)
self.wait()
self.play(TransformMatchingTex(integral1.copy(), integral2, path_arc=30 * DEGREES))
self.wait()
# Expand
box2 = SurroundingRectangle(integral2[2:], buff=SMALL_BUFF)
box2.set_stroke(RED, 1)
self.play(TransformFromCopy(box, box2))
self.wait()
self.play(
FadeOut(box2),
Write(rhs)
)
self.wait()
# Emphasize C^2
C2 = rhs["C^2"]
everything = VGroup(integral1, integral2, sub_int)
self.play(C2.animate.scale(2, about_point=C2.get_left() + 0.1 * LEFT))
self.play(
FlashAround(C2, time_width=1.5, run_time=3),
everything.animate.set_opacity(0.7),
)
self.wait()