diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 70441f4e..f67ac717 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -2,6 +2,7 @@ from active_projects.ode.part2.staging import * from active_projects.ode.part2.fourier_series import * from active_projects.ode.part2.heat_equation import * from active_projects.ode.part2.pi_scenes import * +from active_projects.ode.part2.wordy_scenes import * OUTPUT_DIRECTORY = "ode/part2" ALL_SCENE_CLASSES = [ @@ -30,7 +31,14 @@ ALL_SCENE_CLASSES = [ TwoDBodyWithManyTemperaturesContour, BringTwoRodsTogether, ShowEvolvingTempGraphWithArrows, + TodaysTargetWrapper, WriteHeatEquation, ReactionsToInitialHeatEquation, TalkThrough1DHeatGraph, + ShowCubeFormation, + CompareInputsOfGeneralCaseTo1D, + TransitionToTempVsTime, + ShowNewton, + ShowCupOfWater, + ShowNewtonsLawGraph, ] diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index 7529c528..00a26391 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -603,12 +603,12 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene graph = self.graph x_axis = axes.x_axis x_numbers = x_axis.get_number_mobjects(*range(1, 11)) - x_label = TexMobject("x") - x_label.next_to(x_axis.get_right(), UP) + x_axis_label = TexMobject("x") + x_axis_label.next_to(x_axis.get_right(), UP) self.play( rod.set_opacity, 0.5, - FadeInFrom(x_label, UL), + FadeInFrom(x_axis_label, UL), LaggedStartMap( FadeInFrom, x_numbers, lambda m: (m, UP), @@ -714,6 +714,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene graph_dot, t_label, ) self.set_variables_as_attrs(*self.graph_label_group) + self.set_variables_as_attrs(x_numbers, x_axis_label) def show_changes_over_time(self): graph = self.graph @@ -772,12 +773,22 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene def show_surface(self): axes = self.axes graph = self.graph - - axes_copy = axes.deepcopy() - - # Time axis t_min = 0 t_max = 10 + + axes_copy = axes.deepcopy() + original_axes = self.axes + + # Set rod final state + final_graph = self.get_graph(t_max) + curr_graph = self.graph + self.graph = final_graph + final_rod = self.rod.copy() + final_rod.set_opacity(1) + self.color_rod_by_graph(final_rod) + self.graph = curr_graph + + # Time axis t_axis = NumberLine( x_min=t_min, x_max=t_max, @@ -791,6 +802,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene time_label = TextMobject("Time") time_label.scale(1.5) time_label.next_to(t_axis, UP) + t_axis.time_label = time_label t_axis.add(time_label) # t_axis.rotate(90 * DEGREES, LEFT, about_point=origin) t_axis.rotate(90 * DEGREES, UP, about_point=origin) @@ -804,6 +816,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene for t in np.arange(0, t_max + step, step) ]) graph_slices.set_stroke(width=1) + graph_slices.set_shade_in_3d(True) # Input plane x_axis = self.axes.x_axis @@ -885,12 +898,23 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene LaggedStart(*[ TransformFromCopy(graph, graph_slice) for graph_slice in graph_slices - ]) + ], lag_ratio=0.02) ) self.wait(4) # Show slices self.axes = axes_copy # So get_graph works... + slicing_plane = Rectangle( + stroke_width=0, + fill_color=WHITE, + fill_opacity=0.2, + ) + slicing_plane.set_shade_in_3d(True) + slicing_plane.replace( + Line(axes_copy.c2p(0, 0), axes_copy.c2p(10, 100)), + stretch=True + ) + self.orient_mobject_for_3d(slicing_plane) def get_time_slice(t): new_slice = self.get_graph(t) @@ -905,18 +929,26 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene get_time_slice(t_tracker.get_value()) )) + self.orient_mobject_for_3d(final_rod) + final_rod.shift(10 * UP) + kw = {"run_time": 10, "rate_func": linear} self.play( - t_tracker.set_value, 10, - self.rod.shift, 10 * UP, - ApplyMethod( - graph_slices.set_stroke, {"opacity": 0.5}, - rate_func=squish_rate_func(smooth, 0, 0.2), - ), - run_time=10, - rate_func=linear, + ApplyMethod(t_tracker.set_value, 10, **kw), + Transform(self.rod, final_rod, **kw), + ApplyMethod(slicing_plane.shift, 10 * UP, **kw), ) + graph.clear_updaters() self.wait() + self.set_variables_as_attrs( + t_axis, + input_plane, + surface, + graph_slices, + slicing_plane, + ) + self.axes = original_axes + # def get_graph(self, time=0): graph = self.axes.get_graph( @@ -954,5 +986,177 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene axis=RIGHT, about_point=ORIGIN ) - mob.shift(1 * DOWN) return mob + + +class TransitionToTempVsTime(TalkThrough1DHeatGraph): + def construct(self): + self.force_skipping() + + # super().construct() + self.add_axes() + self.add_graph() + self.add_rod() + + # self.emphasize_graph() + # self.emphasize_rod() + self.rod_word = Point() + self.show_x_axis() + # self.show_changes_over_time() + self.show_surface() + + self.revert_to_original_skipping_status() + + axes = self.axes + t_axis = self.t_axis + y_axis = axes.y_axis + x_axis = axes.x_axis + + self.stop_ambient_camera_rotation() + self.move_camera( + phi=90 * DEGREES, + theta=0 * DEGREES, + added_anims=[ + Rotate( + y_axis, 90 * DEGREES, + axis=OUT, + about_point=y_axis.n2p(0), + ), + FadeOut(VGroup( + self.graph_slices, + self.surface, + self.slicing_plane, + self.rod, + self.graph, + self.x_numbers, + self.x_axis_label, + )), + self.camera.frame_center.shift, 5 * LEFT, + ] + ) + self.play( + VGroup(x_axis, self.input_plane).stretch, + 0, 0, {"about_point": y_axis.n2p(0)}, + ) + self.play( + t_axis.time_label.scale, 1 / 1.5, + t_axis.time_label.next_to, t_axis, IN, MED_LARGE_BUFF, + t_axis.numbers.shift, 0.7 * IN, + ) + self.wait() + + +class ShowNewtonsLawGraph(Scene): + CONFIG = { + "k": 0.2, + "initial_water_temp": 80, + "room_temp": 20, + } + + def construct(self): + self.setup_axes() + self.show_temperatures() + self.show_graph() + self.show_equation() + self.talk_through_examples() + + def setup_axes(self): + axes = Axes( + x_min=0, + x_max=10, + y_min=0, + y_max=100, + y_axis_config={ + "unit_size": 0.06, + "tick_frequency": 10, + }, + center_point=5 * LEFT + 2.5 * DOWN + ) + x_axis = axes.x_axis + y_axis = axes.y_axis + y_axis.add_numbers(*range(20, 100, 20)) + x_axis.add_numbers(*range(1, 11)) + + x_axis.label = TextMobject("Time") + x_axis.label.next_to(x_axis, DOWN, MED_SMALL_BUFF) + + y_axis.label = TexMobject("\\text{Temperature}") + y_axis.label.next_to(y_axis, RIGHT, buff=SMALL_BUFF) + y_axis.label.align_to(axes, UP) + for axis in [x_axis, y_axis]: + axis.add(axis.label) + + self.add(axes) + self.axes = axes + + def show_temperatures(self): + axes = self.axes + + water_dot = Dot() + water_dot.color_using_background_image("VerticalTempGradient") + water_dot.move_to(axes.c2p(0, self.initial_water_temp)) + room_line = DashedLine( + axes.c2p(0, self.room_temp), + axes.c2p(10, self.room_temp), + ) + room_line.set_color(BLUE) + room_line.color_using_background_image("VerticalTempGradient") + + water_arrow = Vector(LEFT, color=WHITE) + water_arrow.next_to(water_dot, RIGHT, SMALL_BUFF) + water_words = TextMobject( + "Initial water temperature" + ) + water_words.next_to(water_arrow, RIGHT) + + room_words = TextMobject("Room temperature") + room_words.next_to(room_line, DOWN, SMALL_BUFF) + + self.play( + FadeInFrom(water_dot, RIGHT), + GrowArrow(water_arrow), + Write(water_words), + ) + self.play(ShowCreation(room_line)) + self.play(FadeInFromDown(room_words)) + self.wait() + + self.set_variables_as_attrs( + water_dot, + water_arrow, + water_words, + room_line, + room_words, + ) + + def show_graph(self): + axes = self.axes + water_dot = self.water_dot + + k = self.k + rt = self.room_temp + t0 = self.initial_water_temp + graph = axes.get_graph( + lambda t: rt + (t0 - rt) * np.exp(-k * t) + ) + graph.color_using_background_image("VerticalTempGradient") + + def get_x(): + return axes.x_axis.p2n(water_dot.get_center()) + + brace_line = always_redraw(lambda: Line( + axes.c2p(get_x(), rt), + water_dot.get_center(), + stroke_width=0, + )) + brace = Brace(brace_line, RIGHT, SMALL_BUFF) + brace.add_updater(lambda b: b.match_height(brace_line)) + brace.add_updater(lambda b: b.next_to(brace_line, RIGHT, SMALL_BUFF)) + + self.add(graph) + + def show_equation(self): + pass + + def talk_through_examples(self): + pass diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index 8bfe90ca..38aad58f 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -83,73 +83,52 @@ class PartTwoOfTour(TourOfDifferentialEquations): } -class CompareODEToPDE(Scene): +class ShowCubeFormation(ThreeDScene): + CONFIG = { + "camera_config": { + "shading_factor": 1.0, + } + } + + def construct(self): + light_source = self.camera.light_source + light_source.move_to(np.array([-6, -3, 6])) + + cube = Cube( + side_length=4, + fill_color=GREY, + stroke_color=WHITE, + stroke_width=0.5, + ) + cube.set_fill(opacity=1) + + # light_source.next_to(cube, np.array([1, -1, 1]), buff=2) + + cube_3d = cube.copy() + cube_2d = cube_3d.copy().stretch(0, 2) + cube_1d = cube_2d.copy().stretch(0, 1) + cube_0d = cube_1d.copy().stretch(0, 0) + + cube.become(cube_0d) + + self.set_camera_orientation( + phi=70 * DEGREES, + theta=-145 * DEGREES, + ) + self.begin_ambient_camera_rotation(rate=0.05) + + for target in [cube_1d, cube_2d, cube_3d]: + self.play( + Transform(cube, target, run_time=1.5) + ) + self.wait(3) + + +class ShowNewton(Scene): def construct(self): pass -class WriteHeatEquation(Scene): +class ShowCupOfWater(Scene): def construct(self): - d1_words = TextMobject("Heat equation\\\\", "(1 dimension)") - d3_words = TextMobject("Heat equation\\\\", "(3 dimensions)") - - kwargs = { - "tex_to_color_map": { - "{T}": YELLOW, - "{t}": WHITE, - "{x}": GREEN, - "{y}": RED, - "{z}": BLUE, - } - } - d1_equation = TexMobject( - "{\\partial {T} \\over \\partial {t}}({x}, {t})=" - "{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})", - **kwargs - ) - d3_equation = 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}", - **kwargs - ) - - d1_group = VGroup(d1_words, d1_equation) - d3_group = VGroup(d3_words, d3_equation) - groups = VGroup(d1_group, d3_group) - for group in groups: - group.arrange(DOWN, buff=MED_LARGE_BUFF) - groups.arrange(RIGHT, buff=2) - groups.to_edge(UP) - - d3_rhs = d3_equation[6:] - d3_brace = Brace(d3_rhs, DOWN) - nabla_words = TextMobject("Sometimes written as") - nabla_words.match_width(d3_brace) - nabla_words.next_to(d3_brace, DOWN) - nabla_exp = TexMobject("\\nabla^2 {T}", **kwargs) - nabla_exp.next_to(nabla_words, DOWN) - # nabla_group = VGroup(nabla_words, nabla_exp) - - d1_group.save_state() - d1_group.center().to_edge(UP) - - self.play( - Write(d1_words), - FadeInFrom(d1_equation, UP), - run_time=1, - ) - self.wait(2) - self.play( - Restore(d1_group), - FadeInFrom(d3_group, LEFT) - ) - self.wait() - self.play( - GrowFromCenter(d3_brace), - Write(nabla_words), - TransformFromCopy(d3_rhs, nabla_exp), - run_time=1, - ) - self.wait() + pass diff --git a/active_projects/ode/part2/wordy_scenes.py b/active_projects/ode/part2/wordy_scenes.py new file mode 100644 index 00000000..7312b67e --- /dev/null +++ b/active_projects/ode/part2/wordy_scenes.py @@ -0,0 +1,155 @@ +from big_ol_pile_of_manim_imports import * + + +class CompareODEToPDE(Scene): + def construct(self): + pass + + +class TodaysTargetWrapper(Scene): + def construct(self): + pass + + +class WriteHeatEquation(Scene): + CONFIG = { + "tex_mobject_config": { + "tex_to_color_map": { + "{T}": YELLOW, + "{t}": WHITE, + "{x}": GREEN, + "{y}": RED, + "{z}": BLUE, + }, + }, + } + + def construct(self): + d1_group = self.get_d1_group() + d3_group = self.get_d3_group() + d1_words, d1_equation = d1_group + d3_words, d3_equation = d3_group + + groups = VGroup(d1_group, d3_group) + for group in groups: + group.arrange(DOWN, buff=MED_LARGE_BUFF) + groups.arrange(RIGHT, buff=2) + groups.to_edge(UP) + + d3_rhs = d3_equation[6:] + d3_brace = Brace(d3_rhs, DOWN) + nabla_words = TextMobject("Sometimes written as") + nabla_words.match_width(d3_brace) + nabla_words.next_to(d3_brace, DOWN) + nabla_exp = TexMobject( + "\\nabla^2 {T}", + **self.tex_mobject_config, + ) + nabla_exp.next_to(nabla_words, DOWN) + # nabla_group = VGroup(nabla_words, nabla_exp) + + d1_group.save_state() + d1_group.center().to_edge(UP) + + self.play( + Write(d1_words), + FadeInFrom(d1_equation, UP), + run_time=1, + ) + self.wait(2) + self.play( + Restore(d1_group), + FadeInFrom(d3_group, LEFT) + ) + self.wait() + self.play( + GrowFromCenter(d3_brace), + Write(nabla_words), + TransformFromCopy(d3_rhs, nabla_exp), + run_time=1, + ) + 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 + ) + + 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 + + +class CompareInputsOfGeneralCaseTo1D(WriteHeatEquation): + def construct(self): + three_d_expr, one_d_expr = [ + TexMobject( + "{T}(" + inputs + ", {t})", + **self.tex_mobject_config, + ) + for inputs in ["{x}, {y}, {z}", "{x}"] + ] + for expr in three_d_expr, one_d_expr: + expr.scale(2) + expr.to_edge(UP) + + x, y, z = [ + three_d_expr.get_part_by_tex(letter) + for letter in ["x", "y", "z"] + ] + + self.play(FadeInFromDown(three_d_expr)) + self.play(LaggedStartMap( + ShowCreationThenFadeAround, + VGroup(x, y, z) + )) + self.wait() + low = 3 + high = -3 + self.play( + ReplacementTransform(three_d_expr[:low], one_d_expr[:low]), + ReplacementTransform(three_d_expr[high:], one_d_expr[high:]), + three_d_expr[low:high].scale, 0, + ) + self.wait()