3b1b-manim/eoc/chapter9.py
2017-04-18 23:22:20 -07:00

1450 lines
43 KiB
Python

from helpers import *
import scipy
import fractions
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from scene import Scene
from scene.zoomed_scene import ZoomedScene
from scene.reconfigurable_scene import ReconfigurableScene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from eoc.graph_scene import GraphScene
from eoc.chapter1 import Thumbnail as Chapter1Thumbnail
from topics.common_scenes import OpeningQuote, PatreonThanks
class Chapter9OpeningQuote(OpeningQuote):
CONFIG = {
"quote" : [
"We often hear that mathematics consists mainly of",
"proving theorems.",
"Is a writer's job mainly that of\\\\",
"writing sentences?"
],
"highlighted_quote_terms" : {
"proving theorems." : MAROON_B,
"writing sentences?" : MAROON_B,
},
"author" : "Gian-Carlo Rota",
}
class AverageOfContinuousVariable(GraphScene):
CONFIG = {
"bounds" : [1, 7],
"bound_colors" : [RED, GREEN],
}
def construct(self):
self.setup_axes()
graph = self.get_graph(
lambda x : 0.1*x*(x-3)*(x-6) + 4
)
graph_label = self.get_graph_label(graph, "f(x)")
boundary_lines = self.get_vertical_lines_to_graph(
graph, *self.bounds, num_lines = 2,
line_class = DashedLine
)
for line, color in zip(boundary_lines, self.bound_colors):
line.highlight(color)
v_line = self.get_vertical_line_to_graph(
self.bounds[0], graph, color = YELLOW,
)
question = TextMobject(
"What is the average \\\\ value of $f(x)$?"
)
question.next_to(boundary_lines, UP)
self.play(ShowCreation(graph), Write(graph_label))
self.play(ShowCreation(boundary_lines))
self.play(FadeIn(
question,
run_time = 2,
submobject_mode = "lagged_start",
))
self.play(ShowCreation(v_line))
for bound in reversed(self.bounds):
self.play(self.get_v_line_change_anim(
v_line, graph, bound,
run_time = 3,
))
self.dither()
self.dither()
def get_v_line_change_anim(self, v_line, graph, target_x, **kwargs):
start_x = self.x_axis.point_to_number(v_line.get_bottom())
def update(v_line, alpha):
new_x = interpolate(start_x, target_x, alpha)
v_line.put_start_and_end_on(
self.coords_to_point(new_x, 0),
self.input_to_graph_point(new_x, graph)
)
return v_line
return UpdateFromAlphaFunc(v_line, update, **kwargs)
class ThisVideo(TeacherStudentsScene):
def construct(self):
series = VideoSeries()
series.to_edge(UP)
this_video = series[8]
self.play(FadeIn(series, submobject_mode = "lagged_start"))
self.teacher_says(
"A new view of \\\\ the fundamental theorem",
bubble_kwargs = {"height" : 3},
added_anims = [
this_video.shift, this_video.get_height()*DOWN/2,
this_video.highlight, YELLOW,
]
)
self.change_student_modes(*["pondering"]*3)
self.dither(3)
class AverageOfSineStart(AverageOfContinuousVariable):
CONFIG = {
"y_min" : -2,
"y_max" : 2,
"x_min" : -1,
"x_max" : 2.5*np.pi,
"x_leftmost_tick" : 0,
"x_tick_frequency" : np.pi/4,
"x_axis_width" : 12,
"graph_origin" : 5*LEFT,
"x_label_scale_val" : 0.75,
"func" : np.sin,
"graph_color" : BLUE,
"bounds" : [0, np.pi],
}
def construct(self):
self.setup_axes()
self.add_graph()
self.ask_about_average()
def add_graph(self, run_time = 1):
graph = self.get_graph(self.func, color = self.graph_color)
graph_label = self.get_graph_label(
graph, "\\sin(x)",
direction = UP
)
self.play(
ShowCreation(graph),
Write(graph_label),
run_time = run_time
)
self.graph = graph
self.graph_label = graph_label
def ask_about_average(self):
half_period_graph = self.get_graph_portion_between_bounds()
question = TextMobject("Average height?")
question.to_edge(UP)
arrow = Arrow(question.get_bottom(), half_period_graph.get_top())
midpoint = np.mean(self.bounds)
v_line = self.get_vertical_line_to_graph(
midpoint, self.graph,
line_class = DashedLine,
color = WHITE
)
self.play(FadeIn(half_period_graph))
self.play(
Write(question, run_time = 2),
ShowCreation(arrow)
)
self.play(ShowCreation(v_line))
for bound in self.bounds + [midpoint]:
self.play(self.get_v_line_change_anim(
v_line, self.graph, bound,
run_time = 3
))
#########
def get_graph_portion_between_bounds(self):
self.graph_portion_between_bounds = self.get_graph(
self.func,
x_min = self.bounds[0],
x_max = self.bounds[1],
color = YELLOW
)
return self.graph_portion_between_bounds
def setup_axes(self):
GraphScene.setup_axes(self)
self.add_x_axis_labels()
def add_x_axis_labels(self):
labels_and_x_values = [
("\\pi/2", np.pi/2),
("\\pi", np.pi),
("3\\pi/2", 3*np.pi/2),
("2\\pi", 2*np.pi),
]
self.x_axis_labels = VGroup()
for label, x in labels_and_x_values:
tex_mob = TexMobject(label)
tex_mob.scale(self.x_label_scale_val)
tex_mob.move_to(
self.coords_to_point(x, -3*self.x_axis.tick_size),
)
self.add(tex_mob)
self.x_axis_labels.add(tex_mob)
class LengthOfDayGraph(GraphScene):
CONFIG = {
"x_min" : 0,
"x_max" : 365,
"x_axis_width" : 12,
"x_tick_frequency" : 25,
"x_labeled_nums" : range(50, 365, 50),
"x_axis_label" : "Days since March 21",
"y_min" : 0,
"y_max" : 16,
"y_axis_height" : 6,
"y_tick_frequency" : 1,
"y_labeled_nums" : range(2, 15, 2),
"y_axis_label" : "Hours of daylight",
"graph_origin" : 6*LEFT + 3*DOWN,
"camera_class" : ThreeDCamera,
"camera_config" : {
"shading_factor" : 1,
}
}
def construct(self):
self.setup_axes()
self.add_graph()
self.show_solar_pannel()
self.highlight_summer_months()
self.mention_constants()
def add_graph(self):
x_label = self.x_axis_label_mob
y_label = self.y_axis_label_mob
graph = self.get_graph(
lambda x : 2.7*np.sin((2*np.pi)*x/365 ) + 12.4,
color = GREEN,
)
graph_label = TexMobject("2.7\\sin(2\\pi x/365) + 12.4")
graph_label.to_corner(UP+RIGHT).shift(LEFT)
VGroup(*graph_label[3:6]).highlight(graph.get_color())
graph_label[9].highlight(YELLOW)
self.remove(x_label, y_label)
for label in y_label, x_label:
self.play(FadeIn(
label,
run_time = 2,
submobject_mode = "lagged_start"
))
self.play(
ShowCreation(graph, rate_func = None),
FadeIn(
graph_label,
rate_func = squish_rate_func(smooth, 0.5, 1),
submobject_mode = "lagged_start"
),
run_time = 3,
)
self.dither()
self.graph = graph
self.graph_label = graph_label
def show_solar_pannel(self):
randy = Randolph()
randy.to_edge(DOWN)
panel = ThreeDMobject(*[
Rectangle(
height = 0.7, width = 0.25,
fill_color = DARK_GREY,
fill_opacity = 1,
stroke_width = 1,
stroke_color = GREY,
)
for x in range(6)
])
panel.arrange_submobjects(RIGHT, buff = SMALL_BUFF)
panel.center()
panels = ThreeDMobject(panel, panel.copy(), panel.copy())
panels.arrange_submobjects(DOWN)
panels.rotate(4*np.pi/12, DOWN)
panels.rotate(-np.pi/6, OUT)
side_vect = RIGHT
side_vect = rotate_vector(side_vect, 4*np.pi/12, DOWN)
side_vect = rotate_vector(side_vect, -np.pi/3, OUT)
panels.next_to(randy.get_corner(UP+RIGHT), RIGHT)
self.play(FadeIn(randy))
self.play(
randy.change, "thinking", panels.get_right(),
FadeIn(
panels,
run_time = 2,
submobject_mode = "lagged_start"
)
)
for angle in -np.pi/4, np.pi/4:
self.play(*[
Rotate(
panel, angle,
axis = side_vect,
in_place = True,
run_time = 2,
rate_func = squish_rate_func(smooth, a, a+0.8)
)
for panel, a in zip(panels, np.linspace(0, 0.2, len(panels)))
])
self.play(Blink(randy))
self.play(*map(FadeOut, [randy, panels]))
def highlight_summer_months(self):
summer_rect = Rectangle()
summer_rect.set_stroke(width = 0)
summer_rect.set_fill(YELLOW, opacity = 0.25)
summer_rect.replace(Line(
self.graph_origin,
self.coords_to_point(365/2, 15.5)
), stretch = True)
winter_rect = Rectangle()
winter_rect.set_stroke(width = 0)
winter_rect.set_fill(BLUE, opacity = 0.25)
winter_rect.replace(Line(
self.coords_to_point(365/2, 15.5),
self.coords_to_point(365, 0),
), stretch = True)
summer_words, winter_words = [
TextMobject("%s \\\\ months"%s).move_to(rect)
for s, rect in [
("Summer", summer_rect),
("Winter", winter_rect),
]
]
for rect, words in (summer_rect, summer_words), (winter_rect, winter_words):
self.play(
FadeIn(rect),
Write(words, run_time = 2)
)
self.dither()
def mention_constants(self):
#2.7\\sin(2\\pi t/365) + 12.4
constants = VGroup(*[
VGroup(*self.graph_label[i:j])
for i, j in [(0, 3), (7, 9), (11, 14), (16, 20)]
])
self.play(*[
ApplyFunction(
lambda c : c.scale_in_place(0.9).shift(SMALL_BUFF*DOWN).highlight(RED),
constant,
run_time = 3,
rate_func = squish_rate_func(there_and_back, a, a+0.7)
)
for constant, a in zip(
constants,
np.linspace(0, 0.3, len(constants))
)
])
self.dither()
#####
class AskAboutAverageOfContinuousVariables(TeacherStudentsScene):
def construct(self):
self.student_says(
"The average \\dots of a \\\\ continuous thing?",
target_mode = "sassy",
)
self.change_student_modes("confused", "sassy", "confused")
self.play(self.teacher.change_mode, "happy")
self.dither(2)
class AverageOfFiniteSet(Scene):
CONFIG = {
"lengths" : [1, 4, 2, 5]
}
def construct(self):
lengths = self.lengths
lines = VGroup(*[
Line(ORIGIN, length*RIGHT)
for length in lengths
])
colors = Color(BLUE).range_to(RED, len(lengths))
lines.gradient_highlight(*colors)
lines.arrange_submobjects(RIGHT)
lines.generate_target()
lines.target.arrange_submobjects(RIGHT, buff = 0)
for mob in lines, lines.target:
mob.shift(UP)
brace = Brace(lines.target, UP)
labels = VGroup(*[
TexMobject(str(d)).next_to(line, UP).highlight(line.get_color())
for d, line in zip(lengths, lines)
])
plusses = [TexMobject("+") for x in range(len(lengths)-1)]
symbols = VGroup(*
plusses + [TexMobject("=")]
)
symbols.set_fill(opacity = 0)
labels.generate_target()
symbols.generate_target()
symbols.target.set_fill(opacity = 1)
sum_eq = VGroup(*it.chain(*zip(labels.target, symbols.target)))
sum_eq.arrange_submobjects(RIGHT)
sum_eq.next_to(brace, UP)
sum_mob = TexMobject(str(sum(lengths)))
sum_mob.next_to(sum_eq, RIGHT)
dividing_lines = VGroup(*[
DashedLine(p + MED_SMALL_BUFF*UP, p + MED_LARGE_BUFF*DOWN)
for alpha in np.linspace(0, 1, len(lengths)+1)
for p in [interpolate(
lines.target.get_left(),
lines.target.get_right(),
alpha
)]
])
lower_braces = VGroup(*[
Brace(VGroup(*dividing_lines[i:i+2]), DOWN)
for i in range(len(lengths))
])
averages = VGroup(*[
lb.get_text("$%d/%d$"%(sum(lengths), len(lengths)))
for lb in lower_braces
])
circle = Circle(color = YELLOW)
circle.replace(averages[1], stretch = True)
circle.scale_in_place(1.5)
self.add(lines)
self.play(FadeIn(
labels,
submobject_mode = "lagged_start",
run_time = 3
))
self.dither()
self.play(
GrowFromCenter(brace),
*map(MoveToTarget, [lines, labels, symbols]),
run_time = 2
)
self.play(Write(sum_mob))
self.dither()
self.play(ShowCreation(dividing_lines, run_time = 2))
self.play(*it.chain(
map(Write, averages),
map(GrowFromCenter, lower_braces)
))
self.play(ShowCreation(circle))
self.dither(2)
class TryToAddInfinitelyManyPoints(AverageOfSineStart):
CONFIG = {
"max_denominator" : 40,
}
def construct(self):
self.add_graph()
self.try_to_add_infinitely_many_values()
self.show_continuum()
self.mention_integral()
def add_graph(self):
self.setup_axes()
AverageOfSineStart.add_graph(self, run_time = 0)
self.add(self.get_graph_portion_between_bounds())
self.graph_label.to_edge(RIGHT)
self.graph_label.shift(DOWN)
def try_to_add_infinitely_many_values(self):
v_lines = VGroup(*[
self.get_vertical_line_to_graph(
numerator*np.pi/denominator, self.graph,
color = YELLOW,
stroke_width = 6./denominator
)
for denominator in range(self.max_denominator)
for numerator in range(1, denominator)
if fractions.gcd(numerator, denominator) == 1
])
ghost_lines = v_lines.copy().set_stroke(GREY)
v_lines.generate_target()
start_lines = VGroup(*v_lines.target[:15])
end_lines = VGroup(*v_lines.target[15:])
plusses = VGroup(*[TexMobject("+") for x in start_lines])
sum_eq = VGroup(*it.chain(*zip(start_lines, plusses)))
sum_eq.add(*end_lines)
sum_eq.arrange_submobjects(RIGHT)
sum_eq.next_to(v_lines[0], UP, aligned_edge = LEFT)
sum_eq.to_edge(UP, buff = MED_SMALL_BUFF)
h_line = Line(LEFT, RIGHT)
h_line.scale_to_fit_width(start_lines.get_width())
h_line.highlight(WHITE)
h_line.next_to(sum_eq, DOWN, aligned_edge = LEFT)
infinity = TexMobject("\\infty")
infinity.next_to(h_line, DOWN)
self.play(ShowCreation(
v_lines,
run_time = 3,
))
self.add(ghost_lines, v_lines)
self.dither(2)
self.play(
MoveToTarget(
v_lines,
run_time = 3,
submobject_mode = "lagged_start"
),
Write(plusses)
)
self.play(ShowCreation(h_line))
self.play(Write(infinity))
self.dither()
def show_continuum(self):
arrow = Arrow(ORIGIN, UP+LEFT)
input_range = Line(*[
self.coords_to_point(bound, 0)
for bound in self.bounds
])
VGroup(arrow, input_range).highlight(RED)
self.play(FadeIn(arrow))
self.play(
arrow.next_to, input_range.get_start(),
DOWN+RIGHT, SMALL_BUFF
)
self.play(
arrow.next_to, input_range.copy().get_end(),
DOWN+RIGHT, SMALL_BUFF,
ShowCreation(input_range),
run_time = 3,
)
self.play(
arrow.next_to, input_range.get_start(),
DOWN+RIGHT, SMALL_BUFF,
run_time = 3
)
self.play(FadeOut(arrow))
self.dither()
def mention_integral(self):
randy = Randolph()
randy.to_edge(DOWN)
randy.shift(3*LEFT)
self.play(FadeIn(randy))
self.play(PiCreatureBubbleIntroduction(
randy, "Use an integral!",
bubble_class = ThoughtBubble,
target_mode = "hooray"
))
self.play(Blink(randy))
curr_bubble = randy.bubble
new_bubble = randy.get_bubble("Somehow...")
self.play(
Transform(curr_bubble, new_bubble),
Transform(curr_bubble.content, new_bubble.content),
randy.change_mode, "shruggie",
)
self.play(Blink(randy))
self.dither()
class FiniteSample(TryToAddInfinitelyManyPoints):
CONFIG = {
"dx" : 0.1,
"graph_origin" : 6*LEFT + 0.5*DOWN,
}
def construct(self):
self.add_graph()
self.show_finite_sample()
def show_finite_sample(self):
v_lines = self.get_sample_lines(dx = self.dx)
summed_v_lines = v_lines.copy()
plusses = VGroup(*[
TexMobject("+").scale(0.75)
for l in v_lines
])
numerator = VGroup(*it.chain(*zip(summed_v_lines, plusses)))
for group in numerator, plusses:
group.remove(plusses[-1])
numerator.arrange_submobjects(
RIGHT,
buff = SMALL_BUFF,
aligned_edge = DOWN
)
# numerator.scale_to_fit_width(SPACE_WIDTH)
numerator.scale(0.5)
numerator.move_to(self.coords_to_point(3*np.pi/2, 0))
numerator.to_edge(UP)
frac_line = TexMobject("\\over \\,")
frac_line.stretch_to_fit_width(numerator.get_width())
frac_line.next_to(numerator, DOWN)
denominator = TextMobject("(Num. samples)")
denominator.next_to(frac_line, DOWN)
self.play(ShowCreation(v_lines, run_time = 3))
self.dither()
self.play(
ReplacementTransform(
v_lines.copy(),
summed_v_lines,
run_time = 3,
submobject_mode = "lagged_start"
),
Write(
plusses,
rate_func = squish_rate_func(smooth, 0.3, 1)
)
)
self.play(Write(frac_line, run_time = 1))
self.play(Write(denominator))
self.dither()
self.plusses = plusses
self.average = VGroup(numerator, frac_line, denominator)
self.v_lines = v_lines
###
def get_sample_lines(self, dx, color = YELLOW, stroke_width = 2):
return VGroup(*[
self.get_vertical_line_to_graph(
x, self.graph,
color = color,
stroke_width = stroke_width,
)
for x in np.arange(
self.bounds[0]+dx,
self.bounds[1],
dx
)
])
class FiniteSampleWithMoreSamplePoints(FiniteSample):
CONFIG = {
"dx" : 0.05
}
class IntegralOfSine(FiniteSample):
CONFIG = {
"thin_dx" : 0.01,
"rect_opacity" : 0.75,
}
def construct(self):
self.force_skipping()
FiniteSample.construct(self)
self.remove(self.y_axis_label_mob)
self.remove(*self.x_axis_labels[::2])
self.revert_to_original_skipping_status()
self.put_average_in_corner()
self.write_integral()
self.show_riemann_rectangles()
self.let_dx_approach_zero()
self.bring_back_average()
self.distribute_dx()
self.let_dx_approach_zero(restore = False)
self.write_area_over_width()
self.show_moving_v_line()
def put_average_in_corner(self):
self.average.save_state()
self.plusses.set_stroke(width = 0.5)
self.play(
self.average.scale, 0.75,
self.average.to_corner, DOWN+RIGHT,
)
def write_integral(self):
integral = TexMobject("\\int_0^\\pi", "\\sin(x)", "\\,dx")
integral.move_to(self.graph_portion_between_bounds)
integral.to_edge(UP)
self.play(Write(integral))
self.dither(2)
self.integral = integral
def show_riemann_rectangles(self):
kwargs = {
"dx" : self.dx,
"x_min" : self.bounds[0],
"x_max" : self.bounds[1],
"fill_opacity" : self.rect_opacity,
}
rects = self.get_riemann_rectangles(self.graph, **kwargs)
rects.set_stroke(YELLOW, width = 1)
flat_rects = self.get_riemann_rectangles(
self.get_graph(lambda x : 0),
**kwargs
)
thin_kwargs = dict(kwargs)
thin_kwargs["dx"] = self.thin_dx
thin_kwargs["stroke_width"] = 0
self.thin_rects = self.get_riemann_rectangles(
self.graph,
**thin_kwargs
)
start_index = 20
end_index = start_index + 5
low_opacity = 0.5
high_opacity = 1
start_rect = rects[start_index]
side_brace = Brace(start_rect, LEFT, buff = SMALL_BUFF)
bottom_brace = Brace(start_rect, DOWN, buff = SMALL_BUFF)
sin_x = TexMobject("\\sin(x)")
sin_x.next_to(side_brace, LEFT, SMALL_BUFF)
dx = bottom_brace.get_text("$dx$", buff = SMALL_BUFF)
self.transform_between_riemann_rects(
flat_rects, rects,
replace_mobject_with_target_in_scene = True,
)
self.remove(self.v_lines)
self.dither()
rects.save_state()
self.play(*it.chain(
[
ApplyMethod(
rect.set_style_data, BLACK, 1,
None, #Fill color
high_opacity if rect is start_rect else low_opacity
)
for rect in rects
],
map(GrowFromCenter, [side_brace, bottom_brace]),
map(Write, [sin_x, dx]),
))
self.dither()
for i in range(start_index+1, end_index):
self.play(
rects[i-1].set_fill, None, low_opacity,
rects[i].set_fill, None, high_opacity,
side_brace.scale_to_fit_height, rects[i].get_height(),
side_brace.next_to, rects[i], LEFT, SMALL_BUFF,
bottom_brace.next_to, rects[i], DOWN, SMALL_BUFF,
MaintainPositionRelativeTo(sin_x, side_brace),
MaintainPositionRelativeTo(dx, bottom_brace),
)
self.dither()
self.play(
rects.restore,
*map(FadeOut, [sin_x, dx, side_brace, bottom_brace])
)
self.rects = rects
self.dx_brace = bottom_brace
self.dx_label = dx
def let_dx_approach_zero(self, restore = True):
start_state = self.rects.copy()
self.transform_between_riemann_rects(
self.rects, self.thin_rects,
run_time = 3
)
self.dither(2)
if restore:
self.transform_between_riemann_rects(
self.rects, start_state.copy(),
run_time = 2,
)
self.remove(self.rects)
self.rects = start_state
self.rects.set_fill(opacity = 1)
self.play(
self.rects.set_fill, None,
self.rect_opacity,
)
self.dither()
def bring_back_average(self):
num_samples = self.average[-1]
example_dx = TexMobject("0.1")
example_dx.move_to(self.dx_label)
input_range = Line(*[
self.coords_to_point(bound, 0)
for bound in self.bounds
])
input_range.highlight(RED)
#Bring back average
self.play(
self.average.restore,
self.average.center,
self.average.to_edge, UP,
self.integral.to_edge, DOWN,
run_time = 2
)
self.dither()
self.play(
Write(self.dx_brace),
Write(self.dx_label),
)
self.dither()
self.play(
FadeOut(self.dx_label),
FadeIn(example_dx)
)
self.play(Indicate(example_dx))
self.dither()
self.play(ShowCreation(input_range))
self.play(FadeOut(input_range))
self.dither()
#Ask how many there are
num_samples_copy = num_samples.copy()
v_lines = self.v_lines
self.play(*[
ApplyFunction(
lambda l : l.shift(0.5*UP).highlight(GREEN),
line,
rate_func = squish_rate_func(
there_and_back, a, a+0.3
),
run_time = 3,
)
for line, a in zip(
self.v_lines,
np.linspace(0, 0.7, len(self.v_lines))
)
] + [
num_samples_copy.highlight, GREEN
])
self.play(FadeOut(v_lines))
self.dither()
#Count number of samples
num_samples_copy.generate_target()
num_samples_copy.target.shift(DOWN + 0.5*LEFT)
rhs = TexMobject("\\approx", "\\pi", "/", "0.1")
rhs.next_to(num_samples_copy.target, RIGHT)
self.play(
MoveToTarget(num_samples_copy),
Write(rhs.get_part_by_tex("approx")),
)
self.play(ShowCreation(input_range))
self.play(ReplacementTransform(
self.x_axis_labels[1].copy(),
rhs.get_part_by_tex("pi")
))
self.play(FadeOut(input_range))
self.play(
ReplacementTransform(
example_dx.copy(),
rhs.get_part_by_tex("0.1")
),
Write(rhs.get_part_by_tex("/"))
)
self.dither(2)
#Substitute number of samples
self.play(ReplacementTransform(
example_dx, self.dx_label
))
dx = rhs.get_part_by_tex("0.1")
self.play(Transform(
dx, TexMobject("dx").move_to(dx)
))
self.dither(2)
approx = rhs.get_part_by_tex("approx")
rhs.remove(approx)
self.play(
FadeOut(num_samples),
FadeOut(num_samples_copy),
FadeOut(approx),
rhs.next_to, self.average[1], DOWN
)
self.dither()
self.pi_over_dx = rhs
def distribute_dx(self):
numerator, frac_line, denominator = self.average
pi, over, dx = self.pi_over_dx
integral = self.integral
dx.generate_target()
lp, rp = parens = TexMobject("()")
parens.scale_to_fit_height(numerator.get_height())
lp.next_to(numerator, LEFT)
rp.next_to(numerator, RIGHT)
dx.target.next_to(rp, RIGHT)
self.play(
MoveToTarget(dx, path_arc = np.pi/2),
Write(parens),
frac_line.stretch_to_fit_width,
parens.get_width() + dx.get_width(),
frac_line.shift, dx.get_width()*RIGHT/2,
FadeOut(over)
)
self.dither(2)
average = VGroup(parens, numerator, dx, frac_line, pi)
integral.generate_target()
over_pi = TexMobject("\\frac{\\phantom{\\int \\sin(x)\\dx}}{\\pi}")
integral.target.scale_to_fit_width(over_pi.get_width())
integral.target.next_to(over_pi, UP)
integral_over_pi = VGroup(integral.target, over_pi)
integral_over_pi.to_corner(UP+RIGHT)
arrow = Arrow(LEFT, RIGHT)
arrow.next_to(integral.target, LEFT)
self.play(
average.scale, 0.9,
average.next_to, arrow, LEFT,
average.shift_onto_screen,
ShowCreation(arrow),
Write(over_pi),
MoveToTarget(integral, run_time = 2)
)
self.dither(2)
self.play(*map(FadeOut, [self.dx_label, self.dx_brace]))
self.integral_over_pi = VGroup(integral, over_pi)
self.average = average
self.average_arrow = arrow
def write_area_over_width(self):
self.play(
self.integral_over_pi.shift, 2*LEFT,
*map(FadeOut, [self.average, self.average_arrow])
)
average_height = TextMobject("Average height = ")
area_over_width = TexMobject(
"{\\text{Area}", "\\over\\,", "\\text{Width}}", "="
)
area_over_width.get_part_by_tex("Area").gradient_highlight(
BLUE, GREEN
)
area_over_width.next_to(self.integral_over_pi[1][0], LEFT)
average_height.next_to(area_over_width, LEFT)
self.play(*map(FadeIn, [average_height, area_over_width]))
self.dither()
def show_moving_v_line(self):
mean = np.mean(self.bounds)
v_line = self.get_vertical_line_to_graph(
mean, self.graph,
line_class = DashedLine,
color = WHITE,
)
self.play(ShowCreation(v_line))
for count in range(2):
for x in self.bounds + [mean]:
self.play(self.get_v_line_change_anim(
v_line, self.graph, x,
run_time = 3
))
class LetsSolveThis(TeacherStudentsScene):
def construct(self):
expression = TexMobject(
"{\\int_0^\\pi ", " \\sin(x)", "\\,dx \\over \\pi}"
)
expression.to_corner(UP+LEFT)
question = TextMobject(
"What's the antiderivative \\\\ of",
"$\\sin(x)$",
"?"
)
for tex_mob in expression, question:
tex_mob.highlight_by_tex("sin", BLUE)
self.add(expression)
self.teacher_says("Let's compute it.")
self.dither()
self.student_thinks(question)
self.dither(2)
class Antiderivative(AverageOfSineStart):
CONFIG = {
"antideriv_color" : GREEN,
"deriv_color" : BLUE,
"riemann_rect_dx" : 0.01,
"y_axis_label" : "",
"graph_origin" : 4*LEFT + DOWN,
}
def construct(self):
self.setup_axes()
self.add_x_axis_labels()
self.negate_derivative_of_cosine()
self.walk_through_slopes()
self.apply_ftoc()
self.show_difference_in_antiderivative()
self.comment_on_area()
self.divide_by_pi()
self.highlight_antiderivative_fraction()
self.show_slope()
self.bring_back_derivative()
self.show_tangent_slope()
def add_x_axis_labels(self):
AverageOfSineStart.add_x_axis_labels(self)
self.remove(*self.x_axis_labels[::2])
def negate_derivative_of_cosine(self):
cos, neg_cos, sin, neg_sin = graphs = [
self.get_graph(func)
for func in [
np.cos,
lambda x : -np.cos(x),
np.sin,
lambda x : -np.sin(x),
]
]
VGroup(cos, neg_cos).highlight(self.antideriv_color)
VGroup(sin, neg_sin).highlight(self.deriv_color)
labels = ["\\cos(x)", "-\\cos(x)", "\\sin(x)", "-\\sin(x)"]
x_vals = [2*np.pi, 2*np.pi, 5*np.pi/2, 5*np.pi/2]
vects = [UP, DOWN, UP, DOWN]
for graph, label, x_val, vect in zip(graphs, labels, x_vals, vects):
graph.label = self.get_graph_label(
graph, label,
x_val = x_val,
direction = vect,
buff = SMALL_BUFF
)
derivs = []
for F, f in ("\\cos", "-\\sin"), ("-\\cos", "\\sin"):
deriv = TexMobject(
"{d(", F, ")", "\\over\\,", "dx}", "(x)",
"=", f, "(x)"
)
deriv.highlight_by_tex(F, self.antideriv_color)
deriv.highlight_by_tex(f, self.deriv_color)
deriv.to_edge(UP)
derivs.append(deriv)
cos_deriv, neg_cos_deriv = derivs
self.add(cos_deriv)
for graph in cos, neg_sin:
self.play(
ShowCreation(graph, rate_func = smooth),
Write(
graph.label,
rate_func = squish_rate_func(smooth, 0.3, 1)
),
run_time = 2
)
self.dither()
self.dither()
self.play(*[
ReplacementTransform(*pair)
for pair in [
(derivs),
(cos, neg_cos),
(cos.label, neg_cos.label),
(neg_sin, sin),
(neg_sin.label, sin.label),
]
])
self.dither(2)
self.neg_cos = neg_cos
self.sin = sin
self.deriv = neg_cos_deriv
def walk_through_slopes(self):
neg_cos = self.neg_cos
sin = self.sin
faders = sin, sin.label
for mob in faders:
mob.save_state()
sin_copy = self.get_graph(
np.sin,
x_min = 0,
x_max = 2*np.pi,
color = BLUE,
)
v_line = self.get_vertical_line_to_graph(
0, neg_cos,
line_class = DashedLine,
color = WHITE
)
ss_group = self.get_secant_slope_group(
0, neg_cos,
dx = 0.001,
secant_line_color = YELLOW
)
def quad_smooth(t):
return 0.25*(np.floor(4*t) + smooth((4*t) % 1))
self.play(*[
ApplyMethod(m.fade, 0.6)
for m in faders
])
self.dither()
self.play(*map(ShowCreation, ss_group), run_time = 2)
kwargs = {
"run_time" : 20,
"rate_func" : quad_smooth,
}
v_line_anim = self.get_v_line_change_anim(
v_line, sin_copy, 2*np.pi,
**kwargs
)
self.animate_secant_slope_group_change(
ss_group,
target_x = 2*np.pi,
added_anims = [
ShowCreation(sin_copy, **kwargs),
v_line_anim
],
**kwargs
)
self.play(
*map(FadeOut, [ss_group, v_line, sin_copy])
)
self.dither()
self.ss_group = ss_group
def apply_ftoc(self):
deriv = self.deriv
integral = TexMobject(
"\\int", "^\\pi", "_0", "\\sin(x)", "\\,dx"
)
rhs = TexMobject(
"=", "\\big(", "-\\cos", "(", "\\pi", ")", "\\big)",
"-", "\\big(", "-\\cos", "(", "0", ")", "\\big)",
)
rhs.next_to(integral, RIGHT)
equation = VGroup(integral, rhs)
equation.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF)
(start_pi, end_pi), (start_zero, end_zero) = start_end_pairs = [
[
m.get_part_by_tex(tex)
for m in integral, rhs
]
for tex in "\\pi", "0"
]
for tex_mob in integral, rhs:
tex_mob.highlight_by_tex("sin", self.deriv_color)
tex_mob.highlight_by_tex("cos", self.antideriv_color)
tex_mob.highlight_by_tex("0", YELLOW)
tex_mob.highlight_by_tex("\\pi", YELLOW)
self.play(
Write(integral),
self.deriv.scale, 0.5,
self.deriv.center,
self.deriv.to_edge, LEFT, MED_SMALL_BUFF,
self.deriv.shift, UP,
)
self.dither()
self.play(FadeIn(
VGroup(*filter(
lambda part : part not in [end_pi, end_zero],
rhs
)),
submobject_mode = "lagged_start",
run_time = 2,
))
self.dither()
for start, end in start_end_pairs:
self.play(ReplacementTransform(
start.copy(), end,
path_arc = np.pi/6,
run_time = 2
))
self.dither()
self.integral = integral
self.rhs = rhs
def show_difference_in_antiderivative(self):
pi_point, zero_point = points = [
self.input_to_graph_point(x, self.neg_cos)
for x in reversed(self.bounds)
]
interim_point = pi_point[0]*RIGHT + zero_point[1]*UP
pi_dot, zero_dot = dots = [
Dot(point, color = YELLOW)
for point in points
]
v_line = DashedLine(pi_point, interim_point)
h_line = DashedLine(interim_point, zero_point)
v_line_brace = Brace(v_line, RIGHT)
two_height_label = v_line_brace.get_text(
"$2$", buff = SMALL_BUFF
)
two_height_label.add_background_rectangle()
pi = self.x_axis_labels[1]
#Horrible hack
black_pi = pi.copy().highlight(BLACK)
self.add(black_pi, pi)
cos_tex = self.rhs.get_part_by_tex("cos")
self.play(ReplacementTransform(
cos_tex.copy(), pi_dot
))
self.dither()
moving_dot = pi_dot.copy()
self.play(
ShowCreation(v_line),
# Animation(pi),
pi.shift, 0.8*pi.get_width()*(LEFT+UP),
moving_dot.move_to, interim_point,
)
self.play(
ShowCreation(h_line),
ReplacementTransform(moving_dot, zero_dot)
)
self.play(GrowFromCenter(v_line_brace))
self.dither(2)
self.play(Write(two_height_label))
self.dither(2)
self.v_line = v_line
self.h_line = h_line
self.pi_dot = pi_dot
self.zero_dot = zero_dot
self.v_line_brace = v_line_brace
self.two_height_label = two_height_label
def comment_on_area(self):
rects = self.get_riemann_rectangles(
self.sin,
dx = self.riemann_rect_dx,
stroke_width = 0,
fill_opacity = 0.7,
x_min = self.bounds[0],
x_max = self.bounds[1],
)
area_two = TexMobject("2").replace(
self.two_height_label
)
self.play(Write(rects))
self.dither()
self.play(area_two.move_to, rects)
self.dither(2)
self.rects = rects
self.area_two = area_two
def divide_by_pi(self):
integral = self.integral
rhs = self.rhs
equals = rhs[0]
rhs_without_eq = VGroup(*rhs[1:])
frac_lines = VGroup(*[
TexMobject("\\over\\,").stretch_to_fit_width(
mob.get_width()
).move_to(mob)
for mob in integral, rhs_without_eq
])
frac_lines.shift(
(integral.get_height()/2 + SMALL_BUFF)*DOWN
)
pi_minus_zeros = VGroup(*[
TexMobject("\\pi", "-", "0").next_to(line, DOWN)
for line in frac_lines
])
for tex_mob in pi_minus_zeros:
for tex in "pi", "0":
tex_mob.highlight_by_tex(tex, YELLOW)
answer = TexMobject(" = \\frac{2}{\\pi}")
answer.next_to(
frac_lines, RIGHT,
align_using_submobjects = True
)
answer.shift_onto_screen()
self.play(
equals.next_to, frac_lines[0].copy(), RIGHT,
rhs_without_eq.next_to, frac_lines[1].copy(), UP,
*map(Write, frac_lines)
)
self.play(*[
ReplacementTransform(
integral.get_part_by_tex(tex).copy(),
pi_minus_zeros[0].get_part_by_tex(tex)
)
for tex in "\\pi","0"
] + [
Write(pi_minus_zeros[0].get_part_by_tex("-"))
])
self.play(*[
ReplacementTransform(
rhs.get_part_by_tex(
tex, substring = False
).copy(),
pi_minus_zeros[1].get_part_by_tex(tex)
)
for tex in "\\pi", "-", "0"
])
self.dither(2)
full_equation = VGroup(
integral, frac_lines, rhs, pi_minus_zeros
)
background_rect = BackgroundRectangle(full_equation, fill_opacity = 1)
background_rect.stretch_in_place(1.2, dim = 1)
full_equation.add_to_back(background_rect)
self.play(
full_equation.shift,
(answer.get_width()+MED_LARGE_BUFF)*LEFT
)
self.play(Write(answer))
self.dither()
self.antiderivative_fraction = VGroup(
rhs_without_eq,
frac_lines[1],
pi_minus_zeros[1]
)
self.integral_fraction = VGroup(
integral,
frac_lines[0],
pi_minus_zeros[0],
equals
)
def highlight_antiderivative_fraction(self):
fraction = self.antiderivative_fraction
big_rect = Rectangle(
stroke_width = 0,
fill_color = BLACK,
fill_opacity = 0.9,
)
big_rect.scale_to_fit_width(2*SPACE_WIDTH)
big_rect.scale_to_fit_height(2*SPACE_HEIGHT)
morty = Mortimer()
morty.to_corner(DOWN+RIGHT)
self.play(
FadeIn(big_rect),
FadeIn(morty),
Animation(fraction)
)
self.play(morty.change, "raise_right_hand", fraction)
self.play(Blink(morty))
self.dither(2)
self.play(
FadeOut(big_rect),
FadeOut(morty),
Animation(fraction)
)
def show_slope(self):
line = Line(
self.zero_dot.get_center(),
self.pi_dot.get_center(),
)
line.highlight(RED)
line.scale_in_place(1.2)
pi = TexMobject("\\pi")
pi.next_to(self.h_line, DOWN)
self.play(
FadeOut(self.rects),
FadeOut(self.area_two)
)
self.play(Write(pi))
self.play(ShowCreation(line, run_time = 2))
self.dither()
def bring_back_derivative(self):
self.play(
FadeOut(self.integral_fraction),
self.deriv.scale, 1.7,
self.deriv.to_corner, UP+LEFT, MED_LARGE_BUFF,
self.deriv.shift, MED_SMALL_BUFF*DOWN,
)
self.dither()
def show_tangent_slope(self):
ss_group = self.get_secant_slope_group(
0, self.neg_cos,
dx = 0.001,
secant_line_color = YELLOW,
secant_line_length = 4,
)
self.play(*map(ShowCreation, ss_group), run_time = 2)
for count in range(2):
for x in reversed(self.bounds):
self.animate_secant_slope_group_change(
ss_group,
target_x = x,
run_time = 6,
)