From ee622987df18921f993dda3afd04bdbb666d1d1d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:15:11 -0700 Subject: [PATCH] Final animations for de chapter2 --- active_projects/ode/all_part1_scenes.py | 2 +- active_projects/ode/all_part2_scenes.py | 43 +- active_projects/ode/part1/staging.py | 7 +- active_projects/ode/part1/wordy_scenes.py | 2 +- active_projects/ode/part2/fourier_series.py | 27 + active_projects/ode/part2/heat_equation.py | 1509 ++++++++++++++++++- active_projects/ode/part2/pi_scenes.py | 121 +- active_projects/ode/part2/staging.py | 776 +++++++++- active_projects/ode/part2/wordy_scenes.py | 657 +++++++- 9 files changed, 2965 insertions(+), 179 deletions(-) diff --git a/active_projects/ode/all_part1_scenes.py b/active_projects/ode/all_part1_scenes.py index 6187244b..0ca648d4 100644 --- a/active_projects/ode/all_part1_scenes.py +++ b/active_projects/ode/all_part1_scenes.py @@ -5,7 +5,7 @@ from active_projects.ode.part1.phase_space import * from active_projects.ode.part1.wordy_scenes import * OUTPUT_DIRECTORY = "ode/part1" -ALL_SCENE_CLASSES = [ +SCENES_IN_ORDER = [ WhenChangeIsEasier, VectorFieldTest, IntroducePendulum, diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 3961f4da..c3883973 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -5,33 +5,24 @@ from active_projects.ode.part2.pi_scenes import * from active_projects.ode.part2.wordy_scenes import * OUTPUT_DIRECTORY = "ode/part2" -ALL_SCENE_CLASSES = [ - # Tests - FourierOfPiSymbol, - FourierOfPiSymbol5, - FourierOfTrebleClef, - FourierOfEighthNote, - FourierOfN, - FourierNailAndGear, - FourierNDQ, - FourierBatman, - FourierGoogleG, - FourierHeart, - # CirclesDrawingWave, - # Scenes for video - ExplainCircleAnimations, - FourierSeriesIntroBackground4, - FourierSeriesIntroBackground8, - FourierSeriesIntroBackground12, - FourierSeriesIntroBackground20, - FourierSeriesIntro, +SCENES_IN_ORDER = [ PartTwoOfTour, + HeatEquationIntroTitle, + BrownianMotion, + BlackScholes, + ContrastChapters1And2, + FourierSeriesIntro, + FourierSeriesIntroBackground20, + ExplainCircleAnimations, + # FourierSeriesIntroBackground4, + # FourierSeriesIntroBackground8, + # FourierSeriesIntroBackground12, TwoDBodyWithManyTemperatures, TwoDBodyWithManyTemperaturesGraph, TwoDBodyWithManyTemperaturesContour, BringTwoRodsTogether, ShowEvolvingTempGraphWithArrows, - TodaysTargetWrapper, + # TodaysTargetWrapper, WriteHeatEquation, ReactionsToInitialHeatEquation, TalkThrough1DHeatGraph, @@ -39,8 +30,12 @@ ALL_SCENE_CLASSES = [ CompareInputsOfGeneralCaseTo1D, ContrastXChangesToTChanges, ShowPartialDerivativeSymbols, + WriteHeatEquation, + ShowCurvatureToRateOfChangeIntuition, + ContrastPDEToODE, TransitionToTempVsTime, - ShowNewton, - ShowCupOfWater, - ShowNewtonsLawGraph, + Show1DAnd3DEquations, + # + AskAboutWhereEquationComesFrom, + DiscreteSetup, ] diff --git a/active_projects/ode/part1/staging.py b/active_projects/ode/part1/staging.py index d9f553f6..607c2989 100644 --- a/active_projects/ode/part1/staging.py +++ b/active_projects/ode/part1/staging.py @@ -108,6 +108,7 @@ class TourOfDifferentialEquations(MovingCameraScene): def construct(self): self.add_title() self.show_thumbnails() + self.zoom_in_to_one_thumbnail() # self.show_words() def add_title(self): @@ -166,9 +167,13 @@ class TourOfDifferentialEquations(MovingCameraScene): ) self.play(Write(dots)) self.wait() + + self.thumbnails = thumbnails + + def zoom_in_to_one_thumbnail(self): self.play( self.camera_frame.replace, - thumbnails[self.zoomed_thumbnail_index], + self.thumbnails[self.zoomed_thumbnail_index], run_time=3, ) self.wait() diff --git a/active_projects/ode/part1/wordy_scenes.py b/active_projects/ode/part1/wordy_scenes.py index a6c53ed7..1106f249 100644 --- a/active_projects/ode/part1/wordy_scenes.py +++ b/active_projects/ode/part1/wordy_scenes.py @@ -185,7 +185,7 @@ class ReasonForSolution(Scene): cu_group.arrange(DOWN, buff=2) group = VGroup(eq_word, s_word, cu_group) group.arrange(RIGHT, buff=2) - words = VGroup(eq_word, s_word, u_word, c_word) + # words = VGroup(eq_word, s_word, u_word, c_word) # Arrows arrows = VGroup( diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/ode/part2/fourier_series.py index 86677e70..2638a7c8 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/ode/part2/fourier_series.py @@ -367,6 +367,33 @@ class FourierOfTrebleClef(FourierOfPiSymbol): return path +class FourierOfIP(FourierOfTrebleClef): + CONFIG = { + "file_name": "IP_logo2", + "height": 6, + "n_circles": 100, + } + + # def construct(self): + # path = self.get_path() + # self.add(path) + + def get_shape(self): + shape = SVGMobject(self.file_name) + return shape + + def get_path(self): + shape = self.get_shape() + path = shape.family_members_with_points()[0] + path.add_line_to(path.get_start()) + # path.make_smooth() + + path.set_height(self.height) + path.set_fill(opacity=0) + path.set_stroke(WHITE, 0) + return path + + class FourierOfEighthNote(FourierOfTrebleClef): CONFIG = { "file_name": "EighthNote" diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index 49be1976..afe4264e 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -191,6 +191,7 @@ class BringTwoRodsTogether(Scene): ) self.axes = axes + self.y_label = y_label def setup_graph(self): graph = self.axes.get_graph( @@ -427,10 +428,12 @@ class BringTwoRodsTogether(Scene): class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): CONFIG = { "alpha": 0.1, - "n_arrows": 20, + "arrow_xs": np.linspace(0, 10, 22)[1:-1], + "arrow_scale_factor": 0.5, + "max_magnitude": 1.5, "wait_time": 30, "freq_amplitude_pairs": [ - (1, 1), + (1, 0.5), (2, 1), (3, 0.5), (4, 0.3), @@ -461,6 +464,7 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): self.setup_clock() self.add(self.clock) self.add(self.time_label) + self.time_label.next_to(self.clock, DOWN) def add_rod(self): rod = self.rod = self.get_rod(0, 10) @@ -471,14 +475,19 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): x_min = self.graph_x_min x_max = self.graph_x_max - xs = np.linspace(x_min, x_max, self.n_arrows + 2)[1:-1] + xs = self.arrow_xs arrows = VGroup(*[Vector(DOWN) for x in xs]) + asf = self.arrow_scale_factor def update_arrows(arrows): for x, arrow in zip(xs, arrows): d2y_dx2 = self.get_second_derivative(x) - mag = np.sign(d2y_dx2) * np.sqrt(abs(d2y_dx2)) - mag = np.clip(mag, -2, 2) + mag = asf * np.sign(d2y_dx2) * abs(d2y_dx2) + mag = np.clip( + mag, + -self.max_magnitude, + self.max_magnitude, + ) arrow.put_start_and_end_on( ORIGIN, mag * UP ) @@ -532,7 +541,7 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene): CONFIG = { "freq_amplitude_pairs": [ - (1, 0.7), + (1, 0.5), (2, 1), (3, 0.5), (4, 0.3), @@ -540,7 +549,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene (7, 0.2), ], "surface_resolution": 20, - "graph_slice_step": 0.5, + "graph_slice_step": 10 / 20, } def construct(self): @@ -1037,7 +1046,7 @@ class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): self.play( Write(words), self.camera.phi_tracker.set_value, 60 * DEGREES, - self.camera.theta_tracker.set_value, -95 * DEGREES, + self.camera.theta_tracker.set_value, -90 * DEGREES, run_time=1 ) self.play( @@ -1124,9 +1133,20 @@ class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): ) ) + plane = Square() + plane.set_stroke(width=0) + plane.set_fill(WHITE, 0.1) + plane.set_shade_in_3d(True) + plane.rotate(90 * DEGREES, RIGHT) + plane.rotate(90 * DEGREES, OUT) + plane.set_height(10) + plane.set_depth(8, stretch=True) + plane.move_to(self.t_axis.n2p(0), IN + DOWN) + plane.shift(RIGHT) + self.play( - self.camera.theta_tracker.set_value, -10 * DEGREES, - self.camera.frame_center.shift, 5 * LEFT, + self.camera.theta_tracker.set_value, -20 * DEGREES, + self.camera.frame_center.shift, 4 * LEFT, ) self.play( @@ -1134,11 +1154,12 @@ class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): graph.copy(), remover=True ), + FadeInFrom(plane, 6 * DOWN, run_time=2), VFadeIn(line), ApplyMethod( alpha_tracker.set_value, 1, run_time=8, - ) + ), ) self.add(graph) @@ -1147,7 +1168,10 @@ class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): lambda m, dt: m.shift(0.05 * dt * RIGHT) ) - self.play(FadeOut(line)) + self.play( + FadeOut(line), + FadeOut(plane), + ) self.wait(30) # Let rotate self.t_graph = graph @@ -1241,6 +1265,1467 @@ class TransitionToTempVsTime(ContrastXChangesToTChanges): self.revert_to_original_skipping_status() +class ShowDelTermsAsTinyNudges(TransitionToTempVsTime): + CONFIG = { + # "surface_resolution": 5, + # "graph_slice_step": 1, + "tangent_line_length": 4, + } + + def construct(self): + self.catchup_with_last_scene() + self.stop_camera() + self.show_del_t() + self.show_del_x() + + def stop_camera(self): + self.stop_ambient_camera_rotation() + for mob in self.get_mobjects(): + mob.clear_updaters() + + def show_del_x(self): + x_tracker = ValueTracker(3) + dx_tracker = ValueTracker(0.5) + + line_group = self.get_line_group( + self.graph, + x_tracker, + dx_tracker, + corner_index=0, + ) + dx_line, dT_line, tan_line = line_group + + del_x = TexMobject("\\partial x") + del_x.set_color(GREEN) + del_x.line = dx_line + del_x.direction = OUT + del_T = TexMobject("\\partial T") + del_T.line = dT_line + del_T.direction = RIGHT + syms = VGroup(del_T, del_x) + for sym in syms: + sym.add_updater(lambda m: m.set_width( + dx_line.get_length() + )) + sym.rect = SurroundingRectangle(sym) + group = VGroup(sym, sym.rect) + group.rotate(90 * DEGREES, RIGHT) + + for sym in syms: + sym.add_updater(lambda m: m.next_to( + m.line, m.direction, SMALL_BUFF, + )) + sym.rect.move_to(sym) + + self.move_camera( + phi=80 * DEGREES, + theta=-90 * DEGREES, + added_anims=[ + self.camera.frame_center.move_to, ORIGIN, + ], + ) + for sym in reversed(syms): + self.play( + FadeInFrom(sym, -sym.direction), + ShowCreation( + sym.line.copy(), + remover=True + ), + ) + self.add(sym.line) + self.play(ShowCreation(tan_line)) + for sym in syms: + self.play( + ShowCreationThenDestruction(sym.rect) + ) + self.wait() + self.wait() + self.add(line_group) + self.play( + dx_tracker.set_value, 0.01, + run_time=5, + ) + self.play( + FadeOut(syms), + FadeOut(line_group), + ) + + def show_del_t(self): + # Largely copy pasted from above. + # Reconsolidate if any of this will actually + # be used later. + t_tracker = ValueTracker(1) + dt_tracker = ValueTracker(1) + + line_group = self.get_line_group( + self.t_graph, t_tracker, dt_tracker, + corner_index=1, + ) + dt_line, dT_line, tan_line = line_group + + del_t = TexMobject("\\partial t") + del_t.set_color(YELLOW) + del_t.line = dt_line + del_t.direction = OUT + del_T = TexMobject("\\partial T") + del_T.line = dT_line + del_T.direction = UP + syms = VGroup(del_T, del_t) + for sym in syms: + sym.rect = SurroundingRectangle(sym) + group = VGroup(sym, sym.rect) + group.rotate(90 * DEGREES, RIGHT) + group.rotate(90 * DEGREES, OUT) + sym.add_updater(lambda m: m.set_height( + 0.8 * dT_line.get_length() + )) + + del_t.add_updater(lambda m: m.set_height( + min(0.5, m.line.get_length()) + )) + del_T.add_updater(lambda m: m.set_depth( + min(0.5, m.line.get_length()) + )) + for sym in syms: + sym.add_updater(lambda m: m.next_to( + m.line, m.direction, SMALL_BUFF, + )) + sym.rect.move_to(sym) + + self.move_camera( + phi=80 * DEGREES, + theta=-10 * DEGREES, + added_anims=[ + self.camera.frame_center.move_to, 5 * LEFT, + ], + ) + for sym in reversed(syms): + self.play( + FadeInFrom(sym, -sym.direction), + ShowCreation( + sym.line.copy(), + remover=True + ), + ) + self.add(sym.line) + self.play(ShowCreation(tan_line)) + for sym in syms: + self.play( + ShowCreationThenDestruction(sym.rect) + ) + self.wait() + self.wait() + self.add(line_group) + self.play( + dt_tracker.set_value, 0.01, + run_time=5, + ) + self.play( + FadeOut(syms), + FadeOut(line_group), + ) + + # + def get_line_group(self, graph, input_tracker, nudge_tracker, corner_index): + get_x = input_tracker.get_value + get_dx = nudge_tracker.get_value + + def get_graph_point(x): + return graph.point_from_proportion( + inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + x, + ) + ) + + def get_corner(p1, p2): + result = np.array(p1) + result[corner_index] = p2[corner_index] + return result + + line_group = VGroup( + Line(color=WHITE), + Line(color=RED), + Line(color=WHITE, stroke_width=2), + ) + + def update_line_group(group): + dxl, dTl, tl = group + p0 = get_graph_point(get_x()) + p2 = get_graph_point(get_x() + get_dx()) + p1 = get_corner(p0, p2) + + dxl.set_points_as_corners([p0, p1]) + dTl.set_points_as_corners([p1, p2]) + tl.set_points_as_corners([p0, p2]) + tl.scale( + self.tangent_line_length / tl.get_length() + ) + line_group.add_updater(update_line_group) + return line_group + + +class ShowCurvatureToRateOfChangeIntuition(ShowEvolvingTempGraphWithArrows): + CONFIG = { + "freq_amplitude_pairs": [ + (1, 0.7), + (2, 1), + (3, 0.5), + (4, 0.3), + (5, 0.3), + (7, 0.2), + ], + "arrow_xs": [0.7, 3.8, 4.6, 5.4, 6.2, 9.3], + "arrow_scale_factor": 0.2, + "max_magnitude": 1.0, + "wait_time": 20, + } + + def let_play(self): + arrows = self.arrows + curves = VGroup(*[ + self.get_mini_curve( + inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + x, + ) + ) + for x in self.arrow_xs + ]) + curves.set_stroke(WHITE, 5) + + curve_words = VGroup() + for curve, arrow in zip(curves, arrows): + word = TextMobject("curve") + word.scale(0.7) + word.next_to(curve, arrow.get_vector()[1] * DOWN, SMALL_BUFF) + curve_words.add(word) + + self.remove(arrows) + + self.play( + ShowCreation(curves), + LaggedStartMap(FadeIn, curve_words), + self.y_label.set_fill, {"opacity": 0}, + ) + self.wait() + self.add(*arrows, curves) + self.play(LaggedStartMap(GrowArrow, arrows)) + self.wait() + + self.play(FadeOut(VGroup(curves, curve_words))) + self.add(arrows) + super().let_play() + + def get_mini_curve(self, alpha, d_alpha=0.02): + result = VMobject() + result.pointwise_become_partial( + self.graph, + alpha - d_alpha, + alpha + d_alpha, + ) + return result + + +class DiscreteSetup(ShowEvolvingTempGraphWithArrows): + CONFIG = { + "step_size": 1, + "rod_piece_size_ratio": 1 / 3, + "dashed_line_stroke_opacity": 1.0, + "dot_radius": DEFAULT_DOT_RADIUS, + "freq_amplitude_pairs": [ + (1, 0.5), + (2, 1), + (3, 0.5), + (4, 0.3), + (5, 0.3), + (7, 0.2), + (21, 0.1), + # (41, 0.05), + ], + } + + def construct(self): + self.add_axes() + self.add_graph() + self.discretize() + self.let_time_pass() + self.show_nieghbor_rule() + self.focus_on_three_points() + self.show_difference_formula() + self.gut_check_new_interpretation() + self.write_second_difference() + self.emphasize_final_expression() + + def add_axes(self): + super().add_axes() + self.axes.shift(MED_SMALL_BUFF * LEFT) + + def add_graph(self): + points = self.get_points(time=0) + graph = VMobject() + graph.set_points_smoothly(points) + graph.color_using_background_image("VerticalTempGradient") + + self.add(graph) + + self.graph = graph + self.points = points + + def discretize(self): + axes = self.axes + x_axis = axes.x_axis + graph = self.graph + + piecewise_graph = CurvesAsSubmobjects(graph) + dots = self.get_dots() + v_lines = VGroup(*map(self.get_v_line, dots)) + + rod_pieces = VGroup() + for x in self.get_sample_inputs(): + piece = Line(LEFT, RIGHT) + piece.set_width( + self.step_size * self.rod_piece_size_ratio + ) + piece.move_to(axes.c2p(x, 0)) + piece.set_color( + self.rod_point_to_color(piece.get_center()) + ) + rod_pieces.add(piece) + + word = TextMobject("Discrete version") + word.scale(1.5) + word.next_to(x_axis, UP) + word.set_stroke(BLACK, 3, background=True) + + self.remove(graph) + self.play( + ReplacementTransform( + piecewise_graph, dots, + ), + Write(word, run_time=1) + ) + self.add(v_lines, word) + self.play( + x_axis.fade, 0.8, + TransformFromCopy( + x_axis.tick_marks[1:], + rod_pieces, + ), + LaggedStartMap(ShowCreation, v_lines) + ) + self.play(FadeOut(word)) + self.wait() + + self.rod_pieces = rod_pieces + self.dots = dots + self.v_lines = v_lines + + def let_time_pass(self): + dots = self.dots + + t_tracker = ValueTracker(0) + t_tracker.add_updater(lambda m, dt: m.increment_value(dt)) + self.add(t_tracker) + + self.add_clock() + self.time_label.next_to(self.clock, DOWN) + self.time_label.add_updater( + lambda m: m.set_value(t_tracker.get_value()) + ) + dots.add_updater(lambda d: d.become( + self.get_dots(t_tracker.get_value()) + )) + run_time = 5 + self.play( + ClockPassesTime( + self.clock, + run_time=run_time, + hours_passed=run_time, + ), + ) + t_tracker.clear_updaters() + t_tracker.set_value(run_time) + self.wait() + self.play( + t_tracker.set_value, 0, + FadeOut(self.clock), + FadeOut(self.time_label), + ) + self.remove(t_tracker) + dots.clear_updaters() + + def show_nieghbor_rule(self): + dots = self.dots + rod_pieces = self.rod_pieces + index = self.index = 2 + + p1, p2, p3 = rod_pieces[index:index + 3] + d1, d2, d3 = dots[index:index + 3] + point_label = TextMobject("Point") + neighbors_label = TextMobject("Neighbors") + words = VGroup(point_label, neighbors_label) + for word in words: + word.scale(0.7) + word.add_background_rectangle() + + point_label.next_to(p2, DOWN) + neighbors_label.next_to(p2, UP, buff=1) + bottom = neighbors_label.get_bottom() + kw = { + "buff": 0.1, + "stroke_width": 2, + "tip_length": 0.15 + } + arrows = VGroup( + Arrow(bottom, p1.get_center(), **kw), + Arrow(bottom, p3.get_center(), **kw), + ) + arrows.set_color(WHITE) + + dot = Dot() + dot.set_fill(GREY, opacity=0.2) + dot.replace(p2) + dot.scale(3) + + self.play( + dot.scale, 0, + dot.set_opacity, 0, + FadeInFrom(point_label, DOWN) + ) + self.play( + FadeInFrom(neighbors_label, DOWN), + *map(GrowArrow, arrows) + ) + self.wait() + + # Let d2 change + self.play( + d1.set_y, 3, + d3.set_y, 3, + ) + + def get_v(): + return 0.25 * np.sum([ + d1.get_y(), + -2 * d2.get_y(), + + d3.get_y(), + ]) + v_vect_fader = ValueTracker(0) + v_vect = always_redraw( + lambda: Vector( + get_v() * UP, + color=temperature_to_color( + get_v(), -2, 2, + ), + ).shift(d2.get_center()).set_opacity( + v_vect_fader.get_value(), + ) + ) + d2.add_updater( + lambda d, dt: d.shift( + get_v() * dt * UP, + ) + ) + + self.add(v_vect) + self.play(v_vect_fader.set_value, 1) + self.wait(3) + self.play( + d1.set_y, 0, + d3.set_y, 0, + ) + self.wait(4) + self.play(FadeOut(VGroup( + point_label, + neighbors_label, + arrows + ))) + + self.v_vect = v_vect + self.example_pieces = VGroup(p1, p2, p3) + self.example_dots = VGroup(d1, d2, d3) + + def focus_on_three_points(self): + dots = self.example_dots + d1, d2, d3 = dots + pieces = self.example_pieces + y_axis = self.axes.y_axis + + x_labels, T_labels = [ + VGroup(*[ + TexMobject("{}_{}".format(s, i)) + for i in [1, 2, 3] + ]).scale(0.8) + for s in ("x", "T") + ] + for xl, piece in zip(x_labels, pieces): + xl.next_to(piece, DOWN) + xl.add_background_rectangle() + for Tl, dot in zip(T_labels, dots): + Tl.dot = dot + Tl.add_updater(lambda m: m.next_to( + m.dot, RIGHT, SMALL_BUFF + )) + Tl.add_background_rectangle() + T1, T2, T3 = T_labels + + d2.movement_updater = d2.get_updaters()[0] + dots.clear_updaters() + self.remove(self.v_vect) + + self.play( + ShowCreationThenFadeAround(pieces), + FadeOut(self.dots[:self.index]), + FadeOut(self.v_lines[:self.index]), + FadeOut(self.rod_pieces[:self.index]), + FadeOut(self.dots[self.index + 3:]), + FadeOut(self.v_lines[self.index + 3:]), + FadeOut(self.rod_pieces[self.index + 3:]), + ) + self.play(LaggedStartMap( + FadeInFrom, x_labels, + lambda m: (m, LEFT), + lag_ratio=0.3, + run_time=2, + )) + self.play( + d3.set_y, 1, + d2.set_y, 0.25, + d1.set_y, 0, + ) + self.wait() + self.play(LaggedStart(*[ + TransformFromCopy(xl, Tl) + for xl, Tl in zip(x_labels, T_labels) + ], lag_ratio=0.3, run_time=2)) + self.wait() + + # Show lines + h_lines = VGroup(*map(self.get_h_line, dots)) + hl1, hl2, hl3 = h_lines + + average_pointer = ArrowTip( + start_angle=0, + length=0.2, + ) + average_pointer.set_color(YELLOW) + average_pointer.stretch(0.25, 1) + average_pointer.add_updater( + lambda m: m.move_to( + 0.5 * (hl1.get_start() + hl3.get_start()), + RIGHT + ) + ) + average_arrows = always_redraw(lambda: VGroup(*[ + Arrow( + hl.get_start(), + average_pointer.get_right(), + color=WHITE, + buff=0.0, + ) + for hl in [hl1, hl3] + ])) + average_label = TexMobject( + "{T_1", "+", "T_3", "\\over", "2}" + ) + average_label.scale(0.5) + average_label.add_updater(lambda m: m.next_to( + average_pointer, LEFT, SMALL_BUFF + )) + + average_rect = SurroundingRectangle(average_label) + average_rect.add_updater( + lambda m: m.move_to(average_label) + ) + average_words = TextMobject("Neighbor\\\\average") + average_words.match_width(average_rect) + average_words.match_color(average_rect) + average_words.add_updater( + lambda m: m.next_to(average_rect, UP, SMALL_BUFF) + ) + + mini_T1 = average_label.get_part_by_tex("T_1") + mini_T3 = average_label.get_part_by_tex("T_3") + for mini, line in (mini_T1, hl1), (mini_T3, hl3): + mini.save_state() + mini.next_to(line, LEFT, SMALL_BUFF) + + self.add(hl1, hl3, T_labels) + y_axis.remove(y_axis.numbers) + self.play( + GrowFromPoint(hl1, hl1.get_end()), + GrowFromPoint(hl3, hl3.get_end()), + TransformFromCopy( + T1, mini_T1, + ), + TransformFromCopy( + T3, mini_T3, + ), + FadeOut(y_axis.numbers), + y_axis.set_stroke, {"width": 1}, + ) + self.play( + FadeIn(average_pointer), + Restore(mini_T1), + Restore(mini_T3), + FadeIn(average_label[1]), + FadeIn(average_label[3:]), + *map(GrowArrow, average_arrows) + ) + self.add(average_arrows, average_label) + self.play( + ShowCreation(average_rect), + FadeIn(average_words), + ) + self.play( + GrowFromPoint(hl2, hl2.get_end()) + ) + self.wait() + + # Show formula + formula = TexMobject( + "\\left(", + "{T_1", "+", "T_3", "\\over", "2}", + "-", "T_2", + "\\right)" + ) + formula.to_corner(UR, buff=MED_LARGE_BUFF) + formula.shift(1.7 * LEFT) + brace = Brace(formula, DOWN) + diff_value = DecimalNumber(include_sign=True) + diff_value.add_updater(lambda m: m.set_value( + y_axis.p2n(average_pointer.get_right()) - + y_axis.p2n(d2.get_center()) + )) + diff_value.next_to(brace, DOWN) + + self.play( + ReplacementTransform( + average_label.deepcopy(), + formula[1:1 + len(average_label)] + ), + TransformFromCopy(T2, formula[-2]), + FadeIn(formula[-3]), + FadeIn(formula[-1]), + FadeIn(formula[0]), + GrowFromCenter(brace), + FadeIn(diff_value) + ) + self.wait() + + # Changes + self.play(FadeIn(self.v_vect)) + d2.add_updater(d2.movement_updater) + self.wait(5) + + self.play( + d3.set_y, 3, + d1.set_y, 2.5, + d2.set_y, -2, + ) + self.wait(5) + self.play( + d3.set_y, 1, + d1.set_y, -1, + ) + self.wait(8) + + # Show derivative + lhs = TexMobject( + "{dT_2", "\\over", "dt}", "=", "\\alpha" + ) + dt = lhs.get_part_by_tex("dt") + alpha = lhs.get_part_by_tex("\\alpha") + lhs.next_to(formula, LEFT, SMALL_BUFF) + + self.play(Write(lhs)) + self.play(ShowCreationThenFadeAround(dt)) + self.wait() + self.play(ShowCreationThenFadeAround(alpha)) + self.wait() + self.play( + FadeOut(brace), + FadeOut(diff_value), + ) + + self.lhs = lhs + self.rhs = formula + + def show_difference_formula(self): + lhs = self.lhs + rhs = self.rhs + d1, d2, d3 = self.example_dots + + new_rhs = TexMobject( + "=", + "{\\alpha", "\\over", "2}", + "\\left(", + "(", "T_3", "-", "T_2", ")", + "-", + "(", "T_2", "-", "T_1", ")", + "\\right)" + ) + big_parens = VGroup( + new_rhs.get_part_by_tex("\\left("), + new_rhs.get_part_by_tex("\\right)"), + ) + for paren in big_parens: + paren.scale(2) + new_rhs.next_to(rhs, DOWN) + new_rhs.align_to(lhs.get_part_by_tex("="), LEFT) + + def p2p_anim(mob1, mob2, tex, index=0): + return TransformFromCopy( + mob1.get_parts_by_tex(tex)[index], + mob2.get_parts_by_tex(tex)[index], + ) + + self.play( + p2p_anim(lhs, new_rhs, "="), + p2p_anim(rhs, new_rhs, "\\left("), + p2p_anim(rhs, new_rhs, "\\right)"), + p2p_anim(lhs, new_rhs, "\\alpha"), + p2p_anim(rhs, new_rhs, "\\over"), + p2p_anim(rhs, new_rhs, "2"), + ) + self.play( + p2p_anim(rhs, new_rhs, "T_3"), + p2p_anim(rhs, new_rhs, "-"), + p2p_anim(rhs, new_rhs, "T_2"), + FadeIn(new_rhs.get_parts_by_tex("(")[1]), + FadeIn(new_rhs.get_parts_by_tex(")")[0]), + ) + self.play( + p2p_anim(rhs, new_rhs, "T_2", -1), + p2p_anim(rhs, new_rhs, "-", -1), + p2p_anim(rhs, new_rhs, "T_1"), + FadeIn(new_rhs.get_parts_by_tex("-")[1]), + FadeIn(new_rhs.get_parts_by_tex("(")[2]), + FadeIn(new_rhs.get_parts_by_tex(")")[1]), + ) + self.wait() + + self.rhs2 = new_rhs + + # Show deltas + T1_index = new_rhs.index_of_part_by_tex("T_1") + T3_index = new_rhs.index_of_part_by_tex("T_3") + diff1 = new_rhs[T1_index - 2:T1_index + 1] + diff2 = new_rhs[T3_index:T3_index + 3] + brace1 = Brace(diff1, DOWN, buff=SMALL_BUFF) + brace2 = Brace(diff2, DOWN, buff=SMALL_BUFF) + delta_T1 = TexMobject("\\Delta T_1") + delta_T1.next_to(brace1, DOWN, SMALL_BUFF) + delta_T2 = TexMobject("\\Delta T_2") + delta_T2.next_to(brace2, DOWN, SMALL_BUFF) + minus = TexMobject("-") + minus.move_to(Line( + delta_T1.get_right(), + delta_T2.get_left(), + )) + braces = VGroup(brace1, brace2) + deltas = VGroup(delta_T1, delta_T2) + + kw = { + "direction": LEFT, + "buff": SMALL_BUFF, + "min_num_quads": 2, + } + lil_brace1 = always_redraw(lambda: Brace( + Line(d1.get_left(), d2.get_left()), **kw + )) + lil_brace2 = always_redraw(lambda: Brace( + Line(d2.get_left(), d3.get_left()), **kw + )) + lil_braces = VGroup(lil_brace1, lil_brace2) + lil_delta_T1 = delta_T1.copy() + lil_delta_T2 = delta_T2.copy() + lil_deltas = VGroup(lil_delta_T1, lil_delta_T2) + for brace, delta in zip(lil_braces, lil_deltas): + delta.brace = brace + delta.add_updater(lambda d: d.next_to( + d.brace, LEFT, SMALL_BUFF, + )) + + delta_T1.set_color(BLUE) + lil_delta_T1.set_color(BLUE) + delta_T2.set_color(RED) + lil_delta_T2.set_color(RED) + + double_difference_brace = Brace(deltas, DOWN) + double_difference_words = TextMobject( + "Difference of differences" + ) + double_difference_words.next_to( + double_difference_brace, DOWN + ) + + self.play( + GrowFromCenter(brace1), + GrowFromCenter(lil_brace1), + FadeIn(delta_T1), + FadeIn(lil_delta_T1), + ) + self.wait() + self.play( + GrowFromCenter(brace2), + GrowFromCenter(lil_brace2), + FadeIn(delta_T2), + FadeIn(lil_delta_T2), + ) + self.wait() + self.play( + Write(minus), + GrowFromCenter(double_difference_brace), + Write(double_difference_words), + ) + self.wait() + + self.braces = braces + self.deltas = deltas + self.delta_minus = minus + self.lil_braces = lil_braces + self.lil_deltas = lil_deltas + self.double_difference_brace = double_difference_brace + self.double_difference_words = double_difference_words + + def gut_check_new_interpretation(self): + lil_deltas = self.lil_deltas + d1, d2, d3 = self.example_dots + + self.play(ShowCreationThenFadeAround(lil_deltas[0])) + self.play(ShowCreationThenFadeAround(lil_deltas[1])) + self.wait() + self.play( + d2.shift, MED_SMALL_BUFF * UP, + rate_func=there_and_back, + ) + self.wait() + self.play( + d3.set_y, 3, + d1.set_y, -0.5, + ) + self.wait(5) + self.play( + d3.set_y, 1.5, + d1.set_y, -2, + ) + self.wait(5) + + def write_second_difference(self): + dd_word = self.double_difference_words + + delta_delta = TexMobject("\\Delta \\Delta T_1") + delta_delta.set_color(MAROON_B) + + delta_delta.move_to(dd_word, UP) + + second_difference_word = TextMobject( + "``Second difference''" + ) + second_difference_word.next_to(delta_delta, DOWN) + + self.play( + FadeOutAndShift(dd_word, UP), + FadeInFrom(delta_delta, UP), + ) + self.wait() + self.play( + Write(second_difference_word), + ) + self.wait() + + # Random play + d1, d2, d3 = self.example_dots + self.play( + d3.set_y, 3, + d1.set_y, -0.5, + ) + self.wait(5) + self.play( + d3.set_y, 1.5, + d1.set_y, -2, + ) + self.wait(5) + + self.delta_delta = delta_delta + self.second_difference_word = second_difference_word + + def emphasize_final_expression(self): + lhs = self.lhs + rhs = self.rhs + rhs2 = self.rhs2 + old_dd = self.delta_delta + dd = old_dd.copy() + old_ao2 = rhs2[1:4] + ao2 = old_ao2.copy() + + new_lhs = lhs[:-1] + full_rhs = VGroup( + lhs[-1], + lhs[-2].copy(), + rhs, + rhs2, + self.braces, + self.deltas, + self.delta_minus, + self.double_difference_brace, + old_dd, + self.second_difference_word, + ) + new_rhs = VGroup(ao2, dd) + new_rhs.arrange(RIGHT, buff=SMALL_BUFF) + new_rhs.next_to(new_lhs, RIGHT) + + self.play( + full_rhs.to_edge, DOWN, {"buff": LARGE_BUFF}, + ) + self.play( + TransformFromCopy(old_ao2, ao2), + TransformFromCopy(old_dd, dd), + ) + self.play( + ShowCreationThenFadeAround( + VGroup(new_lhs, new_rhs) + ) + ) + self.wait() + + # + def get_sample_inputs(self): + return np.arange( + self.graph_x_min, + self.graph_x_max + self.step_size, + self.step_size, + ) + + def get_points(self, time=0): + return [ + self.axes.c2p(x, self.temp_func(x, t=time)) + for x in self.get_sample_inputs() + ] + + def get_dots(self, time=0): + points = self.get_points(time) + dots = VGroup(*[ + Dot( + point, + radius=self.dot_radius + ) + for point in points + ]) + dots.color_using_background_image("VerticalTempGradient") + return dots + + def get_dot_dashed_line(self, dot, index, color=False): + direction = np.zeros(3) + direction[index] = -1 + + def get_line(): + p1 = dot.get_edge_center(direction) + p0 = np.array(p1) + p0[index] = self.axes.c2p(0, 0)[index] + result = DashedLine( + p0, p1, + stroke_width=2, + color=WHITE, + stroke_opacity=self.dashed_line_stroke_opacity, + ) + if color: + result.color_using_background_image( + "VerticalTempGradient" + ) + return result + return always_redraw(get_line) + + def get_h_line(self, dot): + return self.get_dot_dashed_line(dot, 0, True) + + def get_v_line(self, dot): + return self.get_dot_dashed_line(dot, 1) + + +class ShowFinitelyManyX(DiscreteSetup): + def construct(self): + self.setup_axes() + axes = self.axes + axes.fade(1) + points = [ + axes.c2p(x, 0) + for x in self.get_sample_inputs()[1:] + ] + x_labels = VGroup(*[ + TexMobject("x_{}".format(i)).next_to( + p, DOWN + ) + for i, p in enumerate(points) + ]) + + self.play(LaggedStartMap( + FadeInFromLarge, x_labels + )) + self.play(LaggedStartMap(FadeOut, x_labels)) + self.wait() + + +class DiscreteGraphStillImage1(DiscreteSetup): + CONFIG = { + "step_size": 1, + } + + def construct(self): + self.add_axes() + self.add_graph() + self.discretize() + + +class DiscreteGraphStillImageFourth(DiscreteGraphStillImage1): + CONFIG = { + "step_size": 0.25, + } + + +class DiscreteGraphStillImageTenth(DiscreteGraphStillImage1): + CONFIG = { + "step_size": 0.1, + "dashed_line_stroke_opacity": 0.25, + "dot_radius": 0.04, + } + + +class DiscreteGraphStillImageHundredth(DiscreteGraphStillImage1): + CONFIG = { + "step_size": 0.01, + "dashed_line_stroke_opacity": 0.1, + "dot_radius": 0.01, + } + + +class TransitionToContinuousCase(DiscreteSetup): + CONFIG = { + "step_size": 0.1, + "tangent_line_length": 3, + "wait_time": 30, + } + + def construct(self): + self.add_axes() + self.add_graph() + self.show_temperature_difference() + self.show_second_derivative() + self.show_curvature_examples() + self.show_time_changes() + + def add_graph(self): + self.setup_graph() + self.play( + ShowCreation( + self.graph, + run_time=3, + ) + ) + self.wait() + + def show_temperature_difference(self): + x_tracker = ValueTracker(2) + dx_tracker = ValueTracker(1) + + line_group = self.get_line_group( + x_tracker, + dx_tracker, + ) + dx_line, dT_line, tan_line, dx_sym, dT_sym = line_group + tan_line.set_stroke(width=0) + + brace = Brace(dx_line, UP) + fixed_distance = TextMobject("Fixed\\\\distance") + fixed_distance.scale(0.7) + fixed_distance.next_to(brace, UP) + delta_T = TexMobject("\\Delta T") + delta_T.move_to(dT_sym, LEFT) + + self.play( + ShowCreation(VGroup(dx_line, dT_line)), + FadeInFrom(delta_T, LEFT) + ) + self.play( + GrowFromCenter(brace), + FadeInFromDown(fixed_distance), + ) + self.wait() + self.play( + FadeOut(delta_T, UP), + FadeIn(dT_sym, DOWN), + FadeOut(brace, UP), + FadeOut(fixed_distance, UP), + FadeIn(dx_sym, DOWN), + ) + self.add(line_group) + self.play( + dx_tracker.set_value, 0.01, + run_time=5 + ) + self.wait() + self.play( + dx_tracker.set_value, 0.3, + ) + + # Show rate of change + to_zero = TexMobject("\\rightarrow 0") + to_zero.match_height(dT_sym) + to_zero.next_to(dT_sym, buff=SMALL_BUFF) + + ratio = TexMobject( + "{\\partial T", "\\over", "\\partial x}" + ) + ratio[0].match_style(dT_sym) + ratio.to_edge(UP) + + self.play(ShowCreationThenFadeAround( + dT_sym, + surrounding_rectangle_config={ + "buff": 0.05, + "stroke_width": 1, + } + )) + self.play(GrowFromPoint(to_zero, dT_sym.get_right())) + self.wait() + self.play( + TransformFromCopy( + dT_sym, + ratio.get_part_by_tex("\\partial T") + ), + TransformFromCopy( + dx_sym, + ratio.get_part_by_tex("\\partial x") + ), + Write(ratio.get_part_by_tex("\\over")) + ) + self.play( + ShowCreation( + tan_line.copy().set_stroke(width=2), + remover=True + ), + FadeOut(to_zero), + ) + tan_line.set_stroke(width=2) + self.wait() + + # Look at neighbors + x0 = x_tracker.get_value() + dx = dx_tracker.get_value() + v_line, lv_line, rv_line = v_lines = VGroup(*[ + self.get_v_line(x) + for x in [x0, x0 - dx, x0 + dx] + ]) + v_lines[1:].set_color(BLUE) + + self.play(ShowCreation(v_line)) + self.play( + TransformFromCopy(v_line, lv_line), + TransformFromCopy(v_line, rv_line), + ) + self.wait() + self.play( + FadeOut(v_lines[1:]), + ApplyMethod( + dx_tracker.set_value, 0.01, + run_time=2 + ), + ) + + self.line_group = line_group + self.deriv = ratio + self.x_tracker = x_tracker + self.dx_tracker = dx_tracker + self.v_line = v_line + + def show_second_derivative(self): + x_tracker = self.x_tracker + deriv = self.deriv + v_line = self.v_line + + deriv_of_deriv = TexMobject( + "{\\partial", + "\\left(", + "{\\partial T", "\\over", "\\partial x}", + "\\right)", + "\\over", + "\\partial x}" + ) + deriv_of_deriv.set_color_by_tex("\\partial T", RED) + + deriv_of_deriv.to_edge(UP) + dT_index = deriv_of_deriv.index_of_part_by_tex("\\partial T") + inner_deriv = deriv_of_deriv[dT_index:dT_index + 3] + + self.play( + ReplacementTransform(deriv, inner_deriv), + Write(VGroup(*filter( + lambda m: m not in inner_deriv, + deriv_of_deriv, + ))) + ) + v_line.add_updater(lambda m: m.become( + self.get_v_line(x_tracker.get_value()) + )) + for change in [-0.1, 0.1]: + self.play( + x_tracker.increment_value, change, + run_time=3 + ) + + # Write second deriv + second_deriv = TexMobject( + "{\\partial^2 T", "\\over", "\\partial x^2}" + ) + second_deriv[0].set_color(RED) + eq = TexMobject("=") + eq.next_to(deriv_of_deriv, RIGHT) + second_deriv.next_to(eq, RIGHT) + second_deriv.align_to(deriv_of_deriv, DOWN) + eq.match_y(second_deriv.get_part_by_tex("\\over")) + + self.play(Write(eq)) + self.play( + TransformFromCopy( + deriv_of_deriv.get_parts_by_tex("\\partial")[:2], + second_deriv.get_parts_by_tex("\\partial^2 T"), + ), + ) + self.play( + Write(second_deriv.get_part_by_tex("\\over")), + TransformFromCopy( + deriv_of_deriv.get_parts_by_tex("\\partial x"), + second_deriv.get_parts_by_tex("\\partial x"), + ), + ) + self.wait() + + def show_curvature_examples(self): + x_tracker = self.x_tracker + v_line = self.v_line + line_group = self.line_group + + x_tracker.set_value(3.6) + self.wait() + self.play( + x_tracker.set_value, 3.8, + run_time=4, + ) + self.wait() + x_tracker.set_value(6.2) + self.wait() + self.play( + x_tracker.set_value, 6.4, + run_time=4, + ) + self.wait() + + # + dx = 0.2 + neighbor_lines = always_redraw(lambda: VGroup(*[ + self.get_v_line( + x_tracker.get_value() + u * dx, + line_class=Line, + ) + for u in [-1, 1] + ])) + neighbor_lines.set_color(BLUE) + + self.play(FadeOut(line_group)) + self.play(*[ + TransformFromCopy(v_line, nl) + for nl in neighbor_lines + ]) + self.add(neighbor_lines) + self.play( + x_tracker.set_value, 5, + run_time=5, + rate_func=lambda t: smooth(t, 3) + ) + v_line.clear_updaters() + self.play( + FadeOut(v_line), + FadeOut(neighbor_lines), + ) + self.wait() + + def show_time_changes(self): + self.setup_clock() + graph = self.graph + + time_label = self.time_label + clock = self.clock + time_label.next_to(clock, DOWN) + + graph.add_updater(self.update_graph) + time_label.add_updater( + lambda d, dt: d.increment_value(dt) + ) + + self.add(time_label) + self.add_arrows() + self.play( + ClockPassesTime( + clock, + run_time=self.wait_time, + hours_passed=self.wait_time, + ), + ) + + # + def get_v_line(self, x, line_class=DashedLine, stroke_width=2): + axes = self.axes + graph = self.graph + line = line_class( + axes.c2p(x, 0), + graph.point_from_proportion( + inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + x, + ) + ), + stroke_width=stroke_width, + ) + return line + + def get_line_group(self, + x_tracker, + dx_tracker, + dx_tex="\\partial x", + dT_tex="\\partial T", + max_sym_width=0.5, + ): + graph = self.graph + get_x = x_tracker.get_value + get_dx = dx_tracker.get_value + + dx_line = Line(color=WHITE) + dT_line = Line(color=RED) + tan_line = Line(color=WHITE) + lines = VGroup(dx_line, dT_line, tan_line) + lines.set_stroke(width=2) + dx_sym = TexMobject(dx_tex) + dT_sym = TexMobject(dT_tex) + dT_sym.match_color(dT_line) + syms = VGroup(dx_sym, dT_sym) + + group = VGroup(*lines, *syms) + + def update_group(group): + dxl, dTl, tanl, dxs, dTs = group + x = get_x() + dx = get_dx() + p0, p2 = [ + graph.point_from_proportion( + inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + x + ) + ) + for x in [x, x + dx] + ] + p1 = np.array([p2[0], *p0[1:]]) + dxl.put_start_and_end_on(p0, p1) + dTl.put_start_and_end_on(p1, p2) + tanl.put_start_and_end_on(p0, p2) + tanl.scale( + self.tangent_line_length / + tanl.get_length() + ) + dxs.match_width(dxl) + dTs.set_height(0.7 * dTl.get_height()) + for sym in dxs, dTs: + if sym.get_width() > max_sym_width: + sym.set_width(max_sym_width) + dxs.next_to( + dxl, -dTl.get_vector(), SMALL_BUFF, + ) + dTs.next_to( + dTl, dxl.get_vector(), SMALL_BUFF, + ) + + group.add_updater(update_group) + return group + + +class ShowManyVLines(TransitionToContinuousCase): + CONFIG = { + "wait_time": 20, + "max_denom": 10, + "x_step": 0.025, + } + + def construct(self): + self.add_axes() + self.add_graph() + self.add_v_lines() + self.show_time_changes() + + def add_arrows(self): + pass + + def add_v_lines(self): + axes = self.axes + + v_lines = always_redraw(lambda: VGroup(*[ + self.get_v_line( + x, + line_class=Line, + stroke_width=0.5, + ) + for x in np.arange(0, 10, self.x_step) + ])) + group = VGroup(*v_lines) + + x_pointer = ArrowTip(start_angle=PI / 2) + x_pointer.set_color(WHITE) + x_pointer.next_to(axes.c2p(0, 0), DOWN, buff=0) + x_eq = VGroup( + TexMobject("x="), + DecimalNumber(0) + ) + x_eq.add_updater( + lambda m: m.arrange(RIGHT, buff=SMALL_BUFF) + ) + x_eq.add_updater( + lambda m: m[1].set_value(axes.x_axis.p2n(x_pointer.get_top())) + ) + x_eq.add_updater(lambda m: m.next_to( + x_pointer, DOWN, SMALL_BUFF, + submobject_to_align=x_eq[0] + )) + + self.add(x_pointer, x_eq) + self.play( + Write( + group, + remover=True, + lag_ratio=self.x_step / 2, + run_time=6, + ), + ApplyMethod( + x_pointer.next_to, + axes.c2p(10, 0), + DOWN, {"buff": 0}, + rate_func=linear, + run_time=5, + ), + ) + self.add(v_lines) + x_eq.clear_updaters() + self.play( + FadeOut(x_eq), + FadeOut(x_pointer), + ) + + class ShowNewtonsLawGraph(Scene): CONFIG = { "k": 0.2, diff --git a/active_projects/ode/part2/pi_scenes.py b/active_projects/ode/part2/pi_scenes.py index ba1d98c1..6e18ce2a 100644 --- a/active_projects/ode/part2/pi_scenes.py +++ b/active_projects/ode/part2/pi_scenes.py @@ -1,4 +1,5 @@ from big_ol_pile_of_manim_imports import * +from active_projects.ode.part2.wordy_scenes import WriteHeatEquationTemplate class ReactionsToInitialHeatEquation(PiCreatureScene): @@ -19,5 +20,123 @@ class ReactionsToInitialHeatEquation(PiCreatureScene): point.next_to, randy, UR, LARGE_BUFF, ) self.wait(2) - self.play(point.shift, 2 * DOWN) + self.play( + point.shift, 2 * DOWN, + randy.change, "horrified" + ) + self.wait(4) + + +class ContrastPDEToODE(TeacherStudentsScene): + CONFIG = { + "random_seed": 2, + } + + def construct(self): + student = self.students[2] + pde, ode = words = VGroup(*[ + TextMobject( + text + "\\\\", + "Differential\\\\", + "Equation" + ) + for text in ("Partial", "Ordinary") + ]) + pde[0].set_color(YELLOW) + ode[0].set_color(BLUE) + for word in words: + word.arrange(DOWN, aligned_edge=LEFT) + + words.arrange(RIGHT, buff=LARGE_BUFF) + words.next_to(student.get_corner(UR), UP, MED_LARGE_BUFF) + words.shift(UR) + lt = TexMobject("<") + lt.scale(1.5) + lt.move_to(Line(pde.get_right(), ode.get_left())) + + for pi in self.pi_creatures: + pi.add_updater(lambda p: p.look_at(pde)) + + self.play( + FadeInFromDown(VGroup(words, lt)), + student.change, "raise_right_hand", + ) + self.play( + self.get_student_changes("pondering", "pondering", "hooray"), + self.teacher.change, "happy" + ) self.wait(3) + self.play( + Swap(ode, pde), + self.teacher.change, "raise_right_hand", + self.get_student_changes( + "erm", "sassy", "confused" + ) + ) + self.look_at(words) + self.change_student_modes( + "thinking", "thinking", "tease", + ) + self.wait(3) + + +class AskAboutWhereEquationComesFrom(TeacherStudentsScene, WriteHeatEquationTemplate): + def construct(self): + equation = self.get_d1_equation() + equation.move_to(self.hold_up_spot, DOWN) + + self.play( + FadeInFromDown(equation), + self.teacher.change, "raise_right_hand" + ) + self.student_says( + "Um...why?", + target_mode="sassy", + student_index=2, + bubble_kwargs={"direction": RIGHT}, + ) + self.change_student_modes( + "confused", "confused", "sassy", + ) + self.wait() + self.play( + self.teacher.change, "pondering", + ) + self.wait(2) + + +class AskWhyRewriteIt(TeacherStudentsScene): + def construct(self): + self.student_says( + "Why?", student_index=1, + bubble_kwargs={"height": 2, "width": 2}, + ) + self.students[1].bubble = None + self.teacher_says( + "One step closer\\\\to derivatives" + ) + self.change_student_modes( + "thinking", "thinking", "thinking", + look_at_arg=4 * LEFT + 2 * UP + ) + self.wait(2) + + +class ReferenceKhanVideo(TeacherStudentsScene): + def construct(self): + khan_logo = ImageMobject("KhanLogo") + khan_logo.set_height(1) + khan_logo.next_to(self.teacher, UP, buff=2) + khan_logo.shift(2 * LEFT) + + self.play( + self.teacher.change, "raise_right_hand", + ) + self.change_student_modes( + "thinking", "pondering", "thinking", + look_at_arg=self.screen + ) + self.wait() + self.play(FadeInFromDown(khan_logo)) + self.look_at(self.screen) + self.wait(15) diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index 38aad58f..592964dd 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -2,92 +2,376 @@ from big_ol_pile_of_manim_imports import * from active_projects.ode.part1.staging import TourOfDifferentialEquations -class FourierSeriesIntro(Scene): - def construct(self): - title_scale_value = 1.5 - - title = TextMobject( - "Fourier ", "Series", - ) - title.scale(title_scale_value) - title.to_edge(UP) - title.generate_target() - - details_coming = TextMobject("Details coming...") - details_coming.next_to(title.get_corner(DR), DOWN) - details_coming.set_color(LIGHT_GREY) - - physics = TextMobject("Physics") - physics.scale(title_scale_value) - arrow = Arrow(LEFT, RIGHT) - group = VGroup(physics, arrow, title.target) - group.arrange(RIGHT) - physics.align_to(title.target, UP) - group.to_edge(UP) - - rot_square = Square() - rot_square.fade(1) - rot_square.add_updater(lambda m, dt: m.rotate(dt)) - heat = TextMobject("Heat") - heat.scale(title_scale_value) - heat.move_to(physics[0][-1], DR) - - def update_heat_colors(heat): - vertices = rot_square.get_vertices() - letters = heat.family_members_with_points() - for letter, vertex in zip(letters, vertices): - alpha = (normalize(vertex)[0] + 1) / 2 - letter.set_color(interpolate_color( - YELLOW, RED, alpha, - )) - heat.add_updater(update_heat_colors) - - image = ImageMobject("Joseph Fourier") - image.set_height(5) - image.next_to(title, DOWN, MED_LARGE_BUFF) - image.to_edge(LEFT) - name = TextMobject("Joseph", "Fourier") - name.next_to(image, DOWN) - - # self.play(FadeInFromDown(title)) - self.add(title) - self.play( - FadeInFromDown(image), - TransformFromCopy( - title.get_part_by_tex("Fourier"), - name.get_part_by_tex("Fourier"), - path_arc=90 * DEGREES, - ), - FadeIn(name.get_part_by_tex("Joseph")), - ) - self.play(Write(details_coming, run_time=1)) - self.play(LaggedStartMap(FadeOut, details_coming[0], run_time=1)) - self.wait() - self.play( - FadeInFrom(physics, RIGHT), - GrowArrow(arrow), - MoveToTarget(title) - ) - self.wait() - self.add(rot_square) - self.play( - FadeOutAndShift(physics, UP), - FadeInFromDown(heat, DOWN), - ) - self.wait(10) - - class PartTwoOfTour(TourOfDifferentialEquations): CONFIG = { "zoomed_thumbnail_index": 1, } + def construct(self): + self.add_title() + self.show_thumbnails() + self.zoom_in_to_one_thumbnail() + + def zoom_in_to_one_thumbnail(self): + frame = self.camera_frame + thumbnails = self.thumbnails + + ode = TextMobject("Ordinary\\\\", "Differential Equation") + pde = TextMobject("Partial\\\\", "Differential Equation") + for word, thumbnail, vect in zip([ode, pde], thumbnails, [DOWN, UP]): + word.match_width(thumbnail) + word.next_to(thumbnail, vect) + ode[0].set_color(BLUE) + pde[0].set_color(YELLOW) + + self.add(ode) + + frame.save_state() + self.play( + frame.replace, + thumbnails[0], + run_time=1, + ) + self.play( + Restore(frame, run_time=3), + ) + self.play( + TransformFromCopy(ode, pde), + ) + self.play( + ApplyMethod( + frame.replace, thumbnails[1], + path_arc=(-30 * DEGREES), + run_time=3 + ), + ) + self.wait() + + +class BrownianMotion(Scene): + CONFIG = { + "wait_time": 60, + "L": 3, # Box in [-L, L] x [-L, L] + "n_particles": 100, + "m1": 1, + "m2": 100, + "r1": 0.05, + "r2": 0.5, + "max_v": 5, + "random_seed": 2, + } + + def construct(self): + self.add_title() + self.add_particles() + self.wait(self.wait_time) + + def add_title(self): + square = Square(side_length=2 * self.L) + title = TextMobject("Brownian motion") + title.scale(1.5) + title.next_to(square, UP) + + self.add(square) + self.add(title) + + def add_particles(self): + m1 = self.m1 + m2 = self.m2 + r1 = self.r1 + r2 = self.r2 + L = self.L + max_v = self.max_v + n_particles = self.n_particles + + lil_particles = VGroup(*[ + self.get_particle(m1, r1, L, max_v) + for k in range(n_particles) + ]) + big_particle = self.get_particle(m2, r2, L=r2, max_v=0) + big_particle.set_fill(YELLOW, 1) + + for p in lil_particles: + if self.are_colliding(p, big_particle): + lil_particles.remove(p) + all_particles = VGroup(big_particle, *lil_particles) + all_particles.add_updater(self.update_particles) + + path = self.get_traced_path(big_particle) + + self.add(all_particles) + self.add(path) + + self.particles = all_particles + self.big_particle = big_particle + self.path = path + + def get_particle(self, m, r, L, max_v): + dot = Dot(radius=r) + dot.set_fill(WHITE, 0.7) + dot.mass = m + dot.radius = r + dot.center = op.add( + np.random.uniform(-L + r, L - r) * RIGHT, + np.random.uniform(-L + r, L - r) * UP + ) + dot.move_to(dot.center) + dot.velocity = rotate_vector( + np.random.uniform(0, max_v) * RIGHT, + np.random.uniform(0, TAU), + ) + return dot + + def are_colliding(self, p1, p2): + d = get_norm(p1.get_center() - p2.get_center()) + return (d < p1.radius + p2.radius) + + def get_traced_path(self, particle): + path = VMobject() + path.set_stroke(BLUE, 3) + path.start_new_path(particle.get_center()) + + buff = 0.02 + + def update_path(path): + new_point = particle.get_center() + if get_norm(new_point - path.get_last_point()) > buff: + path.add_line_to(new_point) + + path.add_updater(update_path) + return path + + def update_particles(self, particles, dt): + for p1 in particles: + p1.center += p1.velocity * dt + + # Check particle collisions + buff = 0.01 + for p2 in particles: + if p1 is p2: + continue + v = p2.center - p1.center + dist = get_norm(v) + r_sum = p1.radius + p2.radius + diff = dist - r_sum + if diff < 0: + unit_v = v / dist + p1.center += (diff - buff) * unit_v / 2 + p2.center += -(diff - buff) * unit_v / 2 + u1 = p1.velocity + u2 = p2.velocity + m1 = p1.mass + m2 = p2.mass + v1 = ( + (m2 * (u2 - u1) + m1 * u1 + m2 * u2) / + (m1 + m2) + ) + v2 = ( + (m1 * (u1 - u2) + m1 * u1 + m2 * u2) / + (m1 + m2) + ) + p1.velocity = v1 + p2.velocity = v2 + + # Check edge collisions + r1 = p1.radius + c1 = p1.center + for i in [0, 1]: + if abs(c1[i]) + r1 > self.L: + c1[i] = np.sign(c1[i]) * (self.L - r1) + p1.velocity[i] *= -1 * op.mul( + np.sign(p1.velocity[i]), + np.sign(c1[i]) + ) + + for p in particles: + p.move_to(p.center) + return particles + + +class AltBrownianMotion(BrownianMotion): + CONFIG = { + "wait_time": 20, + "n_particles": 100, + "m2": 10, + } + + +class BlackScholes(AltBrownianMotion): + def construct(self): + # For some reason I'm amused by the thought + # Of this graph perfectly matching the Brownian + # Motion y-coordiante + self.add_title() + self.add_particles() + self.particles.set_opacity(0) + self.remove(self.path) + self.add_graph() + self.wait(self.wait_time) + + def add_title(self): + title = TextMobject("Black-Sholes equations") + title.scale(1.5) + title.next_to(2 * UP, UP) + + equation = TexMobject( + "{\\partial V \\over \\partial t}", "+", + "\\frac{1}{2} \\sigma^2 S^2", + "{\\partial^2 V \\over \\partial S^2}", "+", + "rS", "{\\partial V \\over \\partial S}", + "-rV", "=", "0", + ) + equation.scale(0.8) + equation.next_to(title, DOWN) + + self.add(title) + self.add(equation) + self.title = title + self.equation = equation + + def add_graph(self): + axes = Axes( + x_min=-1, + x_max=20, + y_min=0, + y_max=10, + number_line_config={ + "unit_size": 0.5, + }, + ) + axes.set_height(4) + axes.move_to(DOWN) + + def get_graph_point(): + return axes.c2p( + self.get_time(), + 5 + 2 * self.big_particle.get_center()[1] + ) + + graph = VMobject() + graph.match_style(self.path) + graph.start_new_path(get_graph_point()) + graph.add_updater( + lambda g: g.add_line_to(get_graph_point()) + ) + + self.add(axes) + self.add(graph) + + +class ContrastChapters1And2(Scene): + def construct(self): + c1_frame, c2_frame = frames = VGroup(*[ + ScreenRectangle(height=3.5) + for x in range(2) + ]) + frames.arrange(RIGHT, buff=LARGE_BUFF) + + c1_title, c2_title = titles = VGroup( + TextMobject("Chapter 1"), + TextMobject("Chapter 2"), + ) + titles.scale(1.5) + + ode, pde = des = VGroup( + TextMobject( + "Ordinary", + "Differential Equations\\\\", + "ODEs", + ), + TextMobject( + "Partial", + "Differential Equations\\\\", + "PDEs", + ), + ) + ode[0].set_color(BLUE) + pde[0].set_color(YELLOW) + for de in des: + de[-1][0].match_color(de[0]) + de[-1].scale(1.5, about_point=de.get_top()) + + for title, frame, de in zip(titles, frames, des): + title.next_to(frame, UP) + de.match_width(frame) + de.next_to(frame, DOWN) + + lt = TexMobject("<") + lt.move_to(Line(ode.get_right(), pde.get_left())) + lt.scale(2, about_edge=UP) + + c1_words = TextMobject( + "They're", "really\\\\", "{}", + "freaking", "hard\\\\", + "to", "solve\\\\", + ) + c1_words.set_height(0.5 * c1_frame.get_height()) + c1_words.move_to(c1_frame) + + c2_words = TextMobject( + "They're", "really", "\\emph{really}\\\\", + "freaking", "hard\\\\", + "to", "solve\\\\", + ) + c2_words.set_color_by_tex("\\emph", YELLOW) + c2_words.move_to(c2_frame) + edit_shift = MED_LARGE_BUFF * RIGHT + c2_edits = VGroup( + TextMobject("sometimes").next_to( + c2_words[1:3], UP, + aligned_edge=LEFT, + ), + Line( + c2_words[1].get_left(), + c2_words[2].get_right(), + stroke_width=8, + ), + TextMobject("not too").next_to( + c2_words[3], LEFT, + ), + Line( + c2_words[3].get_left(), + c2_words[3].get_right(), + stroke_width=8, + ), + ) + c2_edits.set_color(RED) + c2_edits[2:].shift(edit_shift) + + self.add(titles) + self.add(frames) + self.add(des) + + self.wait() + self.play(LaggedStartMap( + FadeInFromDown, c1_words, + lag_ratio=0.1, + )) + self.wait() + # self.play(FadeIn(ode)) + self.play( + # TransformFromCopy(ode, pde), + TransformFromCopy(c1_words, c2_words), + Write(lt) + ) + self.wait() + self.play( + Write(c2_edits[:2], run_time=1), + ) + self.play( + c2_words[3:5].shift, edit_shift, + Write(c2_edits[2:]), + run_time=1, + ) + self.wait() + class ShowCubeFormation(ThreeDScene): CONFIG = { "camera_config": { "shading_factor": 1.0, - } + }, + "color": False, } def construct(self): @@ -101,6 +385,12 @@ class ShowCubeFormation(ThreeDScene): stroke_width=0.5, ) cube.set_fill(opacity=1) + if self.color: + # cube[0].set_color(BLUE) + # cube[1].set_color(RED) + # for face in cube[2:]: + # face.set_color([BLUE, RED]) + cube.color_using_background_image("VerticalTempGradient") # light_source.next_to(cube, np.array([1, -1, 1]), buff=2) @@ -121,7 +411,341 @@ class ShowCubeFormation(ThreeDScene): self.play( Transform(cube, target, run_time=1.5) ) + self.wait(8) + + +class ShowCubeFormationWithColor(ShowCubeFormation): + CONFIG = { + "color": True, + } + + +class ShowRect(Scene): + CONFIG = { + "height": 1, + "width": 3, + } + + def construct(self): + rect = Rectangle( + height=self.height, + width=self.width, + ) + rect.set_color(YELLOW) + self.play(ShowCreationThenFadeOut(rect)) + + +class ShowSquare(ShowRect): + CONFIG = { + "height": 1, + "width": 1, + } + + +class ShowHLine(Scene): + def construct(self): + line = Line(LEFT, RIGHT) + line.set_color(BLUE) + self.play(ShowCreationThenFadeOut(line)) + + +class ShowCross(Scene): + def construct(self): + cross = Cross(Square()) + cross.set_width(3) + cross.set_height(1, stretch=True) + self.play(ShowCreation(cross)) + + +class TwoBodyEquations(Scene): + def construct(self): + kw = { + "tex_to_color_map": { + "x_1": LIGHT_GREY, + "y_1": LIGHT_GREY, + "x_2": BLUE, + "y_2": BLUE, + "=": WHITE, + } + } + equations = VGroup( + TexMobject( + "{d^2 x_1 \\over dt^2}", + "=", + "{x_2 - x_1 \\over m_1 \\left(", + "(x_2 - x_1)^2 + (y_2 - y_1)^2", + "\\right)^{3/2}", + **kw + ), + TexMobject( + "{d^2 y_1 \\over dt^2}", + "=", + "{y_2 - y_1 \\over m_1 \\left(", + "(x_2 - x_1)^2 + (y_2 - y_1)^2", + "\\right)^{3/2}", + **kw + ), + TexMobject( + "{d^2 x_2 \\over dt^2}", + "=", + "{x_1 - x_2 \\over m_2 \\left(", + "(x_2 - x_1)^2 + (y_2 - y_1)^2", + "\\right)^{3/2}", + **kw + ), + TexMobject( + "{d^2 y_2 \\over dt^2}", + "=", + "{y_1 - y_2 \\over m_2 \\left(", + "(x_2 - x_1)^2 + (y_2 - y_1)^2", + "\\right)^{3/2}", + **kw + ), + ) + + equations.arrange(DOWN, buff=LARGE_BUFF) + equations.set_height(6) + equations.to_edge(LEFT) + + variables = VGroup() + lhss = VGroup() + rhss = VGroup() + for equation in equations: + variable = equation[1] + lhs = equation[:4] + rhs = equation[4:] + variables.add(variable) + lhss.add(lhs) + rhss.add(rhs) + lhss_copy = lhss.copy() + + for variable, lhs in zip(variables, lhss): + variable.save_state() + variable.match_height(lhs) + variable.scale(0.7) + variable.move_to(lhs, LEFT) + + self.play(LaggedStart(*[ + FadeInFrom(v, RIGHT) + for v in variables + ])) + self.wait() + self.play( + LaggedStartMap(Restore, variables), + FadeIn( + lhss_copy, + remover=True, + lag_ratio=0.1, + run_time=2, + ) + ) + self.add(lhss) + self.wait() + self.play(LaggedStartMap( + FadeIn, rhss + )) + self.wait() + self.play( + LaggedStart(*[ + ShowCreationThenFadeAround(lhs[:3]) + for lhs in lhss + ]) + ) + self.wait() + self.play( + LaggedStartMap( + ShowCreationThenFadeAround, + rhss, + ) + ) + self.wait() + + +class LaplacianIntuition(SpecialThreeDScene): + CONFIG = { + "three_d_axes_config": { + "x_min": -5, + "x_max": 5, + "y_min": -5, + "y_max": 5, + }, + "surface_resolution": 32, + } + + def construct(self): + axes = self.get_axes() + axes.scale(0.5, about_point=ORIGIN) + self.set_camera_to_default_position() + self.begin_ambient_camera_rotation() + + def func(x, y): + return np.array([ + x, y, + 2.7 + 0.5 * (np.sin(x) + np.cos(y)) - + 0.025 * (x**2 + y**2) + ]) + + surface_config = { + "u_min": -5, + "u_max": 5, + "v_min": -5, + "v_max": 5, + "resolution": self.surface_resolution, + } + # plane = ParametricSurface( + # lambda u, v: np.array([u, v, 0]), + # **surface_config + # ) + # plane.set_stroke(WHITE, width=0.1) + # plane.set_fill(WHITE, opacity=0.1) + plane = Square( + side_length=10, + stroke_width=0, + fill_color=WHITE, + fill_opacity=0.1, + ) + plane.center() + plane.set_shade_in_3d(True) + + surface = ParametricSurface( + func, **surface_config + ) + surface.set_stroke(BLUE, width=0.1) + surface.set_fill(BLUE, opacity=0.25) + + self.add(axes, plane, surface) + + point = VectorizedPoint(np.array([2, -2, 0])) + dot = Dot() + dot.set_color(GREEN) + dot.add_updater(lambda d: d.move_to(point)) + line = always_redraw(lambda: DashedLine( + point.get_location(), + func(*point.get_location()[:2]), + background_image_file="VerticalTempGradient", + )) + + circle = Circle(radius=0.25) + circle.set_color(YELLOW) + circle.insert_n_curves(20) + circle.add_updater(lambda m: m.move_to(point)) + circle.set_shade_in_3d(True) + surface_circle = always_redraw( + lambda: circle.copy().apply_function( + lambda p: func(*p[:2]) + ).shift( + 0.02 * IN + ).color_using_background_image("VerticalTempGradient") + ) + + self.play(FadeInFromLarge(dot)) + self.play(ShowCreation(line)) + self.play(TransformFromCopy(dot, circle)) + self.play( + Transform( + circle.copy(), + surface_circle.copy().clear_updaters(), + remover=True, + ) + ) + self.add(surface_circle) + + self.wait() + for vect in [4 * LEFT, DOWN, 4 * RIGHT, UP]: + self.play( + point.shift, vect, + run_time=3, + ) + + +class StrogatzMention(PiCreatureScene): + def construct(self): + self.show_book() + self.show_motives() + self.show_pages() + + def show_book(self): + morty = self.pi_creature + book = ImageMobject("InfinitePowers") + book.set_height(5) + book.to_edge(LEFT) + + steve = ImageMobject("Strogatz_by_bricks") + steve.set_height(5) + steve.to_edge(LEFT) + + name = TextMobject("Steven Strogatz") + name.match_width(steve) + name.next_to(steve, DOWN) + + self.think( + "Hmm...many good\\\\lessons here...", + run_time=1 + ) + self.wait() + self.play(FadeInFromDown(steve)) + self.wait() + self.play( + FadeInFrom(book, DOWN), + steve.shift, 4 * RIGHT, + RemovePiCreatureBubble( + morty, target_mode="thinking" + ) + ) self.wait(3) + self.play( + FadeOut(steve), + FadeOut(morty), + ) + + self.book = book + + def show_motives(self): + motives = VGroup( + TextMobject("1) Scratch and itch"), + TextMobject("2) Make people love math"), + ) + motives.scale(1.5) + motives.arrange( + DOWN, LARGE_BUFF, + aligned_edge=LEFT, + ) + motives.move_to( + Line( + self.book.get_right(), + FRAME_WIDTH * RIGHT / 2 + ) + ) + motives.to_edge(UP) + + for motive in motives: + self.play(FadeInFromDown(motive)) + self.wait(2) + self.play(FadeOut(motives)) + + def show_pages(self): + book = self.book + pages = Group(*[ + ImageMobject("IP_Sample_Page{}".format(i)) + for i in range(1, 4) + ]) + for page in pages: + page.match_height(book) + page.next_to(book, RIGHT) + + last_page = VectorizedPoint() + for page in pages: + self.play( + FadeOut(last_page), + FadeIn(page) + ) + self.wait() + last_page = page + + self.play(FadeOut(last_page)) + + def create_pi_creature(self): + return Mortimer().to_corner(DR) class ShowNewton(Scene): diff --git a/active_projects/ode/part2/wordy_scenes.py b/active_projects/ode/part2/wordy_scenes.py index 64ca7bf9..bfc3f982 100644 --- a/active_projects/ode/part2/wordy_scenes.py +++ b/active_projects/ode/part2/wordy_scenes.py @@ -1,6 +1,222 @@ from big_ol_pile_of_manim_imports import * +class WriteHeatEquationTemplate(Scene): + CONFIG = { + "tex_mobject_config": { + "tex_to_color_map": { + "{T}": WHITE, + "{t}": YELLOW, + "{x}": GREEN, + "{y}": RED, + "{z}": BLUE, + "\\partial": WHITE, + "2": WHITE, + }, + }, + } + + def get_d1_equation(self): + return TexMobject( + "{\\partial {T} \\over \\partial {t}}({x}, {t})", "=", + "\\alpha \\cdot", + "{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})", + **self.tex_mobject_config + ) + + def get_d1_equation_without_inputs(self): + return TexMobject( + "{\\partial {T} \\over \\partial {t}}", "=", + "\\alpha \\cdot", + "{\\partial^2 {T} \\over \\partial {x}^2}", + **self.tex_mobject_config + ) + + def get_d3_equation(self): + return TexMobject( + "{\\partial {T} \\over \\partial {t}}", "=", + "\\alpha \\left(", + "{\\partial^2 {T} \\over \\partial {x}^2} + ", + "{\\partial^2 {T} \\over \\partial {y}^2} + ", + "{\\partial^2 {T} \\over \\partial {z}^2}", + "\\right)", + **self.tex_mobject_config + ) + + def get_general_equation(self): + return TexMobject( + "{\\partial {T} \\over \\partial {t}}", "=", + "\\alpha", "\\nabla^2 {T}", + **self.tex_mobject_config, + ) + + def get_d3_equation_with_inputs(self): + return TexMobject( + "{\\partial {T} \\over \\partial {t}}", + "({x}, {y}, {z}, {t})", "=", + "\\alpha \\left(", + "{\\partial^2 {T} \\over \\partial {x}^2}", + "({x}, {y}, {z}, {t}) + ", + "{\\partial^2 {T} \\over \\partial {y}^2}", + "({x}, {y}, {z}, {t}) + ", + "{\\partial^2 {T} \\over \\partial {z}^2}", + "({x}, {y}, {z}, {t})", + "\\right)", + **self.tex_mobject_config + ) + + def get_d1_words(self): + return TextMobject("Heat equation\\\\", "(1 dimension)") + + def get_d3_words(self): + return TextMobject("Heat equation\\\\", "(3 dimensions)") + + def get_d1_group(self): + group = VGroup( + self.get_d1_words(), + self.get_d1_equation(), + ) + group.arrange(DOWN, buff=MED_LARGE_BUFF) + return group + + def get_d3_group(self): + group = VGroup( + self.get_d3_words(), + self.get_d3_equation(), + ) + group.arrange(DOWN, buff=MED_LARGE_BUFF) + return group + + +class HeatEquationIntroTitle(WriteHeatEquationTemplate): + def construct(self): + scale_factor = 1.25 + title = TextMobject("The Heat Equation") + title.scale(scale_factor) + title.to_edge(UP) + + equation = self.get_general_equation() + equation.scale(scale_factor) + equation.next_to(title, DOWN, MED_LARGE_BUFF) + equation.set_color_by_tex("{T}", RED) + + self.play( + FadeInFrom(title, DOWN), + FadeInFrom(equation, UP), + ) + self.wait() + + +class BringTogether(Scene): + def construct(self): + arrows = VGroup(Vector(2 * RIGHT), Vector(2 * LEFT)) + arrows.arrange(RIGHT, buff=2) + words = TextMobject("Bring together")[0] + words.next_to(arrows, DOWN) + words.save_state() + words.space_out_submobjects(1.2) + + self.play( + VFadeIn(words), + Restore(words), + arrows.arrange, RIGHT, {"buff": SMALL_BUFF}, + VFadeIn(arrows), + ) + self.play(FadeOut(words), FadeOut(arrows)) + + +class FourierSeriesIntro(WriteHeatEquationTemplate): + def construct(self): + title_scale_value = 1.5 + + title = TextMobject( + "Fourier ", "Series", + ) + title.scale(title_scale_value) + title.to_edge(UP) + title.generate_target() + + details_coming = TextMobject("Details coming...") + details_coming.next_to(title.get_corner(DR), DOWN) + details_coming.set_color(LIGHT_GREY) + + # physics = TextMobject("Physics") + heat = TextMobject("Heat") + heat.scale(title_scale_value) + physics = self.get_general_equation() + physics.set_color_by_tex("{T}", RED) + arrow1 = Arrow(LEFT, RIGHT) + arrow2 = Arrow(LEFT, RIGHT) + group = VGroup( + heat, arrow1, physics, arrow2, title.target + ) + group.arrange(RIGHT) + # physics.align_to(title.target, UP) + group.to_edge(UP) + + rot_square = Square() + rot_square.fade(1) + rot_square.add_updater(lambda m, dt: m.rotate(dt)) + + def update_heat_colors(heat): + colors = [YELLOW, RED] + vertices = rot_square.get_vertices() + letters = heat.family_members_with_points() + for letter, vertex in zip(letters, vertices): + alpha = (normalize(vertex)[0] + 1) / 2 + i, sa = integer_interpolate(0, len(colors) - 1, alpha) + letter.set_color(interpolate_color( + colors[i], colors[i + 1], alpha, + )) + heat.add_updater(update_heat_colors) + + image = ImageMobject("Joseph Fourier") + image.set_height(5) + image.next_to(title, DOWN, LARGE_BUFF) + image.to_edge(LEFT) + name = TextMobject("Joseph", "Fourier") + name.next_to(image, DOWN) + + bubble = ThoughtBubble( + height=2, + width=2.5, + direction=RIGHT, + ) + bubble.set_fill(opacity=0) + bubble.set_stroke(WHITE) + bubble.set_stroke(BLACK, 5, background=True) + bubble.shift(heat.get_center() - bubble.get_bubble_center()) + bubble[:-1].shift(LEFT + 0.2 * DOWN) + bubble[:-1].rotate(-20 * DEGREES) + for mob in bubble[:-1]: + mob.rotate(20 * DEGREES) + + # self.play(FadeInFromDown(title)) + self.add(title) + self.play( + FadeInFromDown(image), + TransformFromCopy( + title.get_part_by_tex("Fourier"), + name.get_part_by_tex("Fourier"), + path_arc=90 * DEGREES, + ), + FadeIn(name.get_part_by_tex("Joseph")), + ) + self.play(Write(details_coming, run_time=1)) + self.play(LaggedStartMap(FadeOut, details_coming[0], run_time=1)) + self.wait() + self.add(rot_square) + self.play( + FadeInFrom(physics, RIGHT), + GrowArrow(arrow2), + FadeInFrom(heat, RIGHT), + GrowArrow(arrow1), + MoveToTarget(title), + ) + self.play(ShowCreation(bubble)) + self.wait(10) + + class CompareODEToPDE(Scene): def construct(self): pass @@ -11,11 +227,33 @@ class TodaysTargetWrapper(Scene): pass +class TwoGraphTypeTitles(Scene): + def construct(self): + left_title = TextMobject( + "Represent time\\\\with actual time" + ) + left_title.shift(FRAME_WIDTH * LEFT / 4) + right_title = TextMobject( + "Represent time\\\\with an axis" + ) + right_title.shift(FRAME_WIDTH * RIGHT / 4) + + titles = VGroup(left_title, right_title) + for title in titles: + title.scale(1.25) + title.to_edge(UP) + + self.play(FadeInFromDown(right_title)) + self.wait() + self.play(FadeInFromDown(left_title)) + self.wait() + + class ShowPartialDerivativeSymbols(Scene): def construct(self): t2c = { "{x}": GREEN, - "{t}": PINK, + "{t}": YELLOW, } d_derivs, del_derivs = VGroup(*[ VGroup(*[ @@ -94,6 +332,7 @@ class ShowPartialDerivativeSymbols(Scene): num, buff=SMALL_BUFF, stroke_width=2, + color=word[-1].get_color(), ) deriv.rect.mob = num deriv.rect.add_updater(lambda r: r.move_to(r.mob)) @@ -110,19 +349,90 @@ class ShowPartialDerivativeSymbols(Scene): self.wait() -class WriteHeatEquation(Scene): - CONFIG = { - "tex_mobject_config": { - "tex_to_color_map": { - "{T}": YELLOW, - "{t}": WHITE, - "{x}": GREEN, - "{y}": RED, - "{z}": BLUE, - }, - }, - } +class WriteHeatEquation(WriteHeatEquationTemplate): + def construct(self): + title = TextMobject("The Heat Equation") + title.to_edge(UP) + equation = self.get_d1_equation() + equation.next_to(title, DOWN) + + eq_i = equation.index_of_part_by_tex("=") + dt_part = equation[:eq_i] + dx_part = equation[eq_i + 3:] + dt_rect = SurroundingRectangle(dt_part) + dt_rect.set_stroke(YELLOW, 2) + dx_rect = SurroundingRectangle(dx_part) + dx_rect.set_stroke(GREEN, 2) + + two_outlines = equation.get_parts_by_tex("2").copy() + two_outlines.set_stroke(YELLOW, 2) + two_outlines.set_fill(opacity=0) + + to_be_explained = TextMobject( + "To be explained shortly..." + ) + to_be_explained.scale(0.7) + to_be_explained.next_to(equation, RIGHT, MED_LARGE_BUFF) + to_be_explained.fade(1) + + pde = TextMobject("Partial Differential Equation") + pde.move_to(title) + + del_outlines = equation.get_parts_by_tex("\\partial").copy() + del_outlines.set_stroke(YELLOW, 2) + del_outlines.set_fill(opacity=0) + + self.play( + FadeInFrom(title, 0.5 * DOWN), + FadeInFrom(equation, 0.5 * UP), + ) + self.wait() + self.play(ShowCreation(dt_rect)) + self.wait() + self.play(TransformFromCopy(dt_rect, dx_rect)) + self.play(ShowCreationThenDestruction(two_outlines)) + self.wait() + self.play(Write(to_be_explained, run_time=1)) + self.wait(2) + self.play( + ShowCreationThenDestruction( + del_outlines, + lag_ratio=0.1, + ) + ) + self.play( + FadeOutAndShift(title, UP), + FadeInFrom(pde, DOWN), + FadeOut(dt_rect), + FadeOut(dx_rect), + ) + self.wait() + + +class Show3DEquation(WriteHeatEquationTemplate): + def construct(self): + equation = self.get_d3_equation_with_inputs() + equation.set_width(FRAME_WIDTH - 1) + inputs = VGroup(*it.chain(*[ + equation.get_parts_by_tex(s) + for s in ["{x}", "{y}", "{z}", "{t}"] + ])) + inputs.sort() + equation.to_edge(UP) + + self.add(equation) + self.play(LaggedStartMap( + ShowCreationThenFadeAround, inputs, + surrounding_rectangle_config={ + "buff": 0.05, + "stroke_width": 2, + } + )) + self.wait() + + +class Show1DAnd3DEquations(WriteHeatEquationTemplate): def construct(self): d1_group = self.get_d1_group() d3_group = self.get_d3_group() @@ -132,10 +442,10 @@ class WriteHeatEquation(Scene): groups = VGroup(d1_group, d3_group) for group in groups: group.arrange(DOWN, buff=MED_LARGE_BUFF) - groups.arrange(RIGHT, buff=2) + groups.arrange(RIGHT, buff=1.5) groups.to_edge(UP) - d3_rhs = d3_equation[6:] + d3_rhs = d3_equation[9:-2] d3_brace = Brace(d3_rhs, DOWN) nabla_words = TextMobject("Sometimes written as") nabla_words.match_width(d3_brace) @@ -169,55 +479,31 @@ class WriteHeatEquation(Scene): ) self.wait() - def get_d1_equation(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}({x}, {t})=" - "{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})", - **self.tex_mobject_config + +class D1EquationNoInputs(WriteHeatEquationTemplate): + def construct(self): + equation = self.get_d1_equation_without_inputs() + equation.to_edge(UP) + # i1 = equation.index_of_part_by_tex("\\partial") + # i2 = equation.index_of_part_by_tex("\\cdot") + # equation[i1:i1 + 2].set_color(RED) + # equation[i2 + 1:i2 + 6].set_color(RED) + equation.set_color_by_tex("{T}", RED) + self.add(equation) + + +class AltHeatRHS(Scene): + def construct(self): + formula = TexMobject( + "{\\alpha \\over 2}", "\\Big(", + "T({x} - 1, {t}) + T({x} + 1, {t})" + "\\Big)", + tex_to_color_map={ + "{x}": GREEN, + "{t}": YELLOW, + } ) - - def get_d3_equation(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}} = ", - "{\\partial^2 {T} \\over \\partial {x}^2} + ", - "{\\partial^2 {T} \\over \\partial {y}^2} + ", - "{\\partial^2 {T} \\over \\partial {z}^2}", - **self.tex_mobject_config - ) - - def get_d3_equation_with_inputs(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}} = ", - "{\\partial^2 {T} \\over \\partial {x}^2}", - "(x, y, z, t) + ", - "{\\partial^2 {T} \\over \\partial {y}^2}", - "(x, y, z, t) + ", - "{\\partial^2 {T} \\over \\partial {z}^2}", - "(x, y, z, t)", - **self.tex_mobject_config - ) - - def get_d1_words(self): - return TextMobject("Heat equation\\\\", "(1 dimension)") - - def get_d3_words(self): - return TextMobject("Heat equation\\\\", "(3 dimensions)") - - def get_d1_group(self): - group = VGroup( - self.get_d1_words(), - self.get_d1_equation(), - ) - group.arrange(DOWN, buff=MED_LARGE_BUFF) - return group - - def get_d3_group(self): - group = VGroup( - self.get_d3_words(), - self.get_d3_equation(), - ) - group.arrange(DOWN, buff=MED_LARGE_BUFF) - return group + self.add(formula) class CompareInputsOfGeneralCaseTo1D(WriteHeatEquation): @@ -252,3 +538,248 @@ class CompareInputsOfGeneralCaseTo1D(WriteHeatEquation): three_d_expr[low:high].scale, 0, ) self.wait() + + +class ShowLaplacian(WriteHeatEquation): + def construct(self): + equation = self.get_d3_equation() + equation.to_edge(UP, buff=MED_SMALL_BUFF) + + parts = VGroup() + plusses = VGroup() + for char in "xyz": + index = equation.index_of_part_by_tex( + "{" + char + "}" + ) + part = equation[index - 6:index + 3] + rect = SurroundingRectangle(part) + rect.match_color(equation[index]) + parts.add(part) + part.rect = rect + if char in "yz": + plus = equation[index - 8] + part.plus = plus + plusses.add(plus) + + lp = equation.get_part_by_tex("(") + rp = equation.get_part_by_tex(")") + + for part in parts: + part.rp = rp.copy() + part.rp.next_to(part, RIGHT, SMALL_BUFF) + part.rp.align_to(lp, UP) + rp.become(parts[0].rp) + + # Show new second derivatives + self.add(*equation) + self.remove(*plusses, *parts[1], *parts[2]) + for part in parts[1:]: + self.play( + rp.become, part.rp, + FadeInFrom(part, LEFT), + Write(part.plus), + ShowCreation(part.rect), + ) + self.play( + FadeOut(part.rect), + ) + self.wait() + + # Show laplacian + brace = Brace(parts, DOWN) + laplacian = TexMobject("\\nabla^2", "T") + laplacian.next_to(brace, DOWN) + laplacian_name = TextMobject( + "``Laplacian''" + ) + laplacian_name.next_to(laplacian, DOWN) + + T_parts = VGroup(*[part[3] for part in parts]) + non_T_parts = VGroup(*[ + VGroup(*part[:3], *part[4:]) + for part in parts + ]) + + self.play(GrowFromCenter(brace)) + self.play(Write(laplacian_name)) + self.play( + TransformFromCopy(non_T_parts, laplacian[0]) + ) + self.play( + TransformFromCopy(T_parts, laplacian[1]) + ) + self.wait(3) + + +class AskAboutActuallySolving(WriteHeatEquationTemplate): + def construct(self): + equation = self.get_d1_equation() + equation.center() + + q1 = TextMobject("Solve for T?") + q1.next_to(equation, UP, LARGE_BUFF) + q2 = TextMobject("What does it \\emph{mean} to solve this?") + q2.next_to(equation, UP, LARGE_BUFF) + formula = TexMobject( + "T({x}, {t}) = \\sin\\big(a{x}\\big) e^{-\\alpha \\cdot a^2 {t}}", + tex_to_color_map={ + "{x}": GREEN, + "{t}": YELLOW, + } + ) + formula.next_to(equation, DOWN, LARGE_BUFF) + q3 = TextMobject("Is this it?") + arrow = Vector(LEFT, color=WHITE) + arrow.next_to(formula, RIGHT) + q3.next_to(arrow, RIGHT) + + self.add(equation) + self.play(FadeInFromDown(q1)) + self.wait() + self.play( + FadeInFromDown(q2), + q1.shift, 1.5 * UP, + ) + self.play(FadeInFrom(formula, UP)) + self.play( + GrowArrow(arrow), + FadeInFrom(q3, LEFT) + ) + self.wait() + + +class PDEPatreonEndscreen(PatreonEndScreen): + CONFIG = { + "specific_patrons": [ + "Juan Benet", + "Vassili Philippov", + "Burt Humburg", + "Matt Russell", + "Scott Gray", + "soekul", + "Tihan Seale", + "Richard Barthel", + "Ali Yahya", + "dave nicponski", + "Evan Phillips", + "Graham", + "Joseph Kelly", + "Kaustuv DeBiswas", + "LambdaLabs", + "Lukas Biewald", + "Mike Coleman", + "Peter Mcinerney", + "Quantopian", + "Roy Larson", + "Scott Walter, Ph.D.", + "Yana Chernobilsky", + "Yu Jun", + "Jordan Scales", + "D. Sivakumar", + "Lukas -krtek.net- Novy", + "John Shaughnessy", + "Britt Selvitelle", + "David Gow", + "J", + "Jonathan Wilson", + "Joseph John Cox", + "Magnus Dahlström", + "Randy C. Will", + "Ryan Atallah", + "Luc Ritchie", + "1stViewMaths", + "Adrian Robinson", + "Alexis Olson", + "Andreas Benjamin Brössel", + "Andrew Busey", + "Ankalagon", + "Antoine Bruguier", + "Antonio Juarez", + "Arjun Chakroborty", + "Art Ianuzzi", + "Awoo", + "Bernd Sing", + "Boris Veselinovich", + "Brian Staroselsky", + "Chad Hurst", + "Charles Southerland", + "Chris Connett", + "Christian Kaiser", + "Clark Gaebel", + "Cooper Jones", + "Danger Dai", + "Dave B", + "Dave Kester", + "David B. Hill", + "David Clark", + "DeathByShrimp", + "Delton Ding", + "eaglle", + "emptymachine", + "Eric Younge", + "Eryq Ouithaqueue", + "Federico Lebron", + "Giovanni Filippi", + "Hal Hildebrand", + "Hitoshi Yamauchi", + "Isaac Jeffrey Lee", + "j eduardo perez", + "Jacob Magnuson", + "Jameel Syed", + "Jason Hise", + "Jeff Linse", + "Jeff Straathof", + "John Griffith", + "John Haley", + "John V Wertheim", + "Jonathan Eppele", + "Kai-Siang Ang", + "Kanan Gill", + "L0j1k", + "Lee Beck", + "Lee Redden", + "Linh Tran", + "Ludwig Schubert", + "Magister Mugit", + "Mark B Bahu", + "Mark Heising", + "Martin Price", + "Mathias Jansson", + "Matt Langford", + "Matt Roveto", + "Matthew Bouchard", + "Matthew Cocke", + "Michael Faust", + "Michael Hardel", + "Mirik Gogri", + "Mustafa Mahdi", + "Márton Vaitkus", + "Nero Li", + "Nikita Lesnikov", + "Omar Zrien", + "Owen Campbell-Moore", + "Peter Ehrnstrom", + "RedAgent14", + "rehmi post", + "Richard Burgmann", + "Richard Comish", + "Ripta Pasay", + "Rish Kundalia", + "Robert Teed", + "Roobie", + "Ryan Williams", + "Sachit Nagpal", + "Solara570", + "Stevie Metke", + "Tal Einav", + "Ted Suzman", + "Thomas Tarler", + "Tom Fleming", + "Valeriy Skobelev", + "Xavier Bernard", + "Yavor Ivanov", + "Yaw Etse", + "YinYangBalance.Asia", + "Zach Cardwell", + ], + }