diff --git a/active_projects/ode/all_part1_scenes.py b/active_projects/diffyq/all_part1_scenes.py similarity index 100% rename from active_projects/ode/all_part1_scenes.py rename to active_projects/diffyq/all_part1_scenes.py diff --git a/active_projects/ode/all_part2_scenes.py b/active_projects/diffyq/all_part2_scenes.py similarity index 100% rename from active_projects/ode/all_part2_scenes.py rename to active_projects/diffyq/all_part2_scenes.py diff --git a/active_projects/ode/all_part3_scenes.py b/active_projects/diffyq/all_part3_scenes.py similarity index 100% rename from active_projects/ode/all_part3_scenes.py rename to active_projects/diffyq/all_part3_scenes.py diff --git a/active_projects/diffyq/all_part4_scenes.py b/active_projects/diffyq/all_part4_scenes.py new file mode 100644 index 00000000..7e95486f --- /dev/null +++ b/active_projects/diffyq/all_part4_scenes.py @@ -0,0 +1,18 @@ +from active_projects.ode.part4.staging import * +from active_projects.ode.part4.fourier_series_scenes import * +from active_projects.ode.part4.pi_creature_scenes import * + +OUTPUT_DIRECTORY = "ode/part4" +SCENES_IN_ORDER = [ + ComplexFourierSeriesExample, + ComplexFourierSeriesExampleEnd, + FourierSeriesExampleWithRectForZoom, + ZoomedInFourierSeriesExample, + RelationToOtherVideos, + WhyWouldYouCare, + # Oldies + + # FourierSeriesIllustraiton, + # FourierNameIntro, + # CircleAnimationOfF, +] diff --git a/active_projects/ode/name_animations.py b/active_projects/diffyq/name_animations.py similarity index 100% rename from active_projects/ode/name_animations.py rename to active_projects/diffyq/name_animations.py diff --git a/active_projects/ode/part1/pendulum.py b/active_projects/diffyq/part1/pendulum.py similarity index 100% rename from active_projects/ode/part1/pendulum.py rename to active_projects/diffyq/part1/pendulum.py diff --git a/active_projects/ode/part1/phase_space.py b/active_projects/diffyq/part1/phase_space.py similarity index 100% rename from active_projects/ode/part1/phase_space.py rename to active_projects/diffyq/part1/phase_space.py diff --git a/active_projects/ode/part1/pi_scenes.py b/active_projects/diffyq/part1/pi_scenes.py similarity index 100% rename from active_projects/ode/part1/pi_scenes.py rename to active_projects/diffyq/part1/pi_scenes.py diff --git a/active_projects/ode/part1/shared_constructs.py b/active_projects/diffyq/part1/shared_constructs.py similarity index 100% rename from active_projects/ode/part1/shared_constructs.py rename to active_projects/diffyq/part1/shared_constructs.py diff --git a/active_projects/ode/part1/staging.py b/active_projects/diffyq/part1/staging.py similarity index 100% rename from active_projects/ode/part1/staging.py rename to active_projects/diffyq/part1/staging.py diff --git a/active_projects/ode/part1/wordy_scenes.py b/active_projects/diffyq/part1/wordy_scenes.py similarity index 100% rename from active_projects/ode/part1/wordy_scenes.py rename to active_projects/diffyq/part1/wordy_scenes.py diff --git a/active_projects/ode/part2/fourier_series.py b/active_projects/diffyq/part2/fourier_series.py similarity index 84% rename from active_projects/ode/part2/fourier_series.py rename to active_projects/diffyq/part2/fourier_series.py index e9544f4c..6a85ebfc 100644 --- a/active_projects/ode/part2/fourier_series.py +++ b/active_projects/diffyq/part2/fourier_series.py @@ -4,7 +4,7 @@ from manimlib.imports import * class FourierCirclesScene(Scene): CONFIG = { - "n_circles": 10, + "n_vectors": 10, "big_radius": 2, "colors": [ BLUE_D, @@ -15,14 +15,16 @@ class FourierCirclesScene(Scene): "circle_style": { "stroke_width": 2, }, - "arrow_config": { + "vector_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, + "circle_config": { + "stroke_width": 1, + }, "base_frequency": 1, "slow_factor": 0.25, "center_point": ORIGIN, @@ -33,26 +35,35 @@ class FourierCirclesScene(Scene): self.slow_factor_tracker = ValueTracker( self.slow_factor ) + self.vector_clock = ValueTracker(0) + self.vector_clock.add_updater( + lambda m, dt: m.increment_value( + self.get_slow_factor() * dt + ) + ) + self.add(self.vector_clock) def get_slow_factor(self): return self.slow_factor_tracker.get_value() + def get_vector_time(self): + return self.vector_clock.get_value() + # def get_freqs(self): - n = self.n_circles + n = self.n_vectors all_freqs = list(range(n // 2, -n // 2, -1)) all_freqs.sort(key=abs) return all_freqs def get_coefficients(self): - return [complex(0) for x in range(self.n_circles)] + return [complex(0) for x in range(self.n_vectors)] def get_color_iterator(self): return it.cycle(self.colors) - def get_circles(self, freqs=None, coefficients=None): - circles = VGroup() - color_iterator = self.get_color_iterator() + def get_rotating_vectors(self, freqs=None, coefficients=None): + vectors = VGroup() self.center_tracker = VectorizedPoint(self.center_point) if freqs is None: @@ -60,80 +71,74 @@ class FourierCirclesScene(Scene): if coefficients is None: coefficients = self.get_coefficients() - last_circle = None + last_vector = None for freq, coefficient in zip(freqs, coefficients): - if last_circle: - center_func = last_circle.get_start + if last_vector: + center_func = last_vector.get_end else: center_func = self.center_tracker.get_location - circle = self.get_circle( + vector = self.get_rotating_vector( coefficient=coefficient, freq=freq, - color=next(color_iterator), center_func=center_func, ) - circles.add(circle) - last_circle = circle - return circles + vectors.add(vector) + last_vector = vector + return vectors - def get_circle(self, coefficient, freq, color, center_func): - radius = abs(coefficient) - phase = np.log(coefficient).imag - circle = Circle( - radius=radius, - color=color, - **self.circle_style, - ) - line_points = ( - circle.get_center(), - circle.get_start(), - ) - if self.use_vectors: - circle.radial_line = Arrow( - *line_points, - **self.arrow_config, - ) + def get_rotating_vector(self, coefficient, freq, center_func): + vector = Vector(RIGHT, **self.vector_config) + vector.scale(abs(coefficient)) + if abs(coefficient) == 0: + phase = 0 else: - circle.radial_line = Line( - *line_points, - color=WHITE, - **self.circle_style, + phase = np.log(coefficient).imag + vector.rotate(phase, about_point=ORIGIN) + vector.freq = freq + vector.phase = phase + vector.coefficient = coefficient + vector.center_func = center_func + vector.add_updater(self.update_vector) + return vector + + def update_vector(self, vector, dt): + time = self.get_vector_time() + vector.set_angle( + vector.phase + time * vector.freq * TAU + ) + vector.shift( + vector.center_func() - vector.get_start() + ) + return vector + + def get_circles(self, vectors): + return VGroup(*[ + self.get_circle( + vector, + color=color ) - circle.add(circle.radial_line) - circle.freq = freq - circle.phase = phase - circle.rotate(phase) - circle.coefficient = coefficient - circle.center_func = center_func + for vector, color in zip( + vectors, + self.get_color_iterator() + ) + ]) + + def get_circle(self, vector, color=BLUE): + circle = Circle(color=color, **self.circle_config) + circle.center_func = vector.get_start + circle.radius_func = vector.get_length circle.add_updater(self.update_circle) return circle - def update_circle(self, circle, dt): - circle.rotate( - self.get_slow_factor() * circle.freq * dt * TAU - ) + def update_circle(self, circle): + circle.set_width(2 * circle.radius_func()) circle.move_to(circle.center_func()) return circle - def get_rotating_vectors(self, circles): - return VGroup(*[ - self.get_rotating_vector(circle) - for circle in circles - ]) - - def get_rotating_vector(self, circle): - vector = Vector(RIGHT, color=WHITE) - 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): - coefs = [c.coefficient for c in circles] - freqs = [c.freq for c in circles] - center = circles[0].get_center() + def get_vector_sum_path(self, vectors, color=YELLOW): + coefs = [v.coefficient for v in vectors] + freqs = [v.freq for v in vectors] + center = vectors[0].get_start() path = ParametricFunction( lambda t: center + reduce(op.add, [ @@ -150,13 +155,14 @@ class FourierCirclesScene(Scene): return path # TODO, this should be a general animated mobect - def get_drawn_path(self, circles, stroke_width=2, **kwargs): - path = self.get_circle_end_path(circles, **kwargs) + def get_drawn_path(self, vectors, stroke_width=2, **kwargs): + path = self.get_vector_sum_path(vectors, **kwargs) broken_path = CurvesAsSubmobjects(path) broken_path.curr_time = 0 def update_path(path, dt): - alpha = path.curr_time * self.get_slow_factor() + # alpha = path.curr_time * self.get_slow_factor() + alpha = self.get_vector_time() n_curves = len(path) for a, sp in zip(np.linspace(0, 1, n_curves), path): b = alpha - a @@ -173,12 +179,12 @@ class FourierCirclesScene(Scene): return broken_path def get_y_component_wave(self, - circles, + vectors, left_x=1, color=PINK, n_copies=2, right_shift_rate=5): - path = self.get_circle_end_path(circles) + path = self.get_vector_sum_path(vectors) wave = ParametricFunction( lambda t: op.add( right_shift_rate * t * LEFT, @@ -216,15 +222,16 @@ class FourierCirclesScene(Scene): return VGroup(wave, wave_copies) - def get_wave_y_line(self, circles, wave): + def get_wave_y_line(self, vectors, wave): return DashedLine( - circles[-1].get_start(), + vectors[-1].get_end(), wave[0].get_end(), stroke_width=1, dash_length=DEFAULT_DASH_LENGTH * 0.5, ) # Computing Fourier series + # i.e. where all the math happens def get_coefficients_of_path(self, path, n_samples=10000, freqs=None): if freqs is None: freqs = self.get_freqs() @@ -250,7 +257,7 @@ class FourierCirclesScene(Scene): class FourierSeriesIntroBackground4(FourierCirclesScene): CONFIG = { - "n_circles": 4, + "n_vectors": 4, "center_point": 4 * LEFT, "run_time": 30, "big_radius": 1.5, @@ -271,7 +278,7 @@ class FourierSeriesIntroBackground4(FourierCirclesScene): self.wait(self.run_time) def get_ks(self): - return np.arange(1, 2 * self.n_circles + 1, 2) + return np.arange(1, 2 * self.n_vectors + 1, 2) def get_freqs(self): return self.base_frequency * self.get_ks() @@ -282,53 +289,70 @@ class FourierSeriesIntroBackground4(FourierCirclesScene): class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4): CONFIG = { - "n_circles": 8, + "n_vectors": 8, } class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4): CONFIG = { - "n_circles": 12, + "n_vectors": 12, } class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4): CONFIG = { - "n_circles": 20, + "n_vectors": 20, } class FourierOfPiSymbol(FourierCirclesScene): CONFIG = { - "n_circles": 50, + "n_vectors": 51, "center_point": ORIGIN, "slow_factor": 0.1, - "run_time": 30, + "n_cycles": 1, "tex": "\\pi", "start_drawn": False, + "max_circle_stroke_width": 1, } def construct(self): + self.add_vectors_circles_path() + for n in range(self.n_cycles): + self.run_one_cycle() + + def add_vectors_circles_path(self): path = self.get_path() coefs = self.get_coefficients_of_path(path) - - circles = self.get_circles(coefficients=coefs) + vectors = self.get_rotating_vectors(coefficients=coefs) + circles = self.get_circles(vectors) self.set_decreasing_stroke_widths(circles) - # approx_path = self.get_circle_end_path(circles) - drawn_path = self.get_drawn_path(circles) + # approx_path = self.get_vector_sum_path(circles) + drawn_path = self.get_drawn_path(vectors) if self.start_drawn: - drawn_path.curr_time = 1 / self.slow_factor + self.vector_clock.increment_value(1) self.add(path) + self.add(vectors) self.add(circles) self.add(drawn_path) - self.wait(self.run_time) + + self.vectors = vectors + self.circles = circles + self.path = path + self.drawn_path = drawn_path + + def run_one_cycle(self): + time = 1 / self.slow_factor + self.wait(time) def set_decreasing_stroke_widths(self, circles): + mcsw = self.max_circle_stroke_width for k, circle in zip(it.count(1), circles): circle.set_stroke(width=max( - 1 / np.sqrt(k), - 1, + # mcsw / np.sqrt(k), + mcsw / k, + mcsw, )) return circles @@ -343,7 +367,7 @@ class FourierOfPiSymbol(FourierCirclesScene): class FourierOfName(FourierOfPiSymbol): CONFIG = { - "n_circles": 100, + "n_vectors": 100, "name_color": WHITE, "name_text": "Abc", "time_per_symbol": 5, @@ -388,14 +412,14 @@ class FourierOfName(FourierOfPiSymbol): class FourierOfPiSymbol5(FourierOfPiSymbol): CONFIG = { - "n_circles": 5, + "n_vectors": 5, "run_time": 10, } class FourierOfTrebleClef(FourierOfPiSymbol): CONFIG = { - "n_circles": 100, + "n_vectors": 101, "run_time": 10, "start_drawn": True, "file_name": "TrebleClef", @@ -419,7 +443,7 @@ class FourierOfIP(FourierOfTrebleClef): CONFIG = { "file_name": "IP_logo2", "height": 6, - "n_circles": 100, + "n_vectors": 100, } # def construct(self): @@ -451,7 +475,7 @@ class FourierOfEighthNote(FourierOfTrebleClef): class FourierOfN(FourierOfTrebleClef): CONFIG = { "height": 6, - "n_circles": 1000, + "n_vectors": 1000, } def get_shape(self): @@ -461,7 +485,7 @@ class FourierOfN(FourierOfTrebleClef): class FourierNailAndGear(FourierOfTrebleClef): CONFIG = { "height": 6, - "n_circles": 200, + "n_vectors": 200, "run_time": 100, "slow_factor": 0.01, "parametric_function_step_size": 0.0001, @@ -479,7 +503,7 @@ class FourierNailAndGear(FourierOfTrebleClef): class FourierBatman(FourierOfTrebleClef): CONFIG = { "height": 4, - "n_circles": 100, + "n_vectors": 100, "run_time": 10, "arrow_config": { "tip_length": 0.1, @@ -495,7 +519,7 @@ class FourierBatman(FourierOfTrebleClef): class FourierHeart(FourierOfTrebleClef): CONFIG = { "height": 4, - "n_circles": 100, + "n_vectors": 100, "run_time": 10, "arrow_config": { "tip_length": 0.1, @@ -517,7 +541,7 @@ class FourierHeart(FourierOfTrebleClef): class FourierNDQ(FourierOfTrebleClef): CONFIG = { "height": 4, - "n_circles": 1000, + "n_vectors": 1000, "run_time": 10, "arrow_config": { "tip_length": 0.1, @@ -535,7 +559,7 @@ class FourierNDQ(FourierOfTrebleClef): class FourierGoogleG(FourierOfTrebleClef): CONFIG = { - "n_circles": 10, + "n_vectors": 10, "height": 5, "g_colors": [ "#4285F4", @@ -570,7 +594,7 @@ class FourierGoogleG(FourierOfTrebleClef): class ExplainCircleAnimations(FourierCirclesScene): CONFIG = { - "n_circles": 100, + "n_vectors": 100, "center_point": 2 * DOWN, "n_top_circles": 9, "path_height": 3, diff --git a/active_projects/ode/part2/heat_equation.py b/active_projects/diffyq/part2/heat_equation.py similarity index 100% rename from active_projects/ode/part2/heat_equation.py rename to active_projects/diffyq/part2/heat_equation.py diff --git a/active_projects/ode/part2/pi_scenes.py b/active_projects/diffyq/part2/pi_scenes.py similarity index 100% rename from active_projects/ode/part2/pi_scenes.py rename to active_projects/diffyq/part2/pi_scenes.py diff --git a/active_projects/ode/part2/shared_constructs.py b/active_projects/diffyq/part2/shared_constructs.py similarity index 100% rename from active_projects/ode/part2/shared_constructs.py rename to active_projects/diffyq/part2/shared_constructs.py diff --git a/active_projects/ode/part2/staging.py b/active_projects/diffyq/part2/staging.py similarity index 100% rename from active_projects/ode/part2/staging.py rename to active_projects/diffyq/part2/staging.py diff --git a/active_projects/ode/part2/wordy_scenes.py b/active_projects/diffyq/part2/wordy_scenes.py similarity index 100% rename from active_projects/ode/part2/wordy_scenes.py rename to active_projects/diffyq/part2/wordy_scenes.py diff --git a/active_projects/ode/part3/discrete_case.py b/active_projects/diffyq/part3/discrete_case.py similarity index 100% rename from active_projects/ode/part3/discrete_case.py rename to active_projects/diffyq/part3/discrete_case.py diff --git a/active_projects/ode/part3/pi_creature_scenes.py b/active_projects/diffyq/part3/pi_creature_scenes.py similarity index 100% rename from active_projects/ode/part3/pi_creature_scenes.py rename to active_projects/diffyq/part3/pi_creature_scenes.py diff --git a/active_projects/ode/part3/staging.py b/active_projects/diffyq/part3/staging.py similarity index 100% rename from active_projects/ode/part3/staging.py rename to active_projects/diffyq/part3/staging.py diff --git a/active_projects/ode/part3/temperature_graphs.py b/active_projects/diffyq/part3/temperature_graphs.py similarity index 99% rename from active_projects/ode/part3/temperature_graphs.py rename to active_projects/diffyq/part3/temperature_graphs.py index 539a43e8..2ed445b5 100644 --- a/active_projects/ode/part3/temperature_graphs.py +++ b/active_projects/diffyq/part3/temperature_graphs.py @@ -31,6 +31,7 @@ class TemperatureGraphScene(SpecialThreeDScene): "stroke_color": WHITE, "stroke_opacity": 0.5, }, + "temp_text": "Temperature", } def get_three_d_axes(self, include_labels=True, include_numbers=False, **kwargs): @@ -103,7 +104,7 @@ class TemperatureGraphScene(SpecialThreeDScene): t_label.next_to(axes.y_axis.get_end(), UP) axes.y_axis.label = t_label - temp_label = TextMobject("Temperature") + temp_label = TextMobject(self.temp_text) temp_label.rotate(90 * DEGREES, RIGHT) temp_label.next_to(axes.z_axis.get_zenith(), RIGHT) axes.z_axis.label = temp_label diff --git a/active_projects/ode/part3/wordy_scenes.py b/active_projects/diffyq/part3/wordy_scenes.py similarity index 100% rename from active_projects/ode/part3/wordy_scenes.py rename to active_projects/diffyq/part3/wordy_scenes.py diff --git a/active_projects/diffyq/part4/fourier_series_scenes.py b/active_projects/diffyq/part4/fourier_series_scenes.py new file mode 100644 index 00000000..d2ea6fee --- /dev/null +++ b/active_projects/diffyq/part4/fourier_series_scenes.py @@ -0,0 +1,242 @@ +from manimlib.imports import * + +from active_projects.ode.part2.fourier_series import FourierOfTrebleClef + + +class ComplexFourierSeriesExample(FourierOfTrebleClef): + CONFIG = { + "file_name": "EighthNote", + "run_time": 10, + "n_vectors": 200, + "n_cycles": 2, + "max_circle_stroke_width": 0.75, + "drawing_height": 5, + "center_point": DOWN, + "top_row_y": 3, + "top_row_label_y": 2, + "top_row_x_spacing": 1.75, + "top_row_copy_scale_factor": 0.9, + "start_drawn": False, + } + + def construct(self): + self.add_vectors_circles_path() + self.add_top_row(self.vectors, self.circles) + self.write_title() + self.highlight_vectors_one_by_one() + self.change_shape() + + def write_title(self): + title = TextMobject("Complex\\\\Fourier series") + title.scale(1.5) + title.to_edge(LEFT) + title.match_y(self.path) + + self.wait(5) + self.play(FadeInFromDown(title)) + self.wait(2) + self.title = title + + def highlight_vectors_one_by_one(self): + # Don't know why these vectors can't get copied. + # That seems like a problem that will come up again. + labels = self.top_row[-1] + next_anims = [] + for vector, circle, label in zip(self.vectors, self.circles, labels): + # v_color = vector.get_color() + c_color = circle.get_color() + c_stroke_width = circle.get_stroke_width() + + rect = SurroundingRectangle(label, color=PINK) + self.play( + # vector.set_color, PINK, + circle.set_stroke, RED, 3, + FadeIn(rect), + *next_anims + ) + self.wait() + next_anims = [ + # vector.set_color, v_color, + circle.set_stroke, c_color, c_stroke_width, + FadeOut(rect), + ] + self.play(*next_anims) + + def change_shape(self): + # path_mob = TexMobject("\\pi") + path_mob = SVGMobject("Nail_And_Gear") + new_path = path_mob.family_members_with_points()[0] + new_path.set_height(4) + new_path.move_to(self.path, DOWN) + new_path.shift(0.5 * UP) + new_coefs = self.get_coefficients_of_path(new_path) + new_vectors = self.get_rotating_vectors( + coefficients=new_coefs + ) + new_drawn_path = self.get_drawn_path(new_vectors) + + self.vector_clock.set_value(0) + self.vector_clock.suspend_updating(0) + + vectors = self.vectors + anims = [] + + for vect, new_vect in zip(vectors, new_vectors): + new_vect.update() + new_vect.clear_updaters() + + line = Line(stroke_width=0) + line.put_start_and_end_on(*vect.get_start_and_end()) + anims.append(ApplyMethod( + line.put_start_and_end_on, + *new_vect.get_start_and_end() + )) + vect.freq = new_vect.freq + vect.phase = new_vect.phase + vect.coefficient = new_vect.coefficient + + vect.line = line + vect.add_updater( + lambda v: v.put_start_and_end_on( + *v.line.get_start_and_end() + ) + ) + anims += [ + FadeOut(self.drawn_path) + ] + + self.play(*anims, run_time=3) + self.vector_clock.resume_updating() + for vect in self.vectors: + vect.remove_updater(vect.updaters[-1]) + + self.add(new_drawn_path) + for n in range(self.n_cycles): + self.run_one_cycle() + + # + def get_path(self): + path = super().get_path() + path.set_height(self.drawing_height) + path.to_edge(DOWN) + return path + + def add_top_row(self, vectors, circles, max_freq=3): + self.top_row = self.get_top_row( + vectors, circles, max_freq + ) + self.add(self.top_row) + + def get_top_row(self, vectors, circles, max_freq=3): + vector_copies = VGroup() + circle_copies = VGroup() + for vector, circle in zip(vectors, circles): + if vector.freq > max_freq: + break + vcopy = vector.copy() + vcopy.clear_updaters() + ccopy = circle.copy() + ccopy.clear_updaters() + ccopy.original = circle + vcopy.original = vector + + vcopy.center_point = np.array([ + vector.freq * self.top_row_x_spacing, + self.top_row_y, + 0 + ]) + ccopy.center_point = vcopy.center_point + vcopy.add_updater(self.update_top_row_vector_copy) + ccopy.add_updater(self.update_top_row_circle_copy) + vector_copies.add(vcopy) + circle_copies.add(ccopy) + + dots = VGroup(*[ + TexMobject("\\dots").next_to( + circle_copies, direction, + MED_LARGE_BUFF, + ) + for direction in [LEFT, RIGHT] + ]) + labels = self.get_top_row_labels(vector_copies) + return VGroup( + vector_copies, + circle_copies, + dots, + labels, + ) + + def update_top_row_vector_copy(self, vcopy): + vcopy.become(vcopy.original) + vcopy.scale(self.top_row_copy_scale_factor) + vcopy.shift(vcopy.center_point - vcopy.get_start()) + return vcopy + + def update_top_row_circle_copy(self, ccopy): + ccopy.become(ccopy.original) + ccopy.scale(self.top_row_copy_scale_factor) + ccopy.move_to(ccopy.center_point) + return ccopy + + def get_top_row_labels(self, vector_copies): + labels = VGroup() + for vector_copy in vector_copies: + freq = vector_copy.freq + label = Integer(freq) + label.move_to(np.array([ + freq * self.top_row_x_spacing, + self.top_row_label_y, + 0 + ])) + labels.add(label) + return labels + + +class ComplexFourierSeriesExampleEnd(ExternallyAnimatedScene): + pass + + +class FourierSeriesExampleWithRectForZoom(ComplexFourierSeriesExample): + CONFIG = { + "n_vectors": 100, + "slow_factor": 0.01, + "rect_scale_factor": 0.15, + "parametric_function_step_size": 0.0001, + "start_drawn": True, + } + + def construct(self): + self.add_vectors_circles_path() + self.circles.set_stroke(opacity=0.5) + rect = self.get_rect() + rect.set_height(self.rect_scale_factor * FRAME_HEIGHT) + rect.add_updater(lambda m: m.move_to( + center_of_mass([ + v.get_end() + for v in self.vectors + ]) + )) + self.add(rect) + self.run_one_cycle() + + def get_rect(self): + return ScreenRectangle( + color=WHITE, + stroke_width=2, + ) + + +class ZoomedInFourierSeriesExample(FourierSeriesExampleWithRectForZoom, MovingCameraScene): + CONFIG = { + "vector_config": { + "max_tip_length_to_length_ratio": 0.15, + "tip_length": 0.05, + } + } + + def setup(self): + ComplexFourierSeriesExample.setup(self) + MovingCameraScene.setup(self) + + def get_rect(self): + return self.camera_frame diff --git a/active_projects/diffyq/part4/pi_creature_scenes.py b/active_projects/diffyq/part4/pi_creature_scenes.py new file mode 100644 index 00000000..69ef9d32 --- /dev/null +++ b/active_projects/diffyq/part4/pi_creature_scenes.py @@ -0,0 +1,22 @@ +from manimlib.imports import * + + +class WhyWouldYouCare(TeacherStudentsScene): + def construct(self): + self.student_says( + "Who cares!", + target_mode="sassy", + student_index=2, + added_anims=[self.teacher.change, "guilty"], + ) + self.wait() + self.play( + RemovePiCreatureBubble(self.students[2]), + self.teacher.change, "raise_right_hand", + self.get_student_changes( + "pondering", "erm", "thinking", + look_at_arg=self.screen, + ) + ) + self.look_at(self.screen) + self.wait(5) diff --git a/active_projects/diffyq/part4/staging.py b/active_projects/diffyq/part4/staging.py new file mode 100644 index 00000000..0e16ca07 --- /dev/null +++ b/active_projects/diffyq/part4/staging.py @@ -0,0 +1,345 @@ +from manimlib.imports import * +from active_projects.ode.part3.temperature_graphs import TemperatureGraphScene +from active_projects.ode.part2.wordy_scenes import WriteHeatEquationTemplate + + +class RelationToOtherVideos(Scene): + CONFIG = { + "camera_config": { + "background_color": DARK_GREY, + }, + } + + def construct(self): + # Show three videos + videos = self.get_video_thumbnails() + brace = Brace(videos, UP) + text = TextMobject("Heat equation") + text.scale(2) + text.next_to(brace, UP) + + self.play( + LaggedStartMap( + FadeInFrom, videos, + lambda m: (m, LEFT), + lag_ratio=0.4, + run_time=2, + ), + GrowFromCenter(brace), + FadeInFromDown(text), + ) + self.wait() + group = Group(text, brace, videos) + + # Show Fourier thinking + fourier = ImageMobject("Joseph Fourier") + fourier.set_height(4) + fourier.to_edge(RIGHT) + group.generate_target() + group.target.to_edge(DOWN) + fourier.align_to(group.target[0], DOWN) + bubble = ThoughtBubble( + direction=RIGHT, + width=3, + height=2, + fill_opacity=0.5, + stroke_color=WHITE, + ) + bubble[-1].shift(0.25 * DOWN + 0.5 * LEFT) + bubble[:-1].rotate(20 * DEGREES) + for mob in bubble[:-1]: + mob.rotate(-20 * DEGREES) + bubble.move_tip_to( + fourier.get_corner(UL) + DOWN + ) + bubble.to_edge(UP, buff=SMALL_BUFF) + + self.play( + MoveToTarget(group), + FadeInFrom(fourier, LEFT) + ) + self.play(Write(bubble, run_time=1)) + self.wait() + + # Discount first two + first_two = videos[:2] + first_two.generate_target() + first_two.target.scale(0.5) + first_two.target.to_corner(DL) + new_brace = Brace(first_two.target, UP) + + self.play( + # fourier.scale, 0.8, + fourier.match_x, new_brace, + fourier.to_edge, UP, + MoveToTarget(first_two), + Transform(brace, new_brace), + text.scale, 0.7, + text.next_to, new_brace, UP, + FadeOutAndShift(bubble, LEFT), + ) + self.play( + videos[2].scale, 1.7, + videos[2].to_corner, UR, + ) + self.wait() + + # + def get_video_thumbnails(self): + thumbnails = Group( + ImageMobject("diffyq_part2_thumbnail"), + ImageMobject("diffyq_part3_thumbnail"), + ImageMobject("diffyq_part4_thumbnail"), + ) + for thumbnail in thumbnails: + thumbnail.set_height(4) + thumbnail.add(SurroundingRectangle( + thumbnail, + color=WHITE, + stroke_width=2, + buff=0 + )) + thumbnails.arrange(RIGHT, buff=LARGE_BUFF) + thumbnails.set_width(FRAME_WIDTH - 1) + return thumbnails + + +class ShowLinearity(WriteHeatEquationTemplate, TemperatureGraphScene): + CONFIG = { + "temp_text": "Temp", + "alpha": 0.1, + "axes_config": { + "z_max": 2, + "z_min": -2, + "z_axis_config": { + "tick_frequency": 0.5, + "unit_size": 1.5, + }, + }, + "default_surface_config": { + "resolution": (16, 16) + # "resolution": (4, 4) + }, + "freqs": [2, 5], + } + + def setup(self): + TemperatureGraphScene.setup(self) + WriteHeatEquationTemplate.setup(self) + + def construct(self): + self.init_camera() + self.add_three_graphs() + self.show_words() + self.add_function_labels() + self.change_scalars() + + def init_camera(self): + self.camera.set_distance(1000) + + def add_three_graphs(self): + axes_group = self.get_axes_group() + axes0, axes1, axes2 = axes_group + freqs = self.freqs + scalar_trackers = Group( + ValueTracker(1), + ValueTracker(1), + ) + graphs = VGroup( + self.get_graph(axes0, [freqs[0]], [scalar_trackers[0]]), + self.get_graph(axes1, [freqs[1]], [scalar_trackers[1]]), + self.get_graph(axes2, freqs, scalar_trackers), + ) + + plus = TexMobject("+").scale(2) + equals = TexMobject("=").scale(2) + plus.move_to(midpoint( + axes0.get_right(), + axes1.get_left(), + )) + equals.move_to(midpoint( + axes1.get_right(), + axes2.get_left(), + )) + + self.add(axes_group) + self.add(graphs) + self.add(plus) + self.add(equals) + + self.axes_group = axes_group + self.graphs = graphs + self.scalar_trackers = scalar_trackers + self.plus = plus + self.equals = equals + + def show_words(self): + equation = self.get_d1_equation() + name = TextMobject("Heat equation") + name.next_to(equation, DOWN) + name.set_color_by_gradient(RED, YELLOW) + group = VGroup(equation, name) + group.to_edge(UP) + + shift_val = 0.5 * RIGHT + + arrow = Vector(1.5 * RIGHT) + arrow.move_to(group) + arrow.shift(shift_val) + linear_word = TextMobject("``Linear''") + linear_word.scale(2) + linear_word.next_to(arrow, RIGHT) + + self.add(group) + self.wait() + self.play( + ShowCreation(arrow), + group.next_to, arrow, LEFT + ) + self.play(FadeInFrom(linear_word, LEFT)) + self.wait() + + def add_function_labels(self): + axes_group = self.axes_group + graphs = self.graphs + + solution_labels = VGroup() + for axes in axes_group: + label = TextMobject("Solution", "$\\checkmark$") + label.set_color_by_tex("checkmark", GREEN) + label.next_to(axes, DOWN) + solution_labels.add(label) + + kw = { + "tex_to_color_map": { + "T_1": BLUE, + "T_2": GREEN, + } + } + T1 = TexMobject("a", "T_1", **kw) + T2 = TexMobject("b", "T_2", **kw) + T_sum = TexMobject("T_1", "+", "T_2", **kw) + T_sum_with_scalars = TexMobject( + "a", "T_1", "+", "b", "T_2", **kw + ) + + T1.next_to(graphs[0], UP) + T2.next_to(graphs[1], UP) + T_sum.next_to(graphs[2], UP) + T_sum.shift(SMALL_BUFF * DOWN) + T_sum_with_scalars.move_to(T_sum) + + a_brace = Brace(T1[0], UP, buff=SMALL_BUFF) + b_brace = Brace(T2[0], UP, buff=SMALL_BUFF) + s1_decimal = DecimalNumber() + s1_decimal.match_color(T1[1]) + s1_decimal.next_to(a_brace, UP, SMALL_BUFF) + s1_decimal.add_updater(lambda m: m.set_value( + self.scalar_trackers[0].get_value() + )) + s2_decimal = DecimalNumber() + s2_decimal.match_color(T2[1]) + s2_decimal.next_to(b_brace, UP, SMALL_BUFF) + s2_decimal.add_updater(lambda m: m.set_value( + self.scalar_trackers[1].get_value() + )) + + self.play( + FadeInFrom(T1[1], DOWN), + FadeInFrom(solution_labels[0], UP), + ) + self.play( + FadeInFrom(T2[1], DOWN), + FadeInFrom(solution_labels[1], UP), + ) + self.wait() + self.play( + TransformFromCopy(T1[1], T_sum[0]), + TransformFromCopy(T2[1], T_sum[2]), + TransformFromCopy(self.plus, T_sum[1]), + *[ + Transform( + graph.copy().set_fill(opacity=0), + graphs[2].copy().set_fill(opacity=0), + remover=True + ) + for graph in graphs[:2] + ] + ) + self.wait() + self.play(FadeInFrom(solution_labels[2], UP)) + self.wait() + + # Show constants + self.play( + FadeIn(T1[0]), + FadeIn(T2[0]), + FadeIn(a_brace), + FadeIn(b_brace), + FadeIn(s1_decimal), + FadeIn(s2_decimal), + FadeOut(T_sum), + FadeIn(T_sum_with_scalars), + ) + + def change_scalars(self): + s1, s2 = self.scalar_trackers + + kw = { + "run_time": 2, + } + for graph in self.graphs: + graph.resume_updating() + self.play(s2.set_value, -0.5, **kw) + self.play(s1.set_value, -0.2, **kw) + self.play(s2.set_value, 1.5, **kw) + self.play(s1.set_value, 1.2) + self.play(s2.set_value, 0.3) + self.wait() + + # + def get_axes_group(self): + axes_group = VGroup(*[ + self.get_axes() + for x in range(3) + ]) + axes_group.arrange(RIGHT, buff=2) + axes_group.set_width(FRAME_WIDTH - 1) + axes_group.to_edge(DOWN, buff=1) + return axes_group + + def get_axes(self): + axes = self.get_three_d_axes() + # axes.input_plane.set_fill(opacity=0) + # axes.input_plane.set_stroke(width=0.5) + # axes.add(axes.input_plane) + self.orient_three_d_mobject(axes) + axes.rotate(-5 * DEGREES, UP) + axes.set_width(4) + axes.x_axis.label.next_to( + axes.x_axis.get_end(), DOWN, + buff=2 * SMALL_BUFF + ) + return axes + + def get_graph(self, axes, freqs, scalar_trackers): + L = axes.x_max + a = self.alpha + + def func(x, t): + scalars = [st.get_value() for st in scalar_trackers] + return np.sum([ + s * np.cos(k * x) * np.exp(-a * (k**2) * t) + for freq, s in zip(freqs, scalars) + for k in [freq * PI / L] + ]) + + def get_surface_graph_group(): + return VGroup( + self.get_surface(axes, func), + self.get_time_slice_graph(axes, func, t=0), + ) + + result = always_redraw(get_surface_graph_group) + result.suspend_updating() + return result diff --git a/active_projects/ode/solve_pendulum_ode_sample_code.py b/active_projects/diffyq/solve_pendulum_ode_sample_code.py similarity index 100% rename from active_projects/ode/solve_pendulum_ode_sample_code.py rename to active_projects/diffyq/solve_pendulum_ode_sample_code.py diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index fbe16a3d..b6bb4be4 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -333,7 +333,7 @@ class Camera(object): vmobject, vmobject.points ) # TODO, shouldn't this be handled in transform_points_pre_display? - points = points - self.get_frame_center() + # points = points - self.get_frame_center() if len(points) == 0: return diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index a846cc55..2d37a474 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -514,6 +514,9 @@ class Line(TipableVMobject): about_point=self.get_start(), ) + def set_length(self, length): + self.scale(length / self.get_length()) + def set_opacity(self, opacity, family=True): # Overwrite default, which would set # the fill opacity diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 2e7cbaa9..e4419a5b 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -455,9 +455,10 @@ class Bubble(SVGMobject): mover.shift(point - self.get_tip()) return self - def flip(self): - Mobject.flip(self) - self.direction = -np.array(self.direction) + def flip(self, axis=UP): + Mobject.flip(self, axis=axis) + if abs(axis[0]) > 0: + self.direction = -np.array(self.direction) return self def pin_to(self, mobject):