From 1fa8a0dac7f47d07999d10683a28e47056fecd15 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 4 Apr 2019 08:55:57 -0700 Subject: [PATCH 01/32] More Fourier circle scenes --- active_projects/ode/part2/fourier_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/ode/part2/fourier_series.py index 8b22b4b3..050ea2e5 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/ode/part2/fourier_series.py @@ -290,6 +290,7 @@ class FourierOfPiSymbol(FourierCirclesScene): 1 / np.sqrt(k), 1, )) + print(circle.freq, abs(circle.coefficient)) # approx_path = self.get_circle_end_path(circles) drawn_path = self.get_drawn_path(circles) @@ -493,7 +494,7 @@ class ExplainCircleAnimations(FourierCirclesScene): def show_vector_sum(self): top_circles = self.top_circles - top_vectors = self.top_vectors + top_circles = self.top_circles self.play( FadeOut(self.path), From 26c7faabdd4de3c365d98a93973376415bbf5acd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 4 Apr 2019 14:29:10 -0700 Subject: [PATCH 02/32] Hacky fix to Arrow scaling issue. There must be a better way to do this... --- manimlib/mobject/geometry.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index db594e87..dda665e0 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -1,5 +1,6 @@ import warnings import numpy as np +import operator as op from manimlib.constants import * from manimlib.mobject.mobject import Mobject @@ -139,8 +140,8 @@ class TipableVMobject(VMobject): return VMobject.get_start(self) def get_length(self): - start, end = self.get_start_and_end() - return get_norm(start - end) + start, end = self.get_start_and_end() + return get_norm(start - end) def has_tip(self): return hasattr(self, "tip") and self.tip in self @@ -541,7 +542,6 @@ class Arrow(Line): CONFIG = { "stroke_width": 6, "buff": MED_SMALL_BUFF, - "tip_width_to_length_ratio": 1, "max_tip_length_to_length_ratio": 0.25, "max_stroke_width_to_length_ratio": 4, "preserve_tip_size_when_scaling": True, @@ -568,12 +568,19 @@ class Arrow(Line): VMobject.scale(self, factor, **kwargs) self.set_stroke_width_from_length() + # So horribly confusing, must redo if has_tip: self.add_tip() - self.tip.match_style(old_tips[0]) + old_tips[0].points[:, :] = self.tip.points + self.remove(self.tip) + self.tip = old_tips[0] + self.add(self.tip) if has_start_tip: self.add_tip(at_start=True) - self.start_tip.match_style(old_tips[1]) + old_tips[1].points[:, :] = self.start_tip.points + self.remove(self.start_tip) + self.start_tip = old_tips[1] + self.add(self.start_tip) return self def get_normal_vector(self): From 982d437267253fd8ba40e407bb7f3ba1f5f8e998 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 4 Apr 2019 14:29:52 -0700 Subject: [PATCH 03/32] Use get_family_updaters to test if a mobject should be drawn during every frame of an animation --- manimlib/mobject/mobject.py | 6 ++++++ manimlib/scene/scene.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 6eeeec74..ca87344c 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -173,6 +173,12 @@ class Mobject(Container): def get_updaters(self): return self.updaters + def get_family_updaters(self): + return list(it.chain(*[ + sm.get_updaters() + for sm in self.get_family() + ])) + def add_updater(self, update_function, index=None, call_updater=True): if index is None: self.updaters.append(update_function) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 2a0ca6b4..7a8b69f6 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -288,7 +288,7 @@ class Scene(Container): for i, mob in enumerate(mobjects): update_possibilities = [ mob in animation_mobjects, - len(mob.get_updaters()) > 0, + len(mob.get_family_updaters()) > 0, mob in self.foreground_mobjects ] if any(update_possibilities): From ca70bf8e3f06b076188a101273f7b4fba907088c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 4 Apr 2019 14:30:39 -0700 Subject: [PATCH 04/32] A few more Fourier circle animations --- active_projects/ode/part2/fourier_series.py | 237 +++++++++++++++++--- 1 file changed, 206 insertions(+), 31 deletions(-) diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/ode/part2/fourier_series.py index 050ea2e5..6fa53bfc 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/ode/part2/fourier_series.py @@ -15,12 +15,21 @@ class FourierCirclesScene(Scene): "circle_style": { "stroke_width": 2, }, + "use_vectors": True, "base_frequency": 1, "slow_factor": 0.25, "center_point": ORIGIN, "parametric_function_step_size": 0.001, } + def setup(self): + self.slow_factor_tracker = ValueTracker( + self.slow_factor + ) + + def get_slow_factor(self): + return self.slow_factor_tracker.get_value() + # def get_freqs(self): n = self.n_circles @@ -68,10 +77,16 @@ class FourierCirclesScene(Scene): color=color, **self.circle_style, ) - circle.radial_line = Line( + if self.use_vectors: + LineClass = Arrow + else: + LineClass = Line + circle.radial_line = LineClass( circle.get_center(), circle.get_start(), color=WHITE, + buff=0, + max_tip_length_to_length_ratio=0.1, **self.circle_style, ) circle.add(circle.radial_line) @@ -85,7 +100,7 @@ class FourierCirclesScene(Scene): def update_circle(self, circle, dt): circle.rotate( - self.slow_factor * circle.freq * dt * TAU + self.get_slow_factor() * circle.freq * dt * TAU ) circle.move_to(circle.center_func()) return circle @@ -98,9 +113,11 @@ class FourierCirclesScene(Scene): def get_rotating_vector(self, circle): vector = Vector(RIGHT, color=WHITE) - vector.add_updater(lambda v: v.put_start_and_end_on( - *circle.radial_line.get_start_and_end() + vector.add_updater(lambda v, dt: v.put_start_and_end_on( + circle.get_center(), + circle.get_start(), )) + circle.vector = vector return vector def get_circle_end_path(self, circles, color=YELLOW): @@ -123,20 +140,20 @@ class FourierCirclesScene(Scene): return path # TODO, this should be a general animated mobect - def get_drawn_path(self, circles, **kwargs): + def get_drawn_path(self, circles, stroke_width=2, **kwargs): path = self.get_circle_end_path(circles, **kwargs) broken_path = CurvesAsSubmobjects(path) broken_path.curr_time = 0 def update_path(path, dt): - alpha = path.curr_time * self.slow_factor + alpha = path.curr_time * self.get_slow_factor() n_curves = len(path) for a, sp in zip(np.linspace(0, 1, n_curves), path): b = alpha - a if b < 0: width = 0 else: - width = 2 * (1 - (b % 1)) + width = stroke_width * (1 - (b % 1)) sp.set_stroke(YELLOW, width=width) path.curr_time += dt return path @@ -168,7 +185,7 @@ class FourierCirclesScene(Scene): top_point = wave_copies.get_top() wave.creation = ShowCreation( wave, - run_time=(1 / self.slow_factor), + run_time=(1 / self.get_slow_factor()), rate_func=linear, ) cycle_animation(wave.creation) @@ -178,7 +195,7 @@ class FourierCirclesScene(Scene): def update_wave_copies(wcs): index = int( - wave.creation.total_time * self.slow_factor + wave.creation.total_time * self.get_slow_factor() ) wcs[:index].match_style(wave) wcs[index:].set_stroke(width=0) @@ -290,7 +307,6 @@ class FourierOfPiSymbol(FourierCirclesScene): 1 / np.sqrt(k), 1, )) - print(circle.freq, abs(circle.coefficient)) # approx_path = self.get_circle_end_path(circles) drawn_path = self.get_drawn_path(circles) @@ -323,36 +339,56 @@ class FourierOfTrebleClef(FourierOfPiSymbol): "n_circles": 100, "run_time": 10, "start_drawn": True, + "file_name": "TrebleClef", + "height": 7.5, } + def get_shape(self): + shape = SVGMobject(self.file_name) + return shape + def get_path(self): - path = SVGMobject("TrebleClef") - path = path.family_members_with_points()[0] - path.set_height(7.5) + shape = self.get_shape() + path = shape.family_members_with_points()[0] + path.set_height(self.height) path.set_fill(opacity=0) path.set_stroke(WHITE, 0) return path +class FourierOfEighthNote(FourierOfTrebleClef): + CONFIG = { + "file_name": "EighthNote" + } + + +class FourierOfN(FourierOfTrebleClef): + CONFIG = { + "height": 6, + "n_circles": 200, + } + + def get_shape(self): + return TexMobject("N") + + class ExplainCircleAnimations(FourierCirclesScene): CONFIG = { - # "n_circles": 100, - "n_circles": 20, + "n_circles": 100, "center_point": 2 * DOWN, "n_top_circles": 9, - # "slow_factor": 0.1, "path_height": 3, } def construct(self): self.add_path() self.add_circles() + self.wait(8) self.organize_circles_in_a_row() self.show_frequencies() self.show_examples_for_frequencies() self.show_as_vectors() self.show_vector_sum() - self.moons_of_moons_of_moons() self.tweak_starting_vectors() def add_path(self): @@ -367,11 +403,9 @@ class ExplainCircleAnimations(FourierCirclesScene): self.drawn_path = self.get_drawn_path(self.circles) self.add(self.drawn_path) - self.wait(8) - def organize_circles_in_a_row(self): circles = self.circles - top_circles = circles[:self.n_top_circles].deepcopy() + top_circles = circles[:self.n_top_circles].copy() center_trackers = VGroup() for circle in top_circles: @@ -436,6 +470,9 @@ class ExplainCircleAnimations(FourierCirclesScene): ) self.wait(2) + self.freq_numbers = freq_numbers + self.freq_word = freq_word + def show_examples_for_frequencies(self): top_circles = self.top_circles c1, c2, c3 = [ @@ -479,41 +516,179 @@ class ExplainCircleAnimations(FourierCirclesScene): ]) ) self.wait(3) + self.play(FadeOut(self.freq_word)) def show_as_vectors(self): top_circles = self.top_circles top_vectors = self.get_rotating_vectors(top_circles) + top_vectors.set_color(RED) + lines = VGroup(*filter( + lambda sm: isinstance(sm, Line), + top_circles.family_members_with_points() + )) + self.add(lines, top_circles) self.play( - top_circles.set_stroke, {"width": 0.5}, FadeIn(top_vectors), + lines.fade, 1, ) self.wait(3) self.top_vectors = top_vectors def show_vector_sum(self): - top_circles = self.top_circles - top_circles = self.top_circles + # trackers = self.center_trackers.deepcopy() + trackers = self.center_trackers.copy() + trackers.sort( + submob_func=lambda t: abs(t.circle.freq) + ) + plane = self.plane = NumberPlane( + x_min=-3, + x_max=3, + y_min=-2, + y_max=2, + axis_config={ + "stroke_color": LIGHT_GREY, + } + ) + plane.set_stroke(width=1) + plane.fade(0.5) + plane.move_to(self.center_point) self.play( - FadeOut(self.path), + # FadeOut(self.path), + FadeOut(self.drawn_path), FadeOut(self.circles), + self.slow_factor_tracker.set_value, 0.05, ) + self.add(plane, self.path) + self.play(FadeIn(plane)) - def moons_of_moons_of_moons(self): - pass + new_circles = VGroup() + new_vectors = VGroup() + last_tracker = None + for tracker in trackers: + if last_tracker: + tracker.new_location_func = last_tracker.circle.get_start + else: + tracker.new_location_func = lambda: self.center_point + + original_circle = tracker.circle + original_vector = tracker.circle.vector + tracker.circle = original_circle.copy() + tracker.circle.center_func = tracker.get_location + tracker.vector = original_vector.copy() + tracker.vector.clear_updaters() + tracker.vector.circle = tracker.circle + tracker.vector.add_updater(lambda v: v.put_start_and_end_on( + v.circle.get_center(), + v.circle.get_start(), + )) + new_circles.add(tracker.circle) + new_vectors.add(tracker.vector) + + self.add(tracker, tracker.circle, tracker.vector) + start_point = tracker.get_location() + self.play( + UpdateFromAlphaFunc( + tracker, lambda t, a: t.move_to( + interpolate( + start_point, + tracker.new_location_func(), + a, + ) + ), + run_time=2 + ) + ) + tracker.add_updater(lambda t: t.move_to( + t.new_location_func() + )) + self.wait(2) + last_tracker = tracker + + self.wait(3) + + self.clear() + self.slow_factor_tracker.set_value(0.1) + self.add( + self.top_circles, + self.top_vectors, + self.freq_numbers, + self.path, + ) + self.add_circles() + for tc in self.top_circles: + for c in self.circles: + if c.freq == tc.freq: + tc.rotate( + angle_of_vector(c.get_start() - c.get_center()) - + angle_of_vector(tc.get_start() - tc.get_center()) + ) + self.wait(10) def tweak_starting_vectors(self): - pass + top_circles = self.top_circles + top_vectors = self.top_vectors + circles = self.circles + path = self.path + drawn_path = self.drawn_path + + new_path = self.get_new_path() + new_coefs = self.get_coefficients_of_path(new_path) + new_circles = self.get_circles(coefficients=new_coefs) + + new_top_circles = VGroup() + new_top_vectors = VGroup() + for top_circle in top_circles: + for circle in new_circles: + if circle.freq == top_circle.freq: + new_top_circle = circle.copy() + new_top_circle.center_func = top_circle.get_center + new_top_vector = self.get_rotating_vector( + new_top_circle + ) + new_top_circles.add(new_top_circle) + new_top_vectors.add(new_top_vector) + + self.play( + self.slow_factor_tracker.set_value, 0, + FadeOut(drawn_path) + ) + self.wait() + self.play( + ReplacementTransform(top_circles, new_top_circles), + ReplacementTransform(top_vectors, new_top_vectors), + ReplacementTransform(circles, new_circles), + # ReplacementTransform(path, new_path), + FadeOut(path), + run_time=3, + ) + new_drawn_path = self.get_drawn_path( + new_circles, stroke_width=4, + ) + self.add(new_drawn_path) + self.slow_factor_tracker.set_value(0.1) + self.wait(20) # - def get_path(self): - tex = TexMobject("f") - path = tex.family_members_with_points()[0] + def configure_path(self, path): path.set_stroke(WHITE, 1) - path.set_fill(opacity=0) + path.set_fill(BLACK, opacity=1) path.set_height(self.path_height) path.move_to(self.center_point) return path + + def get_path(self): + tex = TexMobject("f") + path = tex.family_members_with_points()[0] + self.configure_path(path) + return path # return Square().set_height(3) + + def get_new_path(self): + shape = SVGMobject("TrebleClef") + path = shape.family_members_with_points()[0] + self.configure_path(path) + path.scale(1.5, about_edge=DOWN) + return path From b536c21b6bca8dfffc904f3f3c119f979be8a7c2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 4 Apr 2019 14:30:52 -0700 Subject: [PATCH 05/32] FourierSeriesIntro --- active_projects/ode/all_part2_scenes.py | 5 +- active_projects/ode/part1/staging.py | 9 ++- active_projects/ode/part2/staging.py | 73 +++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index c39a02e1..3ce14270 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -8,12 +8,15 @@ ALL_SCENE_CLASSES = [ FourierOfPiSymbol, FourierOfPiSymbol5, FourierOfTrebleClef, + FourierOfEighthNote, + FourierOfN, # CirclesDrawingWave, # Scenes for video ExplainCircleAnimations, - FourierSeriesIntro, FourierSeriesIntroBackground4, FourierSeriesIntroBackground8, FourierSeriesIntroBackground12, FourierSeriesIntroBackground20, + FourierSeriesIntro, + PartTwoOfTour, ] diff --git a/active_projects/ode/part1/staging.py b/active_projects/ode/part1/staging.py index 1deb25b0..d9f553f6 100644 --- a/active_projects/ode/part1/staging.py +++ b/active_projects/ode/part1/staging.py @@ -102,6 +102,7 @@ class TourOfDifferentialEquations(MovingCameraScene): "fill_color": BLACK, }, "camera_config": {"background_color": DARKER_GREY}, + "zoomed_thumbnail_index": 0, } def construct(self): @@ -140,6 +141,7 @@ class TourOfDifferentialEquations(MovingCameraScene): dots = TexMobject("\\dots") dots.next_to(thumbnails[-1], RIGHT) + self.add_phase_space_preview(thumbnails[0]) self.add_heat_preview(thumbnails[1]) self.add_fourier_series(thumbnails[2]) self.add_matrix_exponent(thumbnails[3]) @@ -166,7 +168,7 @@ class TourOfDifferentialEquations(MovingCameraScene): self.wait() self.play( self.camera_frame.replace, - thumbnails[0], + thumbnails[self.zoomed_thumbnail_index], run_time=3, ) self.wait() @@ -203,6 +205,11 @@ class TourOfDifferentialEquations(MovingCameraScene): self.wait() # + def add_phase_space_preview(self, thumbnail): + image = ImageMobject("LovePhaseSpace") + image.replace(thumbnail) + thumbnail.add(image) + def add_heat_preview(self, thumbnail): image = ImageMobject("HeatSurfaceExample") image.replace(thumbnail) diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index a4ceac3b..bbaac905 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -1,15 +1,47 @@ 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", ":", - " An origin story", - arg_separator="", + "Fourier ", "Series", ) - title.scale(2) + 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) @@ -17,6 +49,35 @@ class FourierSeriesIntro(Scene): name = TextMobject("Joseph", "Fourier") name.next_to(image, DOWN) + # self.play(FadeInFromDown(title)) self.add(title) - self.add(image) - self.add(name) + self.play( + FadeInFromDown(image), + TransformFromCopy( + title.get_part_by_tex("Fourier"), + name.get_part_by_tex("Fourier"), + path_arc=-45 * 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, + } From 5dc6018df5c3336be11052967d6a017979473e1e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 11:52:23 -0700 Subject: [PATCH 06/32] Added default args for Line and Vector --- manimlib/mobject/geometry.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index dda665e0..a75a1012 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -386,7 +386,7 @@ class Line(TipableVMobject): "path_arc": None, # angle of arc specified here } - def __init__(self, start, end, **kwargs): + def __init__(self, start=LEFT, end=RIGHT, **kwargs): digest_config(self, kwargs) self.set_start_and_end_attrs(start, end) VMobject.__init__(self, **kwargs) @@ -444,6 +444,16 @@ class Line(TipableVMobject): return mob.get_boundary_point(direction) return np.array(mob_or_point) + def put_start_and_end_on(self, start, end): + curr_start, curr_end = self.get_start_and_end() + if np.all(curr_start == curr_end): + # TODO, any problems with resetting + # these attrs? + self.start = start + self.end = end + self.generate_points() + super().put_start_and_end_on(start, end) + def get_vector(self): return self.get_end() - self.get_start() @@ -620,7 +630,7 @@ class Vector(Arrow): "buff": 0, } - def __init__(self, direction, **kwargs): + def __init__(self, direction=RIGHT, **kwargs): if len(direction) == 2: direction = np.append(np.array(direction), 0) Arrow.__init__(self, ORIGIN, direction, **kwargs) From 5e7cbbdf24a879809b02c2d136b4b8eb0b20568c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 11:52:44 -0700 Subject: [PATCH 07/32] Tiny formatting changes to drawings.py --- manimlib/mobject/svg/drawings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 42c0764f..85672d8b 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -1,4 +1,5 @@ import itertools as it +import string from manimlib.animation.animation import Animation from manimlib.animation.rotation import Rotating @@ -832,10 +833,7 @@ class Logo(VMobject): return blue_part, brown_part - # Cards - - class DeckOfCards(VGroup): def __init__(self, **kwargs): possible_values = list(map(str, list(range(1, 11)))) + ["J", "Q", "K"] From 408da1871df5969d829538e22989b151a30ab6d0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 11:52:56 -0700 Subject: [PATCH 08/32] More Fourier + Heat animations --- active_projects/ode/all_part2_scenes.py | 8 + active_projects/ode/part2/fourier_series.py | 175 ++++++++++++++---- active_projects/ode/part2/heat_equation.py | 152 ++++++++++++++- .../ode/part2/shared_constructs.py | 35 ++++ active_projects/ode/part2/staging.py | 7 +- 5 files changed, 339 insertions(+), 38 deletions(-) create mode 100644 active_projects/ode/part2/shared_constructs.py diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 3ce14270..50671114 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -10,6 +10,11 @@ ALL_SCENE_CLASSES = [ FourierOfTrebleClef, FourierOfEighthNote, FourierOfN, + FourierNailAndGear, + FourierNDQ, + FourierBatman, + FourierGoogleG, + FourierHeart, # CirclesDrawingWave, # Scenes for video ExplainCircleAnimations, @@ -19,4 +24,7 @@ ALL_SCENE_CLASSES = [ FourierSeriesIntroBackground20, FourierSeriesIntro, PartTwoOfTour, + TwoDBodyWithManyTemperatures, + TwoDBodyWithManyTemperaturesGraph, + TwoDBodyWithManyTemperaturesContour, ] diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/ode/part2/fourier_series.py index 6fa53bfc..c3e0a3be 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/ode/part2/fourier_series.py @@ -15,6 +15,13 @@ class FourierCirclesScene(Scene): "circle_style": { "stroke_width": 2, }, + "arrow_config": { + "buff": 0, + "max_tip_length_to_length_ratio": 0.35, + "tip_length": 0.15, + "max_stroke_width_to_length_ratio": 10, + "stroke_width": 2, + }, "use_vectors": True, "base_frequency": 1, "slow_factor": 0.25, @@ -77,18 +84,21 @@ class FourierCirclesScene(Scene): color=color, **self.circle_style, ) - if self.use_vectors: - LineClass = Arrow - else: - LineClass = Line - circle.radial_line = LineClass( + line_points = ( circle.get_center(), circle.get_start(), - color=WHITE, - buff=0, - max_tip_length_to_length_ratio=0.1, - **self.circle_style, ) + if self.use_vectors: + circle.radial_line = Arrow( + *line_points, + **self.arrow_config, + ) + else: + circle.radial_line = Line( + *line_points, + color=WHITE, + **self.circle_style, + ) circle.add(circle.radial_line) circle.freq = freq circle.phase = phase @@ -154,10 +164,11 @@ class FourierCirclesScene(Scene): width = 0 else: width = stroke_width * (1 - (b % 1)) - sp.set_stroke(YELLOW, width=width) + sp.set_stroke(width=width) path.curr_time += dt return path + broken_path.set_color(YELLOW) broken_path.add_updater(update_path) return broken_path @@ -365,13 +376,121 @@ class FourierOfEighthNote(FourierOfTrebleClef): class FourierOfN(FourierOfTrebleClef): CONFIG = { "height": 6, - "n_circles": 200, + "n_circles": 1000, } def get_shape(self): return TexMobject("N") +class FourierNailAndGear(FourierOfTrebleClef): + CONFIG = { + "height": 6, + "n_circles": 200, + "run_time": 10, + "arrow_config": { + "tip_length": 0.1, + "stroke_width": 2, + } + } + + def get_shape(self): + shape = SVGMobject("Nail_And_Gear")[1] + return shape + + +class FourierBatman(FourierOfTrebleClef): + CONFIG = { + "height": 4, + "n_circles": 100, + "run_time": 10, + "arrow_config": { + "tip_length": 0.1, + "stroke_width": 2, + } + } + + def get_shape(self): + shape = SVGMobject("BatmanLogo")[1] + return shape + + +class FourierHeart(FourierOfTrebleClef): + CONFIG = { + "height": 4, + "n_circles": 100, + "run_time": 10, + "arrow_config": { + "tip_length": 0.1, + "stroke_width": 2, + } + } + + def get_shape(self): + shape = SuitSymbol("hearts") + return shape + + def get_drawn_path(self, *args, **kwargs): + kwargs["stroke_width"] = 5 + path = super().get_drawn_path(*args, **kwargs) + path.set_color(PINK) + return path + + +class FourierNDQ(FourierOfTrebleClef): + CONFIG = { + "height": 4, + "n_circles": 1000, + "run_time": 10, + "arrow_config": { + "tip_length": 0.1, + "stroke_width": 2, + } + } + + def get_shape(self): + path = VMobject() + shape = TexMobject("Hayley") + for sp in shape.family_members_with_points(): + path.append_points(sp.points) + return path + + +class FourierGoogleG(FourierOfTrebleClef): + CONFIG = { + "n_circles": 10, + "height": 5, + "g_colors": [ + "#4285F4", + "#DB4437", + "#F4B400", + "#0F9D58", + ] + } + + def get_shape(self): + g = SVGMobject("google_logo")[5] + g.center() + self.add(g) + return g + + def get_drawn_path(self, *args, **kwargs): + kwargs["stroke_width"] = 7 + path = super().get_drawn_path(*args, **kwargs) + + blue, red, yellow, green = self.g_colors + + path[:250].set_color(blue) + path[250:333].set_color(green) + path[333:370].set_color(yellow) + path[370:755].set_color(red) + path[755:780].set_color(yellow) + path[780:860].set_color(green) + path[860:].set_color(blue) + + return path + + class ExplainCircleAnimations(FourierCirclesScene): CONFIG = { "n_circles": 100, @@ -521,26 +640,25 @@ class ExplainCircleAnimations(FourierCirclesScene): def show_as_vectors(self): top_circles = self.top_circles top_vectors = self.get_rotating_vectors(top_circles) - top_vectors.set_color(RED) - lines = VGroup(*filter( - lambda sm: isinstance(sm, Line), - top_circles.family_members_with_points() - )) + top_vectors.set_color(WHITE) - self.add(lines, top_circles) + original_circles = top_circles.copy() self.play( FadeIn(top_vectors), - lines.fade, 1, + top_circles.set_opacity, 0, ) self.wait(3) + self.play( + top_circles.match_style, original_circles + ) + self.remove(top_vectors) self.top_vectors = top_vectors def show_vector_sum(self): - # trackers = self.center_trackers.deepcopy() trackers = self.center_trackers.copy() trackers.sort( - submob_func=lambda t: abs(t.circle.freq) + submob_func=lambda t: abs(t.circle.freq - 0.1) ) plane = self.plane = NumberPlane( x_min=-3, @@ -556,7 +674,6 @@ class ExplainCircleAnimations(FourierCirclesScene): plane.move_to(self.center_point) self.play( - # FadeOut(self.path), FadeOut(self.drawn_path), FadeOut(self.circles), self.slow_factor_tracker.set_value, 0.05, @@ -565,7 +682,6 @@ class ExplainCircleAnimations(FourierCirclesScene): self.play(FadeIn(plane)) new_circles = VGroup() - new_vectors = VGroup() last_tracker = None for tracker in trackers: if last_tracker: @@ -574,20 +690,11 @@ class ExplainCircleAnimations(FourierCirclesScene): tracker.new_location_func = lambda: self.center_point original_circle = tracker.circle - original_vector = tracker.circle.vector tracker.circle = original_circle.copy() tracker.circle.center_func = tracker.get_location - tracker.vector = original_vector.copy() - tracker.vector.clear_updaters() - tracker.vector.circle = tracker.circle - tracker.vector.add_updater(lambda v: v.put_start_and_end_on( - v.circle.get_center(), - v.circle.get_start(), - )) new_circles.add(tracker.circle) - new_vectors.add(tracker.vector) - self.add(tracker, tracker.circle, tracker.vector) + self.add(tracker, tracker.circle) start_point = tracker.get_location() self.play( UpdateFromAlphaFunc( @@ -613,7 +720,6 @@ class ExplainCircleAnimations(FourierCirclesScene): self.slow_factor_tracker.set_value(0.1) self.add( self.top_circles, - self.top_vectors, self.freq_numbers, self.path, ) @@ -629,7 +735,6 @@ class ExplainCircleAnimations(FourierCirclesScene): def tweak_starting_vectors(self): top_circles = self.top_circles - top_vectors = self.top_vectors circles = self.circles path = self.path drawn_path = self.drawn_path @@ -658,9 +763,7 @@ class ExplainCircleAnimations(FourierCirclesScene): self.wait() self.play( ReplacementTransform(top_circles, new_top_circles), - ReplacementTransform(top_vectors, new_top_vectors), ReplacementTransform(circles, new_circles), - # ReplacementTransform(path, new_path), FadeOut(path), run_time=3, ) diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index 2b8b4e67..c1fd1c49 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -1,6 +1,156 @@ from big_ol_pile_of_manim_imports import * +from active_projects.ode.part2.shared_constructs import * -class NewSceneName(Scene): +class TwoDBodyWithManyTemperatures(ThreeDScene): + CONFIG = { + "cells_per_side": 20, + "body_height": 6, + } + + def construct(self): + self.introduce_body() + self.show_temperature_at_all_points() + + def introduce_body(self): + height = self.body_height + buff = 0.025 + rows = VGroup(*[ + VGroup(*[ + Dot( + # stroke_width=0.5, + stroke_width=0, + fill_opacity=1, + ) + for x in range(self.cells_per_side) + ]).arrange(RIGHT, buff=buff) + for y in range(self.cells_per_side) + ]).arrange(DOWN, buff=buff) + for row in rows[1::2]: + row.submobjects.reverse() + + body = self.body = VGroup(*it.chain(*rows)) + body.set_height(height) + body.center() + body.to_edge(LEFT) + + axes = self.axes = Axes( + x_min=-5, x_max=5, + y_min=-5, y_max=5, + ) + axes.match_height(body) + axes.move_to(body) + + for cell in body: + self.color_cell(cell) + # body.set_stroke(WHITE, 0.5) # Do this? + + plate = Square( + stroke_width=0, + fill_color=DARK_GREY, + sheen_direction=UL, + sheen_factor=1, + fill_opacity=1, + ) + plate.replace(body) + + plate_words = TextMobject("Piece of \\\\ metal") + plate_words.scale(2) + plate_words.set_stroke(BLACK, 2, background=True) + plate_words.set_color(BLACK) + plate_words.move_to(plate) + + self.play( + DrawBorderThenFill(plate), + Write( + plate_words, + run_time=2, + rate_func=squish_rate_func(smooth, 0.5, 1) + ) + ) + self.wait() + + self.remove(plate_words) + + def show_temperature_at_all_points(self): + body = self.body + start_corner = body[0].get_center() + + dot = Dot(radius=0.01, color=WHITE) + dot.move_to(start_corner) + + get_point = dot.get_center + + lhs = TexMobject("T = ") + lhs.next_to(body, RIGHT, LARGE_BUFF) + + decimal = DecimalNumber( + num_decimal_places=1, + unit="^\\circ" + ) + decimal.next_to(lhs, RIGHT, MED_SMALL_BUFF, DOWN) + decimal.add_updater( + lambda d: d.set_value( + 40 + 50 * self.point_to_temp(get_point()) + ) + ) + + arrow = Arrow(color=YELLOW) + arrow.set_stroke(BLACK, 8, background=True) + arrow.tip.set_stroke(BLACK, 2, background=True) + # arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) + arrow.add_updater(lambda a: a.put_start_and_end_on( + lhs.get_left() + MED_SMALL_BUFF * LEFT, + get_point(), + )) + + dot.add_updater(lambda p: p.move_to( + body[-1] if (1 < len(body)) else start_corner + )) + self.add(body, dot, lhs, decimal, arrow) + self.play( + ShowIncreasingSubsets( + body, + run_time=10, + rate_func=linear, + ) + ) + self.wait() + self.remove(dot) + self.play( + FadeOut(arrow), + FadeOut(lhs), + FadeOut(decimal), + ) + + # + def point_to_temp(self, point, time=0): + x, y = self.axes.point_to_coords(point) + return two_d_temp_func( + 0.3 * x, 0.3 * y, t=time + ) + + def color_cell(self, cell, vect=RIGHT): + p0 = cell.get_corner(-vect) + p1 = cell.get_corner(vect) + colors = [] + for point in p0, p1: + temp = self.point_to_temp(point) + color = temperature_to_color(temp) + colors.append(color) + cell.set_color(color=colors) + cell.set_sheen_direction(vect) + return cell + + +class TwoDBodyWithManyTemperaturesGraph(ExternallyAnimatedScene): + pass + + +class TwoDBodyWithManyTemperaturesContour(ExternallyAnimatedScene): + pass + + +class BringTwoRodsTogether(Scene): def construct(self): pass diff --git a/active_projects/ode/part2/shared_constructs.py b/active_projects/ode/part2/shared_constructs.py new file mode 100644 index 00000000..baa02956 --- /dev/null +++ b/active_projects/ode/part2/shared_constructs.py @@ -0,0 +1,35 @@ +from big_ol_pile_of_manim_imports import * + +TIME_COLOR = YELLOW +X_COLOR = GREEN + + +def get_heat_equation(): + pass + + +def temperature_to_color(temp, min_temp=-1, max_temp=1): + colors = [BLUE, TEAL, GREEN, YELLOW, "#ff0000"] + + alpha = inverse_interpolate(min_temp, max_temp, temp) + index, sub_alpha = integer_interpolate( + 0, len(colors) - 1, alpha + ) + return interpolate_color( + colors[index], colors[index + 1], sub_alpha + ) + + +def two_d_temp_func(x, y, t): + return np.sum([ + c * np.sin(f * var) * np.exp(-(f**2) * t) + for c, f, var in [ + (0.2, 1, x), + (0.3, 3, x), + (0.02, 5, x), + (0.01, 7, x), + (0.5, 2, y), + (0.1, 10, y), + (0.01, 20, y), + ] + ]) diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index bbaac905..52675785 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -56,7 +56,7 @@ class FourierSeriesIntro(Scene): TransformFromCopy( title.get_part_by_tex("Fourier"), name.get_part_by_tex("Fourier"), - path_arc=-45 * DEGREES, + path_arc=90 * DEGREES, ), FadeIn(name.get_part_by_tex("Joseph")), ) @@ -81,3 +81,8 @@ class PartTwoOfTour(TourOfDifferentialEquations): CONFIG = { "zoomed_thumbnail_index": 1, } + + +class CompareODEToPDE(Scene): + def construct(self): + pass From 0ff7331dbe87a52e4ab7cabb8f596bd3cf2788a8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 14:00:52 -0700 Subject: [PATCH 09/32] Move axis_label method higher to general CoordinateSystems --- manimlib/mobject/coordinate_systems.py | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index ebce02f6..75424364 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -49,6 +49,34 @@ class CoordinateSystem(): def get_z_axis(self): return self.get_axis(2) + def get_x_axis_label(self, label_tex, edge=RIGHT, direction=DL, **kwargs): + return self.get_axis_label( + label_tex, self.get_x_axis(), + edge, direction, **kwargs + ) + + def get_y_axis_label(self, label_tex, edge=UP, direction=DR, **kwargs): + return self.get_axis_label( + label_tex, self.get_y_axis(), + edge, direction, **kwargs + ) + + def get_axis_label(self, label_tex, axis, edge, direction, buff=MED_SMALL_BUFF): + label = TexMobject(label_tex) + label.next_to( + axis.get_edge_center(edge), direction, + buff=buff + ) + label.shift_onto_screen(buff=MED_SMALL_BUFF) + return label + + def get_axis_labels(self, x_label_tex="x", y_label_tex="y"): + self.axis_labels = VGroup( + self.get_x_axis_label(x_label_tex), + self.get_y_axis_label(y_label_tex), + ) + return self.axis_labels + def get_graph(self, function, **kwargs): x_min = kwargs.pop("x_min", self.x_min) x_max = kwargs.pop("x_max", self.x_max) @@ -318,34 +346,6 @@ class NumberPlane(Axes): def get_axes(self): return self.axes - def get_x_axis_label(self, label_tex, edge=RIGHT, direction=DL, **kwargs): - return self.get_axis_label( - label_tex, self.get_x_axis(), - edge, direction, **kwargs - ) - - def get_y_axis_label(self, label_tex, edge=UP, direction=DR, **kwargs): - return self.get_axis_label( - label_tex, self.get_y_axis(), - edge, direction, **kwargs - ) - - def get_axis_label(self, label_tex, axis, edge, direction, buff=MED_SMALL_BUFF): - label = TexMobject(label_tex) - label.next_to( - axis.get_edge_center(edge), direction, - buff=buff - ) - label.shift_onto_screen(buff=MED_SMALL_BUFF) - return label - - def get_axis_labels(self, x_label_tex="x", y_label_tex="y"): - self.axis_labels = VGroup( - self.get_x_axis_label(x_label_tex), - self.get_y_axis_label(y_label_tex), - ) - return self.axis_labels - def get_vector(self, coords, **kwargs): kwargs["buff"] = 0 return Arrow( From 81d91da752d2a335a2570c8a7a0129c0e19dff85 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 14:01:04 -0700 Subject: [PATCH 10/32] Moved increment_value method to DecimalNumber --- manimlib/mobject/numbers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manimlib/mobject/numbers.py b/manimlib/mobject/numbers.py index d3f76a8e..4c14b402 100644 --- a/manimlib/mobject/numbers.py +++ b/manimlib/mobject/numbers.py @@ -132,14 +132,14 @@ class DecimalNumber(VMobject): def get_value(self): return self.number + def increment_value(self, delta_t=1): + self.set_value(self.get_value() + delta_t) + class Integer(DecimalNumber): CONFIG = { "num_decimal_places": 0, } - def increment_value(self): - self.set_value(self.get_value() + 1) - def get_value(self): return int(np.round(super().get_value())) From d5eecb813752dcd9b9df438fb1d400026d86fdfa Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 14:01:17 -0700 Subject: [PATCH 11/32] Tiny changes to Clock --- manimlib/mobject/svg/drawings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 85672d8b..2e7cbaa9 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -350,7 +350,7 @@ class Clock(VGroup): CONFIG = {} def __init__(self, **kwargs): - circle = Circle() + circle = Circle(color=WHITE) ticks = [] for x in range(12): alpha = x / 12. From e05dbfe2d2232192b09813ae5241f82354529246 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 6 Apr 2019 14:01:37 -0700 Subject: [PATCH 12/32] half of 1d heat equation scene --- active_projects/ode/all_part2_scenes.py | 1 + active_projects/ode/part2/heat_equation.py | 125 +++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 50671114..9db9861d 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -27,4 +27,5 @@ ALL_SCENE_CLASSES = [ TwoDBodyWithManyTemperatures, TwoDBodyWithManyTemperaturesGraph, TwoDBodyWithManyTemperaturesContour, + BringTwoRodsTogether, ] diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index c1fd1c49..c1c07768 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -152,5 +152,130 @@ class TwoDBodyWithManyTemperaturesContour(ExternallyAnimatedScene): class BringTwoRodsTogether(Scene): + CONFIG = { + "step_size": 0.1, + "axes_config": { + "x_min": -1, + "x_max": 11, + "y_min": -10, + "y_max": 100, + "y_axis_config": { + "unit_size": 0.06, + "tick_frequency": 10, + # "numbers_with_elongated_ticks": range(20, 100, 20) + }, + }, + "wait_time": 5, + } + def construct(self): + self.setup_axes() + self.setup_graph() + self.setup_clock() + + self.add(self.axes) + self.add(self.graph) + + self.show_rods() + self.show_equilibration() + + def setup_axes(self): + axes = Axes(**self.axes_config) + axes.center().to_edge(UP) + + y_label = axes.get_y_axis_label("\\text{Temperature}") + y_label.to_edge(UP) + axes.y_axis.add(y_label) + axes.y_axis.add_numbers( + *range(20, 100, 20) + ) + + self.axes = axes + + def setup_graph(self): + graph = self.axes.get_graph( + lambda x: 90 if x <= 5 else 10, + x_min=0, + x_max=10, + step_size=self.step_size, + discontinuities=[5], + ) + graph.color_using_background_image("VerticalTempGradient") + + self.graph = graph + + def setup_clock(self): + clock = Clock() + clock.set_height(1) + clock.to_corner(UR) + clock.shift(MED_LARGE_BUFF * LEFT) + + time_lhs = TextMobject("Time: ") + time_label = DecimalNumber( + 0, num_decimal_places=2, + ) + time_rhs = TextMobject("s") + time_group = VGroup( + time_lhs, + time_label, + # time_rhs + ) + time_group.arrange(RIGHT, aligned_edge=DOWN) + time_rhs.shift(SMALL_BUFF * LEFT) + time_group.next_to(clock, DOWN) + + self.time_group = time_group + self.time_label = time_label + self.clock = clock + + def show_rods(self): pass + + def show_equilibration(self): + self.add(self.time_group) + self.add(self.clock) + + self.graph.add_updater(self.update_graph) + self.time_label.add_updater( + lambda d, dt: d.increment_value(dt) + ) + + self.play( + ClockPassesTime( + self.clock, + run_time=self.wait_time, + hours_passed=self.wait_time, + ) + ) + + # + def update_graph(self, graph, dt, alpha=1.0, n_mini_steps=100): + points = np.append( + graph.get_start_anchors(), + [graph.get_last_point()], + axis=0, + ) + for k in range(n_mini_steps): + change = np.zeros(points.shape) + dx = points[1][0] - points[0][0] + for i in range(len(points)): + p = points[i] + lp = points[max(i - 1, 0)] + rp = points[min(i + 1, len(points) - 1)] + d2y = (rp[1] - 2 * p[1] + lp[1]) + + if (0 < i < len(points) - 1): + second_deriv = d2y / (dx**2) + else: + second_deriv = 0.5 * d2y / dx + second_deriv = 0 + + change[i][1] = alpha * second_deriv * dt / n_mini_steps + + change[0][1] = change[1][1] + change[-1][1] = change[-2][1] + # change[0][1] = 0 + # change[-1][1] = 0 + points += change + graph.set_points_smoothly(points) + return graph From 644ac5c94a1ae285c1d202476ed737bf25991942 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 9 Apr 2019 18:51:18 -0700 Subject: [PATCH 13/32] Added abbreviated names for number_to_point and point_to_number --- manimlib/mobject/coordinate_systems.py | 8 ++++++++ manimlib/mobject/number_line.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 75424364..bddf3548 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -34,6 +34,14 @@ class CoordinateSystem(): def point_to_coords(self, point): raise Exception("Not implemented") + def c2p(self, *coords): + """Abbreviation for coords_to_point""" + return self.coords_to_point(*coords) + + def p2c(self, point): + """Abbreviation for point_to_coords""" + return self.point_to_coords(point) + def get_axes(self): raise Exception("Not implemented") diff --git a/manimlib/mobject/number_line.py b/manimlib/mobject/number_line.py index 365ceccc..5e662d3a 100644 --- a/manimlib/mobject/number_line.py +++ b/manimlib/mobject/number_line.py @@ -122,6 +122,14 @@ class NumberLine(Line): ) return interpolate(self.x_min, self.x_max, proportion) + def n2p(self, number): + """Abbreviation for number_to_point""" + return self.number_to_point(number) + + def p2n(self, point): + """Abbreviation for point_to_number""" + return self.point_to_number(point) + def get_unit_size(self): return (self.x_max - self.x_min) / self.get_length() From c1e2ee78431dd0914479afc8aae393201c7c39df Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 9 Apr 2019 18:51:44 -0700 Subject: [PATCH 14/32] Changed arrow default max_stroke_width_to_length_ratio --- manimlib/mobject/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index a75a1012..965497e1 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -553,7 +553,7 @@ class Arrow(Line): "stroke_width": 6, "buff": MED_SMALL_BUFF, "max_tip_length_to_length_ratio": 0.25, - "max_stroke_width_to_length_ratio": 4, + "max_stroke_width_to_length_ratio": 5, "preserve_tip_size_when_scaling": True, "rectangular_stem_width": 0.05, } From a1878308e07012f3ca07826ea0c109afa1980f5a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 9 Apr 2019 18:52:59 -0700 Subject: [PATCH 15/32] More animations for the heat equation --- active_projects/ode/all_part2_scenes.py | 5 + active_projects/ode/part2/fourier_series.py | 4 +- active_projects/ode/part2/heat_equation.py | 300 ++++++++++++++++++-- active_projects/ode/part2/pi_scenes.py | 23 ++ active_projects/ode/part2/staging.py | 67 +++++ 5 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 active_projects/ode/part2/pi_scenes.py diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 9db9861d..70441f4e 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -1,6 +1,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 * OUTPUT_DIRECTORY = "ode/part2" ALL_SCENE_CLASSES = [ @@ -28,4 +29,8 @@ ALL_SCENE_CLASSES = [ TwoDBodyWithManyTemperaturesGraph, TwoDBodyWithManyTemperaturesContour, BringTwoRodsTogether, + ShowEvolvingTempGraphWithArrows, + WriteHeatEquation, + ReactionsToInitialHeatEquation, + TalkThrough1DHeatGraph, ] diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/ode/part2/fourier_series.py index c3e0a3be..86677e70 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/ode/part2/fourier_series.py @@ -387,7 +387,9 @@ class FourierNailAndGear(FourierOfTrebleClef): CONFIG = { "height": 6, "n_circles": 200, - "run_time": 10, + "run_time": 100, + "slow_factor": 0.01, + "parametric_function_step_size": 0.0001, "arrow_config": { "tip_length": 0.1, "stroke_width": 2, diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index c1c07768..f56acbf8 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -153,7 +153,7 @@ class TwoDBodyWithManyTemperaturesContour(ExternallyAnimatedScene): class BringTwoRodsTogether(Scene): CONFIG = { - "step_size": 0.1, + "step_size": 0.05, "axes_config": { "x_min": -1, "x_max": 11, @@ -162,10 +162,12 @@ class BringTwoRodsTogether(Scene): "y_axis_config": { "unit_size": 0.06, "tick_frequency": 10, - # "numbers_with_elongated_ticks": range(20, 100, 20) }, }, - "wait_time": 5, + "graph_x_min": 0, + "graph_x_max": 10, + "wait_time": 30, + "alpha": 1.0, } def construct(self): @@ -173,9 +175,6 @@ class BringTwoRodsTogether(Scene): self.setup_graph() self.setup_clock() - self.add(self.axes) - self.add(self.graph) - self.show_rods() self.show_equilibration() @@ -194,9 +193,9 @@ class BringTwoRodsTogether(Scene): def setup_graph(self): graph = self.axes.get_graph( - lambda x: 90 if x <= 5 else 10, - x_min=0, - x_max=10, + self.initial_function, + x_min=self.graph_x_min, + x_max=self.graph_x_max, step_size=self.step_size, discontinuities=[5], ) @@ -229,34 +228,115 @@ class BringTwoRodsTogether(Scene): self.clock = clock def show_rods(self): - pass + rod1, rod2 = rods = VGroup( + self.get_rod(0, 5), + self.get_rod(5, 10), + ) + rod1.set_color(rod1[0].get_color()) + rod2.set_color(rod2[-1].get_color()) + + rods.save_state() + rods.space_out_submobjects(1.5) + rods.center() + + labels = VGroup( + TexMobject("90^\\circ"), + TexMobject("10^\\circ"), + ) + for rod, label in zip(rods, labels): + label.next_to(rod, DOWN) + rod.label = label + + self.play( + FadeInFrom(rod1, UP), + Write(rod1.label), + ) + self.play( + FadeInFrom(rod2, DOWN), + Write(rod2.label) + ) + self.wait() + + self.rods = rods + self.rod_labels = labels def show_equilibration(self): - self.add(self.time_group) - self.add(self.clock) + rods = self.rods + axes = self.axes + graph = self.graph + labels = self.rod_labels + self.play( + Write(axes), + rods.restore, + rods.space_out_submobjects, 1.1, + FadeIn(self.time_group), + FadeIn(self.clock), + *[ + MaintainPositionRelativeTo( + rod.label, rod + ) + for rod in rods + ], + ) - self.graph.add_updater(self.update_graph) + br1 = Rectangle(height=0.2, width=1) + br1.set_stroke(width=0) + br1.set_fill(BLACK, opacity=1) + br2 = br1.copy() + br1.add_updater(lambda b: b.move_to(axes.c2p(0, 90))) + br1.add_updater( + lambda b: b.align_to(rods[0].get_right(), LEFT) + ) + br2.add_updater(lambda b: b.move_to(axes.c2p(0, 10))) + br2.add_updater( + lambda b: b.align_to(rods[1].get_left(), RIGHT) + ) + + self.add(graph, br1, br2) + self.play( + ShowCreation(graph), + labels[0].align_to, axes.c2p(0, 87), UP, + labels[1].align_to, axes.c2p(0, 13), DOWN, + ) + self.play() + self.play( + rods.restore, + rate_func=rush_into, + ) + self.remove(br1, br2) + + graph.add_updater(self.update_graph) self.time_label.add_updater( lambda d, dt: d.increment_value(dt) ) + rods.add_updater(self.update_rods) self.play( ClockPassesTime( self.clock, run_time=self.wait_time, hours_passed=self.wait_time, - ) + ), + FadeOut(labels) ) # - def update_graph(self, graph, dt, alpha=1.0, n_mini_steps=100): + def initial_function(self, x): + if x <= 5: + return 90 + else: + return 10 + + def update_graph(self, graph, dt, alpha=None, n_mini_steps=100): + if alpha is None: + alpha = self.alpha points = np.append( graph.get_start_anchors(), [graph.get_last_point()], axis=0, ) for k in range(n_mini_steps): - change = np.zeros(points.shape) + y_change = np.zeros(points.shape[0]) dx = points[1][0] - points[0][0] for i in range(len(points)): p = points[i] @@ -270,12 +350,188 @@ class BringTwoRodsTogether(Scene): second_deriv = 0.5 * d2y / dx second_deriv = 0 - change[i][1] = alpha * second_deriv * dt / n_mini_steps + y_change[i] = alpha * second_deriv * dt / n_mini_steps - change[0][1] = change[1][1] - change[-1][1] = change[-2][1] - # change[0][1] = 0 - # change[-1][1] = 0 - points += change + # y_change[0] = y_change[1] + # y_change[-1] = y_change[-2] + y_change[0] = 0 + y_change[-1] = 0 + y_change -= np.mean(y_change) + points[:, 1] += y_change graph.set_points_smoothly(points) return graph + + def get_second_derivative(self, x, dx=0.001): + graph = self.graph + x_min = self.graph_x_min + x_max = self.graph_x_max + + ly, y, ry = [ + graph.point_from_proportion( + inverse_interpolate(x_min, x_max, alt_x) + )[1] + for alt_x in (x - dx, x, x + dx) + ] + d2y = ry - 2 * y + ly + return d2y / (dx**2) + + def get_rod(self, x_min, x_max, n_pieces=20): + axes = self.axes + line = Line(axes.c2p(x_min, 0), axes.c2p(x_max, 0)) + rod = VGroup(*[ + Square() + for n in range(n_pieces) + ]) + rod.arrange(RIGHT, buff=0) + rod.match_width(line) + rod.set_height(0.2, stretch=True) + rod.move_to(axes.c2p(x_min, 0), LEFT) + rod.set_fill(opacity=1) + rod.set_stroke(width=1) + rod.set_sheen_direction(RIGHT) + self.color_rod_by_graph(rod) + return rod + + def update_rods(self, rods): + for rod in rods: + self.color_rod_by_graph(rod) + + def color_rod_by_graph(self, rod): + for piece in rod: + piece.set_color(color=[ + self.rod_point_to_color(piece.get_left()), + self.rod_point_to_color(piece.get_right()), + ]) + + def rod_point_to_color(self, point): + axes = self.axes + x = axes.x_axis.p2n(point) + + graph = self.graph + alpha = inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + x, + ) + y = axes.y_axis.p2n( + graph.point_from_proportion(alpha) + ) + return temperature_to_color( + (y - 45) / 45 + ) + + +class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): + CONFIG = { + "alpha": 0.1, + "n_arrows": 20, + "wait_time": 30, + } + + def construct(self): + self.add_axes() + self.add_graph() + self.add_clock() + self.add_rod() + self.add_arrows() + self.let_play() + + def add_axes(self): + self.setup_axes() + self.add(self.axes) + + def add_graph(self): + self.setup_graph() + self.add(self.graph) + + def add_clock(self): + self.setup_clock() + self.add(self.clock) + self.add(self.time_label) + + def add_rod(self): + rod = self.rod = self.get_rod(0, 10) + self.add(rod) + + def add_arrows(self): + graph = self.graph + 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] + arrows = VGroup(*[Vector(DOWN) for x in xs]) + + 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) + arrow.put_start_and_end_on( + ORIGIN, mag * UP + ) + point = graph.point_from_proportion( + inverse_interpolate(x_min, x_max, x) + ) + arrow.shift(point - arrow.get_start()) + arrow.set_color( + self.rod_point_to_color(point) + ) + + arrows.add_updater(update_arrows) + + self.add(arrows) + self.arrows = arrows + + def let_play(self): + graph = self.graph + rod = self.rod + clock = self.clock + time_label = self.time_label + + graph.add_updater(self.update_graph) + time_label.add_updater( + lambda d, dt: d.increment_value(dt) + ) + rod.add_updater(self.color_rod_by_graph) + + # return + self.play( + ClockPassesTime( + clock, + run_time=self.wait_time, + hours_passed=self.wait_time, + ), + ) + + # + def initial_function(self, x): + new_x = TAU * x / 10 + return 50 + 20 * np.sum([ + np.sin(new_x), + np.sin(2 * new_x), + 0.5 * np.sin(3 * new_x), + 0.3 * np.sin(4 * new_x), + 0.3 * np.sin(5 * new_x), + 0.2 * np.sin(7 * new_x), + 0.1 * np.sin(21 * new_x), + 0.05 * np.sin(41 * new_x), + ]) + + +class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows): + def construct(self): + self.add_axes() + self.add_graph() + self.add_rod() + + # + def initial_function(self, x): + new_x = TAU * x / 10 + return 50 + 20 * np.sum([ + np.sin(new_x), + np.sin(2 * new_x), + 0.5 * np.sin(3 * new_x), + 0.3 * np.sin(4 * new_x), + 0.3 * np.sin(5 * new_x), + 0.2 * np.sin(7 * new_x), + ]) diff --git a/active_projects/ode/part2/pi_scenes.py b/active_projects/ode/part2/pi_scenes.py new file mode 100644 index 00000000..ba1d98c1 --- /dev/null +++ b/active_projects/ode/part2/pi_scenes.py @@ -0,0 +1,23 @@ +from big_ol_pile_of_manim_imports import * + + +class ReactionsToInitialHeatEquation(PiCreatureScene): + def construct(self): + randy = self.pi_creature + randy.set_color(BLUE_C) + randy.center() + + point = VectorizedPoint().next_to(randy, UL, LARGE_BUFF) + randy.add_updater(lambda r: r.look_at(point)) + + self.play(randy.change, "horrified") + self.wait() + self.play(randy.change, "pondering") + self.wait() + self.play( + randy.change, "confused", + point.next_to, randy, UR, LARGE_BUFF, + ) + self.wait(2) + self.play(point.shift, 2 * DOWN) + self.wait(3) diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index 52675785..8bfe90ca 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -86,3 +86,70 @@ class PartTwoOfTour(TourOfDifferentialEquations): class CompareODEToPDE(Scene): def construct(self): pass + + +class WriteHeatEquation(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() From fd0030085b1c437f35d2e16125a7eb1f72fcbd31 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 10 Apr 2019 14:57:15 -0700 Subject: [PATCH 16/32] Added Mobject.match_updaters --- manimlib/mobject/mobject.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index ca87344c..4b03f490 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -200,6 +200,12 @@ class Mobject(Container): submob.clear_updaters() return self + def match_updaters(self, mobject): + self.clear_updaters() + for updater in mobject.get_updaters(): + self.add_updater(updater) + return self + def suspend_updating(self, recursive=True): self.updating_suspended = True if recursive: From 4972c71190a0e3740b70852f4cf86bb0a5dfcb11 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 10 Apr 2019 14:57:34 -0700 Subject: [PATCH 17/32] Finished TalkThrough1DHeatGraph --- active_projects/ode/part2/heat_equation.py | 463 ++++++++++++++++++++- 1 file changed, 442 insertions(+), 21 deletions(-) diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index f56acbf8..7529c528 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -167,6 +167,7 @@ class BringTwoRodsTogether(Scene): "graph_x_min": 0, "graph_x_max": 10, "wait_time": 30, + "default_n_rod_pieces": 20, "alpha": 1.0, } @@ -375,7 +376,9 @@ class BringTwoRodsTogether(Scene): d2y = ry - 2 * y + ly return d2y / (dx**2) - def get_rod(self, x_min, x_max, n_pieces=20): + def get_rod(self, x_min, x_max, n_pieces=None): + if n_pieces is None: + n_pieces = self.default_n_rod_pieces axes = self.axes line = Line(axes.c2p(x_min, 0), axes.c2p(x_max, 0)) rod = VGroup(*[ @@ -426,6 +429,16 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): "alpha": 0.1, "n_arrows": 20, "wait_time": 30, + "freq_amplitude_pairs": [ + (1, 1), + (2, 1), + (3, 0.5), + (4, 0.3), + (5, 0.3), + (7, 0.2), + (21, 0.1), + (41, 0.05), + ], } def construct(self): @@ -504,34 +517,442 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): ) # - def initial_function(self, x): + def temp_func(self, x, t): new_x = TAU * x / 10 return 50 + 20 * np.sum([ - np.sin(new_x), - np.sin(2 * new_x), - 0.5 * np.sin(3 * new_x), - 0.3 * np.sin(4 * new_x), - 0.3 * np.sin(5 * new_x), - 0.2 * np.sin(7 * new_x), - 0.1 * np.sin(21 * new_x), - 0.05 * np.sin(41 * new_x), + amp * np.sin(freq * new_x) * + np.exp(-(self.alpha * freq**2) * t) + for freq, amp in self.freq_amplitude_pairs ]) + def initial_function(self, x, time=0): + return self.temp_func(x, 0) + + +class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene): + CONFIG = { + "freq_amplitude_pairs": [ + (1, 0.7), + (2, 1), + (3, 0.5), + (4, 0.3), + (5, 0.3), + (7, 0.2), + ], + } -class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows): def construct(self): self.add_axes() self.add_graph() self.add_rod() - # - def initial_function(self, x): - new_x = TAU * x / 10 - return 50 + 20 * np.sum([ - np.sin(new_x), - np.sin(2 * new_x), - 0.5 * np.sin(3 * new_x), - 0.3 * np.sin(4 * new_x), - 0.3 * np.sin(5 * new_x), - 0.2 * np.sin(7 * new_x), + self.emphasize_graph() + self.emphasize_rod() + self.show_x_axis() + self.show_changes_over_time() + self.show_surface() + + def add_graph(self): + self.graph = self.get_graph() + self.add(self.graph) + + def emphasize_graph(self): + graph = self.graph + q_marks = VGroup(*[ + TexMobject("?").move_to( + graph.point_from_proportion(a), + UP, + ).set_stroke(BLACK, 3, background=True) + for a in np.linspace(0, 1, 20) ]) + + self.play(LaggedStart(*[ + Succession( + FadeInFromLarge(q_mark), + FadeOutAndShift(q_mark, DOWN), + ) + for q_mark in q_marks + ])) + self.wait() + + def emphasize_rod(self): + alt_rod = self.get_rod(0, 10, 50) + word = TextMobject("Rod") + word.scale(2) + word.next_to(alt_rod, UP, MED_SMALL_BUFF) + + self.play( + LaggedStart( + *[ + Rotating(piece, rate_func=smooth) + for piece in alt_rod + ], + run_time=2, + lag_ratio=0.01, + ), + Write(word) + ) + self.remove(*alt_rod) + self.wait() + + self.rod_word = word + + def show_x_axis(self): + rod = self.rod + axes = self.axes + 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) + + self.play( + rod.set_opacity, 0.5, + FadeInFrom(x_label, UL), + LaggedStartMap( + FadeInFrom, x_numbers, + lambda m: (m, UP), + ) + ) + self.wait() + + # Show x-values + triangle = ArrowTip( + start_angle=-90 * DEGREES, + color=LIGHT_GREY, + ) + x_tracker = ValueTracker(PI) + get_x = x_tracker.get_value + + def get_x_point(): + return x_axis.n2p(get_x()) + + def get_graph_point(): + return graph.point_from_proportion( + inverse_interpolate( + self.graph_x_min, + self.graph_x_max, + get_x(), + ) + ) + + triangle.add_updater( + lambda t: t.next_to(get_x_point(), UP) + ) + x_label = VGroup( + TexMobject("x"), + TexMobject("="), + DecimalNumber( + 0, + num_decimal_places=3, + include_background_rectangle=True, + ).scale(0.9) + ) + x_label.set_stroke(BLACK, 5, background=True) + x_label.add_updater(lambda m: m[-1].set_value(get_x())) + x_label.add_updater(lambda m: m.arrange(RIGHT, buff=SMALL_BUFF)) + x_label.add_updater(lambda m: m[-1].align_to(m[0], DOWN)) + x_label.add_updater(lambda m: m.next_to(triangle, UP, SMALL_BUFF)) + x_label.add_updater(lambda m: m.shift(SMALL_BUFF * RIGHT)) + rod_piece = always_redraw( + lambda: self.get_rod( + get_x() - 0.05, get_x() + 0.05, + n_pieces=1, + ) + ) + + self.play( + FadeInFrom(triangle, UP), + FadeIn(x_label), + FadeIn(rod_piece), + FadeOut(self.rod_word), + ) + for value in [np.exp(2), (np.sqrt(5) + 1) / 2]: + self.play(x_tracker.set_value, value, run_time=2) + self.wait() + + # Show graph + v_line = always_redraw( + lambda: DashedLine( + get_x_point(), + get_graph_point(), + color=self.rod_point_to_color(get_x_point()), + ) + ) + graph_dot = Dot() + graph_dot.add_updater( + lambda m: m.set_color( + self.rod_point_to_color(m.get_center()) + ) + ) + graph_dot.add_updater( + lambda m: m.move_to(get_graph_point()) + ) + t_label = TexMobject("T(", "x", ")") + t_label.set_stroke(BLACK, 3, background=True) + t_label.add_updater( + lambda m: m.next_to(graph_dot, UR, buff=0) + ) + + self.add(v_line, rod_piece, x_label, triangle) + self.play( + TransformFromCopy(x_label[0], t_label[1]), + FadeIn(t_label[0::2]), + ShowCreation(v_line), + GrowFromPoint(graph_dot, get_x_point()), + ) + self.add(t_label) + self.wait() + self.play( + x_tracker.set_value, TAU, + run_time=5, + ) + + self.x_tracker = x_tracker + self.graph_label_group = VGroup( + v_line, rod_piece, triangle, x_label, + graph_dot, t_label, + ) + self.set_variables_as_attrs(*self.graph_label_group) + + def show_changes_over_time(self): + graph = self.graph + t_label = self.t_label + new_t_label = TexMobject("T(", "x", ",", "t", ")") + new_t_label.set_color_by_tex("t", YELLOW) + new_t_label.match_updaters(t_label) + + self.setup_clock() + clock = self.clock + time_label = self.time_label + time_group = self.time_group + + time = 5 + self.play( + FadeIn(clock), + FadeIn(time_group), + ) + self.play( + self.get_graph_time_change_animation( + graph, time + ), + ClockPassesTime(clock), + ChangeDecimalToValue( + time_label, time, + rate_func=linear, + ), + ReplacementTransform( + t_label, + new_t_label, + rate_func=squish_rate_func(smooth, 0.5, 0.7), + ), + run_time=time + ) + self.play( + ShowCreationThenFadeAround( + new_t_label.get_part_by_tex("t") + ), + ) + self.wait() + self.play( + FadeOut(clock), + ChangeDecimalToValue(time_label, 0), + VFadeOut(time_group), + self.get_graph_time_change_animation( + graph, + new_time=0, + ), + run_time=1, + rate_func=smooth, + ) + + self.graph_label_group.remove(t_label) + self.graph_label_group.add(new_t_label) + + def show_surface(self): + axes = self.axes + graph = self.graph + + axes_copy = axes.deepcopy() + + # Time axis + t_min = 0 + t_max = 10 + t_axis = NumberLine( + x_min=t_min, + x_max=t_max, + ) + origin = axes.c2p(0, 0) + t_axis.shift(origin - t_axis.n2p(0)) + t_axis.add_numbers( + *range(1, t_max + 1), + direction=UP, + ) + time_label = TextMobject("Time") + time_label.scale(1.5) + time_label.next_to(t_axis, UP) + t_axis.add(time_label) + # t_axis.rotate(90 * DEGREES, LEFT, about_point=origin) + t_axis.rotate(90 * DEGREES, UP, about_point=origin) + + # New parts of graph + step = 0.25 + graph_slices = VGroup(*[ + self.get_graph(time=t).shift( + t * IN + ) + for t in np.arange(0, t_max + step, step) + ]) + graph_slices.set_stroke(width=1) + + # Input plane + x_axis = self.axes.x_axis + y = axes.c2p(0, 0)[1] + surface_config = { + "u_min": self.graph_x_min, + "u_max": self.graph_x_max, + "v_min": t_min, + "v_max": t_max, + "resolution": 20, + } + input_plane = ParametricSurface( + lambda x, t: np.array([ + x_axis.n2p(x)[0], + y, + t_axis.n2p(t)[2], + ]), + **surface_config, + ) + input_plane.set_style( + fill_opacity=0.5, + fill_color=BLUE_B, + stroke_width=0.5, + stroke_color=WHITE, + ) + + # Surface + y_axis = axes.y_axis + surface = ParametricSurface( + lambda x, t: np.array([ + x_axis.n2p(x)[0], + y_axis.n2p(self.temp_func(x, t))[1], + t_axis.n2p(t)[2], + ]), + **surface_config, + ) + surface.set_style( + fill_opacity=0, + stroke_width=0.5, + stroke_color=WHITE, + stroke_opacity=0.5, + ) + + # Rotate everything on screen and move camera + # in such a way that it looks the same + curr_group = Group(*self.get_mobjects()) + curr_group.clear_updaters() + self.set_camera_orientation( + phi=90 * DEGREES, + ) + mobs = [ + curr_group, + graph_slices, + t_axis, + input_plane, + surface, + ] + for mob in mobs: + self.orient_mobject_for_3d(mob) + + # Clean current mobjects + self.x_label.set_stroke(BLACK, 2, background=True) + self.x_label[-1][0].fade(1) + + self.move_camera( + phi=80 * DEGREES, + theta=-85 * DEGREES, + added_anims=[ + Write(input_plane), + Write(t_axis), + FadeOut(self.graph_label_group), + self.rod.set_opacity, 1, + ] + ) + self.begin_ambient_camera_rotation() + self.add(*graph_slices, *self.get_mobjects()) + self.play( + FadeIn(surface), + LaggedStart(*[ + TransformFromCopy(graph, graph_slice) + for graph_slice in graph_slices + ]) + ) + self.wait(4) + + # Show slices + self.axes = axes_copy # So get_graph works... + + def get_time_slice(t): + new_slice = self.get_graph(t) + new_slice.set_shade_in_3d(True) + self.orient_mobject_for_3d(new_slice) + new_slice.shift(t * UP) + return new_slice + + graph.set_shade_in_3d(True) + t_tracker = ValueTracker(0) + graph.add_updater(lambda g: g.become( + get_time_slice(t_tracker.get_value()) + )) + + 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, + ) + self.wait() + + # + def get_graph(self, time=0): + graph = self.axes.get_graph( + lambda x: self.temp_func(x, time), + x_min=self.graph_x_min, + x_max=self.graph_x_max, + step_size=self.step_size, + ) + graph.time = time + graph.color_using_background_image("VerticalTempGradient") + return graph + + def get_graph_time_change_animation(self, graph, new_time, **kwargs): + old_time = graph.time + graph.time = new_time + config = { + "run_time": abs(new_time - old_time), + "rate_func": linear, + } + config.update(kwargs) + + return UpdateFromAlphaFunc( + graph, + lambda g, a: g.become( + self.get_graph(interpolate( + old_time, new_time, a + )) + ), + **config + ) + + def orient_mobject_for_3d(self, mob): + mob.rotate( + 90 * DEGREES, + axis=RIGHT, + about_point=ORIGIN + ) + mob.shift(1 * DOWN) + return mob From cc7c2cfb73cb9f7e5bbb1b3c3a2e3208310bb536 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 11 Apr 2019 09:55:56 -0700 Subject: [PATCH 18/32] Remove Transform(...).update(1) call from GraphScene --- manimlib/scene/graph_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/scene/graph_scene.py b/manimlib/scene/graph_scene.py index 88616b8f..7d5dcf48 100644 --- a/manimlib/scene/graph_scene.py +++ b/manimlib/scene/graph_scene.py @@ -552,7 +552,7 @@ class GraphScene(Scene): kwargs["dx"] = dx kwargs["x"] = x new_group = self.get_secant_slope_group(**kwargs) - Transform(group, new_group).update(1) + group.become(new_group) return group self.play( From aeff47b3e1501f94dda53c6a4990f1c70075f88d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 11 Apr 2019 09:56:19 -0700 Subject: [PATCH 19/32] xt-plane to heat graph animations --- active_projects/ode/all_part2_scenes.py | 8 + active_projects/ode/part2/heat_equation.py | 238 +++++++++++++++++++-- active_projects/ode/part2/staging.py | 109 ++++------ active_projects/ode/part2/wordy_scenes.py | 155 ++++++++++++++ 4 files changed, 428 insertions(+), 82 deletions(-) create mode 100644 active_projects/ode/part2/wordy_scenes.py 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() From 14a9316643279772083f4a9bb1e863bada17ccc6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 16 Apr 2019 12:50:56 -0700 Subject: [PATCH 20/32] Make sure skipped wait calls use a longer dt --- manimlib/scene/scene.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 7a8b69f6..c02bc98a 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -493,13 +493,15 @@ class Scene(Container): @handle_play_like_call def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): - dt = 1 / self.camera.frame_rate self.update_mobjects(dt=0) # Any problems with this? if self.should_update_mobjects(): time_progression = self.get_wait_time_progression(duration, stop_condition) # TODO, be smart about setting a static image # the same way Scene.play does + last_t = 0 for t in time_progression: + dt = t - last_t + last_t = t self.update_mobjects(dt) self.update_frame() self.add_frames(self.get_frame()) @@ -511,6 +513,7 @@ class Scene(Container): return self else: self.update_frame() + dt = 1 / self.camera.frame_rate n_frames = int(duration / dt) frame = self.get_frame() self.add_frames(*[frame] * n_frames) From d50b2c0e3b60bc31f94b9bae54a24792e73fe0be Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 16 Apr 2019 12:52:06 -0700 Subject: [PATCH 21/32] ContrastXChangesToTChanges --- active_projects/ode/all_part2_scenes.py | 1 + active_projects/ode/part2/heat_equation.py | 339 +++++++++++++++++++-- 2 files changed, 321 insertions(+), 19 deletions(-) diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index f67ac717..718d0c9e 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -37,6 +37,7 @@ ALL_SCENE_CLASSES = [ TalkThrough1DHeatGraph, ShowCubeFormation, CompareInputsOfGeneralCaseTo1D, + ContrastXChangesToTChanges, TransitionToTempVsTime, ShowNewton, ShowCupOfWater, diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index 00a26391..9f0fcbb9 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -539,6 +539,8 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene (5, 0.3), (7, 0.2), ], + "surface_resolution": 20, + "graph_slice_step": 0.5, } def construct(self): @@ -777,7 +779,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene t_max = 10 axes_copy = axes.deepcopy() - original_axes = self.axes + self.original_axes = self.axes # Set rod final state final_graph = self.get_graph(t_max) @@ -808,7 +810,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene t_axis.rotate(90 * DEGREES, UP, about_point=origin) # New parts of graph - step = 0.25 + step = self.graph_slice_step graph_slices = VGroup(*[ self.get_graph(time=t).shift( t * IN @@ -826,7 +828,7 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene "u_max": self.graph_x_max, "v_min": t_min, "v_max": t_max, - "resolution": 20, + "resolution": self.surface_resolution, } input_plane = ParametricSurface( lambda x, t: np.array([ @@ -854,7 +856,8 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene **surface_config, ) surface.set_style( - fill_opacity=0, + fill_opacity=0.1, + fill_color=LIGHT_GREY, stroke_width=0.5, stroke_color=WHITE, stroke_opacity=0.5, @@ -932,12 +935,12 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene self.orient_mobject_for_3d(final_rod) final_rod.shift(10 * UP) kw = {"run_time": 10, "rate_func": linear} + self.rod.save_state() self.play( 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( @@ -946,8 +949,8 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene surface, graph_slices, slicing_plane, + t_tracker, ) - self.axes = original_axes # def get_graph(self, time=0): @@ -989,29 +992,200 @@ class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene return mob -class TransitionToTempVsTime(TalkThrough1DHeatGraph): +class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): + CONFIG = { + # "surface_resolution": 5, + # "graph_slice_step": 1, + } + def construct(self): + self.catchup_with_last_scene() + self.emphasize_dimensions_of_input_space() + self.reset_time_to_zero() + + self.show_changes_with_x() + self.show_changes_with_t() + + def catchup_with_last_scene(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 + def emphasize_dimensions_of_input_space(self): + plane = self.input_plane + plane_copy = plane.copy() + plane_copy.set_color(BLUE_E) + + plane_copy1 = plane_copy.copy() + plane_copy1.stretch(0.01, 1, about_edge=DOWN) + plane_copy0 = plane_copy1.copy() + plane_copy0.stretch(0, 0, about_edge=LEFT) + + words = TextMobject("2d input\\\\space") + words.scale(2) + words.move_to(plane.get_center() + SMALL_BUFF * OUT) + + self.play( + Write(words), + self.camera.phi_tracker.set_value, 60 * DEGREES, + self.camera.theta_tracker.set_value, -95 * DEGREES, + run_time=1 + ) + self.play( + ReplacementTransform(plane_copy0, plane_copy1) + ) + self.play( + ReplacementTransform(plane_copy1, plane_copy) + ) + self.wait(2) + self.play(FadeOut(plane_copy)) + + self.input_plane_words = words + + def reset_time_to_zero(self): + self.play( + self.t_tracker.set_value, 0, + VFadeOut(self.slicing_plane), + Restore(self.rod), + ) + + def show_changes_with_x(self): + alpha_tracker = ValueTracker(0) + line = always_redraw( + lambda: self.get_tangent_line( + self.graph, alpha_tracker.get_value(), + ) + ) + + self.stop_ambient_camera_rotation() + self.play( + ShowCreation(line), + FadeOut(self.input_plane_words), + self.camera.phi_tracker.set_value, 80 * DEGREES, + self.camera.theta_tracker.set_value, -90 * DEGREES, + ) + self.play( + alpha_tracker.set_value, 0.425, + run_time=5, + rate_func=bezier([0, 0, 1, 1]), + ) + + # Show dx and dT + p0 = line.point_from_proportion(0.3) + p2 = line.point_from_proportion(0.7) + p1 = np.array([p2[0], *p0[1:]]) + dx_line = DashedLine(p0, p1) + dT_line = DashedLine(p1, p2) + dx = TexMobject("dx") + dT = TexMobject("dT") + VGroup(dx, dT).scale(0.7) + VGroup(dx, dT).rotate(90 * DEGREES, RIGHT) + dx.next_to(dx_line, IN, SMALL_BUFF) + dT.next_to(dT_line, RIGHT, SMALL_BUFF) + + self.play( + ShowCreation(dx_line), + FadeInFrom(dx, LEFT) + ) + self.wait() + self.play( + ShowCreation(dT_line), + FadeInFrom(dT, IN) + ) + self.wait() + self.play(*map(FadeOut, [ + line, dx_line, dT_line, dx, dT, + ])) + + def show_changes_with_t(self): + slices = self.graph_slices + slice_alpha = 0.075 + graph = VMobject() + graph.set_points_smoothly([ + gs.point_from_proportion(slice_alpha) + for gs in slices + ]) + graph.color_using_background_image("VerticalTempGradient") + graph.set_shade_in_3d(True) + + alpha_tracker = ValueTracker(0) + line = always_redraw( + lambda: self.get_tangent_line( + graph, alpha_tracker.get_value(), + ) + ) + + self.play( + self.camera.theta_tracker.set_value, -10 * DEGREES, + self.camera.frame_center.shift, 5 * LEFT, + ) + + self.play( + ShowCreation( + graph.copy(), + remover=True + ), + VFadeIn(line), + ApplyMethod( + alpha_tracker.set_value, 1, + run_time=8, + ) + ) + self.add(graph) + + self.begin_ambient_camera_rotation(-0.02) + self.camera.frame_center.add_updater( + lambda m, dt: m.shift(0.05 * dt * RIGHT) + ) + + self.play(FadeOut(line)) + self.wait(30) # Let rotate + + self.t_graph = graph + + # + def get_tangent_line(self, graph, alpha, d_alpha=0.001, length=2): + if alpha < 1 - d_alpha: + a1 = alpha + a2 = alpha + d_alpha + else: + a1 = alpha - d_alpha + a2 = alpha + + p1 = graph.point_from_proportion(a1) + p2 = graph.point_from_proportion(a2) + line = Line(p1, p2, color=WHITE) + line.scale( + length / line.get_length() + ) + line.move_to(p1) + return line + + +class TransitionToTempVsTime(ContrastXChangesToTChanges): + CONFIG = { + # "surface_resolution": 5, + # "graph_slice_step": 1, + } + + def construct(self): + self.catchup_with_last_scene() + + axes = self.original_axes t_axis = self.t_axis y_axis = axes.y_axis x_axis = axes.x_axis + for mob in self.get_mobjects(): + mob.clear_updaters() self.stop_ambient_camera_rotation() self.move_camera( phi=90 * DEGREES, @@ -1030,8 +1204,9 @@ class TransitionToTempVsTime(TalkThrough1DHeatGraph): self.graph, self.x_numbers, self.x_axis_label, + self.t_graph, )), - self.camera.frame_center.shift, 5 * LEFT, + self.camera.frame_center.move_to, 5 * LEFT, ] ) self.play( @@ -1045,12 +1220,32 @@ class TransitionToTempVsTime(TalkThrough1DHeatGraph): ) self.wait() + def catchup_with_last_scene(self): + self.force_skipping() + + self.add_axes() + self.add_graph() + self.add_rod() + + self.rod_word = Point() + self.show_x_axis() + self.show_surface() + + self.emphasize_dimensions_of_input_space() + self.reset_time_to_zero() + + self.show_changes_with_x() + self.show_changes_with_t() + + self.revert_to_original_skipping_status() + class ShowNewtonsLawGraph(Scene): CONFIG = { "k": 0.2, "initial_water_temp": 80, "room_temp": 20, + "delta_T_color": YELLOW, } def construct(self): @@ -1105,17 +1300,20 @@ class ShowNewtonsLawGraph(Scene): water_arrow = Vector(LEFT, color=WHITE) water_arrow.next_to(water_dot, RIGHT, SMALL_BUFF) water_words = TextMobject( - "Initial water temperature" + "Initial water\\\\temperature" ) + water_words.scale(0.7) water_words.next_to(water_arrow, RIGHT) room_words = TextMobject("Room temperature") + room_words.scale(0.7) room_words.next_to(room_line, DOWN, SMALL_BUFF) self.play( FadeInFrom(water_dot, RIGHT), GrowArrow(water_arrow), Write(water_words), + run_time=1, ) self.play(ShowCreation(room_line)) self.play(FadeInFromDown(room_words)) @@ -1149,14 +1347,117 @@ class ShowNewtonsLawGraph(Scene): 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)) + brace = always_redraw( + lambda: Brace( + brace_line, RIGHT, buff=SMALL_BUFF + ) + ) - self.add(graph) + delta_T = TexMobject("\\Delta T") + delta_T.set_color(self.delta_T_color) + delta_T.add_updater(lambda m: m.next_to( + brace, RIGHT, SMALL_BUFF + )) + + self.add(brace_line) + self.play( + GrowFromCenter(brace), + Write(delta_T), + ) + self.play( + ShowCreation(graph), + UpdateFromFunc( + water_dot, + lambda m: m.move_to(graph.get_end()) + ), + run_time=10, + rate_func=linear, + ) + self.wait() + + self.graph = graph + self.brace = brace + self.delta_T = delta_T def show_equation(self): - pass + delta_T = self.delta_T + + equation = TexMobject( + "{d ({\\Delta T}) \\over dt} = -k \\cdot {\\Delta T}", + tex_to_color_map={ + "{\\Delta T}": self.delta_T_color, + "-k": WHITE, + "=": WHITE, + } + ) + equation.to_corner(UR) + equation.shift(LEFT) + + delta_T_parts = equation.get_parts_by_tex("\\Delta T") + eq_i = equation.index_of_part_by_tex("=") + deriv = equation[:eq_i] + prop_to = equation.get_part_by_tex("-k") + parts = VGroup(deriv, prop_to, delta_T_parts[1]) + + words = TextMobject( + "Rate of change", + "is proportional to", + "itself", + ) + words.scale(0.7) + words.next_to(equation, DOWN) + colors = [BLUE, WHITE, YELLOW] + for part, word, color in zip(parts, words, colors): + part.word = word + word.set_color(color) + word.save_state() + words[0].next_to(parts[0], DOWN) + + self.play( + TransformFromCopy( + VGroup(delta_T), + delta_T_parts, + ), + Write(VGroup(*filter( + lambda p: p not in delta_T_parts, + equation + ))) + ) + + rects = VGroup() + for part in parts: + rect = SurroundingRectangle( + part, + color=part.word.get_color(), + buff=SMALL_BUFF, + stroke_width=2, + ) + anims = [ + ShowCreation(rect), + FadeIn(part.word), + ] + if part is parts[1]: + anims.append(Restore(words[0])) + self.play(*anims) + rects.add(rect) + + self.play(FadeOut(rects, lag_ratio=0.2)) + + self.equation = equation + self.equation_words = words def talk_through_examples(self): + dot = self.water_dot + graph = self.graph + + self.play( + MoveAlongPath( + dot, graph, + rate_func=lambda t: smooth(1 - t), + run_time=2, + ) + ) + + # + def get_slope_line(self, graph, x): pass From 814c9d1252f9f0fe3a043191a945bd133cd02752 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 16 Apr 2019 14:03:37 -0700 Subject: [PATCH 22/32] Heat equation scenes up to ShowPartialDerivativeSymbols --- active_projects/ode/all_part2_scenes.py | 1 + active_projects/ode/part2/heat_equation.py | 1 + active_projects/ode/part2/wordy_scenes.py | 99 ++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/ode/all_part2_scenes.py index 718d0c9e..3961f4da 100644 --- a/active_projects/ode/all_part2_scenes.py +++ b/active_projects/ode/all_part2_scenes.py @@ -38,6 +38,7 @@ ALL_SCENE_CLASSES = [ ShowCubeFormation, CompareInputsOfGeneralCaseTo1D, ContrastXChangesToTChanges, + ShowPartialDerivativeSymbols, TransitionToTempVsTime, ShowNewton, ShowCupOfWater, diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/ode/part2/heat_equation.py index 9f0fcbb9..49be1976 100644 --- a/active_projects/ode/part2/heat_equation.py +++ b/active_projects/ode/part2/heat_equation.py @@ -1023,6 +1023,7 @@ class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): plane = self.input_plane plane_copy = plane.copy() plane_copy.set_color(BLUE_E) + plane_copy.shift(SMALL_BUFF * 0.5 * OUT) plane_copy1 = plane_copy.copy() plane_copy1.stretch(0.01, 1, about_edge=DOWN) diff --git a/active_projects/ode/part2/wordy_scenes.py b/active_projects/ode/part2/wordy_scenes.py index 7312b67e..64ca7bf9 100644 --- a/active_projects/ode/part2/wordy_scenes.py +++ b/active_projects/ode/part2/wordy_scenes.py @@ -11,6 +11,105 @@ class TodaysTargetWrapper(Scene): pass +class ShowPartialDerivativeSymbols(Scene): + def construct(self): + t2c = { + "{x}": GREEN, + "{t}": PINK, + } + d_derivs, del_derivs = VGroup(*[ + VGroup(*[ + TexMobject( + "{" + sym, "T", "\\over", sym, var + "}", + "(", "{x}", ",", "{t}", ")", + ).set_color_by_tex_to_color_map(t2c) + for var in ("{x}", "{t}") + ]) + for sym in ("d", "\\partial") + ]) + dTdx, dTdt = d_derivs + delTdelx, delTdelx = del_derivs + dels = VGroup(*it.chain(*[ + del_deriv.get_parts_by_tex("\\partial") + for del_deriv in del_derivs + ])) + + dTdx.to_edge(UP) + self.play(FadeInFrom(dTdx, DOWN)) + self.wait() + self.play(ShowCreationThenFadeAround(dTdx[3:5])) + self.play(ShowCreationThenFadeAround(dTdx[:2])) + self.wait() + + dTdt.move_to(dTdx) + self.play( + dTdx.next_to, dTdt, RIGHT, {"buff": 1.5}, + dTdx.set_opacity, 0.5, + FadeInFromDown(dTdt) + ) + self.wait() + + for m1, m2 in zip(d_derivs, del_derivs): + m2.move_to(m1) + + pd_words = TextMobject("Partial derivatives") + pd_words.next_to(del_derivs, DOWN, MED_LARGE_BUFF) + + self.play( + Write(pd_words), + dTdx.set_opacity, 1, + run_time=1, + ) + self.wait() + self.play( + ReplacementTransform(d_derivs, del_derivs) + ) + self.play( + LaggedStartMap( + ShowCreationThenFadeAround, + dels, + surrounding_rectangle_config={ + "color": BLUE, + "buff": 0.5 * SMALL_BUFF, + "stroke_width": 2, + } + ) + ) + self.wait() + + num_words = VGroup(*[ + TextMobject( + "Change in $T$\\\\caused by {}", + "$\\partial$", "${}$".format(var), + arg_separator="", + ).set_color_by_tex_to_color_map(t2c) + for var in ("{x}", "{t}") + ]) + num_words.scale(0.8) + for word, deriv in zip(num_words, del_derivs): + num = deriv[:2] + word.move_to(num, UP) + word.to_edge(UP, buff=MED_SMALL_BUFF) + deriv.rect = SurroundingRectangle( + num, + buff=SMALL_BUFF, + stroke_width=2, + ) + deriv.rect.mob = num + deriv.rect.add_updater(lambda r: r.move_to(r.mob)) + + self.play( + Write(num_words[1]), + VGroup(del_derivs, pd_words).shift, DOWN, + ShowCreation(del_derivs[1].rect), + ) + self.play( + Write(num_words[0]), + ShowCreation(del_derivs[0].rect), + ) + self.wait() + + class WriteHeatEquation(Scene): CONFIG = { "tex_mobject_config": { From d9fcbea8233b270c612775adadd6a8a7ab04aea6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:12:05 -0700 Subject: [PATCH 23/32] Remove Transform.__str__ --- manimlib/animation/transform.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/manimlib/animation/transform.py b/manimlib/animation/transform.py index 32c54506..32871c03 100644 --- a/manimlib/animation/transform.py +++ b/manimlib/animation/transform.py @@ -5,7 +5,7 @@ import numpy as np from manimlib.animation.animation import Animation from manimlib.constants import DEFAULT_POINTWISE_FUNCTION_RUN_TIME from manimlib.constants import OUT -from manimlib.constants import PI +from manimlib.constants import DEGREES from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Mobject from manimlib.utils.config_ops import digest_config @@ -28,12 +28,6 @@ class Transform(Animation): self.target_mobject = target_mobject self.init_path_func() - def __str__(self): - return "{}To{}".format( - super().__str__(), - str(self.target_mobject) - ) - def init_path_func(self): if self.path_func is not None: return @@ -275,7 +269,7 @@ class ApplyComplexFunction(ApplyMethod): class CyclicReplace(Transform): CONFIG = { - "path_arc": PI / 2, + "path_arc": 90 * DEGREES, } def __init__(self, *mobjects, **kwargs): From 4fa782b8b5f16378a8352315b7975581b9fd87fe Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:12:44 -0700 Subject: [PATCH 24/32] Remove extract_scene use of an All_SCENES list --- manimlib/extract_scene.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/manimlib/extract_scene.py b/manimlib/extract_scene.py index 143e338c..0cf78363 100644 --- a/manimlib/extract_scene.py +++ b/manimlib/extract_scene.py @@ -64,8 +64,6 @@ def is_child_scene(obj, module): return False if obj == Scene: return False - if not obj.__module__.startswith(module.__name__): - return False return True @@ -120,16 +118,13 @@ def get_scenes_to_render(scene_classes, config): def get_scene_classes_from_module(module): - if hasattr(module, "ALL_SCENE_CLASSES"): - return module.ALL_SCENE_CLASSES - else: - return [ - member[1] - for member in inspect.getmembers( - module, - lambda x: is_child_scene(x, module) - ) - ] + return [ + member[1] + for member in inspect.getmembers( + module, + lambda x: is_child_scene(x, module) + ) + ] def main(config): From 76f61c504b9dd8b2b2df4279b8052376dffe9ba5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:13:05 -0700 Subject: [PATCH 25/32] Remove unnecessary import --- manimlib/mobject/geometry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 965497e1..6eabd6ef 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -1,6 +1,5 @@ import warnings import numpy as np -import operator as op from manimlib.constants import * from manimlib.mobject.mobject import Mobject From aa5ac955571c532b57276d8641f065396634a994 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:13:30 -0700 Subject: [PATCH 26/32] Add PiCreature.get_look_at_spot --- manimlib/for_3b1b_videos/pi_creature.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/manimlib/for_3b1b_videos/pi_creature.py b/manimlib/for_3b1b_videos/pi_creature.py index 429868e9..fa3864d3 100644 --- a/manimlib/for_3b1b_videos/pi_creature.py +++ b/manimlib/for_3b1b_videos/pi_creature.py @@ -3,7 +3,6 @@ import warnings import numpy as np -from manimlib.animation.transform import Transform from manimlib.constants import * from manimlib.mobject.mobject import Mobject from manimlib.mobject.geometry import Circle @@ -14,6 +13,7 @@ from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.config_ops import digest_config from manimlib.utils.space_ops import get_norm +from manimlib.utils.space_ops import normalize pi_creature_dir_maybe = os.path.join(MEDIA_DIR, "designs", "PiCreature") if os.path.exists(pi_creature_dir_maybe): @@ -197,10 +197,11 @@ class PiCreature(SVGMobject): return self def get_looking_direction(self): - return np.sign(np.round( - self.pupils.get_center() - self.eyes.get_center(), - decimals=2 - )) + vect = self.pupils.get_center() - self.eyes.get_center() + return normalize(vect) + + def get_look_at_spot(self): + return self.eyes.get_center() + self.get_looking_direction() def is_flipped(self): return self.eyes.submobjects[0].get_center()[0] > \ From fbbfa1ce9cd357b6a6bc682893174887d1faf39e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:13:45 -0700 Subject: [PATCH 27/32] Change patron name scroll --- manimlib/for_3b1b_videos/common_scenes.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/manimlib/for_3b1b_videos/common_scenes.py b/manimlib/for_3b1b_videos/common_scenes.py index c4cfa7f8..659fbe6d 100644 --- a/manimlib/for_3b1b_videos/common_scenes.py +++ b/manimlib/for_3b1b_videos/common_scenes.py @@ -239,14 +239,11 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene): if columns.get_width() > self.max_patron_width: columns.set_width(total_width - 1) - thanks.to_edge(RIGHT) - columns.next_to(thanks, DOWN, 3 * LARGE_BUFF) + thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) + columns.next_to(underline, DOWN, buff=2) columns.generate_target() - columns.target.move_to(2 * DOWN, DOWN) - columns.target.align_to( - thanks, alignment_vect=RIGHT - ) + columns.target.to_edge(DOWN, buff=2) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 From 7774c191c66dc88ac92c2f85df5e6e81d95b5f88 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:14:13 -0700 Subject: [PATCH 28/32] ALL_SCENE_CLASSES -> SCENES_IN_ORDER --- old_projects/clacks/all_s2_scenes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/old_projects/clacks/all_s2_scenes.py b/old_projects/clacks/all_s2_scenes.py index fb43af09..55635afe 100644 --- a/old_projects/clacks/all_s2_scenes.py +++ b/old_projects/clacks/all_s2_scenes.py @@ -7,7 +7,7 @@ from old_projects.clacks.solution2 import simple_scenes from old_projects.clacks.solution2 import wordy_scenes OUTPUT_DIRECTORY = "clacks/solution2" -ALL_SCENE_CLASSES = [ +SCENES_IN_ORDER = [ question.NameIntro, block_collision_scenes.IntroducePreviousTwoVideos, block_collision_scenes.PreviousTwoVideos, From 77a3b47a09c5a7896abe9df40190dd2f7923fb39 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:14:35 -0700 Subject: [PATCH 29/32] Changed how pi creatures follow what happens --- manimlib/for_3b1b_videos/pi_creature_scene.py | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/manimlib/for_3b1b_videos/pi_creature_scene.py b/manimlib/for_3b1b_videos/pi_creature_scene.py index 4af617e1..1f0fad7e 100644 --- a/manimlib/for_3b1b_videos/pi_creature_scene.py +++ b/manimlib/for_3b1b_videos/pi_creature_scene.py @@ -1,11 +1,10 @@ import itertools as it import random -import copy -from manimlib.animation.transform import ApplyMethod from manimlib.animation.transform import ReplacementTransform from manimlib.animation.transform import Transform from manimlib.animation.composition import LaggedStart +from manimlib.animation.update import UpdateFromAlphaFunc from manimlib.constants import * from manimlib.for_3b1b_videos.pi_creature import Mortimer from manimlib.for_3b1b_videos.pi_creature import PiCreature @@ -18,6 +17,7 @@ from manimlib.mobject.svg.drawings import SpeechBubble from manimlib.mobject.svg.drawings import ThoughtBubble from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.scene.scene import Scene +from manimlib.utils.bezier import interpolate from manimlib.utils.rate_functions import squish_rate_func from manimlib.utils.rate_functions import there_and_back from manimlib.utils.space_ops import get_norm @@ -157,34 +157,31 @@ class PiCreatureScene(Scene): if not self.any_pi_creatures_on_screen(): return animations - non_pi_creature_anims = [anim for anim in animations if anim.mobject not in self.get_pi_creatures()] + pi_creatures = self.get_on_screen_pi_creatures() + non_pi_creature_anims = [ + anim + for anim in animations + if len(set(anim.mobject.get_family()).intersection(pi_creatures)) == 0 + ] if len(non_pi_creature_anims) == 0: return animations - # Look at ending state + # Get pi creatures to look at whatever + # is being animated first_anim = non_pi_creature_anims[0] - first_anim_copy = copy.deepcopy(first_anim) - first_anim_copy.begin() - first_anim_copy.update(1) - point_of_interest = first_anim_copy.mobject.get_center() - - for pi_creature in self.get_pi_creatures(): - if pi_creature not in self.get_mobjects(): - continue - if pi_creature in first_anim.mobject.get_family(): - continue - anims_with_pi_creature = [anim for anim in animations if pi_creature in anim.mobject.get_family()] - for anim in anims_with_pi_creature: - continue # TODO, this is broken - if isinstance(anim, Transform): - index = anim.mobject.get_family().index(pi_creature) - target_family = anim.target_mobject.get_family() - target = target_family[index] - if isinstance(target, PiCreature): - target.look_at(point_of_interest) - if not anims_with_pi_creature: - animations.append( - ApplyMethod(pi_creature.look_at, point_of_interest) - ) + main_mobject = first_anim.mobject + animations += [ + UpdateFromAlphaFunc( + pi_creature, + lambda p, a: p.look_at( + interpolate( + p.get_look_at_spot(), + main_mobject.get_center(), + a, + ) + ), + ) + for pi_creature in pi_creatures + ] return animations def blink(self): From 3bdc57aac4278f33f7ef2d74359f687d26888643 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:14:52 -0700 Subject: [PATCH 30/32] Dumb hard-coded file in stage_scenes --- stage_scenes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stage_scenes.py b/stage_scenes.py index ab84fe49..8ef370d2 100644 --- a/stage_scenes.py +++ b/stage_scenes.py @@ -12,8 +12,8 @@ from manimlib.extract_scene import is_child_scene def get_sorted_scene_classes(module_name): module = get_module(module_name) - if hasattr(module, "ALL_SCENE_CLASSES"): - return module.ALL_SCENE_CLASSES + if hasattr(module, "SCENES_IN_ORDER"): + return module.SCENES_IN_ORDER # Otherwise, deduce from the order in which # they're defined in a file importlib.import_module(module.__name__) @@ -43,7 +43,7 @@ def stage_scenes(module_name): # } # TODO, fix this animation_dir = os.path.join( - VIDEO_DIR, "ode", "part1", "1440p60" + VIDEO_DIR, "ode", "part2", "1440p60" ) # files = os.listdir(animation_dir) From ee622987df18921f993dda3afd04bdbb666d1d1d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 21 Apr 2019 08:15:11 -0700 Subject: [PATCH 31/32] 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", + ], + } From 08a1447b4394fa5ea67d1ded9937a39182ee175a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 22 Apr 2019 10:15:53 -0700 Subject: [PATCH 32/32] Added pde thumbnail --- active_projects/ode/part2/staging.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/active_projects/ode/part2/staging.py b/active_projects/ode/part2/staging.py index 592964dd..741970fa 100644 --- a/active_projects/ode/part2/staging.py +++ b/active_projects/ode/part2/staging.py @@ -748,6 +748,42 @@ class StrogatzMention(PiCreatureScene): return Mortimer().to_corner(DR) +class Thumbnail(Scene): + def construct(self): + image = ImageMobject("HeatSurfaceExampleFlipped") + image.set_height(6.5) + image.to_edge(DOWN, buff=-SMALL_BUFF) + self.add(image) + + equation = TexMobject( + "{\\partial {T} \\over \\partial {t}}", "=", + "\\alpha", "\\nabla^2 {T}", + tex_to_color_map={ + "{t}": YELLOW, + "{T}": RED, + } + ) + equation.scale(2) + equation.to_edge(UP) + + self.add(equation) + + Group(equation, image).shift(1.5 * RIGHT) + + question = TextMobject("What is\\\\this?") + question.scale(2.5) + question.to_edge(LEFT) + arrow = Arrow( + question.get_top(), + equation.get_left(), + buff=0.5, + path_arc=-90 * DEGREES, + ) + arrow.set_stroke(width=5) + + self.add(question, arrow) + + class ShowNewton(Scene): def construct(self): pass