From caf41f43d9d38c7283331a26c121caa17bc1ab03 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 26 Mar 2019 17:52:53 -0700 Subject: [PATCH] More scenes for ode1 --- active_projects/ode/all_part1_scenes.py | 8 +- active_projects/ode/part1/phase_space.py | 124 +++++++++ active_projects/ode/part1/pi_scenes.py | 177 ++++++++++++- active_projects/ode/part1/staging.py | 304 +++++++++++++++++++---- 4 files changed, 563 insertions(+), 50 deletions(-) diff --git a/active_projects/ode/all_part1_scenes.py b/active_projects/ode/all_part1_scenes.py index 2d2306b5..8065580c 100644 --- a/active_projects/ode/all_part1_scenes.py +++ b/active_projects/ode/all_part1_scenes.py @@ -17,7 +17,8 @@ ALL_SCENE_CLASSES = [ SmallAngleApproximationTex, VeryLowAnglePendulum, FormulasAreLies, - FollowThisThread, + TourOfDifferentialEquations, + # FollowThisThread, StrogatzQuote, # Something... ShowGravityAcceleration, @@ -40,6 +41,11 @@ ALL_SCENE_CLASSES = [ FromODEToVectorField, LorenzVectorField, ThreeBodiesInSpace, + AltThreeBodiesInSpace, ThreeBodySymbols, AskAboutActuallySolving, + WriteODESolvingCode, + TakeManyTinySteps, + InaccurateComputation, + HungerForExactness, ] diff --git a/active_projects/ode/part1/phase_space.py b/active_projects/ode/part1/phase_space.py index 3e7a0812..baf80d8d 100644 --- a/active_projects/ode/part1/phase_space.py +++ b/active_projects/ode/part1/phase_space.py @@ -1113,3 +1113,127 @@ class TweakMuInVectorField(ShowPendulumPhaseFlow): ) self.add(animated_stream_lines) self.wait(self.flow_time) + + +class TakeManyTinySteps(IntroduceVectorField): + CONFIG = { + "initial_theta": 60 * DEGREES, + "initial_theta_dot": 0, + } + + def construct(self): + self.initialize_plane() + self.initialize_vector_field() + field = self.vector_field + field.set_opacity(0.35) + self.add(self.plane, field) + + self.take_many_time_steps() + + def take_many_time_steps(self): + delta_t_tracker = ValueTracker(0.5) + get_delta_t = delta_t_tracker.get_value + + time_tracker = ValueTracker(10) + get_t = time_tracker.get_value + + traj = always_redraw( + lambda: self.get_time_step_trajectory( + get_delta_t(), get_t() + ) + ) + vectors = always_redraw( + lambda: self.get_path_vectors( + get_delta_t(), get_t() + ) + ) + + t_label, dt_label = labels = VGroup(*[ + VGroup( + TexMobject("{} = ".format(s)), + DecimalNumber(0) + ).arrange(RIGHT, aligned_edge=DOWN) + for s in ("t", "{\\Delta t}") + ]) + init_labels = VGroup( + TexMobject( + "\\theta_0", "= \\pi / 3", + tex_to_color_map={"\\theta": BLUE}, + ), + TexMobject( + "{\\dot\\theta}_0 = 0", + tex_to_color_map={"{\\dot\\theta}": YELLOW}, + ), + ) + for group in labels, init_labels: + for label in group: + label.scale(1.25) + label.add_background_rectangle() + group.arrange(DOWN) + group.shift(FRAME_WIDTH * RIGHT / 4) + labels.to_edge(UP) + init_labels.shift(2 * DOWN) + + dt_label[-1].add_updater( + lambda d: d.set_value(get_delta_t()) + ) + t_label[-1].add_updater( + lambda d: d.set_value( + int(get_t() / get_delta_t()) * get_delta_t() + ) + ) + + self.add(traj, vectors, init_labels, labels) + time_tracker.set_value(0) + self.play( + time_tracker.set_value, 10, + run_time=5, + rate_func=linear, + ) + self.wait() + t_label[-1].clear_updaters() + self.play( + delta_t_tracker.set_value, 0.01, + run_time=7, + ) + self.wait() + + # + def get_time_step_points(self, delta_t, total_time): + plane = self.plane + field = self.vector_field + curr_point = plane.coords_to_point( + self.initial_theta, + self.initial_theta_dot, + ) + points = [curr_point] + t = 0 + while t < total_time: + new_point = curr_point + field.func(curr_point) * delta_t + points.append(new_point) + curr_point = new_point + t += delta_t + return points + + def get_time_step_trajectory(self, delta_t, total_time): + traj = VMobject() + traj.set_points_as_corners( + self.get_time_step_points(delta_t, total_time) + ) + traj.set_stroke(WHITE, 2) + return traj + + def get_path_vectors(self, delta_t, total_time): + corners = self.get_time_step_points( + delta_t, total_time + ) + result = VGroup() + for a1, a2 in zip(corners, corners[1:]): + vector = Arrow( + a1, a2, buff=0, + ) + vector.match_style( + self.vector_field.get_vector(a1) + ) + result.add(vector) + return result diff --git a/active_projects/ode/part1/pi_scenes.py b/active_projects/ode/part1/pi_scenes.py index 284ff054..f3d304a3 100644 --- a/active_projects/ode/part1/pi_scenes.py +++ b/active_projects/ode/part1/pi_scenes.py @@ -103,6 +103,11 @@ class FormulasAreLies(PiCreatureScene): return You().flip().to_corner(DR) +# class TourOfDifferentialEquations(Scene): +# def construct(self): +# pass + + class ProveTeacherWrong(TeacherStudentsScene): def construct(self): tex_config = { @@ -197,15 +202,183 @@ class ProveTeacherWrong(TeacherStudentsScene): self.wait(8) -class AskAboutActuallySolving(Scene): +class AskAboutActuallySolving(TeacherStudentsScene): def construct(self): ode = get_ode() ode.to_corner(UL) + self.add(ode) morty = self.teacher self.student_says( - "Yeah, yeah, but how do\\\\" + "Yeah yeah, but how do\\\\" "you acutally \\emph{solve} it?", + student_index=1, target_mode="sassy", added_anims=[morty.change, "thinking"], ) + self.change_student_modes( + "confused", "sassy", "confused", + look_at_arg=ode, + ) + self.wait() + self.teacher_says( + "What do you mean\\\\ by ``solve''?", + target_mode="speaking", + added_anims=[self.get_student_changes( + *3 * ["erm"] + )] + ) + self.play(self.students[1].change, "angry") + self.wait(3) + + +class HungerForExactness(TeacherStudentsScene): + def construct(self): + students = self.students + you = students[2] + teacher = self.teacher + + ode = get_ode() + ode.to_corner(UL) + left_part = ode[:5] + friction_part = ode[5:11] + right_part = ode[11:] + self.add(ode) + frictionless_group = VGroup(left_part, right_part) + + proposed_solution = TexMobject( + "\\theta_0\\cos((\\sqrt{g/L})t)e^{-\\mu t}" + ) + proposed_solution.next_to( + you.get_corner(UL), UP, buff=0.7 + ) + proposed_solution_rect = SurroundingRectangle( + proposed_solution, buff=MED_SMALL_BUFF, + ) + proposed_solution_rect.set_color(BLUE) + proposed_solution_rect.round_corners() + + solution_p1 = TexMobject( + """ + \\theta(t) = 2\\text{am}\\left( + \\frac{\\sqrt{2g + Lc_1} (t + c_2)}{2\\sqrt{L}}, + \\frac{4g}{2g + Lc_1} + \\right) + """, + ) + solution_p1.to_corner(UL) + solution_p2 = TexMobject( + "c_1, c_2 = \\text{Constants depending on initial conditions}" + ) + solution_p2.set_color(LIGHT_GREY) + solution_p2.scale(0.75) + solution_p3 = TexMobject( + """ + \\text{am}(u, k) = + \\int_0^u \\text{dn}(v, k)\\,dv + """ + ) + solution_p3.name = TextMobject( + "(Jacobi amplitude function)" + ) + solution_p4 = TexMobject( + """ + \\text{dn}(u, k) = + \\sqrt{1 - k^2 \\sin^2(\\phi)} + """ + ) + solution_p4.name = TextMobject( + "(Jacobi elliptic function)" + ) + solution_p5 = TextMobject("Where $\\phi$ satisfies") + solution_p6 = TexMobject( + """ + u = \\int_0^\\phi \\frac{dt}{\\sqrt{1 - k^2 \\sin^2(t)}} + """ + ) + + solution = VGroup( + solution_p1, + solution_p2, + solution_p3, + solution_p4, + solution_p5, + solution_p6, + ) + solution.arrange(DOWN) + solution.scale(0.7) + solution.to_corner(UL, buff=MED_SMALL_BUFF) + solution.set_stroke(width=0, background=True) + + solution.remove(solution_p2) + solution_p1.add(solution_p2) + solution.remove(solution_p5) + solution_p6.add(solution_p5) + + for part in [solution_p3, solution_p4]: + part.name.scale(0.7 * 0.7) + part.name.set_color(LIGHT_GREY) + part.name.next_to(part, RIGHT) + part.add(part.name) + + self.student_says( + "Right, but like,\\\\" + "what \\emph{is} $\\theta(t)$?", + target_mode="sassy", + added_anims=[teacher.change, "guilty"], + ) + self.wait() + self.play( + FadeInFromDown(proposed_solution), + RemovePiCreatureBubble( + you, + target_mode="raise_left_hand", + look_at_arg=proposed_solution, + ), + teacher.change, "pondering", + students[0].change, "pondering", + students[1].change, "hesitant", + ) + self.play(ShowCreation(proposed_solution_rect)) + self.play( + proposed_solution.shift, 3 * RIGHT, + proposed_solution_rect.shift, 3 * RIGHT, + you.change, "raise_right_hand", teacher.eyes, + ) + self.wait(3) + + self.play( + FadeOut(proposed_solution), + FadeOut(proposed_solution_rect), + ode.move_to, self.hold_up_spot, DOWN, + ode.shift, LEFT, + teacher.change, "raise_right_hand", + self.get_student_changes(*3 * ["pondering"]) + ) + self.wait() + ode.save_state() + self.play( + left_part.move_to, friction_part, RIGHT, + left_part.match_y, left_part, + friction_part.to_corner, DR, + friction_part.fade, 0.5, + ) + self.wait() + + modes = ["erm", "sad", "sad", "horrified"] + for part, mode in zip(solution, modes): + self.play( + FadeInFrom(part, UP), + self.get_student_changes( + *3 * [mode], + look_at_arg=part, + ) + ) + self.wait() + self.wait(3) + self.play( + FadeOutAndShift(solution, 2 * LEFT), + Restore(ode), + self.get_student_changes(*3 * ["sick"]) + ) + self.wait(3) diff --git a/active_projects/ode/part1/staging.py b/active_projects/ode/part1/staging.py index 471586f4..8fb884da 100644 --- a/active_projects/ode/part1/staging.py +++ b/active_projects/ode/part1/staging.py @@ -39,28 +39,37 @@ class VectorFieldTest(Scene): self.wait(10) -class FollowThisThread(Scene): +class TourOfDifferentialEquations(MovingCameraScene): CONFIG = { "screen_rect_style": { "stroke_width": 2, "stroke_color": WHITE, "fill_opacity": 1, - "fill_color": DARKER_GREY, - } + "fill_color": BLACK, + }, + "camera_config": {"background_color": DARKER_GREY}, } def construct(self): + self.add_title() self.show_thumbnails() - self.show_words() + # self.show_words() + + def add_title(self): + title = TextMobject( + "A Tourist's Guide \\\\to Differential\\\\Equations" + ) + title.scale(1.5) + title.to_corner(UR) + self.add(title) def show_thumbnails(self): - # TODO, replace each of these with a picture? - thumbnails = self.thumbnails = VGroup( - ScreenRectangle(**self.screen_rect_style), - ScreenRectangle(**self.screen_rect_style), - ScreenRectangle(**self.screen_rect_style), - ScreenRectangle(**self.screen_rect_style), - ScreenRectangle(**self.screen_rect_style), + thumbnails = self.thumbnails = Group( + Group(ScreenRectangle(**self.screen_rect_style)), + Group(ScreenRectangle(**self.screen_rect_style)), + Group(ScreenRectangle(**self.screen_rect_style)), + Group(ScreenRectangle(**self.screen_rect_style)), + Group(ScreenRectangle(**self.screen_rect_style)), ) n = len(thumbnails) thumbnails.set_height(1.5) @@ -71,8 +80,16 @@ class FollowThisThread(Scene): [-3, -3, 0], [5, -3, 0], ]) + line.shift(MED_SMALL_BUFF * LEFT) for thumbnail, a in zip(thumbnails, np.linspace(0, 1, n)): thumbnail.move_to(line.point_from_proportion(a)) + dots = TexMobject("\\dots") + dots.next_to(thumbnails[-1], RIGHT) + + self.add_heat_preview(thumbnails[1]) + self.add_fourier_series(thumbnails[2]) + self.add_matrix_exponent(thumbnails[3]) + self.add_laplace_symbol(thumbnails[4]) self.play( ShowCreation( @@ -91,6 +108,14 @@ class FollowThisThread(Scene): ], lag_ratio=1), run_time=5 ) + self.play(Write(dots)) + self.wait() + self.play( + self.camera_frame.replace, + thumbnails[0], + run_time=3, + ) + self.wait() def show_words(self): words = VGroup( @@ -123,6 +148,71 @@ class FollowThisThread(Scene): ) self.wait() + # + def add_heat_preview(self, thumbnail): + image = ImageMobject("HeatSurfaceExample") + image.replace(thumbnail) + thumbnail.add(image) + + def add_matrix_exponent(self, thumbnail): + matrix = IntegerMatrix( + [[3, 1], [4, 1]], + v_buff=MED_LARGE_BUFF, + h_buff=MED_LARGE_BUFF, + bracket_h_buff=SMALL_BUFF, + bracket_v_buff=SMALL_BUFF, + ) + e = TexMobject("e") + t = TexMobject("t") + t.scale(1.5) + t.next_to(matrix, RIGHT, SMALL_BUFF) + e.scale(2) + e.move_to(matrix.get_corner(DL), UR) + group = VGroup(e, matrix, t) + group.set_height(0.7 * thumbnail.get_height()) + randy = Randolph(mode="confused", height=0.75) + randy.next_to(group, LEFT, aligned_edge=DOWN) + randy.look_at(matrix) + group.add(randy) + group.move_to(thumbnail) + thumbnail.add(group) + + def add_fourier_series(self, thumbnail): + colors = [BLUE, GREEN, YELLOW, RED, RED_E, PINK] + + waves = VGroup(*[ + self.get_square_wave_approx(N, color) + for N, color in enumerate(colors) + ]) + waves.set_stroke(width=1.5) + waves.replace(thumbnail, stretch=True) + waves.scale(0.8) + waves.move_to(thumbnail) + thumbnail.add(waves) + + def get_square_wave_approx(self, N, color): + return FunctionGraph( + lambda x: sum([ + (1 / n) * np.sin(n * PI * x) + for n in range(1, 2 * N + 3, 2) + ]), + x_min=0, + x_max=2, + color=color + ) + + def add_laplace_symbol(self, thumbnail): + mob = TexMobject( + "\\mathcal{L}\\left\\{f(t)\\right\\}" + ) + mob.set_width(0.8 * thumbnail.get_width()) + mob.move_to(thumbnail) + thumbnail.add(mob) + + +class HeatEquationPreview(ExternallyAnimatedScene): + pass + class ShowGravityAcceleration(Scene): def construct(self): @@ -395,15 +485,7 @@ class DefineODE(Scene): de_word = TextMobject("Differential", "Equation") de_word.to_edge(UP) - equation = TexMobject( - "\\ddot \\theta({t})", "=", - "-\\mu \\dot \\theta({t})", - "-{g \\over L} \\sin\\big(\\theta({t})\\big)", - tex_to_color_map={ - "\\theta": BLUE, - "{t}": WHITE - } - ) + equation = get_ode() equation.next_to(de_word, DOWN) thetas = equation.get_parts_by_tex("\\theta") @@ -475,11 +557,18 @@ class DefineODE(Scene): ) tex_config = { - "tex_to_color_map": {"\\theta": BLUE}, + "tex_to_color_map": { + "{\\theta}": BLUE, + "{\\dot\\theta}": YELLOW, + "{\\ddot\\theta}": RED, + }, "height": 0.5, } theta, d_theta, dd_theta = [ - TexMobject(s + "\\theta(t)", **tex_config) + TexMobject( + "{" + s + "\\theta}(t)", + **tex_config + ) for s in ("", "\\dot", "\\ddot") ] @@ -500,7 +589,7 @@ class DefineODE(Scene): return DashedLine( x_point, point, dash_length=0.025, - stroke_color=WHITE, + stroke_color=BLUE, stroke_width=2, ) @@ -545,7 +634,7 @@ class DefineODE(Scene): thetas = VGroup(theta, d_theta, dd_theta) words = VGroup( - TextMobject("= Height"), + TextMobject("= Height").set_color(BLUE), TextMobject("= Slope").set_color(YELLOW), TextMobject("= ``Curvature''").set_color(RED), ) @@ -887,7 +976,8 @@ class LorenzVectorField(ExternallyAnimatedScene): class ThreeBodiesInSpace(SpecialThreeDScene): CONFIG = { - "masses": [1, 2, 3], + "masses": [1, 6, 3], + "colors": [RED_E, GREEN_E, BLUE_E], "G": 0.5, "play_time": 60, } @@ -900,26 +990,48 @@ class ThreeBodiesInSpace(SpecialThreeDScene): def add_axes(self): axes = self.axes = self.get_axes() + axes.set_stroke(width=0.5) self.add(axes) def add_bodies(self): + masses = self.masses + colors = self.colors + bodies = self.bodies = VGroup() velocity_vectors = VGroup() - for mass in self.masses: - body = self.get_sphere( - checkerboard_colors=[DARK_BROWN, DARK_BROWN], - stroke_width=0.1, - ) - body.mass = mass - body.set_width(0.2 * mass) - - point = np.dot( - 2 * (np.random.random(3) - 0.5), + centers = [ + np.dot( + 4 * (np.random.random(3) - 0.5), [RIGHT, UP, OUT] ) - velocity = normalize(np.cross(point, OUT)) - body.move_to(point) + for x in range(len(masses)) + ] + + for mass, color, center in zip(masses, colors, centers): + body = self.get_sphere( + checkerboard_colors=[ + color, color + ], + color=color, + stroke_width=0.1, + ) + body.set_opacity(0.75) + body.mass = mass + body.set_width(0.15 * np.sqrt(mass)) + + body.point = center + body.move_to(center) + + to_others = [ + center - center2 + for center2 in centers + ] + velocity = 0.2 * mass * normalize(np.cross(*filter( + lambda diff: get_norm(diff) > 0, + to_others + ))) + body.velocity = velocity body.add_updater(self.update_body) @@ -946,27 +1058,28 @@ class ThreeBodiesInSpace(SpecialThreeDScene): def add_trajectories(self): def update_trajectory(traj, dt): - new_point = traj.body.get_center() + new_point = traj.body.point if get_norm(new_point - traj.points[-1]) > 0.01: traj.add_smooth_curve_to(new_point) for body in self.bodies: traj = VMobject() traj.body = body - traj.start_new_path(body.get_center()) - traj.set_stroke(WHITE, 1) + traj.start_new_path(body.point) + traj.set_stroke(body.color, 1, opacity=0.75) traj.add_updater(update_trajectory) self.add(traj, body) def let_play(self): - self.move_camera( + self.set_camera_orientation( phi=70 * DEGREES, theta=-110 * DEGREES, - run_time=3, ) self.begin_ambient_camera_rotation() - for x in range(6): - self.wait(self.play_time / 6) + # Break it up to see partial files as + # it's rendered + for x in range(int(self.play_time)): + self.wait() # def get_velocity_vector_mob(self, body): @@ -994,13 +1107,110 @@ class ThreeBodiesInSpace(SpecialThreeDScene): for body2 in self.bodies: if body2 is body: continue - diff = body2.get_center() - body.get_center() + diff = body2.point - body.point m2 = body2.mass R = get_norm(diff) acceleration += G * m2 * diff / (R**3) - body.shift(body.velocity * dt) - body.velocity += acceleration * dt + num_mid_steps = 100 + for x in range(num_mid_steps): + body.point += body.velocity * dt / num_mid_steps + body.velocity += acceleration * dt / num_mid_steps + body.move_to(body.point) + return body + + +class AltThreeBodiesInSpace(ThreeBodiesInSpace): + CONFIG = { + "random_seed": 6, + "masses": [1, 2, 6], + } + + +class DefineODECopy(DefineODE): + pass + + +class WriteODESolvingCode(ExternallyAnimatedScene): + pass + + +class InaccurateComputation(Scene): + def construct(self): + h_line = DashedLine(LEFT_SIDE, RIGHT_SIDE) + h_line.to_edge(UP, buff=1.5) + words = VGroup( + TextMobject("Real number"), + TextMobject("IEEE 754\\\\representation"), + TextMobject("Error"), + ) + for i, word in zip([-1, 0, 1], words): + word.next_to(h_line, UP) + word.shift(i * FRAME_WIDTH * RIGHT / 3) + + lines = VGroup(*[ + DashedLine(TOP, BOTTOM) + for x in range(4) + ]) + lines.arrange(RIGHT) + lines.stretch_to_fit_width(FRAME_WIDTH) + + self.add(h_line, lines[1:-1], words) + + numbers = VGroup( + TexMobject("\\pi").scale(2), + TexMobject("e^{\\sqrt{163}\\pi}").scale(1.5), + ) + numbers.set_color(YELLOW) + numbers.set_stroke(width=0, background=True) + + bit_strings = VGroup( + TexMobject( + "01000000", + "01001001", + "00001111", + "11011011", + ), + TexMobject( + "01011100", + "01101001", + "00101110", + "00011001", + ) + ) + for mob in bit_strings: + mob.arrange(DOWN, buff=SMALL_BUFF) + for word in mob: + for submob, bit in zip(word, word.get_tex_string()): + if bit == "0": + submob.set_color(LIGHT_GREY) + errors = VGroup( + TexMobject( + "\\approx 8.7422 \\times 10^{-8}" + ), + TexMobject( + "\\approx 5{,}289{,}803{,}032.00", + ), + ) + errors.set_color(RED) + + content = VGroup(numbers, bit_strings, errors) + + for group, word in zip(content, words): + group[1].shift(3 * DOWN) + group.move_to(DOWN) + group.match_x(word) + + self.play(*map(Write, numbers)) + self.wait() + self.play( + TransformFromCopy(numbers, bit_strings), + lag_ratio=0.01, + run_time=2, + ) + self.wait() + self.play(FadeInFrom(errors, 3 * LEFT)) + self.wait() class NewSceneName(Scene):