From ab2318ff9d005c98fe95299b81b694901e31a940 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 29 May 2019 18:28:40 -0700 Subject: [PATCH] New scenes for diffyq part 3 --- active_projects/ode/all_part3_scenes.py | 8 + .../ode/part3/pi_creature_scenes.py | 59 ++ active_projects/ode/part3/staging.py | 82 -- .../ode/part3/temperature_graphs.py | 781 +++++++++++++++++- active_projects/ode/part3/wordy_scenes.py | 153 ++++ 5 files changed, 956 insertions(+), 127 deletions(-) create mode 100644 active_projects/ode/part3/wordy_scenes.py diff --git a/active_projects/ode/all_part3_scenes.py b/active_projects/ode/all_part3_scenes.py index dd6d024b..58be7b50 100644 --- a/active_projects/ode/all_part3_scenes.py +++ b/active_projects/ode/all_part3_scenes.py @@ -1,6 +1,7 @@ from active_projects.ode.part3.staging import * from active_projects.ode.part3.temperature_graphs import * from active_projects.ode.part3.pi_creature_scenes import * +from active_projects.ode.part3.wordy_scenes import * OUTPUT_DIRECTORY = "ode/part3" @@ -12,4 +13,11 @@ SCENES_IN_ORDER = [ ThreeMainObservations, SimpleCosExpGraph, AddMultipleSolutions, + IveHeardOfThis, + FourierSeriesOfLineIllustration, + BreakDownAFunction, + ThreeConstraints, + OceanOfPossibilities, + InFouriersShoes, + AnalyzeSineCurve, ] diff --git a/active_projects/ode/part3/pi_creature_scenes.py b/active_projects/ode/part3/pi_creature_scenes.py index e79bacbb..01a269a2 100644 --- a/active_projects/ode/part3/pi_creature_scenes.py +++ b/active_projects/ode/part3/pi_creature_scenes.py @@ -1,4 +1,5 @@ from manimlib.imports import * +from active_projects.ode.part2.wordy_scenes import * class IveHeardOfThis(TeacherStudentsScene): @@ -39,3 +40,61 @@ class IveHeardOfThis(TeacherStudentsScene): added_anims=[self.teacher.change, "guilty"] ) self.wait(5) + + +class InFouriersShoes(PiCreatureScene, WriteHeatEquationTemplate): + def construct(self): + randy = self.pi_creature + fourier = ImageMobject("Joseph Fourier") + fourier.set_height(4) + fourier.next_to(randy, RIGHT, LARGE_BUFF) + fourier.align_to(randy, DOWN) + + equation = self.get_d1_equation() + equation.next_to(fourier, UP, MED_LARGE_BUFF) + + decades = list(range(1740, 2040, 20)) + time_line = NumberLine( + x_min=decades[0], + x_max=decades[-1], + tick_frequency=1, + tick_size=0.05, + longer_tick_multiple=4, + unit_size=0.2, + numbers_with_elongated_ticks=decades, + numbers_to_show=decades, + decimal_number_config={ + "group_with_commas": False, + }, + stroke_width=2, + ) + time_line.add_numbers() + time_line.move_to(ORIGIN, RIGHT) + time_line.to_edge(UP) + triangle = ArrowTip(start_angle=-90 * DEGREES) + triangle.set_height(0.25) + triangle.move_to(time_line.n2p(2019), DOWN) + triangle.set_color(WHITE) + + self.play(FadeInFrom(fourier, 2 * LEFT)) + self.play(randy.change, "pondering") + self.wait() + self.play( + DrawBorderThenFill(triangle, run_time=1), + FadeInFromDown(equation), + FadeIn(time_line), + ) + self.play( + Animation(triangle), + ApplyMethod( + time_line.shift, + time_line.n2p(2019) - time_line.n2p(1822), + run_time=5 + ), + ) + self.wait() + + +class SineCurveIsUnrealistic(TeacherStudentsScene): + def construct(self): + pass diff --git a/active_projects/ode/part3/staging.py b/active_projects/ode/part3/staging.py index 4bd3f8fb..279d08b7 100644 --- a/active_projects/ode/part3/staging.py +++ b/active_projects/ode/part3/staging.py @@ -423,88 +423,6 @@ class CircleAnimationOfF(FourierOfTrebleClef): return path -class LastChapterWrapper(Scene): - def construct(self): - full_rect = FullScreenFadeRectangle( - fill_color=DARK_GREY, - fill_opacity=1, - ) - rect = ScreenRectangle(height=6) - rect.set_stroke(WHITE, 2) - rect.set_fill(BLACK, 1) - title = TextMobject("Last chapter") - title.scale(2) - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.add(full_rect) - self.play( - FadeIn(rect), - Write(title, run_time=2), - ) - self.wait() - - -class ThreeMainObservations(Scene): - def construct(self): - fourier = ImageMobject("Joseph Fourier") - fourier.set_height(5) - fourier.to_corner(DR) - fourier.shift(LEFT) - bubble = ThoughtBubble( - direction=RIGHT, - height=3, - width=4, - ) - bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR) - - observations = VGroup( - TextMobject( - "1)", - # "Sine waves", - # "H", - # "Heat equation", - ), - TextMobject( - "2)", - # "Linearity" - ), - TextMobject( - "3)", - # "Any$^{*}$ function is\\\\", - # "a sum of sine waves", - ), - ) - # heart = SuitSymbol("hearts") - # heart.replace(observations[0][2]) - # observations[0][2].become(heart) - # observations[0][1].add(happiness) - # observations[2][2].align_to( - # observations[2][1], LEFT, - # ) - - observations.arrange( - DOWN, - aligned_edge=LEFT, - buff=LARGE_BUFF, - ) - observations.set_height(FRAME_HEIGHT - 2) - observations.to_corner(UL, buff=LARGE_BUFF) - - self.add(fourier) - self.play(ShowCreation(bubble)) - self.wait() - self.play(LaggedStart(*[ - TransformFromCopy(bubble, observation) - for observation in observations - ], lag_ratio=0.2)) - self.play( - FadeOut(fourier), - FadeOut(bubble), - ) - self.wait() - - class NewSceneName(Scene): def construct(self): pass diff --git a/active_projects/ode/part3/temperature_graphs.py b/active_projects/ode/part3/temperature_graphs.py index 4d0df958..daa7f574 100644 --- a/active_projects/ode/part3/temperature_graphs.py +++ b/active_projects/ode/part3/temperature_graphs.py @@ -23,7 +23,12 @@ class TemperatureGraphScene(SpecialThreeDScene): "stroke_color": WHITE, "background_image_file": "VerticalTempGradient", }, - "default_surface_style": { + "default_surface_config": { + "u_min": 0, + "u_max": TAU, + "v_min": 0, + "v_max": 10, + "resolution": (16, 10), "fill_opacity": 0.1, "checkerboard_colors": [LIGHT_GREY], "stroke_width": 0.5, @@ -32,32 +37,17 @@ class TemperatureGraphScene(SpecialThreeDScene): }, } - def get_three_d_axes(self, include_labels=True, **kwargs): + def get_three_d_axes(self, include_labels=True, include_numbers=False, **kwargs): config = dict(self.axes_config) config.update(kwargs) axes = ThreeDAxes(**config) axes.set_stroke(width=2) - # Add number labels - # TODO? + if include_numbers: + self.add_axes_numbers(axes) - # Add axis labels if include_labels: - x_label = TexMobject("x") - x_label.next_to(axes.x_axis.get_right(), DOWN) - axes.x_axis.label = x_label - - t_label = TextMobject("Time") - t_label.rotate(90 * DEGREES, OUT) - t_label.next_to(axes.y_axis.get_top(), DL) - axes.y_axis.label = t_label - - temp_label = TextMobject("Temperature") - temp_label.rotate(90 * DEGREES, RIGHT) - temp_label.next_to(axes.z_axis.get_zenith(), RIGHT) - axes.z_axis.label = temp_label - for axis in axes: - axis.add(axis.label) + self.add_axes_labels(axes) # Adjust axis orinetations axes.x_axis.rotate( @@ -70,18 +60,8 @@ class TemperatureGraphScene(SpecialThreeDScene): ) # Add xy-plane - surface_config = { - "u_min": 0, - "u_max": axes.x_max, - "v_min": 0, - "v_max": axes.y_max, - "resolution": (16, 10), - } - axes.surface_config = surface_config - input_plane = ParametricSurface( - lambda x, t: axes.c2p(x, t, 0), - # lambda x, t: np.array([x, t, 0]), - **surface_config, + input_plane = self.get_surface( + axes, lambda x, t: 0 ) input_plane.set_style( fill_opacity=0.5, @@ -94,13 +74,53 @@ class TemperatureGraphScene(SpecialThreeDScene): return axes + def add_axes_numbers(self, axes): + x_axis = axes.x_axis + y_axis = axes.y_axis + tex_vals = [ + ("\\pi \\over 2", PI / 2), + ("\\pi", PI), + ("3 \\pi \\over 2", 3 * PI / 2), + ("\\tau", TAU) + ] + x_labels = VGroup() + for tex, val in tex_vals: + label = TexMobject(tex) + label.scale(0.5) + label.next_to(x_axis.n2p(val), DOWN) + x_labels.add(label) + x_axis.add(x_labels) + + y_axis.add_numbers() + for number in y_axis.numbers: + number.rotate(90 * DEGREES) + return axes + + def add_axes_labels(self, axes): + x_label = TexMobject("x") + x_label.next_to(axes.x_axis.get_end(), RIGHT) + axes.x_axis.label = x_label + + t_label = TextMobject("Time") + t_label.rotate(90 * DEGREES, OUT) + t_label.next_to(axes.y_axis.get_top(), DL) + axes.y_axis.label = t_label + + temp_label = TextMobject("Temperature") + temp_label.rotate(90 * DEGREES, RIGHT) + temp_label.next_to(axes.z_axis.get_zenith(), RIGHT) + axes.z_axis.label = temp_label + for axis in axes: + axis.add(axis.label) + return axes + def get_time_slice_graph(self, axes, func, t, **kwargs): config = dict() config.update(self.default_graph_style) config.update(kwargs) return ParametricFunction( lambda x: axes.c2p( - x, t, func(x) + x, t, func(x, t) ), t_min=axes.x_min, t_max=axes.x_max, @@ -113,10 +133,10 @@ class TemperatureGraphScene(SpecialThreeDScene): ) def get_surface(self, axes, func, **kwargs): - config = dict() - config.update(axes.surface_config) - config.update(self.default_surface_style) - config.update(kwargs) + config = merge_dicts_recursively( + self.default_surface_config, + kwargs + ) return ParametricSurface( lambda x, t: axes.c2p( x, t, func(x, t) @@ -375,6 +395,7 @@ class BreakDownAFunction(SimpleCosExpGraph): discontinuities=self.get_initial_func_discontinuities(), color=YELLOW, ) + top_graph.set_stroke(width=4) fourier_terms = self.get_fourier_cosine_terms( self.initial_func @@ -436,7 +457,7 @@ class BreakDownAFunction(SimpleCosExpGraph): plusses = VGroup(*[ TexMobject("+").next_to( axes.x_axis.get_end(), - RIGHT, MED_LARGE_BUFF + RIGHT, MED_SMALL_BUFF ) for axes in low_axes_group ]) @@ -469,16 +490,15 @@ class BreakDownAFunction(SimpleCosExpGraph): self.wait() self.play( LaggedStartMap(FadeIn, low_axes_group), + FadeInFrom(low_words, UP), *[ TransformFromCopy(top_graph, low_graph) for low_graph in low_graphs ] ) - self.play(FadeInFrom(low_words, UP)) self.wait() self.play( - LaggedStartMap(FadeInFromDown, plusses), - Write(dots) + LaggedStartMap(FadeInFromDown, [*plusses, dots]), ) self.play(ShowCreation(arrow)) self.wait() @@ -506,7 +526,7 @@ class BreakDownAFunction(SimpleCosExpGraph): anims2.append(AnimationGroup( TransformFromCopy(graph, top_graph.copy()), Transform( - surface.copy().fade(1), + surface.copy().set_fill(opacity=0), top_surface, ) )) @@ -530,9 +550,10 @@ class BreakDownAFunction(SimpleCosExpGraph): self.play(LaggedStartMap(FadeInFromDown, low_checkmarks)) self.wait() - self.play(TransformFromCopy( - low_checkmarks, VGroup(top_checkmark) - )) + self.play(*[ + TransformFromCopy(low_checkmark, top_checkmark.copy()) + for low_checkmark in low_checkmarks + ]) self.wait() # @@ -578,3 +599,673 @@ class BreakDownAFunction(SimpleCosExpGraph): ] result[0] = result[0] / 2 return result + + +class OceanOfPossibilities(TemperatureGraphScene): + CONFIG = { + "axes_config": { + "z_min": 0, + "z_max": 4, + }, + "k": 0.2, + "default_surface_config": { + # "resolution": (32, 20), + # "resolution": (8, 5), + } + } + + def construct(self): + self.setup_camera() + self.setup_axes() + self.setup_surface() + self.show_solution() + self.reference_boundary_conditions() + self.reference_initial_condition() + self.ambiently_change_solution() + + def setup_camera(self): + self.set_camera_orientation( + phi=80 * DEGREES, + theta=-80 * DEGREES, + ) + self.camera.frame_center.move_to( + 3 * RIGHT + ) + self.begin_ambient_camera_rotation(rate=0.01) + + def setup_axes(self): + axes = self.get_three_d_axes(include_numbers=True) + axes.add(axes.input_plane) + # axes.scale(1.25) + axes.shift(1.5 * IN) + + self.add(axes) + self.axes = axes + + def setup_surface(self): + axes = self.axes + k = self.k + + # Parameters for surface function + initial_As = [2] + [ + random.choice([-1, 1]) / n + for n in range(1, 20) + ] + A_trackers = Group(*[ + ValueTracker(A) + for A in initial_As + ]) + + def get_As(): + return [At.get_value() for At in A_trackers] + + omegas = [n / 2 for n in range(0, 10)] + + def func(x, t): + return np.sum([ + np.prod([ + A * np.cos(omega * x), + np.exp(-k * omega**2 * t) + ]) + for A, omega in zip(get_As(), omegas) + ]) + + # Surface and graph + surface = always_redraw( + lambda: self.get_surface(axes, func) + ) + t_tracker = ValueTracker(0) + graph = always_redraw( + lambda: self.get_time_slice_graph( + axes, func, t_tracker.get_value(), + ) + ) + + surface.suspend_updating() + graph.suspend_updating() + + self.surface_func = func + self.surface = surface + self.graph = graph + self.t_tracker = t_tracker + self.A_trackers = A_trackers + self.omegas = omegas + + def show_solution(self): + axes = self.axes + surface = self.surface + graph = self.graph + t_tracker = self.t_tracker + get_t = t_tracker.get_value + + opacity_tracker = ValueTracker(0) + plane = always_redraw(lambda: Polygon( + *[ + axes.c2p(x, get_t(), T) + for x, T in [ + (0, 0), (TAU, 0), (TAU, 4), (0, 4) + ] + ], + stroke_width=0, + fill_color=WHITE, + fill_opacity=opacity_tracker.get_value(), + )) + + self.add(surface, plane, graph) + graph.resume_updating() + self.play( + opacity_tracker.set_value, 0.2, + ApplyMethod( + t_tracker.set_value, 1, + rate_func=linear + ), + run_time=1 + ) + self.play( + ApplyMethod( + t_tracker.set_value, 10, + rate_func=linear, + run_time=9 + ) + ) + self.wait() + + self.plane = plane + + def reference_boundary_conditions(self): + axes = self.axes + t_numbers = axes.y_axis.numbers + + lines = VGroup(*[ + Line( + axes.c2p(x, 0, 0), + axes.c2p(x, axes.y_max, 0), + stroke_width=3, + stroke_color=MAROON_B, + ) + for x in [0, axes.x_max] + ]) + surface_boundary_lines = always_redraw(lambda: VGroup(*[ + ParametricFunction( + lambda t: axes.c2p( + x, t, + self.surface_func(x, t) + ), + t_max=axes.y_max + ).match_style(self.graph) + for x in [0, axes.x_max] + ])) + # surface_boundary_lines.suspend_updating() + words = VGroup() + for line in lines: + word = TextMobject("Boundary") + word.set_stroke(BLACK, 3, background=True) + word.scale(1.5) + word.match_color(line) + word.rotate(90 * DEGREES, RIGHT) + word.rotate(90 * DEGREES, OUT) + word.next_to(line, OUT, SMALL_BUFF) + words.add(word) + + self.stop_ambient_camera_rotation() + self.move_camera( + theta=-45 * DEGREES, + frame_center=ORIGIN, + added_anims=[ + LaggedStartMap(ShowCreation, lines), + LaggedStartMap( + FadeInFrom, words, + lambda m: (m, IN) + ), + FadeOut(t_numbers), + ] + ) + self.play( + LaggedStart(*[ + TransformFromCopy(l1, l2) + for l1, l2 in zip(lines, surface_boundary_lines) + ]) + ) + self.add(surface_boundary_lines) + self.wait() + self.move_camera( + theta=-70 * DEGREES, + frame_center=3 * RIGHT, + ) + + self.surface_boundary_lines = surface_boundary_lines + + def reference_initial_condition(self): + plane = self.plane + t_tracker = self.t_tracker + + self.play( + t_tracker.set_value, 0, + run_time=2 + ) + plane.clear_updaters() + self.play(FadeOut(plane)) + + def ambiently_change_solution(self): + A_trackers = self.A_trackers + + def generate_A_updater(A, rate): + def update(m, dt): + m.total_time += dt + m.set_value( + 2 * A * np.sin(rate * m.total_time + PI / 6) + ) + return update + + rates = [0, 0.2] + [ + 0.5 + 0.5 * np.random.random() + for x in range(len(A_trackers) - 2) + ] + + for tracker, rate in zip(A_trackers, rates): + tracker.total_time = 0 + tracker.add_updater(generate_A_updater( + tracker.get_value(), + rate + )) + + self.add(*A_trackers) + self.surface_boundary_lines.resume_updating() + self.surface.resume_updating() + self.graph.resume_updating() + self.wait(30) + + +class AnalyzeSineCurve(TemperatureGraphScene): + CONFIG = { + "origin_point": 3 * LEFT, + "axes_config": { + "z_min": -1.5, + "z_max": 1.5, + "z_axis_config": { + "unit_size": 2, + "tick_frequency": 0.5, + } + }, + "tex_to_color_map": { + "{x}": GREEN, + "T": YELLOW, + "=": WHITE, + "0": WHITE, + "\\Delta t": WHITE, + "\\sin": WHITE, + "{t}": PINK, + } + } + + def construct(self): + self.setup_axes() + self.ask_about_sine_curve() + self.show_sine_wave_on_axes() + self.reference_curvature() + self.show_derivatives() + self.show_curvature_matching_height() + self.show_time_step_scalings() + self.smooth_evolution() + + def setup_axes(self): + axes = self.get_three_d_axes() + axes.rotate(90 * DEGREES, LEFT) + axes.shift(self.origin_point - axes.c2p(0, 0, 0)) + y_axis = axes.y_axis + y_axis.fade(1) + z_axis = axes.z_axis + z_axis.label.next_to(z_axis.get_end(), UP, SMALL_BUFF) + + self.add_axes_numbers(axes) + y_axis.remove(y_axis.numbers) + axes.z_axis.add_numbers( + *range(-1, 2), + direction=LEFT, + ) + + self.axes = axes + + def ask_about_sine_curve(self): + curve = FunctionGraph( + lambda t: np.sin(t), + x_min=0, + x_max=TAU, + ) + curve.move_to(DR) + curve.set_width(5) + curve.set_color(YELLOW) + question = TextMobject("What's so special?") + question.scale(1.5) + question.to_edge(UP) + question.shift(2 * LEFT) + arrow = Arrow( + question.get_bottom(), + curve.point_from_proportion(0.25) + ) + + self.play( + ShowCreation(curve), + Write(question, run_time=1), + GrowArrow(arrow), + ) + self.wait() + + self.quick_sine_curve = curve + self.question_group = VGroup(question, arrow) + + def show_sine_wave_on_axes(self): + axes = self.axes + graph = self.get_initial_state_graph( + axes, lambda x, t: np.sin(x) + ) + graph.set_stroke(width=4) + graph_label = TexMobject( + "T({x}, 0) = \\sin\\left({x}\\right)", + tex_to_color_map=self.tex_to_color_map, + ) + graph_label.next_to( + graph.point_from_proportion(0.25), UR, + buff=SMALL_BUFF, + ) + + v_line, x_tracker = self.get_v_line_with_x_tracker(graph) + + xs = VGroup( + *graph_label.get_parts_by_tex("x"), + axes.x_axis.label, + ) + + self.play( + Write(axes), + self.quick_sine_curve.become, graph, + FadeOutAndShift(self.question_group, UP), + ) + self.play( + FadeInFromDown(graph_label), + FadeIn(graph), + ) + self.remove(self.quick_sine_curve) + self.add(v_line) + self.play( + ApplyMethod( + x_tracker.set_value, TAU, + rate_func=lambda t: smooth(t, 3), + run_time=5, + ), + LaggedStartMap( + ShowCreationThenFadeAround, xs, + run_time=3, + lag_ratio=0.2, + ) + ) + self.remove(v_line, x_tracker) + self.wait() + + self.graph = graph + self.graph_label = graph_label + self.v_line = v_line + self.x_tracker = x_tracker + + def reference_curvature(self): + curve_segment, curve_x_tracker = \ + self.get_curve_segment_with_x_tracker(self.graph) + + self.add(curve_segment) + self.play( + curve_x_tracker.set_value, TAU, + run_time=5, + rate_func=lambda t: smooth(t, 3), + ) + self.play(FadeOut(curve_segment)) + + self.curve_segment = curve_segment + self.curve_x_tracker = curve_x_tracker + + def show_derivatives(self): + deriv1 = TexMobject( + "{\\partial T \\over \\partial {x}}({x}, 0)", + "= \\cos\\left({x}\\right)", + tex_to_color_map=self.tex_to_color_map, + ) + deriv2 = TexMobject( + "{\\partial^2 T \\over \\partial {x}^2}({x}, 0)", + "= -\\sin\\left({x}\\right)", + tex_to_color_map=self.tex_to_color_map, + ) + + deriv1.to_corner(UR) + deriv2.next_to( + deriv1, DOWN, + buff=0.75, + aligned_edge=LEFT, + ) + VGroup(deriv1, deriv2).shift(1.4 * RIGHT) + + self.play( + Animation(Group(*self.get_mobjects())), + FadeInFrom(deriv1, LEFT), + self.camera.frame_center.shift, 2 * RIGHT, + ) + self.wait() + self.play( + FadeInFrom(deriv2, UP) + ) + self.wait() + + self.deriv1 = deriv1 + self.deriv2 = deriv2 + + def show_curvature_matching_height(self): + axes = self.axes + graph = self.graph + curve_segment = self.curve_segment + curve_x_tracker = self.curve_x_tracker + + d2_graph = self.get_initial_state_graph( + axes, lambda x, t: -np.sin(x), + ) + dashed_d2_graph = DashedVMobject(d2_graph, num_dashes=50) + dashed_d2_graph.color_using_background_image(None) + dashed_d2_graph.set_stroke(RED, 2) + + vector, x_tracker = self.get_v_line_with_x_tracker( + d2_graph, + line_creator=lambda p1, p2: Arrow( + p1, p2, color=RED, buff=0 + ) + ) + + lil_vectors = self.get_many_lil_vectors(graph) + lil_vector = always_redraw( + lambda: self.get_lil_vector( + graph, x_tracker.get_value() + ) + ) + + d2_rect = SurroundingRectangle( + self.deriv2[-5:], + color=RED, + ) + self.play(ShowCreation(d2_rect)) + self.add(vector) + self.add(lil_vector) + self.add(curve_segment) + curve_x_tracker.set_value(0) + self.play( + ShowCreation(dashed_d2_graph), + x_tracker.set_value, TAU, + curve_x_tracker.set_value, TAU, + ShowIncreasingSubsets(lil_vectors[1:]), + run_time=8, + rate_func=linear, + ) + self.remove(vector) + self.remove(lil_vector) + self.add(lil_vectors) + self.play( + FadeOut(curve_segment), + FadeOut(d2_rect), + ) + + self.lil_vectors = lil_vectors + self.dashed_d2_graph = dashed_d2_graph + + def show_time_step_scalings(self): + axes = self.axes + graph_label = self.graph_label + dashed_d2_graph = self.dashed_d2_graph + lil_vectors = self.lil_vectors + graph = self.graph + + factor = 0.9 + + new_label = TexMobject( + "T({x}, \\Delta t) = c \\cdot \\sin\\left({x}\\right)", + tex_to_color_map=self.tex_to_color_map, + ) + final_label = TexMobject( + "T({x}, {t}) = (\\text{something}) \\cdot \\sin\\left({x}\\right)", + tex_to_color_map=self.tex_to_color_map, + ) + for label in (new_label, final_label): + label.shift( + graph_label.get_part_by_tex("=").get_center() - + label.get_part_by_tex("=").get_center() + ) + final_label.shift(1.5 * LEFT) + + h_lines = VGroup( + DashedLine(axes.c2p(0, 0, 1), axes.c2p(TAU, 0, 1)), + DashedLine(axes.c2p(0, 0, -1), axes.c2p(TAU, 0, -1)), + ) + + lil_vectors.add_updater(lambda m: m.become( + self.get_many_lil_vectors(graph) + )) + + i = 4 + self.play( + ReplacementTransform( + graph_label[:i], new_label[:i], + ), + ReplacementTransform( + graph_label[i + 1:i + 3], + new_label[i + 1:i + 3], + ), + FadeOutAndShift(graph_label[i], UP), + FadeInFrom(new_label[i], DOWN), + ) + self.play( + ReplacementTransform( + graph_label[i + 3:], + new_label[i + 4:] + ), + FadeInFromDown(new_label[i + 3]) + ) + self.play( + FadeOut(dashed_d2_graph), + FadeIn(h_lines), + ) + self.play( + graph.stretch, factor, 1, + h_lines.stretch, factor, 1, + ) + self.wait() + + # Repeat + last_coef = None + last_exp = None + delta_T = new_label.get_part_by_tex("\\Delta t") + c = new_label.get_part_by_tex("c")[0] + prefix = new_label[:4] + prefix.generate_target() + for x in range(5): + coef = Integer(x + 2) + exp = coef.copy().scale(0.7) + coef.next_to( + delta_T, LEFT, SMALL_BUFF, + aligned_edge=DOWN, + ) + exp.move_to(c.get_corner(UR), DL) + anims1 = [FadeInFrom(coef, 0.25 * DOWN)] + anims2 = [FadeInFrom(exp, 0.25 * DOWN)] + if last_coef: + anims1.append( + FadeOutAndShift(last_coef, 0.25 * UP) + ) + anims2.append( + FadeOutAndShift(last_exp, 0.25 * UP) + ) + last_coef = coef + last_exp = exp + prefix.target.next_to(coef, LEFT, SMALL_BUFF) + prefix.target.match_y(prefix) + anims1.append(MoveToTarget(prefix)) + + self.play(*anims1) + self.play( + graph.stretch, factor, 1, + h_lines.stretch, factor, 1, + *anims2, + ) + self.play( + ReplacementTransform( + new_label[:4], + final_label[:4], + ), + ReplacementTransform( + VGroup(last_coef, delta_T), + final_label.get_part_by_tex("{t}"), + ), + ReplacementTransform( + last_exp, + final_label.get_part_by_tex("something"), + ), + FadeOut(new_label.get_part_by_tex("\\cdot"), UP), + ReplacementTransform( + new_label[-4:], + final_label[-4:], + ), + ReplacementTransform( + new_label.get_part_by_tex("="), + final_label.get_part_by_tex("="), + ), + ReplacementTransform( + new_label.get_part_by_tex(")"), + final_label.get_part_by_tex(")"), + ), + ) + final_label.add_background_rectangle(opacity=1) + self.add(final_label) + self.wait() + + group = VGroup(graph, h_lines) + group.add_updater(lambda m, dt: m.stretch( + (1 - 0.1 * dt), 1 + )) + self.add(group) + self.wait(10) + + def smooth_evolution(self): + pass + + # + def get_rod(self, temp_func): + pass + + def get_v_line_with_x_tracker(self, graph, line_creator=DashedLine): + axes = self.axes + x_min = axes.x_axis.p2n(graph.get_start()) + x_max = axes.x_axis.p2n(graph.get_end()) + x_tracker = ValueTracker(x_min) + get_x = x_tracker.get_value + v_line = always_redraw(lambda: line_creator( + axes.c2p(get_x(), 0, 0), + graph.point_from_proportion( + inverse_interpolate( + x_min, x_max, get_x() + ) + ), + )) + return v_line, x_tracker + + def get_curve_segment_with_x_tracker(self, graph, delta_x=0.5): + axes = self.axes + x_min = axes.x_axis.p2n(graph.get_start()) + x_max = axes.x_axis.p2n(graph.get_end()) + x_tracker = ValueTracker(x_min) + get_x = x_tracker.get_value + + def x2a(x): + return inverse_interpolate(x_min, x_max, x) + + curve = VMobject( + stroke_color=WHITE, + stroke_width=5 + ) + curve.add_updater(lambda m: m.pointwise_become_partial( + graph, + max(x2a(get_x() - delta_x), 0), + min(x2a(get_x() + delta_x), 1), + )) + return curve, x_tracker + + def get_lil_vector(self, graph, x): + x_axis = self.axes.x_axis + point = graph.point_from_proportion(x / TAU) + x_axis_point = x_axis.n2p(x_axis.p2n(point)) + return Arrow( + point, + interpolate( + point, x_axis_point, 0.5, + ), + buff=0, + color=RED + ) + + def get_many_lil_vectors(self, graph, n=13): + return VGroup(*[ + self.get_lil_vector(graph, x) + for x in np.linspace(0, TAU, n) + ]) diff --git a/active_projects/ode/part3/wordy_scenes.py b/active_projects/ode/part3/wordy_scenes.py new file mode 100644 index 00000000..125fa214 --- /dev/null +++ b/active_projects/ode/part3/wordy_scenes.py @@ -0,0 +1,153 @@ +from manimlib.imports import * +from active_projects.ode.part2.wordy_scenes import * + + +class ThreeMainObservations(Scene): + def construct(self): + fourier = ImageMobject("Joseph Fourier") + fourier.set_height(5) + fourier.to_corner(DR) + fourier.shift(LEFT) + bubble = ThoughtBubble( + direction=RIGHT, + height=3, + width=4, + ) + bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR) + + observations = VGroup( + TextMobject( + "1)", + # "Sine waves", + # "H", + # "Heat equation", + ), + TextMobject( + "2)", + # "Linearity" + ), + TextMobject( + "3)", + # "Any$^{*}$ function is\\\\", + # "a sum of sine waves", + ), + ) + # heart = SuitSymbol("hearts") + # heart.replace(observations[0][2]) + # observations[0][2].become(heart) + # observations[0][1].add(happiness) + # observations[2][2].align_to( + # observations[2][1], LEFT, + # ) + + observations.arrange( + DOWN, + aligned_edge=LEFT, + buff=LARGE_BUFF, + ) + observations.set_height(FRAME_HEIGHT - 2) + observations.to_corner(UL, buff=LARGE_BUFF) + + self.add(fourier) + self.play(ShowCreation(bubble)) + self.wait() + self.play(LaggedStart(*[ + TransformFromCopy(bubble, observation) + for observation in observations + ], lag_ratio=0.2)) + self.play( + FadeOut(fourier), + FadeOut(bubble), + ) + self.wait() + + +class LastChapterWrapper(Scene): + def construct(self): + full_rect = FullScreenFadeRectangle( + fill_color=DARK_GREY, + fill_opacity=1, + ) + rect = ScreenRectangle(height=6) + rect.set_stroke(WHITE, 2) + rect.set_fill(BLACK, 1) + title = TextMobject("Last chapter") + title.scale(2) + title.to_edge(UP) + rect.next_to(title, DOWN) + + self.add(full_rect) + self.play( + FadeIn(rect), + Write(title, run_time=2), + ) + self.wait() + + +class ThreeConstraints(WriteHeatEquationTemplate): + def construct(self): + self.cross_out_solving() + self.show_three_conditions() + + def cross_out_solving(self): + equation = self.get_d1_equation() + words = TextMobject("Solve this equation") + words.to_edge(UP) + equation.next_to(words, DOWN) + cross = Cross(words) + + self.add(words, equation) + self.wait() + self.play(ShowCreation(cross)) + self.wait() + + self.equation = equation + self.to_remove = VGroup(words, cross) + + def show_three_conditions(self): + equation = self.equation + to_remove = self.to_remove + + title = TexMobject( + "\\text{Constraints }" + "T({x}, {t})" + "\\text{ must satisfy:}", + **self.tex_mobject_config + ) + title.to_edge(UP) + + items = VGroup( + TextMobject("1)", "The PDE"), + TextMobject("2)", "Boundary condition"), + TextMobject("3)", "Initial condition"), + ) + items.scale(0.7) + items.arrange(RIGHT, buff=LARGE_BUFF) + items.set_width(FRAME_WIDTH - 2) + items.next_to(title, DOWN, LARGE_BUFF) + items[1].set_color(MAROON_B) + items[2].set_color(RED) + + bc_paren = TextMobject("(Explained soon)") + bc_paren.scale(0.7) + bc_paren.next_to(items[1], DOWN) + + self.play( + FadeInFromDown(title), + FadeOutAndShift(to_remove, UP), + equation.scale, 0.6, + equation.next_to, items[0], DOWN, + equation.shift_onto_screen, + LaggedStartMap(FadeIn, [ + items[0], + items[1][0], + items[2][0], + ]) + ) + self.wait() + self.play(Write(items[1][1])) + bc_paren.match_y(equation) + self.play(FadeInFrom(bc_paren, UP)) + self.wait(2) + self.play(Write(items[2][1])) + self.wait(2)