A few more Fourier series scenes

This commit is contained in:
Grant Sanderson 2019-04-03 20:40:22 -07:00
parent 200c479759
commit 247c440d05
5 changed files with 417 additions and 47 deletions

View file

@ -1,7 +1,19 @@
from active_projects.ode.part2.staging import *
from active_projects.ode.part2.fourier_series import *
from active_projects.ode.part2.heat_equation import *
OUTPUT_DIRECTORY = "ode/part2"
ALL_SCENE_CLASSES = [
CirclesDrawingWave,
# Tests
FourierOfPiSymbol,
FourierOfPiSymbol5,
FourierOfTrebleClef,
# CirclesDrawingWave,
# Scenes for video
ExplainCircleAnimations,
FourierSeriesIntro,
FourierSeriesIntroBackground4,
FourierSeriesIntroBackground8,
FourierSeriesIntroBackground12,
FourierSeriesIntroBackground20,
]

View file

@ -2012,19 +2012,25 @@ class ManyStepsFromDifferentStartingPoints(TakeManyTinySteps):
class Thumbnail(IntroduceVectorField):
CONFIG = {
"vector_field_config": {
"delta_x": 1,
"delta_y": 1,
"delta_x": 0.5,
"delta_y": 0.5,
"max_magnitude": 5,
"length_func": lambda norm: 0.75 * sigmoid(norm),
"length_func": lambda norm: 0.5 * sigmoid(norm),
}
}
def construct(self):
self.initialize_plane()
self.plane.axes.set_stroke(width=0.5)
self.initialize_vector_field()
field = self.vector_field
field.set_stroke(width=5)
for vector in field:
vector.set_stroke(width=3)
vector.tip.set_stroke(width=0)
vector.tip.scale(1.5, about_point=vector.get_last_point())
vector.set_opacity(1)
title = TextMobject("Differential\\\\", "equations")
title.space_out_submobjects(0.8)

View file

@ -1,9 +1,10 @@
from big_ol_pile_of_manim_imports import *
# import scipy
class CirclesDrawing(Scene):
class FourierCirclesScene(Scene):
CONFIG = {
"n_circles": 4,
"n_circles": 10,
"big_radius": 2,
"colors": [
BLUE_D,
@ -14,38 +15,54 @@ class CirclesDrawing(Scene):
"circle_style": {
"stroke_width": 2,
},
"base_frequency": 0.25 * TAU,
"base_frequency": 1,
"slow_factor": 0.25,
"center_point": ORIGIN,
"parametric_function_step_size": 0.001,
}
#
def get_freqs_and_radii(self):
raise Exception("Not implemented")
def get_freqs(self):
n = self.n_circles
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)]
def get_color_iterator(self):
return it.cycle(self.colors)
def get_circles(self):
def get_circles(self, freqs=None, coefficients=None):
circles = VGroup()
color_iterator = self.get_color_iterator()
self.center_tracker = VectorizedPoint(self.center_point)
if freqs is None:
freqs = self.get_freqs()
if coefficients is None:
coefficients = self.get_coefficients()
last_circle = None
for freq, radius in self.get_freqs_and_radii():
for freq, coefficient in zip(freqs, coefficients):
if last_circle:
center_func = last_circle.get_start
else:
center_func = self.center_tracker.get_location
circle = self.get_circle(
radius=radius,
color=next(color_iterator),
coefficient=coefficient,
freq=freq,
center_func=center_func
color=next(color_iterator),
center_func=center_func,
)
circles.add(circle)
last_circle = circle
return circles
def get_circle(self, radius, color, freq, center_func):
def get_circle(self, coefficient, freq, color, center_func):
radius = abs(coefficient)
phase = np.log(coefficient).imag
circle = Circle(
radius=radius,
color=color,
@ -59,41 +76,60 @@ class CirclesDrawing(Scene):
)
circle.add(circle.radial_line)
circle.freq = freq
circle.phase = phase
circle.rotate(phase)
circle.coefficient = coefficient
circle.center_func = center_func
circle.add_updater(self.update_circle)
return circle
def update_circle(self, circle, dt):
circle.rotate(circle.freq * dt)
circle.rotate(
self.slow_factor * circle.freq * dt * TAU
)
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: v.put_start_and_end_on(
*circle.radial_line.get_start_and_end()
))
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]
radii = [c.radius for c in circles]
total_time = TAU / self.base_frequency
center = circles[0].get_center()
return ParametricFunction(
path = ParametricFunction(
lambda t: center + reduce(op.add, [
radius * rotate_vector(RIGHT, freq * t)
for freq, radius in zip(freqs, radii)
complex_to_R3(
coef * np.exp(TAU * 1j * freq * t)
)
for coef, freq in zip(coefs, freqs)
]),
t_min=0,
t_max=total_time,
t_max=1,
color=color,
step_size=0.005,
step_size=self.parametric_function_step_size,
)
return path
# TODO, this should be a general animated mobect
def get_drawn_path(self, circles, **kwargs):
path = self.get_circle_end_path(circles, **kwargs)
total_time = path.t_max
broken_path = CurvesAsSubmobjects(path)
broken_path.curr_time = 0
def update_path(path, dt):
alpha = (path.curr_time / total_time)
alpha = path.curr_time * self.slow_factor
n_curves = len(path)
for a, sp in zip(np.linspace(0, 1, n_curves), path):
b = alpha - a
@ -111,9 +147,9 @@ class CirclesDrawing(Scene):
def get_y_component_wave(self,
circles,
left_x=1,
color=BLUE,
color=PINK,
n_copies=2,
right_shift_rate=1.5):
right_shift_rate=5):
path = self.get_circle_end_path(circles)
wave = ParametricFunction(
lambda t: op.add(
@ -132,7 +168,7 @@ class CirclesDrawing(Scene):
top_point = wave_copies.get_top()
wave.creation = ShowCreation(
wave,
run_time=wave.t_max,
run_time=(1 / self.slow_factor),
rate_func=linear,
)
cycle_animation(wave.creation)
@ -141,7 +177,9 @@ class CirclesDrawing(Scene):
))
def update_wave_copies(wcs):
index = int(np.ceil(wave.creation.total_time / wave.t_max)) - 1
index = int(
wave.creation.total_time * self.slow_factor
)
wcs[:index].match_style(wave)
wcs[index:].set_stroke(width=0)
wcs.next_to(wave, RIGHT, buff=0)
@ -154,35 +192,327 @@ class CirclesDrawing(Scene):
return DashedLine(
circles[-1].get_start(),
wave[0].get_end(),
stroke_width=1,
dash_length=DEFAULT_DASH_LENGTH * 0.5,
)
# Computing Fourier series
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
if freqs is None:
freqs = self.get_freqs()
dt = 1 / n_samples
ts = np.arange(0, 1, dt)
samples = np.array([
path.point_from_proportion(t)
for t in ts
])
samples -= self.center_point
complex_samples = samples[:, 0] + 1j * samples[:, 1]
class CirclesDrawingWave(CirclesDrawing):
result = []
for freq in freqs:
riemann_sum = np.array([
np.exp(-TAU * 1j * freq * t) * cs
for t, cs in zip(ts, complex_samples)
]).sum() * dt
result.append(riemann_sum)
return result
class FourierSeriesIntroBackground4(FourierCirclesScene):
CONFIG = {
"n_circles": 4,
"center_point": 3 * LEFT,
"center_point": 4 * LEFT,
"run_time": 30,
"big_radius": 1.5,
}
def construct(self):
circles = self.get_circles()
path = self.get_drawn_path(circles)
wave = self.get_y_component_wave(circles)
# small_path = self.get_drawn_path(circles[:1])
wave1 = self.get_y_component_wave(circles[:1], color=PINK)
wave2 = self.get_y_component_wave(circles[:2], color=RED)
# Why?
circles.update(-1 / self.camera.frame_rate)
#
h_line = always_redraw(
lambda: self.get_wave_y_line(circles, wave)
)
self.add(circles, path, wave, h_line)
self.add(wave1, wave2)
self.wait(10)
def get_freqs_and_radii(self):
return [
(k * self.base_frequency, self.big_radius / k)
for k in range(1, 2 * self.n_circles + 1, 2)
# Why?
circles.update(-1 / self.camera.frame_rate)
#
self.add(circles, path, wave, h_line)
self.wait(self.run_time)
def get_ks(self):
return np.arange(1, 2 * self.n_circles + 1, 2)
def get_freqs(self):
return self.base_frequency * self.get_ks()
def get_coefficients(self):
return self.big_radius / self.get_ks()
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 8,
}
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 12,
}
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 20,
}
class FourierOfPiSymbol(FourierCirclesScene):
CONFIG = {
"n_circles": 50,
"center_point": ORIGIN,
"slow_factor": 0.1,
"run_time": 30,
"tex": "\\pi",
"start_drawn": False,
}
def construct(self):
path = self.get_path()
coefs = self.get_coefficients_of_path(path)
circles = self.get_circles(coefficients=coefs)
for k, circle in zip(it.count(1), circles):
circle.set_stroke(width=max(
1 / np.sqrt(k),
1,
))
# approx_path = self.get_circle_end_path(circles)
drawn_path = self.get_drawn_path(circles)
if self.start_drawn:
drawn_path.curr_time = 1 / self.slow_factor
self.add(path)
self.add(circles)
self.add(drawn_path)
self.wait(self.run_time)
def get_path(self):
tex_mob = TexMobject(self.tex)
tex_mob.set_height(6)
path = tex_mob.family_members_with_points()[0]
path.set_fill(opacity=0)
path.set_stroke(WHITE, 1)
return path
class FourierOfPiSymbol5(FourierOfPiSymbol):
CONFIG = {
"n_circles": 5,
"run_time": 10,
}
class FourierOfTrebleClef(FourierOfPiSymbol):
CONFIG = {
"n_circles": 100,
"run_time": 10,
"start_drawn": True,
}
def get_path(self):
path = SVGMobject("TrebleClef")
path = path.family_members_with_points()[0]
path.set_height(7.5)
path.set_fill(opacity=0)
path.set_stroke(WHITE, 0)
return path
class ExplainCircleAnimations(FourierCirclesScene):
CONFIG = {
# "n_circles": 100,
"n_circles": 20,
"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.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):
self.path = self.get_path()
self.add(self.path)
def add_circles(self):
coefs = self.get_coefficients_of_path(self.path)
self.circles = self.get_circles(coefficients=coefs)
self.add(self.circles)
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()
center_trackers = VGroup()
for circle in top_circles:
tracker = VectorizedPoint(circle.center_func())
circle.center_func = tracker.get_location
center_trackers.add(tracker)
tracker.freq = circle.freq
tracker.circle = circle
center_trackers.submobjects.sort(
key=lambda m: m.freq
)
center_trackers.generate_target()
right_buff = 1.45
center_trackers.target.arrange(RIGHT, buff=right_buff)
center_trackers.target.to_edge(UP, buff=1.25)
self.add(top_circles)
self.play(
MoveToTarget(center_trackers),
run_time=2
)
self.wait(4)
self.top_circles = top_circles
self.center_trackers = center_trackers
def show_frequencies(self):
center_trackers = self.center_trackers
freq_numbers = VGroup()
for ct in center_trackers:
number = Integer(ct.freq)
number.next_to(ct, DOWN, buff=1)
freq_numbers.add(number)
ct.circle.number = number
ld, rd = [
TexMobject("\\dots")
for x in range(2)
]
ld.next_to(freq_numbers, LEFT, MED_LARGE_BUFF)
rd.next_to(freq_numbers, RIGHT, MED_LARGE_BUFF)
freq_numbers.add_to_back(ld)
freq_numbers.add(rd)
freq_word = TextMobject("Frequencies")
freq_word.scale(1.5)
freq_word.set_color(YELLOW)
freq_word.next_to(freq_numbers, DOWN, MED_LARGE_BUFF)
self.play(
LaggedStartMap(
FadeInFromDown, freq_numbers
)
)
self.play(
Write(freq_word),
LaggedStartMap(
ShowCreationThenFadeAround, freq_numbers,
)
)
self.wait(2)
def show_examples_for_frequencies(self):
top_circles = self.top_circles
c1, c2, c3 = [
list(filter(
lambda c: c.freq == k,
top_circles
))[0]
for k in (1, 2, 3)
]
neg_circles = VGroup(*filter(
lambda c: c.freq < 0,
top_circles
))
for c in [c1, c2, c3, *neg_circles]:
c.rect = SurroundingRectangle(c)
self.play(
ShowCreation(c2.rect),
WiggleOutThenIn(c2.number),
)
self.wait(2)
self.play(
ReplacementTransform(c2.rect, c1.rect),
)
self.play(FadeOut(c1.rect))
self.wait()
self.play(
ShowCreation(c3.rect),
WiggleOutThenIn(c3.number),
)
self.play(
FadeOut(c3.rect),
)
self.wait(2)
self.play(
LaggedStart(*[
ShowCreationThenFadeOut(c.rect)
for c in neg_circles
])
)
self.wait(3)
def show_as_vectors(self):
top_circles = self.top_circles
top_vectors = self.get_rotating_vectors(top_circles)
self.play(
top_circles.set_stroke, {"width": 0.5},
FadeIn(top_vectors),
)
self.wait(3)
self.top_vectors = top_vectors
def show_vector_sum(self):
top_circles = self.top_circles
top_vectors = self.top_vectors
self.play(
FadeOut(self.path),
FadeOut(self.circles),
)
def moons_of_moons_of_moons(self):
pass
def tweak_starting_vectors(self):
pass
#
def get_path(self):
tex = TexMobject("f")
path = tex.family_members_with_points()[0]
path.set_stroke(WHITE, 1)
path.set_fill(opacity=0)
path.set_height(self.path_height)
path.move_to(self.center_point)
return path
# return Square().set_height(3)

View file

@ -0,0 +1,6 @@
from big_ol_pile_of_manim_imports import *
class NewSceneName(Scene):
def construct(self):
pass

View file

@ -1,6 +1,22 @@
from big_ol_pile_of_manim_imports import *
class NewSceneName(Scene):
class FourierSeriesIntro(Scene):
def construct(self):
pass
title = TextMobject(
"Fourier ", "Series", ":",
" An origin story",
arg_separator="",
)
title.scale(2)
title.to_edge(UP)
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.add(title)
self.add(image)
self.add(name)