mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
7d596d0840
29 changed files with 760 additions and 104 deletions
18
active_projects/diffyq/all_part4_scenes.py
Normal file
18
active_projects/diffyq/all_part4_scenes.py
Normal file
|
@ -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,
|
||||
]
|
|
@ -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,
|
|
@ -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
|
242
active_projects/diffyq/part4/fourier_series_scenes.py
Normal file
242
active_projects/diffyq/part4/fourier_series_scenes.py
Normal file
|
@ -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
|
22
active_projects/diffyq/part4/pi_creature_scenes.py
Normal file
22
active_projects/diffyq/part4/pi_creature_scenes.py
Normal file
|
@ -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)
|
345
active_projects/diffyq/part4/staging.py
Normal file
345
active_projects/diffyq/part4/staging.py
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue