3b1b-manim/old_projects/fourier.py
2019-05-02 20:36:14 -07:00

4291 lines
138 KiB
Python

# -*- coding: utf-8 -*-
from constants import *
import scipy.integrate
from manimlib.imports import *
USE_ALMOST_FOURIER_BY_DEFAULT = True
NUM_SAMPLES_FOR_FFT = 1000
DEFAULT_COMPLEX_TO_REAL_FUNC = lambda z : z.real
def get_fourier_graph(
axes, time_func, t_min, t_max,
n_samples = NUM_SAMPLES_FOR_FFT,
complex_to_real_func = lambda z : z.real,
color = RED,
):
# N = n_samples
# T = time_range/n_samples
time_range = float(t_max - t_min)
time_step_size = time_range/n_samples
time_samples = np.vectorize(time_func)(np.linspace(t_min, t_max, n_samples))
fft_output = np.fft.fft(time_samples)
frequencies = np.linspace(0.0, n_samples/(2.0*time_range), n_samples//2)
# #Cycles per second of fouier_samples[1]
# (1/time_range)*n_samples
# freq_step_size = 1./time_range
graph = VMobject()
graph.set_points_smoothly([
axes.coords_to_point(
x, complex_to_real_func(y)/n_samples,
)
for x, y in zip(frequencies, fft_output[:n_samples//2])
])
graph.set_color(color)
f_min, f_max = [
axes.x_axis.point_to_number(graph.points[i])
for i in (0, -1)
]
graph.underlying_function = lambda f : axes.y_axis.point_to_number(
graph.point_from_proportion((f - f_min)/(f_max - f_min))
)
return graph
def get_fourier_transform(
func, t_min, t_max,
complex_to_real_func = DEFAULT_COMPLEX_TO_REAL_FUNC,
use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT,
**kwargs ##Just eats these
):
scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0
def fourier_transform(f):
z = scalar*scipy.integrate.quad(
lambda t : func(t)*np.exp(complex(0, -TAU*f*t)),
t_min, t_max
)[0]
return complex_to_real_func(z)
return fourier_transform
##
class Introduction(TeacherStudentsScene):
def construct(self):
title = TextMobject("Fourier Transform")
title.scale(1.2)
title.to_edge(UP, buff = MED_SMALL_BUFF)
func = lambda t : np.cos(2*TAU*t) + np.cos(3*TAU*t)
graph = FunctionGraph(func, x_min = 0, x_max = 5)
graph.stretch(0.25, 1)
graph.next_to(title, DOWN)
graph.to_edge(LEFT)
graph.set_color(BLUE)
fourier_graph = FunctionGraph(
get_fourier_transform(func, 0, 5),
x_min = 0, x_max = 5
)
fourier_graph.move_to(graph)
fourier_graph.to_edge(RIGHT)
fourier_graph.set_color(RED)
arrow = Arrow(graph, fourier_graph, color = WHITE)
self.add(title, graph)
self.student_thinks(
"What's that?",
look_at_arg = title,
target_mode = "confused",
student_index = 1,
)
self.play(
GrowArrow(arrow),
ReplacementTransform(graph.copy(), fourier_graph)
)
self.wait(2)
self.student_thinks(
"Pssht, I got this",
target_mode = "tease",
student_index = 2,
added_anims = [RemovePiCreatureBubble(self.students[1])]
)
self.play(self.teacher.change, "hesitant")
self.wait(2)
class TODOInsertUnmixingSound(TODOStub):
CONFIG = {
"message" : "Show unmixing sound"
}
class OtherContexts(PiCreatureScene):
def construct(self):
items = VGroup(*list(map(TextMobject, [
"Extracting frequencies from sound",
"Uncertainty principle",
"Riemann Zeta function and primes",
"Differential equations",
])))
items.arrange(
DOWN, buff = MED_LARGE_BUFF,
aligned_edge = LEFT
)
items.to_corner(UP+LEFT)
items[1:].set_fill(opacity = 0.2)
morty = self.pi_creature
morty.to_corner(UP+RIGHT)
self.add(items)
for item in items[1:]:
self.play(
LaggedStartMap(
ApplyMethod, item,
lambda m : (m.set_fill, {"opacity" : 1}),
),
morty.change, "thinking",
)
self.wait()
class TODOInsertCosineWrappingAroundCircle(TODOStub):
CONFIG = {
"message" : "Give a picture-in-picture \\\\ of cosine wrapping around circle",
}
class AddingPureFrequencies(PiCreatureScene):
CONFIG = {
"A_frequency" : 2.1,
"A_color" : YELLOW,
"D_color" : PINK,
"F_color" : TEAL,
"C_color" : RED,
"sum_color" : GREEN,
"equilibrium_height" : 1.5,
}
def construct(self):
self.add_speaker()
self.play_A440()
self.measure_air_pressure()
self.play_lower_pitch()
self.play_mix()
self.separate_out_parts()
self.draw_sum_at_single_point()
self.draw_full_sum()
self.add_more_notes()
def add_speaker(self):
speaker = SVGMobject(file_name = "speaker")
speaker.to_edge(DOWN)
self.add(speaker)
self.speaker = speaker
def play_A440(self):
randy = self.pi_creature
A_label = TextMobject("A440")
A_label.set_color(self.A_color)
A_label.next_to(self.speaker, UP)
self.broadcast(
FadeIn(A_label),
Succession(
ApplyMethod, randy.change, "pondering",
Animation, randy,
Blink, randy
)
)
self.set_variables_as_attrs(A_label)
def measure_air_pressure(self):
randy = self.pi_creature
axes = Axes(
y_min = -2, y_max = 2,
x_min = 0, x_max = 10,
number_line_config = {"include_tip" : False},
)
axes.stretch_to_fit_height(2)
axes.to_corner(UP+LEFT)
axes.shift(LARGE_BUFF*DOWN)
eh = self.equilibrium_height
equilibrium_line = DashedLine(
axes.coords_to_point(0, eh),
axes.coords_to_point(axes.x_max, eh),
stroke_width = 2,
stroke_color = LIGHT_GREY
)
frequency = self.A_frequency
graph = self.get_wave_graph(frequency, axes)
func = graph.underlying_function
graph.set_color(self.A_color)
pressure = TextMobject("Pressure")
time = TextMobject("Time")
for label in pressure, time:
label.scale_in_place(0.8)
pressure.next_to(axes.y_axis, UP)
pressure.to_edge(LEFT, buff = MED_SMALL_BUFF)
time.next_to(axes.x_axis.get_right(), DOWN+LEFT)
axes.labels = VGroup(pressure, time)
n = 10
brace = Brace(Line(
axes.coords_to_point(n/frequency, func(n/frequency)),
axes.coords_to_point((n+1)/frequency, func((n+1)/frequency)),
), UP)
words = brace.get_text("Imagine 440 per second", buff = SMALL_BUFF)
words.scale(0.8, about_point = words.get_bottom())
self.play(
FadeIn(pressure),
ShowCreation(axes.y_axis)
)
self.play(
Write(time),
ShowCreation(axes.x_axis)
)
self.broadcast(
ShowCreation(graph, run_time = 4, rate_func=linear),
ShowCreation(equilibrium_line),
)
axes.add(equilibrium_line)
self.play(
randy.change, "erm", graph,
GrowFromCenter(brace),
Write(words)
)
self.wait()
graph.save_state()
self.play(
FadeOut(brace),
FadeOut(words),
VGroup(axes, graph, axes.labels).shift, 0.8*UP,
graph.fade, 0.85,
graph.shift, 0.8*UP,
)
graph.saved_state.move_to(graph)
self.set_variables_as_attrs(axes, A_graph = graph)
def play_lower_pitch(self):
axes = self.axes
randy = self.pi_creature
frequency = self.A_frequency*(2.0/3.0)
graph = self.get_wave_graph(frequency, axes)
graph.set_color(self.D_color)
D_label = TextMobject("D294")
D_label.set_color(self.D_color)
D_label.move_to(self.A_label)
self.play(
FadeOut(self.A_label),
GrowFromCenter(D_label),
)
self.broadcast(
ShowCreation(graph, run_time = 4, rate_func=linear),
randy.change, "happy",
n_circles = 6,
)
self.play(randy.change, "confused", graph)
self.wait(2)
self.set_variables_as_attrs(
D_label,
D_graph = graph
)
def play_mix(self):
self.A_graph.restore()
self.broadcast(
self.get_broadcast_animation(n_circles = 6),
self.pi_creature.change, "thinking",
*[
ShowCreation(graph, run_time = 4, rate_func=linear)
for graph in (self.A_graph, self.D_graph)
]
)
self.wait()
def separate_out_parts(self):
axes = self.axes
speaker = self.speaker
randy = self.pi_creature
A_axes = axes.deepcopy()
A_graph = self.A_graph
A_label = self.A_label
D_axes = axes.deepcopy()
D_graph = self.D_graph
D_label = self.D_label
movers = [A_axes, A_graph, A_label, D_axes, D_graph, D_label]
for mover in movers:
mover.generate_target()
D_target_group = VGroup(D_axes.target, D_graph.target)
A_target_group = VGroup(A_axes.target, A_graph.target)
D_target_group.next_to(axes, DOWN, MED_LARGE_BUFF)
A_target_group.next_to(D_target_group, DOWN, MED_LARGE_BUFF)
A_label.fade(1)
A_label.target.next_to(A_graph.target, UP)
D_label.target.next_to(D_graph.target, UP)
self.play(*it.chain(
list(map(MoveToTarget, movers)),
[
ApplyMethod(mob.shift, FRAME_Y_RADIUS*DOWN, remover = True)
for mob in (randy, speaker)
]
))
self.wait()
self.set_variables_as_attrs(A_axes, D_axes)
def draw_sum_at_single_point(self):
axes = self.axes
A_axes = self.A_axes
D_axes = self.D_axes
A_graph = self.A_graph
D_graph = self.D_graph
x = 2.85
A_line = self.get_A_graph_v_line(x)
D_line = self.get_D_graph_v_line(x)
lines = VGroup(A_line, D_line)
sum_lines = lines.copy()
sum_lines.generate_target()
self.stack_v_lines(x, sum_lines.target)
top_axes_point = axes.coords_to_point(x, self.equilibrium_height)
x_point = np.array(top_axes_point)
x_point[1] = 0
v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS).move_to(x_point)
self.revert_to_original_skipping_status()
self.play(GrowFromCenter(v_line))
self.play(FadeOut(v_line))
self.play(*list(map(ShowCreation, lines)))
self.wait()
self.play(MoveToTarget(sum_lines, path_arc = np.pi/4))
self.wait(2)
# self.play(*[
# Transform(
# line,
# VectorizedPoint(axes.coords_to_point(0, self.equilibrium_height)),
# remover = True
# )
# for line, axes in [
# (A_line, A_axes),
# (D_line, D_axes),
# (sum_lines, axes),
# ]
# ])
self.lines_to_fade = VGroup(A_line, D_line, sum_lines)
def draw_full_sum(self):
axes = self.axes
def new_func(x):
result = self.A_graph.underlying_function(x)
result += self.D_graph.underlying_function(x)
result -= self.equilibrium_height
return result
sum_graph = axes.get_graph(new_func)
sum_graph.set_color(self.sum_color)
thin_sum_graph = sum_graph.copy().fade()
A_graph = self.A_graph
D_graph = self.D_graph
D_axes = self.D_axes
rect = Rectangle(
height = 2.5*FRAME_Y_RADIUS,
width = MED_SMALL_BUFF,
stroke_width = 0,
fill_color = YELLOW,
fill_opacity = 0.4
)
self.play(
ReplacementTransform(A_graph.copy(), thin_sum_graph),
ReplacementTransform(D_graph.copy(), thin_sum_graph),
# FadeOut(self.lines_to_fade)
)
self.play(
self.get_graph_line_animation(self.A_axes, self.A_graph),
self.get_graph_line_animation(self.D_axes, self.D_graph),
self.get_graph_line_animation(axes, sum_graph.deepcopy()),
ShowCreation(sum_graph),
run_time = 15,
rate_func=linear
)
self.remove(thin_sum_graph)
self.wait()
for x in 2.85, 3.57:
rect.move_to(D_axes.coords_to_point(x, 0))
self.play(GrowFromPoint(rect, rect.get_top()))
self.wait()
self.play(FadeOut(rect))
self.sum_graph = sum_graph
def add_more_notes(self):
axes = self.axes
A_group = VGroup(self.A_axes, self.A_graph, self.A_label)
D_group = VGroup(self.D_axes, self.D_graph, self.D_label)
squish_group = VGroup(A_group, D_group)
squish_group.generate_target()
squish_group.target.stretch(0.5, 1)
squish_group.target.next_to(axes, DOWN, buff = -SMALL_BUFF)
for group in squish_group.target:
label = group[-1]
bottom = label.get_bottom()
label.stretch_in_place(0.5, 0)
label.move_to(bottom, DOWN)
self.play(
MoveToTarget(squish_group),
FadeOut(self.lines_to_fade),
)
F_axes = self.D_axes.deepcopy()
C_axes = self.A_axes.deepcopy()
VGroup(F_axes, C_axes).next_to(squish_group, DOWN)
F_graph = self.get_wave_graph(self.A_frequency*4.0/5, F_axes)
F_graph.set_color(self.F_color)
C_graph = self.get_wave_graph(self.A_frequency*6.0/5, C_axes)
C_graph.set_color(self.C_color)
F_label = TextMobject("F349")
C_label = TextMobject("C523")
for label, graph in (F_label, F_graph), (C_label, C_graph):
label.scale(0.5)
label.set_color(graph.get_stroke_color())
label.next_to(graph, UP, SMALL_BUFF)
graphs = VGroup(self.A_graph, self.D_graph, F_graph, C_graph)
def new_sum_func(x):
result = sum([
graph.underlying_function(x) - self.equilibrium_height
for graph in graphs
])
result *= 0.5
return result + self.equilibrium_height
new_sum_graph = self.axes.get_graph(
new_sum_func,
num_graph_points = 200
)
new_sum_graph.set_color(BLUE_C)
thin_new_sum_graph = new_sum_graph.copy().fade()
self.play(*it.chain(
list(map(ShowCreation, [F_axes, C_axes, F_graph, C_graph])),
list(map(Write, [F_label, C_label])),
list(map(FadeOut, [self.sum_graph]))
))
self.play(ReplacementTransform(
graphs.copy(), thin_new_sum_graph
))
kwargs = {"rate_func" : None, "run_time" : 10}
self.play(ShowCreation(new_sum_graph.copy(), **kwargs), *[
self.get_graph_line_animation(curr_axes, graph, **kwargs)
for curr_axes, graph in [
(self.A_axes, self.A_graph),
(self.D_axes, self.D_graph),
(F_axes, F_graph),
(C_axes, C_graph),
(axes, new_sum_graph),
]
])
self.wait()
####
def broadcast(self, *added_anims, **kwargs):
self.play(self.get_broadcast_animation(**kwargs), *added_anims)
def get_broadcast_animation(self, **kwargs):
kwargs["run_time"] = kwargs.get("run_time", 5)
kwargs["n_circles"] = kwargs.get("n_circles", 10)
return Broadcast(self.speaker[1], **kwargs)
def get_wave_graph(self, frequency, axes):
tail_len = 3.0
x_min, x_max = axes.x_min, axes.x_max
def func(x):
value = 0.7*np.cos(2*np.pi*frequency*x)
if x - x_min < tail_len:
value *= smooth((x-x_min)/tail_len)
if x_max - x < tail_len:
value *= smooth((x_max - x )/tail_len)
return value + self.equilibrium_height
ngp = 2*(x_max - x_min)*frequency + 1
graph = axes.get_graph(func, num_graph_points = int(ngp))
return graph
def get_A_graph_v_line(self, x):
return self.get_graph_v_line(x, self.A_axes, self.A_graph)
def get_D_graph_v_line(self, x):
return self.get_graph_v_line(x, self.D_axes, self.D_graph)
def get_graph_v_line(self, x, axes, graph):
result = Line(
axes.coords_to_point(x, self.equilibrium_height),
# axes.coords_to_point(x, graph.underlying_function(x)),
graph.point_from_proportion(float(x)/axes.x_max),
color = WHITE,
buff = 0,
)
return result
def stack_v_lines(self, x, lines):
point = self.axes.coords_to_point(x, self.equilibrium_height)
A_line, D_line = lines
A_line.shift(point - A_line.get_start())
D_line.shift(A_line.get_end()-D_line.get_start())
A_line.set_color(self.A_color)
D_line.set_color(self.D_color)
return lines
def create_pi_creature(self):
return Randolph().to_corner(DOWN+LEFT)
def get_graph_line_animation(self, axes, graph, **kwargs):
line = self.get_graph_v_line(0, axes, graph)
x_max = axes.x_max
def update_line(line, alpha):
x = alpha*x_max
Transform(line, self.get_graph_v_line(x, axes, graph)).update(1)
return line
return UpdateFromAlphaFunc(line, update_line, **kwargs)
class BreakApartSum(Scene):
CONFIG = {
"frequencies" : [0.5, 1.5, 2, 2.5, 5],
"equilibrium_height" : 2.0,
}
def construct(self):
self.show_initial_sound()
self.decompose_sound()
self.ponder_question()
def show_initial_sound(self):
def func(x):
return self.equilibrium_height + 0.2*np.sum([
np.cos(2*np.pi*f*x)
for f in self.frequencies
])
axes = Axes(
x_min = 0, x_max = 5,
y_min = -1, y_max = 5,
x_axis_config = {
"include_tip" : False,
"unit_size" : 2.0,
},
y_axis_config = {
"include_tip" : False,
"unit_size" : 0.5,
},
)
axes.stretch_to_fit_width(FRAME_WIDTH - 2)
axes.stretch_to_fit_height(3)
axes.center()
axes.to_edge(LEFT)
graph = axes.get_graph(func, num_graph_points = 200)
graph.set_color(YELLOW)
v_line = Line(ORIGIN, 4*UP)
v_line.move_to(axes.coords_to_point(0, 0), DOWN)
dot = Dot(color = PINK)
dot.move_to(graph.point_from_proportion(0))
self.add(axes, graph)
self.play(
v_line.move_to, axes.coords_to_point(5, 0), DOWN,
MoveAlongPath(dot, graph),
run_time = 8,
rate_func=linear,
)
self.play(*list(map(FadeOut, [dot, v_line])))
self.set_variables_as_attrs(axes, graph)
def decompose_sound(self):
axes, graph = self.axes, self.graph
pure_graphs = VGroup(*[
axes.get_graph(
lambda x : 0.5*np.cos(2*np.pi*freq*x),
num_graph_points = 100,
)
for freq in self.frequencies
])
pure_graphs.set_color_by_gradient(BLUE, RED)
pure_graphs.arrange(DOWN, buff = MED_LARGE_BUFF)
h_line = DashedLine(6*LEFT, 6*RIGHT)
self.play(
FadeOut(axes),
graph.to_edge, UP
)
pure_graphs.next_to(graph, DOWN, LARGE_BUFF)
h_line.next_to(graph, DOWN, MED_LARGE_BUFF)
self.play(ShowCreation(h_line))
for pure_graph in reversed(pure_graphs):
self.play(ReplacementTransform(graph.copy(), pure_graph))
self.wait()
self.all_graphs = VGroup(graph, h_line, pure_graphs)
self.pure_graphs = pure_graphs
def ponder_question(self):
all_graphs = self.all_graphs
pure_graphs = self.pure_graphs
randy = Randolph()
randy.to_corner(DOWN+LEFT)
self.play(
FadeIn(randy),
all_graphs.scale, 0.75,
all_graphs.to_corner, UP+RIGHT,
)
self.play(randy.change, "pondering", all_graphs)
self.play(Blink(randy))
rect = SurroundingRectangle(pure_graphs, color = WHITE)
self.play(
ShowCreation(rect),
LaggedStartMap(
ApplyFunction, pure_graphs,
lambda g : (lambda m : m.shift(SMALL_BUFF*UP).set_color(YELLOW), g),
rate_func = wiggle
)
)
self.play(FadeOut(rect))
self.play(Blink(randy))
self.wait()
class Quadrant(VMobject):
CONFIG = {
"radius" : 2,
"stroke_width": 0,
"fill_opacity" : 1,
"density" : 50,
"density_exp" : 2.0,
}
def generate_points(self):
points = [r*RIGHT for r in np.arange(0, self.radius, 1./self.density)]
points += [
self.radius*(np.cos(theta)*RIGHT + np.sin(theta)*UP)
for theta in np.arange(0, TAU/4, 1./(self.radius*self.density))
]
points += [r*UP for r in np.arange(self.radius, 0, -1./self.density)]
self.set_points_smoothly(points)
class UnmixMixedPaint(Scene):
CONFIG = {
"colors" : [BLUE, RED, YELLOW, GREEN],
}
def construct(self):
angles = np.arange(4)*np.pi/2
quadrants = VGroup(*[
Quadrant().rotate(angle, about_point = ORIGIN).set_color(color)
for color, angle in zip(self.colors, angles)
])
quadrants.add(*it.chain(*[
quadrants.copy().rotate(angle)
for angle in np.linspace(0, 0.02*TAU, 10)
]))
quadrants.set_fill(opacity = 0.5)
mud_color = average_color(*self.colors)
mud_circle = Circle(radius = 2, stroke_width = 0)
mud_circle.set_fill(mud_color, 1)
mud_circle.save_state()
mud_circle.scale(0)
def update_quadrant(quadrant, alpha):
points = quadrant.get_anchors()
dt = 0.03 #Hmm, this has no dependency on frame rate...
norms = np.apply_along_axis(get_norm, 1, points)
points[:,0] -= dt*points[:,1]/np.clip(norms, 0.1, np.inf)
points[:,1] += dt*points[:,0]/np.clip(norms, 0.1, np.inf)
new_norms = np.apply_along_axis(get_norm, 1, points)
new_norms = np.clip(new_norms, 0.001, np.inf)
radius = np.max(norms)
multiplier = norms/new_norms
multiplier = multiplier.reshape((len(multiplier), 1))
multiplier.repeat(points.shape[1], axis = 1)
points *= multiplier
quadrant.set_points_smoothly(points)
self.add(quadrants)
run_time = 30
self.play(
*[
UpdateFromAlphaFunc(quadrant, update_quadrant)
for quadrant in quadrants
] + [
ApplyMethod(mud_circle.restore, rate_func=linear)
],
run_time = run_time
)
#Incomplete, and probably not useful
class MachineThatTreatsOneFrequencyDifferently(Scene):
def construct(self):
graph = self.get_cosine_graph(0.5)
frequency_mob = DecimalNumber(220, num_decimal_places = 0)
frequency_mob.next_to(graph, UP, buff = MED_LARGE_BUFF)
self.graph = graph
self.frequency_mob = frequency_mob
self.add(graph, frequency_mob)
arrow1, q_marks, arrow2 = group = VGroup(
Vector(DOWN), TextMobject("???").scale(1.5), Vector(DOWN)
)
group.set_color(WHITE)
group.arrange(DOWN)
group.next_to(graph, DOWN)
self.add(group)
self.change_graph_frequency(1)
graph.set_color(GREEN)
self.wait()
graph.set_color(YELLOW)
self.change_graph_frequency(2)
self.wait()
def change_graph_frequency(self, frequency, run_time = 2):
graph = self.graph
frequency_mob = self.frequency_mob
curr_frequency = graph.frequency
self.play(
UpdateFromAlphaFunc(
graph, self.get_signal_update_func(graph, frequency),
),
ChangingDecimal(
frequency_mob,
lambda a : 440*interpolate(curr_frequency, frequency, a)
),
run_time = run_time,
)
graph.frequency = frequency
def get_signal_update_func(self, graph, target_frequency):
curr_frequency = graph.frequency
def update(graph, alpha):
frequency = interpolate(curr_frequency, target_frequency, alpha)
new_graph = self.get_cosine_graph(frequency)
Transform(graph, new_graph).update(1)
return graph
return update
def get_cosine_graph(self, frequency, num_steps = 200, color = YELLOW):
result = FunctionGraph(
lambda x : 0.5*np.cos(2*np.pi*frequency*x),
num_steps = num_steps
)
result.frequency = frequency
result.shift(2*UP)
return result
class FourierMachineScene(Scene):
CONFIG = {
"time_axes_config" : {
"x_min" : 0,
"x_max" : 4.4,
"x_axis_config" : {
"unit_size" : 3,
"tick_frequency" : 0.25,
"numbers_with_elongated_ticks" : [1, 2, 3],
},
"y_min" : 0,
"y_max" : 2,
"y_axis_config" : {"unit_size" : 0.8},
},
"time_label_t" : 3.4,
"circle_plane_config" : {
"x_radius" : 2.1,
"y_radius" : 2.1,
"x_unit_size" : 1,
"y_unit_size" : 1,
},
"frequency_axes_config" : {
"number_line_config" : {
"color" : TEAL,
},
"x_min" : 0,
"x_max" : 5.0,
"x_axis_config" : {
"unit_size" : 1.4,
"numbers_to_show" : list(range(1, 6)),
},
"y_min" : -1.0,
"y_max" : 1.0,
"y_axis_config" : {
"unit_size" : 1.8,
"tick_frequency" : 0.5,
"label_direction" : LEFT,
},
"color" : TEAL,
},
"frequency_axes_box_color" : TEAL_E,
"text_scale_val" : 0.75,
"default_graph_config" : {
"num_graph_points" : 100,
"color" : YELLOW,
},
"equilibrium_height" : 1,
"default_y_vector_animation_config" : {
"run_time" : 5,
"rate_func" : None,
"remover" : True,
},
"default_time_sweep_config" : {
"rate_func" : None,
"run_time" : 5,
},
"default_num_v_lines_indicating_periods" : 20,
}
def get_time_axes(self):
time_axes = Axes(**self.time_axes_config)
time_axes.x_axis.add_numbers()
time_label = TextMobject("Time")
intensity_label = TextMobject("Intensity")
labels = VGroup(time_label, intensity_label)
for label in labels:
label.scale(self.text_scale_val)
time_label.next_to(
time_axes.coords_to_point(self.time_label_t,0),
DOWN
)
intensity_label.next_to(time_axes.y_axis.get_top(), RIGHT)
time_axes.labels = labels
time_axes.add(labels)
time_axes.to_corner(UP+LEFT)
self.time_axes = time_axes
return time_axes
def get_circle_plane(self):
circle_plane = NumberPlane(**self.circle_plane_config)
circle_plane.to_corner(DOWN+LEFT)
circle = DashedLine(ORIGIN, TAU*UP).apply_complex_function(np.exp)
circle.scale(circle_plane.x_unit_size)
circle.move_to(circle_plane.coords_to_point(0, 0))
circle_plane.circle = circle
circle_plane.add(circle)
circle_plane.fade()
self.circle_plane = circle_plane
return circle_plane
def get_frequency_axes(self):
frequency_axes = Axes(**self.frequency_axes_config)
frequency_axes.x_axis.add_numbers()
frequency_axes.y_axis.add_numbers(
*frequency_axes.y_axis.get_tick_numbers()
)
box = SurroundingRectangle(
frequency_axes,
buff = MED_SMALL_BUFF,
color = self.frequency_axes_box_color,
)
frequency_axes.box = box
frequency_axes.add(box)
frequency_axes.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF)
frequency_label = TextMobject("Frequency")
frequency_label.scale(self.text_scale_val)
frequency_label.next_to(
frequency_axes.x_axis.get_right(), DOWN,
buff = MED_LARGE_BUFF,
aligned_edge = RIGHT,
)
frequency_axes.label = frequency_label
frequency_axes.add(frequency_label)
self.frequency_axes = frequency_axes
return frequency_axes
def get_time_graph(self, func, **kwargs):
if not hasattr(self, "time_axes"):
self.get_time_axes()
config = dict(self.default_graph_config)
config.update(kwargs)
graph = self.time_axes.get_graph(func, **config)
return graph
def get_cosine_wave(self, freq = 1, shift_val = 1, scale_val = 0.9):
return self.get_time_graph(
lambda t : shift_val + scale_val*np.cos(TAU*freq*t)
)
def get_fourier_transform_graph(self, time_graph, **kwargs):
if not hasattr(self, "frequency_axes"):
self.get_frequency_axes()
func = time_graph.underlying_function
t_axis = self.time_axes.x_axis
t_min = t_axis.point_to_number(time_graph.points[0])
t_max = t_axis.point_to_number(time_graph.points[-1])
f_max = self.frequency_axes.x_max
# result = get_fourier_graph(
# self.frequency_axes, func, t_min, t_max,
# **kwargs
# )
# too_far_right_point_indices = [
# i
# for i, point in enumerate(result.points)
# if self.frequency_axes.x_axis.point_to_number(point) > f_max
# ]
# if too_far_right_point_indices:
# i = min(too_far_right_point_indices)
# prop = float(i)/len(result.points)
# result.pointwise_become_partial(result, 0, prop)
# return result
return self.frequency_axes.get_graph(
get_fourier_transform(func, t_min, t_max, **kwargs),
color = self.center_of_mass_color,
**kwargs
)
def get_polarized_mobject(self, mobject, freq = 1.0):
if not hasattr(self, "circle_plane"):
self.get_circle_plane()
polarized_mobject = mobject.copy()
polarized_mobject.apply_function(lambda p : self.polarize_point(p, freq))
# polarized_mobject.make_smooth()
mobject.polarized_mobject = polarized_mobject
polarized_mobject.frequency = freq
return polarized_mobject
def polarize_point(self, point, freq = 1.0):
t, y = self.time_axes.point_to_coords(point)
z = y*np.exp(complex(0, -2*np.pi*freq*t))
return self.circle_plane.coords_to_point(z.real, z.imag)
def get_polarized_animation(self, mobject, freq = 1.0):
p_mob = self.get_polarized_mobject(mobject, freq = freq)
def update_p_mob(p_mob):
Transform(
p_mob,
self.get_polarized_mobject(mobject, freq = freq)
).update(1)
mobject.polarized_mobject = p_mob
return p_mob
return UpdateFromFunc(p_mob, update_p_mob)
def animate_frequency_change(self, mobjects, new_freq, **kwargs):
kwargs["run_time"] = kwargs.get("run_time", 3.0)
added_anims = kwargs.get("added_anims", [])
self.play(*[
self.get_frequency_change_animation(mob, new_freq, **kwargs)
for mob in mobjects
] + added_anims)
def get_frequency_change_animation(self, mobject, new_freq, **kwargs):
if not hasattr(mobject, "polarized_mobject"):
mobject.polarized_mobject = self.get_polarized_mobject(mobject)
start_freq = mobject.polarized_mobject.frequency
def update(pm, alpha):
freq = interpolate(start_freq, new_freq, alpha)
new_pm = self.get_polarized_mobject(mobject, freq)
Transform(pm, new_pm).update(1)
mobject.polarized_mobject = pm
mobject.polarized_mobject.frequency = freq
return pm
return UpdateFromAlphaFunc(mobject.polarized_mobject, update, **kwargs)
def get_time_graph_y_vector_animation(self, graph, **kwargs):
config = dict(self.default_y_vector_animation_config)
config.update(kwargs)
vector = Vector(UP, color = WHITE)
graph_copy = graph.copy()
x_axis = self.time_axes.x_axis
x_min = x_axis.point_to_number(graph.points[0])
x_max = x_axis.point_to_number(graph.points[-1])
def update_vector(vector, alpha):
x = interpolate(x_min, x_max, alpha)
vector.put_start_and_end_on(
self.time_axes.coords_to_point(x, 0),
self.time_axes.input_to_graph_point(x, graph_copy)
)
return vector
return UpdateFromAlphaFunc(vector, update_vector, **config)
def get_polarized_vector_animation(self, polarized_graph, **kwargs):
config = dict(self.default_y_vector_animation_config)
config.update(kwargs)
vector = Vector(RIGHT, color = WHITE)
origin = self.circle_plane.coords_to_point(0, 0)
graph_copy = polarized_graph.copy()
def update_vector(vector, alpha):
# Not sure why this is needed, but without smoothing
# out the alpha like this, the vector would occasionally
# jump around
point = center_of_mass([
graph_copy.point_from_proportion(alpha+d)
for d in np.linspace(-0.001, 0.001, 5)
])
vector.put_start_and_end_on_with_projection(origin, point)
return vector
return UpdateFromAlphaFunc(vector, update_vector, **config)
def get_vector_animations(self, graph, draw_polarized_graph = True, **kwargs):
config = dict(self.default_y_vector_animation_config)
config.update(kwargs)
anims = [
self.get_time_graph_y_vector_animation(graph, **config),
self.get_polarized_vector_animation(graph.polarized_mobject, **config),
]
if draw_polarized_graph:
new_config = dict(config)
new_config["remover"] = False
anims.append(ShowCreation(graph.polarized_mobject, **new_config))
return anims
def animate_time_sweep(self, freq, n_repeats = 1, t_max = None, **kwargs):
added_anims = kwargs.pop("added_anims", [])
config = dict(self.default_time_sweep_config)
config.update(kwargs)
circle_plane = self.circle_plane
time_axes = self.time_axes
ctp = time_axes.coords_to_point
t_max = t_max or time_axes.x_max
v_line = DashedLine(
ctp(0, 0), ctp(0, time_axes.y_max),
stroke_width = 6,
)
v_line.set_color(RED)
for x in range(n_repeats):
v_line.move_to(ctp(0, 0), DOWN)
self.play(
ApplyMethod(
v_line.move_to,
ctp(t_max, 0), DOWN
),
self.get_polarized_animation(v_line, freq = freq),
*added_anims,
**config
)
self.remove(v_line.polarized_mobject)
self.play(FadeOut(VGroup(v_line, v_line.polarized_mobject)))
def get_v_lines_indicating_periods(self, freq, n_lines = None):
if n_lines is None:
n_lines = self.default_num_v_lines_indicating_periods
period = np.divide(1., max(freq, 0.01))
v_lines = VGroup(*[
DashedLine(ORIGIN, 1.5*UP).move_to(
self.time_axes.coords_to_point(n*period, 0),
DOWN
)
for n in range(1, n_lines + 1)
])
v_lines.set_stroke(LIGHT_GREY)
return v_lines
def get_period_v_lines_update_anim(self):
def update_v_lines(v_lines):
freq = self.graph.polarized_mobject.frequency
Transform(
v_lines,
self.get_v_lines_indicating_periods(freq)
).update(1)
return UpdateFromFunc(
self.v_lines_indicating_periods, update_v_lines
)
class WrapCosineGraphAroundCircle(FourierMachineScene):
CONFIG = {
"initial_winding_frequency" : 0.5,
"signal_frequency" : 3.0,
}
def construct(self):
self.show_initial_signal()
self.show_finite_interval()
self.wrap_around_circle()
self.show_time_sweeps()
self.compare_two_frequencies()
self.change_wrapping_frequency()
def show_initial_signal(self):
axes = self.get_time_axes()
graph = self.get_cosine_wave(freq = self.signal_frequency)
self.graph = graph
braces = VGroup(*self.get_peak_braces()[3:6])
v_lines = VGroup(*[
DashedLine(
ORIGIN, 2*UP, color = RED
).move_to(axes.coords_to_point(x, 0), DOWN)
for x in (1, 2)
])
words = self.get_bps_label()
words.save_state()
words.next_to(axes.coords_to_point(1.5, 0), DOWN, MED_LARGE_BUFF)
self.add(axes)
self.play(ShowCreation(graph, run_time = 2, rate_func=linear))
self.play(
FadeIn(words),
LaggedStartMap(FadeIn, braces),
*list(map(ShowCreation, v_lines))
)
self.wait()
self.play(
FadeOut(VGroup(braces, v_lines)),
words.restore,
)
self.wait()
self.beats_per_second_label = words
self.graph = graph
def show_finite_interval(self):
axes = self.time_axes
v_line = DashedLine(
axes.coords_to_point(0, 0),
axes.coords_to_point(0, axes.y_max),
color = RED,
stroke_width = 6,
)
h_line = Line(
axes.coords_to_point(0, 0),
axes.coords_to_point(axes.x_max, 0),
)
rect = Rectangle(
stroke_width = 0,
fill_color = TEAL,
fill_opacity = 0.5,
)
rect.match_height(v_line)
rect.match_width(h_line, stretch = True)
rect.move_to(v_line, DOWN+LEFT)
right_v_line = v_line.copy()
right_v_line.move_to(rect, RIGHT)
rect.save_state()
rect.stretch(0, 0, about_edge = ORIGIN)
self.play(rect.restore, run_time = 2)
self.play(FadeOut(rect))
for line in v_line, right_v_line:
self.play(ShowCreation(line))
self.play(FadeOut(line))
self.wait()
def wrap_around_circle(self):
graph = self.graph
freq = self.initial_winding_frequency
low_freq = freq/3
polarized_graph = self.get_polarized_mobject(graph, low_freq)
circle_plane = self.get_circle_plane()
moving_graph = graph.copy()
self.play(ShowCreation(circle_plane, lag_ratio = 0))
self.play(ReplacementTransform(
moving_graph,
polarized_graph,
run_time = 3,
path_arc = -TAU/2
))
self.animate_frequency_change([graph], freq)
self.wait()
pg_copy = polarized_graph.copy()
self.remove(polarized_graph)
self.play(pg_copy.fade, 0.75)
self.play(*self.get_vector_animations(graph), run_time = 15)
self.remove(pg_copy)
self.wait()
def show_time_sweeps(self):
freq = self.initial_winding_frequency
graph = self.graph
v_lines = self.get_v_lines_indicating_periods(freq)
winding_freq_label = self.get_winding_frequency_label()
self.animate_time_sweep(
freq = freq,
t_max = 4,
run_time = 6,
added_anims = [FadeIn(v_lines)]
)
self.play(
FadeIn(winding_freq_label),
*self.get_vector_animations(graph)
)
self.wait()
self.v_lines_indicating_periods = v_lines
def compare_two_frequencies(self):
bps_label = self.beats_per_second_label
wps_label = self.winding_freq_label
for label in bps_label, wps_label:
label.rect = SurroundingRectangle(
label, color = RED
)
graph = self.graph
freq = self.initial_winding_frequency
braces = self.get_peak_braces(buff = 0)
self.play(ShowCreation(bps_label.rect))
self.play(FadeOut(bps_label.rect))
self.play(LaggedStartMap(FadeIn, braces, run_time = 3))
self.play(FadeOut(braces))
self.play(ShowCreation(wps_label.rect))
self.play(FadeOut(wps_label.rect))
self.animate_time_sweep(freq = freq, t_max = 4)
self.wait()
def change_wrapping_frequency(self):
graph = self.graph
v_lines = self.v_lines_indicating_periods
freq_label = self.winding_freq_label[0]
count = 0
for target_freq in [1.23, 0.2, 0.79, 1.55, self.signal_frequency]:
self.play(
Transform(
v_lines,
self.get_v_lines_indicating_periods(target_freq)
),
ChangeDecimalToValue(freq_label, target_freq),
self.get_frequency_change_animation(graph, target_freq),
run_time = 4,
)
self.wait()
if count == 2:
self.play(LaggedStartMap(
ApplyFunction, v_lines,
lambda mob : (
lambda m : m.shift(0.25*UP).set_color(YELLOW),
mob
),
rate_func = there_and_back
))
self.animate_time_sweep(target_freq, t_max = 2)
count += 1
self.wait()
self.play(
*self.get_vector_animations(graph, False),
run_time = 15
)
##
def get_winding_frequency_label(self):
freq = self.initial_winding_frequency
winding_freq_label = VGroup(
DecimalNumber(freq, num_decimal_places=2),
TextMobject("cycles/second")
)
winding_freq_label.arrange(RIGHT)
winding_freq_label.next_to(
self.circle_plane, RIGHT, aligned_edge = UP
)
self.winding_freq_label = winding_freq_label
return winding_freq_label
def get_peak_braces(self, **kwargs):
peak_points = [
self.time_axes.input_to_graph_point(x, self.graph)
for x in np.arange(0, 3.5, 1./self.signal_frequency)
]
return VGroup(*[
Brace(Line(p1, p2), UP, **kwargs)
for p1, p2 in zip(peak_points, peak_points[1:])
])
def get_bps_label(self, freq = 3):
braces = VGroup(*self.get_peak_braces()[freq:2*freq])
words = TextMobject("%d beats/second"%freq)
words.set_width(0.9*braces.get_width())
words.move_to(braces, DOWN)
return words
class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
CONFIG = {
"initial_winding_frequency" : 3.0,
"center_of_mass_color" : RED,
"center_of_mass_multiple" : 1,
}
def construct(self):
self.remove(self.pi_creature)
self.setup_graph()
self.indicate_weight_of_wire()
self.show_center_of_mass_dot()
self.change_to_various_frequencies()
self.introduce_frequency_plot()
self.draw_full_frequency_plot()
self.recap_objects_on_screen()
self.lower_graph()
self.label_as_almost_fourier()
def setup_graph(self):
self.add(self.get_time_axes())
self.add(self.get_circle_plane())
self.graph = self.get_cosine_wave(self.signal_frequency)
self.add(self.graph)
self.add(self.get_polarized_mobject(
self.graph, self.initial_winding_frequency
))
self.add(self.get_winding_frequency_label())
self.beats_per_second_label = self.get_bps_label()
self.add(self.beats_per_second_label)
self.v_lines_indicating_periods = self.get_v_lines_indicating_periods(
self.initial_winding_frequency
)
self.add(self.v_lines_indicating_periods)
self.change_frequency(1.03)
self.wait()
def indicate_weight_of_wire(self):
graph = self.graph
pol_graph = graph.polarized_mobject.copy()
pol_graph.save_state()
morty = self.pi_creature
morty.change("raise_right_hand")
morty.save_state()
morty.change("plain")
morty.fade(1)
self.play(
morty.restore,
pol_graph.scale, 0.5,
pol_graph.next_to, morty.get_corner(UP+LEFT), UP, -SMALL_BUFF,
)
self.play(
morty.change, "lower_right_hand", pol_graph.get_bottom(),
pol_graph.shift, 0.45*DOWN,
rate_func = there_and_back,
run_time = 2,
)
self.wait()
metal_wire = pol_graph.copy().set_stroke(LIGHT_GREY)
self.play(
ShowCreationThenDestruction(metal_wire),
run_time = 2,
)
self.play(
pol_graph.restore,
morty.change, "pondering"
)
self.remove(pol_graph)
def show_center_of_mass_dot(self):
color = self.center_of_mass_color
dot = self.get_center_of_mass_dot()
dot.save_state()
arrow = Vector(DOWN+2*LEFT, color = color)
arrow.next_to(dot.get_center(), UP+RIGHT, buff = SMALL_BUFF)
dot.move_to(arrow.get_start())
words = TextMobject("Center of mass")
words.next_to(arrow.get_start(), RIGHT)
words.set_color(color)
self.play(
GrowArrow(arrow),
dot.restore,
)
self.play(Write(words))
self.play(FadeOut(arrow), FadeOut(self.pi_creature))
self.wait()
self.generate_center_of_mass_dot_update_anim()
self.center_of_mass_label = words
def change_to_various_frequencies(self):
self.change_frequency(
3.0, run_time = 30,
rate_func = bezier([0, 0, 1, 1])
)
self.wait()
self.play(
*self.get_vector_animations(self.graph),
run_time = 15
)
def introduce_frequency_plot(self):
wps_label = self.winding_freq_label
wps_label.add_to_back(BackgroundRectangle(wps_label))
com_label = self.center_of_mass_label
com_label.add_background_rectangle()
frequency_axes = self.get_frequency_axes()
x_coord_label = TextMobject("$x$-coordinate for center of mass")
x_coord_label.set_color(self.center_of_mass_color)
x_coord_label.scale(self.text_scale_val)
x_coord_label.next_to(
frequency_axes.y_axis.get_top(),
RIGHT, aligned_edge = UP, buff = LARGE_BUFF
)
x_coord_label.add_background_rectangle()
flower_path = ParametricFunction(
lambda t : self.circle_plane.coords_to_point(
np.sin(2*t)*np.cos(t),
np.sin(2*t)*np.sin(t),
),
t_min = 0, t_max = TAU,
)
flower_path.move_to(self.center_of_mass_dot)
self.play(
wps_label.move_to, self.circle_plane.get_top(),
com_label.move_to, self.circle_plane, DOWN,
)
self.play(LaggedStartMap(FadeIn, frequency_axes))
self.wait()
self.play(MoveAlongPath(
self.center_of_mass_dot, flower_path,
run_time = 4,
))
self.play(ReplacementTransform(
com_label.copy(), x_coord_label
))
self.wait()
self.x_coord_label = x_coord_label
def draw_full_frequency_plot(self):
graph = self.graph
fourier_graph = self.get_fourier_transform_graph(graph)
fourier_graph.save_state()
fourier_graph_update = self.get_fourier_graph_drawing_update_anim(
fourier_graph
)
v_line = DashedLine(
self.frequency_axes.coords_to_point(0, 0),
self.frequency_axes.coords_to_point(0, 1),
stroke_width = 6,
color = fourier_graph.get_color()
)
self.change_frequency(0.0)
self.generate_fourier_dot_transform(fourier_graph)
self.wait()
self.play(ShowCreation(v_line))
self.play(
GrowFromCenter(self.fourier_graph_dot),
FadeOut(v_line)
)
f_max = int(self.frequency_axes.x_max)
for freq in [0.2, 1.5, 3.0, 4.0, 5.0]:
fourier_graph.restore()
self.change_frequency(
freq,
added_anims = [fourier_graph_update],
run_time = 8,
)
self.wait()
self.fourier_graph = fourier_graph
def recap_objects_on_screen(self):
rect = FullScreenFadeRectangle()
time_group = VGroup(
self.graph,
self.time_axes,
self.beats_per_second_label,
).copy()
circle_group = VGroup(
self.graph.polarized_mobject,
self.circle_plane,
self.winding_freq_label,
self.center_of_mass_label,
self.center_of_mass_dot,
).copy()
frequency_group = VGroup(
self.fourier_graph,
self.frequency_axes,
self.x_coord_label,
).copy()
groups = [time_group, circle_group, frequency_group]
self.play(FadeIn(rect))
self.wait()
for group in groups:
graph_copy = group[0].copy().set_color(PINK)
self.play(FadeIn(group))
self.play(ShowCreation(graph_copy))
self.play(FadeOut(graph_copy))
self.wait()
self.play(FadeOut(group))
self.wait()
self.play(FadeOut(rect))
def lower_graph(self):
graph = self.graph
time_axes = self.time_axes
shift_vect = time_axes.coords_to_point(0, 1)
shift_vect -= time_axes.coords_to_point(0, 0)
fourier_graph = self.fourier_graph
new_graph = self.get_cosine_wave(
self.signal_frequency, shift_val = 0
)
new_fourier_graph = self.get_fourier_transform_graph(new_graph)
for mob in graph, time_axes, fourier_graph:
mob.save_state()
new_freq = 0.03
self.change_frequency(new_freq)
self.wait()
self.play(
time_axes.shift, shift_vect/2,
graph.shift, -shift_vect/2,
self.get_frequency_change_animation(
self.graph, new_freq
),
self.center_of_mass_dot_anim,
self.get_period_v_lines_update_anim(),
Transform(fourier_graph, new_fourier_graph),
self.fourier_graph_dot.move_to,
self.frequency_axes.coords_to_point(new_freq, 0),
run_time = 2
)
self.wait()
self.remove(self.fourier_graph_dot)
self.generate_fourier_dot_transform(new_fourier_graph)
self.change_frequency(3.0, run_time = 15, rate_func=linear)
self.wait()
self.play(
graph.restore,
time_axes.restore,
self.get_frequency_change_animation(
self.graph, 3.0
),
self.center_of_mass_dot_anim,
self.get_period_v_lines_update_anim(),
fourier_graph.restore,
Animation(self.fourier_graph_dot),
run_time = 2
)
self.generate_fourier_dot_transform(self.fourier_graph)
self.wait()
self.play(FocusOn(self.fourier_graph_dot))
self.wait()
def label_as_almost_fourier(self):
x_coord_label = self.x_coord_label
almost_fourier_label = TextMobject(
"``Almost Fourier Transform''",
)
almost_fourier_label.move_to(x_coord_label, UP+LEFT)
x_coord_label.generate_target()
x_coord_label.target.next_to(almost_fourier_label, DOWN)
self.play(
MoveToTarget(x_coord_label),
Write(almost_fourier_label)
)
self.wait(2)
##
def get_center_of_mass_dot(self):
dot = Dot(
self.get_pol_graph_center_of_mass(),
color = self.center_of_mass_color
)
self.center_of_mass_dot = dot
return dot
def get_pol_graph_center_of_mass(self):
pg = self.graph.polarized_mobject
result = center_of_mass(pg.get_anchors())
if self.center_of_mass_multiple != 1:
mult = self.center_of_mass_multiple
origin = self.circle_plane.coords_to_point(0, 0)
result = mult*(result - origin) + origin
return result
def generate_fourier_dot_transform(self, fourier_graph):
self.fourier_graph_dot = Dot(color = WHITE, radius = 0.05)
def update_dot(dot):
f = self.graph.polarized_mobject.frequency
dot.move_to(self.frequency_axes.input_to_graph_point(
f, fourier_graph
))
self.fourier_graph_dot_anim = UpdateFromFunc(
self.fourier_graph_dot, update_dot
)
self.fourier_graph_dot_anim.update(0)
def get_fourier_graph_drawing_update_anim(self, fourier_graph):
fourier_graph_copy = fourier_graph.copy()
max_freq = self.frequency_axes.x_max
def update_fourier_graph(fg):
freq = self.graph.polarized_mobject.frequency
fg.pointwise_become_partial(
fourier_graph_copy,
0, freq/max_freq
)
return fg
self.fourier_graph_drawing_update_anim = UpdateFromFunc(
fourier_graph, update_fourier_graph
)
return self.fourier_graph_drawing_update_anim
def generate_center_of_mass_dot_update_anim(self, multiplier = 1):
origin = self.circle_plane.coords_to_point(0, 0)
com = self.get_pol_graph_center_of_mass
self.center_of_mass_dot_anim = UpdateFromFunc(
self.center_of_mass_dot,
lambda d : d.move_to(
multiplier*(com()-origin)+origin
)
)
def change_frequency(self, new_freq, **kwargs):
kwargs["run_time"] = kwargs.get("run_time", 3)
rate_func = kwargs.pop("rate_func", None)
if rate_func is None:
rate_func = bezier([0, 0, 1, 1])
added_anims = kwargs.get("added_anims", [])
anims = [self.get_frequency_change_animation(self.graph, new_freq)]
if hasattr(self, "winding_freq_label"):
freq_label = [
sm for sm in self.winding_freq_label
if isinstance(sm, DecimalNumber)
][0]
self.add(freq_label)
anims.append(
ChangeDecimalToValue(freq_label, new_freq)
)
if hasattr(self, "v_lines_indicating_periods"):
anims.append(self.get_period_v_lines_update_anim())
if hasattr(self, "center_of_mass_dot"):
anims.append(self.center_of_mass_dot_anim)
if hasattr(self, "fourier_graph_dot"):
anims.append(self.fourier_graph_dot_anim)
if hasattr(self, "fourier_graph_drawing_update_anim"):
anims.append(self.fourier_graph_drawing_update_anim)
for anim in anims:
anim.rate_func = rate_func
anims += added_anims
self.play(*anims, **kwargs)
def create_pi_creature(self):
return Mortimer().to_corner(DOWN+RIGHT)
class StudentsHorrifiedAtScene(TeacherStudentsScene):
def construct(self):
self.change_student_modes(
*3*["horrified"],
look_at_arg = 2*UP + 3*LEFT
)
self.wait(4)
class AskAboutAlmostFouierName(TeacherStudentsScene):
def construct(self):
self.student_says(
"``Almost'' Fourier transform?",
target_mode = "sassy"
)
self.change_student_modes("confused", "sassy", "confused")
self.wait()
self.teacher_says(
"We'll get to the real \\\\ one in a few minutes",
added_anims = [self.get_student_changes(*["plain"]*3)]
)
self.wait(2)
class ShowLowerFrequency(DrawFrequencyPlot):
CONFIG = {
"signal_frequency" : 2.0,
"higher_signal_frequency" : 3.0,
"lower_signal_color" : PINK,
}
def construct(self):
self.setup_all_axes()
self.show_lower_frequency_signal()
self.play_with_lower_frequency_signal()
def setup_all_axes(self):
self.add(self.get_time_axes())
self.add(self.get_circle_plane())
self.add(self.get_frequency_axes())
self.remove(self.pi_creature)
def show_lower_frequency_signal(self):
axes = self.time_axes
start_graph = self.get_cosine_wave(freq = self.higher_signal_frequency)
graph = self.get_cosine_wave(
freq = self.signal_frequency,
)
graph.set_color(self.lower_signal_color)
self.graph = graph
ratio = float(self.higher_signal_frequency)/self.signal_frequency
braces = VGroup(*self.get_peak_braces()[2:4])
v_lines = VGroup(*[
DashedLine(ORIGIN, 1.5*UP).move_to(
axes.coords_to_point(x, 0), DOWN
)
for x in (1, 2)
])
bps_label = self.get_bps_label(2)
bps_label.save_state()
bps_label.next_to(braces, UP, SMALL_BUFF)
# self.add(start_graph)
self.play(
start_graph.stretch, ratio, 0, {"about_edge" : LEFT},
start_graph.set_color, graph.get_color(),
)
self.play(FadeOut(start_graph), Animation(graph))
self.remove(start_graph)
self.play(
Write(bps_label),
LaggedStartMap(FadeIn, braces),
*list(map(ShowCreation, v_lines)),
run_time = 1
)
self.wait()
self.play(
FadeOut(v_lines),
FadeOut(braces),
bps_label.restore,
)
def play_with_lower_frequency_signal(self):
freq = 0.1
#Wind up graph
graph = self.graph
pol_graph = self.get_polarized_mobject(graph, freq)
v_lines = self.get_v_lines_indicating_periods(freq)
self.v_lines_indicating_periods = v_lines
wps_label = self.get_winding_frequency_label()
ChangeDecimalToValue(wps_label[0], freq).update(1)
wps_label.add_to_back(BackgroundRectangle(wps_label))
wps_label.move_to(self.circle_plane, UP)
self.play(
ReplacementTransform(
graph.copy(), pol_graph,
run_time = 2,
path_arc = -TAU/4,
),
FadeIn(wps_label),
)
self.change_frequency(freq, run_time = 0)
self.change_frequency(0.7)
self.wait()
#Show center of mass
dot = Dot(
self.get_pol_graph_center_of_mass(),
color = self.center_of_mass_color
)
dot.save_state()
self.center_of_mass_dot = dot
com_words = TextMobject("Center of mass")
com_words.add_background_rectangle()
com_words.move_to(self.circle_plane, DOWN)
arrow = Arrow(
com_words.get_top(),
dot.get_center(),
buff = SMALL_BUFF,
color = self.center_of_mass_color
)
dot.move_to(arrow.get_start())
self.generate_center_of_mass_dot_update_anim()
self.play(
GrowArrow(arrow),
dot.restore,
Write(com_words)
)
self.wait()
self.play(*list(map(FadeOut, [arrow, com_words])))
self.change_frequency(0.0)
self.wait()
#Show fourier graph
fourier_graph = self.get_fourier_transform_graph(graph)
fourier_graph_update = self.get_fourier_graph_drawing_update_anim(
fourier_graph
)
x_coord_label = TextMobject(
"x-coordinate of center of mass"
)
x_coord_label.scale(self.text_scale_val)
x_coord_label.next_to(
self.frequency_axes.input_to_graph_point(
self.signal_frequency, fourier_graph
), UP
)
x_coord_label.set_color(self.center_of_mass_color)
self.generate_fourier_dot_transform(fourier_graph)
self.play(Write(x_coord_label))
self.change_frequency(
self.signal_frequency,
run_time = 10,
rate_func = smooth,
)
self.wait()
self.change_frequency(
self.frequency_axes.x_max,
run_time = 15,
rate_func = smooth,
)
self.wait()
self.set_variables_as_attrs(
fourier_graph,
fourier_graph_update,
)
class MixingUnmixingTODOStub(TODOStub):
CONFIG = {
"message" : "Insert mixing and unmixing of signals"
}
class ShowLinearity(DrawFrequencyPlot):
CONFIG = {
"high_freq_color": YELLOW,
"low_freq_color": PINK,
"sum_color": GREEN,
"low_freq" : 3.0,
"high_freq" : 4.0,
"circle_plane_config" : {
"x_radius" : 2.5,
"y_radius" : 2.7,
"x_unit_size" : 0.8,
"y_unit_size" : 0.8,
},
}
def construct(self):
self.remove(self.pi_creature)
self.show_sum_of_signals()
self.show_winding_with_sum_graph()
self.show_vector_rotation()
def show_sum_of_signals(self):
low_freq, high_freq = self.low_freq, self.high_freq
axes = self.get_time_axes()
axes_copy = axes.copy()
low_freq_graph, high_freq_graph = [
self.get_cosine_wave(
freq = freq,
scale_val = 0.5,
shift_val = 0.55,
)
for freq in (low_freq, high_freq)
]
sum_graph = self.get_time_graph(
lambda t : sum([
low_freq_graph.underlying_function(t),
high_freq_graph.underlying_function(t),
])
)
VGroup(axes_copy, high_freq_graph).next_to(
axes, DOWN, MED_LARGE_BUFF
)
low_freq_label = TextMobject("%d Hz"%int(low_freq))
high_freq_label = TextMobject("%d Hz"%int(high_freq))
sum_label = TextMobject(
"%d Hz"%int(low_freq), "+",
"%d Hz"%int(high_freq)
)
trips = [
(low_freq_label, low_freq_graph, self.low_freq_color),
(high_freq_label, high_freq_graph, self.high_freq_color),
(sum_label, sum_graph, self.sum_color),
]
for label, graph, color in trips:
label.next_to(graph, UP)
graph.set_color(color)
label.set_color(color)
sum_label[0].match_color(low_freq_graph)
sum_label[2].match_color(high_freq_graph)
self.add(axes, low_freq_graph)
self.play(
FadeIn(axes_copy),
ShowCreation(high_freq_graph),
)
self.play(LaggedStartMap(
FadeIn, VGroup(high_freq_label, low_freq_label)
))
self.wait()
self.play(
ReplacementTransform(axes_copy, axes),
ReplacementTransform(high_freq_graph, sum_graph),
ReplacementTransform(low_freq_graph, sum_graph),
ReplacementTransform(
VGroup(low_freq_label, high_freq_label),
sum_label
)
)
self.wait()
self.graph = graph
def show_winding_with_sum_graph(self):
graph = self.graph
circle_plane = self.get_circle_plane()
frequency_axes = self.get_frequency_axes()
pol_graph = self.get_polarized_mobject(graph, freq = 0.0)
wps_label = self.get_winding_frequency_label()
ChangeDecimalToValue(wps_label[0], 0.0).update(1)
wps_label.add_to_back(BackgroundRectangle(wps_label))
wps_label.move_to(circle_plane, UP)
v_lines = self.get_v_lines_indicating_periods(0.001)
self.v_lines_indicating_periods = v_lines
dot = Dot(
self.get_pol_graph_center_of_mass(),
color = self.center_of_mass_color
)
self.center_of_mass_dot = dot
self.generate_center_of_mass_dot_update_anim()
fourier_graph = self.get_fourier_transform_graph(graph)
fourier_graph_update = self.get_fourier_graph_drawing_update_anim(
fourier_graph
)
x_coord_label = TextMobject(
"x-coordinate of center of mass"
)
x_coord_label.scale(self.text_scale_val)
x_coord_label.next_to(
self.frequency_axes.input_to_graph_point(
self.signal_frequency, fourier_graph
), UP
)
x_coord_label.set_color(self.center_of_mass_color)
almost_fourier_label = TextMobject(
"``Almost-Fourier transform''"
)
self.generate_fourier_dot_transform(fourier_graph)
self.play(LaggedStartMap(
FadeIn, VGroup(
circle_plane, wps_label,
frequency_axes, x_coord_label,
),
run_time = 1,
))
self.play(
ReplacementTransform(graph.copy(), pol_graph),
GrowFromCenter(dot)
)
freqs = [
self.low_freq, self.high_freq,
self.frequency_axes.x_max
]
for freq in freqs:
self.change_frequency(
freq,
run_time = 8,
rate_func = bezier([0, 0, 1, 1]),
)
def show_vector_rotation(self):
self.fourier_graph_drawing_update_anim = Animation(Mobject())
self.change_frequency(self.low_freq)
self.play(*self.get_vector_animations(
self.graph, draw_polarized_graph = False,
run_time = 20,
))
self.wait()
class ShowCommutativeDiagram(ShowLinearity):
CONFIG = {
"time_axes_config" : {
"x_max" : 1.9,
"y_max" : 2.0,
"y_min" : -2.0,
"y_axis_config" : {
"unit_size" : 0.5,
},
"x_axis_config" : {
"numbers_to_show" : [1],
}
},
"time_label_t" : 1.5,
"frequency_axes_config" : {
"x_min" : 0.0,
"x_max" : 4.0,
"y_min" : -0.1,
"y_max" : 0.5,
"y_axis_config" : {
"unit_size" : 1.5,
"tick_frequency" : 0.5,
},
}
}
def construct(self):
self.show_diagram()
self.point_out_spikes()
def show_diagram(self):
self.remove(self.pi_creature)
#Setup axes
time_axes = self.get_time_axes()
time_axes.scale(0.8)
ta_group = VGroup(
time_axes, time_axes.deepcopy(), time_axes.deepcopy(),
)
ta_group.arrange(DOWN, buff = MED_LARGE_BUFF)
ta_group.to_corner(UP+LEFT, buff = MED_SMALL_BUFF)
frequency_axes = Axes(**self.frequency_axes_config)
frequency_axes.set_color(TEAL)
freq_label = TextMobject("Frequency")
freq_label.scale(self.text_scale_val)
freq_label.next_to(frequency_axes.x_axis, DOWN, SMALL_BUFF, RIGHT)
frequency_axes.label = freq_label
frequency_axes.add(freq_label)
frequency_axes.scale(0.8)
fa_group = VGroup(
frequency_axes, frequency_axes.deepcopy(), frequency_axes.deepcopy()
)
VGroup(ta_group[1], fa_group[1]).shift(MED_LARGE_BUFF*UP)
for ta, fa in zip(ta_group, fa_group):
fa.next_to(
ta.x_axis, RIGHT,
submobject_to_align = fa.x_axis
)
fa.to_edge(RIGHT)
ta.remove(ta.labels)
fa.remove(fa.label)
## Add graphs
funcs = [
lambda t : np.cos(2*TAU*t),
lambda t : np.cos(3*TAU*t),
]
funcs.append(lambda t : funcs[0](t)+funcs[1](t))
colors = [
self.low_freq_color,
self.high_freq_color,
self.sum_color,
]
labels = [
TextMobject("2 Hz"),
TextMobject("3 Hz"),
# TextMobject("2 Hz", "+", "3 Hz"),
VectorizedPoint()
]
for func, color, label, ta, fa in zip(funcs, colors, labels, ta_group, fa_group):
time_graph = ta.get_graph(func)
time_graph.set_color(color)
label.set_color(color)
label.scale(0.75)
label.next_to(time_graph, UP, SMALL_BUFF)
fourier = get_fourier_transform(
func, ta.x_min, 4*ta.x_max
)
fourier_graph = fa.get_graph(fourier)
fourier_graph.set_color(self.center_of_mass_color)
arrow = Arrow(
ta.x_axis, fa.x_axis,
color = WHITE,
buff = MED_LARGE_BUFF,
)
words = TextMobject("Almost-Fourier \\\\ transform")
words.scale(0.6)
words.next_to(arrow, UP)
arrow.words = words
ta.graph = time_graph
ta.graph_label = label
ta.arrow = arrow
ta.add(time_graph, label)
fa.graph = fourier_graph
fa.add(fourier_graph)
# labels[-1][0].match_color(labels[0])
# labels[-1][2].match_color(labels[1])
#Add arrows
sum_arrows = VGroup()
for group in ta_group, fa_group:
arrow = Arrow(
group[1].graph, group[2].graph,
color = WHITE,
buff = SMALL_BUFF
)
arrow.scale(0.8, about_edge = UP)
arrow.words = TextMobject("Sum").scale(0.75)
arrow.words.next_to(arrow, RIGHT, buff = MED_SMALL_BUFF)
sum_arrows.add(arrow)
def apply_transform(index):
ta = ta_group[index].deepcopy()
fa = fa_group[index]
anims = [
ReplacementTransform(
getattr(ta, attr), getattr(fa, attr)
)
for attr in ("x_axis", "y_axis", "graph")
]
anims += [
GrowArrow(ta.arrow),
Write(ta.arrow.words),
]
if index == 0:
anims.append(ReplacementTransform(
ta.labels[0],
fa.label
))
self.play(*anims, run_time = 1.5)
#Animations
self.add(*ta_group[:2])
self.add(ta_group[0].labels)
self.wait()
apply_transform(0)
apply_transform(1)
self.wait()
self.play(
GrowArrow(sum_arrows[1]),
Write(sum_arrows[1].words),
*[
ReplacementTransform(
fa.copy(), fa_group[2]
)
for fa in fa_group[:2]
]
)
self.wait(2)
self.play(
GrowArrow(sum_arrows[0]),
Write(sum_arrows[0].words),
*[
ReplacementTransform(
mob.copy(), ta_group[2],
run_time = 1
)
for mob in ta_group[:2]
]
)
self.wait()
apply_transform(2)
self.wait()
self.time_axes_group = ta_group
self.frequency_axes_group = fa_group
def point_out_spikes(self):
fa_group = self.frequency_axes_group
freqs = self.low_freq, self.high_freq
flat_rects = VGroup()
for freq, axes in zip(freqs, fa_group[:2]):
flat_rect = SurroundingRectangle(axes.x_axis)
flat_rect.stretch(0.5, 1)
spike_rect = self.get_spike_rect(axes, freq)
flat_rect.match_style(spike_rect)
flat_rect.target = spike_rect
flat_rects.add(flat_rect)
self.play(LaggedStartMap(GrowFromCenter, flat_rects))
self.wait()
self.play(LaggedStartMap(MoveToTarget, flat_rects))
self.wait()
sum_spike_rects = VGroup(*[
self.get_spike_rect(fa_group[2], freq)
for freq in freqs
])
self.play(ReplacementTransform(
flat_rects, sum_spike_rects
))
self.play(LaggedStartMap(
WiggleOutThenIn, sum_spike_rects,
run_time = 1,
lag_ratio = 0.7,
))
self.wait()
##
def get_spike_rect(self, axes, freq):
peak_point = axes.input_to_graph_point(
freq, axes.graph
)
f_axis_point = axes.coords_to_point(freq, 0)
line = Line(f_axis_point, peak_point)
spike_rect = SurroundingRectangle(line)
spike_rect.set_stroke(width = 0)
spike_rect.set_fill(YELLOW, 0.5)
return spike_rect
class PauseAndPonder(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"Pause and \\\\ ponder!",
target_mode = "hooray"
)
self.change_student_modes(*["thinking"]*3)
self.wait(4)
class BeforeGettingToTheFullMath(TeacherStudentsScene):
def construct(self):
formula = TexMobject(
"\\hat{g}(f) = \\int_{-\\infty}^{\\infty}" + \
"g(t)e^{-2\\pi i f t}dt"
)
formula.next_to(self.teacher, UP+LEFT)
self.play(
Write(formula),
self.teacher.change, "raise_right_hand",
self.get_student_changes(*["confused"]*3)
)
self.wait()
self.play(
ApplyMethod(
formula.next_to, FRAME_X_RADIUS*RIGHT, RIGHT,
path_arc = TAU/16,
rate_func = running_start,
),
self.get_student_changes(*["pondering"]*3)
)
self.teacher_says("Consider sound editing\\dots")
self.wait(3)
class FilterOutHighPitch(AddingPureFrequencies, ShowCommutativeDiagram):
def construct(self):
self.add_speaker()
self.play_sound()
self.show_intensity_vs_time_graph()
self.take_fourier_transform()
self.filter_out_high_pitch()
self.mention_inverse_transform()
def play_sound(self):
randy = self.pi_creature
self.play(
Succession(
ApplyMethod, randy.look_at, self.speaker,
Animation, randy,
ApplyMethod, randy.change, "telepath", randy,
Animation, randy,
Blink, randy,
Animation, randy, {"run_time" : 2},
),
*self.get_broadcast_anims(),
run_time = 7
)
self.play(randy.change, "angry", self.speaker)
self.wait()
def show_intensity_vs_time_graph(self):
randy = self.pi_creature
axes = Axes(
x_min = 0,
x_max = 12,
y_min = -6,
y_max = 6,
y_axis_config = {
"unit_size" : 0.15,
"tick_frequency" : 3,
}
)
axes.set_stroke(width = 2)
axes.to_corner(UP+LEFT)
time_label = TextMobject("Time")
intensity_label = TextMobject("Intensity")
labels = VGroup(time_label, intensity_label)
labels.scale(0.75)
time_label.next_to(
axes.x_axis, DOWN,
aligned_edge = RIGHT,
buff = SMALL_BUFF
)
intensity_label.next_to(
axes.y_axis, RIGHT,
aligned_edge = UP,
buff = SMALL_BUFF
)
axes.labels = labels
func = lambda t : sum([
np.cos(TAU*f*t)
for f in (0.5, 0.7, 1.0, 1.2, 3.0,)
])
graph = axes.get_graph(func)
graph.set_color(BLUE)
self.play(
FadeIn(axes),
FadeIn(axes.labels),
randy.change, "pondering", axes,
ShowCreation(
graph, run_time = 4,
rate_func = bezier([0, 0, 1, 1])
),
*self.get_broadcast_anims(run_time = 6)
)
self.wait()
self.time_axes = axes
self.time_graph = graph
def take_fourier_transform(self):
time_axes = self.time_axes
time_graph = self.time_graph
randy = self.pi_creature
speaker = self.speaker
frequency_axes = Axes(
x_min = 0,
x_max = 3.5,
x_axis_config = {"unit_size" : 3.5},
y_min = 0,
y_max = 1,
y_axis_config = {"unit_size" : 2},
)
frequency_axes.set_color(TEAL)
frequency_axes.next_to(time_axes, DOWN, LARGE_BUFF, LEFT)
freq_label = TextMobject("Frequency")
freq_label.scale(0.75)
freq_label.next_to(frequency_axes.x_axis, DOWN, MED_SMALL_BUFF, RIGHT)
frequency_axes.label = freq_label
fourier_func = get_fourier_transform(
time_graph.underlying_function,
t_min = 0, t_max = 30,
)
# def alt_fourier_func(t):
# bell = smooth(t)*0.3*np.exp(-0.8*(t-0.9)**2)
# return bell + (smooth(t/3)+0.2)*fourier_func(t)
fourier_graph = frequency_axes.get_graph(
fourier_func, num_graph_points = 150,
)
fourier_graph.set_color(RED)
frequency_axes.graph = fourier_graph
arrow = Arrow(time_graph, fourier_graph, color = WHITE)
ft_words = TextMobject("Fourier \\\\ transform")
ft_words.next_to(arrow, RIGHT)
spike_rect = self.get_spike_rect(frequency_axes, 3)
spike_rect.stretch(2, 0)
self.play(
ReplacementTransform(time_axes.copy(), frequency_axes),
ReplacementTransform(time_graph.copy(), fourier_graph),
ReplacementTransform(time_axes.labels[0].copy(), freq_label),
GrowArrow(arrow),
Write(ft_words),
VGroup(randy, speaker).shift, FRAME_Y_RADIUS*DOWN,
)
self.remove(randy, speaker)
self.wait()
self.play(DrawBorderThenFill(spike_rect))
self.wait()
self.frequency_axes = frequency_axes
self.fourier_graph = fourier_graph
self.spike_rect = spike_rect
self.to_fourier_arrow = arrow
def filter_out_high_pitch(self):
fourier_graph = self.fourier_graph
spike_rect = self.spike_rect
frequency_axes = self.frequency_axes
def filtered_func(f):
result = fourier_graph.underlying_function(f)
result *= np.clip(smooth(3-f), 0, 1)
return result
new_graph = frequency_axes.get_graph(
filtered_func, num_graph_points = 300
)
new_graph.set_color(RED)
self.play(spike_rect.stretch, 4, 0)
self.play(
Transform(fourier_graph, new_graph),
spike_rect.stretch, 0.01, 1, {
"about_point" : frequency_axes.coords_to_point(0, 0)
},
run_time = 2
)
self.wait()
def mention_inverse_transform(self):
time_axes = self.time_axes
time_graph = self.time_graph
fourier_graph = self.fourier_graph
frequency_axes = self.frequency_axes
f_min = frequency_axes.x_min
f_max = frequency_axes.x_max
filtered_graph = time_axes.get_graph(
lambda t : time_graph.underlying_function(t)-np.cos(TAU*3*t)
)
filtered_graph.set_color(BLUE_C)
to_fourier_arrow = self.to_fourier_arrow
arrow = to_fourier_arrow.copy()
arrow.rotate(TAU/2, about_edge = LEFT)
arrow.shift(MED_SMALL_BUFF*LEFT)
inv_fourier_words = TextMobject("Inverse Fourier \\\\ transform")
inv_fourier_words.next_to(arrow, LEFT)
VGroup(arrow, inv_fourier_words).set_color(MAROON_B)
self.play(
GrowArrow(arrow),
Write(inv_fourier_words)
)
self.wait()
self.play(
time_graph.fade, 0.9,
ReplacementTransform(
fourier_graph.copy(), filtered_graph
)
)
self.wait()
##
def get_broadcast_anims(self, run_time = 7, **kwargs):
return [
self.get_broadcast_animation(
n_circles = n,
run_time = run_time,
big_radius = 7,
start_stroke_width = 5,
**kwargs
)
for n in (5, 7, 10, 12)
]
class AskAboutInverseFourier(TeacherStudentsScene):
def construct(self):
self.student_says("Inverse Fourier?")
self.change_student_modes("confused", "raise_right_hand", "confused")
self.wait(2)
class ApplyFourierToFourier(DrawFrequencyPlot):
CONFIG = {
"time_axes_config" : {
"y_min" : -1.5,
"y_max" : 1.5,
"x_max" : 5,
"x_axis_config" : {
"numbers_to_show" : list(range(1, 5)),
"unit_size" : 2.5,
},
},
"frequency_axes_config" : {
"y_min" : -0.6,
"y_max" : 0.6,
},
"circle_plane_config" : {
"x_radius" : 1.5,
"y_radius" : 1.35,
"x_unit_size" : 1.5,
"y_unit_size" : 1.5,
},
"default_num_v_lines_indicating_periods" : 0,
"signal_frequency" : 2,
}
def construct(self):
self.setup_fourier_display()
self.swap_graphs()
def setup_fourier_display(self):
self.force_skipping()
self.setup_graph()
self.show_center_of_mass_dot()
self.introduce_frequency_plot()
self.draw_full_frequency_plot()
self.time_axes.remove(self.time_axes.labels)
self.remove(self.beats_per_second_label)
VGroup(
self.time_axes, self.graph,
self.frequency_axes, self.fourier_graph,
self.x_coord_label,
self.fourier_graph_dot,
).to_edge(UP, buff = MED_SMALL_BUFF)
self.revert_to_original_skipping_status()
def swap_graphs(self):
fourier_graph = self.fourier_graph
time_graph = self.graph
wound_up_graph = time_graph.polarized_mobject
time_axes = self.time_axes
frequency_axes = self.frequency_axes
f_max = self.frequency_axes.x_max
new_fourier_graph = time_axes.get_graph(
lambda t : 2*fourier_graph.underlying_function(t)
)
new_fourier_graph.match_style(fourier_graph)
self.remove(fourier_graph)
self.play(
ReplacementTransform(
fourier_graph.copy(),
new_fourier_graph
),
ApplyMethod(
time_graph.shift, 3*UP+10*LEFT,
remover = True,
),
)
self.play(
wound_up_graph.next_to, FRAME_X_RADIUS*LEFT, LEFT,
remover = True
)
self.wait()
self.graph = new_fourier_graph
wound_up_graph = self.get_polarized_mobject(new_fourier_graph, freq = 0)
double_fourier_graph = frequency_axes.get_graph(
lambda t : 0.25*np.cos(TAU*2*t)
).set_color(PINK)
self.fourier_graph = double_fourier_graph
self.remove(self.fourier_graph_dot)
self.get_fourier_graph_drawing_update_anim(double_fourier_graph)
self.generate_fourier_dot_transform(double_fourier_graph)
self.center_of_mass_dot.set_color(PINK)
self.generate_center_of_mass_dot_update_anim()
def new_get_pol_graph_center_of_mass():
result = DrawFrequencyPlot.get_pol_graph_center_of_mass(self)
result -= self.circle_plane.coords_to_point(0, 0)
result *= 25
result += self.circle_plane.coords_to_point(0, 0)
return result
self.get_pol_graph_center_of_mass = new_get_pol_graph_center_of_mass
self.play(
ReplacementTransform(self.graph.copy(), wound_up_graph),
ChangeDecimalToValue(
self.winding_freq_label[1], 0.0,
run_time = 0.2,
)
)
self.change_frequency(5.0, run_time = 15, rate_func=linear)
self.wait()
##
def get_cosine_wave(self, freq, **kwargs):
kwargs["shift_val"] = 0
kwargs["scale_val"] = 1.0
return DrawFrequencyPlot.get_cosine_wave(self, freq, **kwargs)
class WriteComplexExponentialExpression(DrawFrequencyPlot):
CONFIG = {
"signal_frequency" : 2.0,
"default_num_v_lines_indicating_periods" : 0,
"time_axes_scale_val" : 0.7,
"initial_winding_frequency" : 0.1,
"circle_plane_config" : {
"unit_size" : 2,
"y_radius" : FRAME_Y_RADIUS+LARGE_BUFF,
"x_radius" : FRAME_X_RADIUS+LARGE_BUFF
}
}
def construct(self):
self.remove(self.pi_creature)
self.setup_plane()
self.setup_graph()
self.show_winding_with_both_coordinates()
self.show_plane_as_complex_plane()
self.show_eulers_formula()
self.show_winding_graph_expression()
self.find_center_of_mass()
def setup_plane(self):
circle_plane = ComplexPlane(**self.circle_plane_config)
circle_plane.shift(DOWN+LEFT)
circle = DashedLine(ORIGIN, TAU*UP)
circle.apply_complex_function(
lambda z : R3_to_complex(
circle_plane.number_to_point(np.exp(z))
)
)
circle_plane.add(circle)
time_axes = self.get_time_axes()
time_axes.background_rectangle = BackgroundRectangle(
time_axes,
fill_opacity = 0.9,
buff = MED_SMALL_BUFF,
)
time_axes.add_to_back(time_axes.background_rectangle)
time_axes.scale(self.time_axes_scale_val)
time_axes.to_corner(UP+LEFT, buff = 0)
time_axes.set_stroke(color = WHITE, width = 1)
self.add(circle_plane)
self.add(time_axes)
self.circle_plane = circle_plane
self.time_axes = time_axes
def setup_graph(self):
plane = self.circle_plane
graph = self.graph = self.get_cosine_wave(
freq = self.signal_frequency,
scale_val = 0.5,
shift_val = 0.75,
)
freq = self.initial_winding_frequency
pol_graph = self.get_polarized_mobject(graph, freq = freq)
wps_label = self.get_winding_frequency_label()
ChangeDecimalToValue(wps_label[0], freq).update(1)
wps_label.add_to_back(BackgroundRectangle(wps_label))
wps_label.next_to(plane.coords_to_point(0, 1), DOWN)
wps_label.to_edge(LEFT)
self.get_center_of_mass_dot()
self.generate_center_of_mass_dot_update_anim()
self.add(graph, pol_graph, wps_label)
self.set_variables_as_attrs(pol_graph, wps_label)
self.time_axes_group = VGroup(self.time_axes, graph)
def show_winding_with_both_coordinates(self):
com_dot = self.center_of_mass_dot
plane = self.circle_plane
v_line = Line(ORIGIN, UP)
h_line = Line(ORIGIN, RIGHT)
lines = VGroup(v_line, h_line)
lines.set_color(PINK)
def lines_update(lines):
point = com_dot.get_center()
x, y = plane.point_to_coords(point)
h_line.put_start_and_end_on(
plane.coords_to_point(0, y), point
)
v_line.put_start_and_end_on(
plane.coords_to_point(x, 0), point
)
lines_update_anim = Mobject.add_updater(lines, lines_update)
lines_update_anim.update(0)
self.add(lines_update_anim)
self.change_frequency(
2.04,
added_anims = [
self.center_of_mass_dot_anim,
],
run_time = 15,
rate_func = bezier([0, 0, 1, 1])
)
self.wait()
self.dot_component_anim = lines_update_anim
def show_plane_as_complex_plane(self):
to_fade = VGroup(
self.time_axes_group, self.pol_graph, self.wps_label
)
plane = self.circle_plane
dot = self.center_of_mass_dot
complex_plane_title = TextMobject("Complex plane")
complex_plane_title.add_background_rectangle()
complex_plane_title.to_edge(UP)
coordinate_labels = plane.get_coordinate_labels()
number_label = DecimalNumber(
0, include_background_rectangle = True,
)
number_label_update_anim = ContinualChangingDecimal(
number_label,
lambda a : plane.point_to_number(dot.get_center()),
position_update_func = lambda l : l.next_to(
dot, DOWN+RIGHT,
buff = SMALL_BUFF
),
)
number_label_update_anim.update(0)
flower_path = ParametricFunction(
lambda t : plane.coords_to_point(
np.sin(2*t)*np.cos(t),
np.sin(2*t)*np.sin(t),
),
t_min = 0, t_max = TAU,
)
flower_path.move_to(self.center_of_mass_dot)
self.play(FadeOut(to_fade))
self.play(Write(complex_plane_title))
self.play(Write(coordinate_labels))
self.wait()
self.play(FadeIn(number_label))
self.add(number_label_update_anim)
self.play(MoveAlongPath(
dot, flower_path,
run_time = 10,
rate_func = bezier([0, 0, 1, 1])
))
self.wait()
self.play(ShowCreation(
self.pol_graph, run_time = 3,
))
self.play(FadeOut(self.pol_graph))
self.wait()
self.play(FadeOut(VGroup(
dot, self.dot_component_anim.mobject, number_label
)))
self.remove(self.dot_component_anim)
self.remove(number_label_update_anim)
self.set_variables_as_attrs(
number_label,
number_label_update_anim,
complex_plane_title,
)
def show_eulers_formula(self):
plane = self.circle_plane
ghost_dot = Dot(ORIGIN, fill_opacity = 0)
def get_t():
return ghost_dot.get_center()[0]
def get_circle_point(scalar = 1, t_shift = 0):
return plane.number_to_point(
scalar*np.exp(complex(0, get_t()+t_shift))
)
vector = Vector(plane.number_to_point(1), color = GREEN)
exp_base = TexMobject("e").scale(1.3)
exp_base.add_background_rectangle()
exp_decimal = DecimalNumber(0, unit = "i", include_background_rectangle = True)
exp_decimal.scale(0.75)
VGroup(exp_base, exp_decimal).match_color(vector)
exp_decimal_update = ContinualChangingDecimal(
exp_decimal, lambda a : get_t(),
position_update_func = lambda d : d.move_to(
exp_base.get_corner(UP+RIGHT), DOWN+LEFT
)
)
exp_base_update = Mobject.add_updater(
exp_base, lambda e : e.move_to(get_circle_point(
scalar = 1.1, t_shift = 0.01*TAU
))
)
vector_update = Mobject.add_updater(
vector, lambda v : v.put_start_and_end_on(
plane.number_to_point(0), get_circle_point()
)
)
updates = [exp_base_update, exp_decimal_update, vector_update]
for update in updates:
update.update(0)
#Show initial vector
self.play(
GrowArrow(vector),
FadeIn(exp_base),
Write(exp_decimal)
)
self.add(*updates)
self.play(ghost_dot.shift, 2*RIGHT, run_time = 3)
self.wait()
#Show arc
arc, circle = [
Line(ORIGIN, t*UP)
for t in (get_t(), TAU)
]
for mob in arc, circle:
mob.insert_n_curves(20)
mob.set_stroke(RED, 4)
mob.apply_function(
lambda p : plane.number_to_point(
np.exp(R3_to_complex(p))
)
)
distance_label = DecimalNumber(
exp_decimal.number,
unit = "\\text{units}"
)
distance_label[-1].shift(SMALL_BUFF*RIGHT)
distance_label.match_color(arc)
distance_label.add_background_rectangle()
distance_label.move_to(
plane.number_to_point(
1.1*np.exp(complex(0, 0.4*get_t())),
),
DOWN+LEFT
)
self.play(ShowCreation(arc))
self.play(ReplacementTransform(
exp_decimal.copy(), distance_label
))
self.wait()
self.play(FadeOut(distance_label))
#Show full cycle
self.remove(arc)
self.play(
ghost_dot.move_to, TAU*RIGHT,
ShowCreation(
circle,
rate_func = lambda a : interpolate(
2.0/TAU, 1, smooth(a)
),
),
run_time = 6,
)
self.wait()
#Write exponential expression
exp_expression = TexMobject("e", "^{-", "2\\pi i", "f", "t}")
e, minus, two_pi_i, f, t = exp_expression
exp_expression.next_to(
plane.coords_to_point(1, 1),
UP+RIGHT
)
f.set_color(RED)
t.set_color(YELLOW)
exp_expression.add_background_rectangle()
two_pi_i_f_t_group = VGroup(two_pi_i, f, t)
two_pi_i_f_t_group.save_state()
two_pi_i_f_t_group.move_to(minus, LEFT)
exp_expression[1].remove(minus)
t.save_state()
t.align_to(f, LEFT)
exp_expression[1].remove(f)
labels = VGroup()
for sym, word in (t, "Time"), (f, "Frequency"):
label = TextMobject(word)
label.match_style(sym)
label.next_to(sym, UP, buff = MED_LARGE_BUFF)
label.add_background_rectangle()
label.arrow = Arrow(label, sym, buff = SMALL_BUFF)
label.arrow.match_style(sym)
labels.add(label)
time_label, frequency_label = labels
example_frequency = TexMobject("f = 1/10")
example_frequency.add_background_rectangle()
example_frequency.match_style(frequency_label)
example_frequency.move_to(frequency_label, DOWN)
self.play(ReplacementTransform(
VGroup(exp_base[1], exp_decimal[1]).copy(),
exp_expression
))
self.play(FadeOut(circle))
self.wait()
ghost_dot.move_to(ORIGIN)
always_shift(ghost_dot, rate = TAU)
self.add(ghost_dot)
self.play(
Write(time_label),
GrowArrow(time_label.arrow),
)
self.wait(12.5) #Leave time to say let's slow down
self.remove(ghost_dot)
self.play(
FadeOut(time_label),
FadeIn(frequency_label),
t.restore,
GrowFromPoint(f, frequency_label.get_center()),
ReplacementTransform(
time_label.arrow,
frequency_label.arrow,
)
)
ghost_dot.move_to(ORIGIN)
ghost_dot.clear_updaters()
always_shift(ghost_dot, rate=0.1*TAU)
self.add(ghost_dot)
self.wait(3)
self.play(
FadeOut(frequency_label),
FadeIn(example_frequency)
)
self.wait(15) #Give time to reference other video
#Reverse directions
ghost_dot.clear_updaters()
always_shift(ghost_dot, rate=-0.1 * TAU)
self.play(
FadeOut(example_frequency),
FadeOut(frequency_label.arrow),
GrowFromCenter(minus),
two_pi_i_f_t_group.restore
)
self.wait(4)
ghost_dot.clear_updaters()
self.remove(*updates)
self.play(*list(map(FadeOut, [
update.mobject
for update in updates
if update.mobject is not vector
])))
self.play(ghost_dot.move_to, ORIGIN)
exp_expression[1].add(minus, f)
exp_expression[1].sort(lambda p : p[0])
self.set_variables_as_attrs(
ambient_ghost_dot_movement, ghost_dot,
vector, vector_update, exp_expression
)
def show_winding_graph_expression(self):
ambient_ghost_dot_movement = self.ambient_ghost_dot_movement
ghost_dot = self.ghost_dot
vector = self.vector
exp_expression = self.exp_expression
plane = self.circle_plane
time_axes_group = self.time_axes_group
graph = self.graph
pol_graph = self.get_polarized_mobject(graph, freq = 0.2)
g_label = TexMobject("g(t)")
g_label.match_color(graph)
g_label.next_to(graph, UP)
g_label.add_background_rectangle()
g_scalar = g_label.copy()
g_scalar.move_to(exp_expression, DOWN+LEFT)
vector_animations = self.get_vector_animations(graph)
vector_animations[1].mobject = vector
graph_y_vector = vector_animations[0].mobject
self.play(
FadeIn(time_axes_group),
FadeOut(self.complex_plane_title)
)
self.play(Write(g_label))
self.wait()
self.play(
ReplacementTransform(g_label.copy(), g_scalar),
exp_expression.next_to, g_scalar, RIGHT, SMALL_BUFF,
exp_expression.shift, 0.5*SMALL_BUFF*UP,
)
self.play(*vector_animations, run_time = 15)
self.add(*self.mobjects_from_last_animation)
self.wait()
integrand = VGroup(g_scalar, exp_expression)
rect = SurroundingRectangle(integrand)
morty = Mortimer()
morty.next_to(rect, DOWN+RIGHT)
morty.shift_onto_screen()
self.play(
ShowCreation(rect),
FadeIn(morty)
)
self.play(morty.change, "raise_right_hand")
self.play(Blink(morty))
self.play(morty.change, "hooray", rect)
self.wait(2)
self.play(*list(map(FadeOut, [
morty, rect, graph_y_vector, vector
])))
self.integrand = integrand
def find_center_of_mass(self):
integrand = self.integrand
integrand.generate_target()
integrand.target.to_edge(RIGHT, buff = LARGE_BUFF)
integrand.target.shift(MED_LARGE_BUFF*DOWN)
sum_expr = TexMobject(
"{1", "\\over", "N}",
"\\sum", "_{k = 1}", "^N",
)
sum_expr.add_background_rectangle()
sum_expr.shift(SMALL_BUFF*(UP+5*RIGHT))
sum_expr.next_to(integrand.target, LEFT)
integral = TexMobject(
"{1", "\\over", "t_2 - t_1}",
"\\int", "_{t_1}", "^{t_2}"
)
integral.move_to(sum_expr, RIGHT)
time_interval_indicator = SurroundingRectangle(integral[2])
integral.add_background_rectangle()
axes = self.time_axes
time_interval = Line(
axes.coords_to_point(axes.x_min, 0),
axes.coords_to_point(axes.x_max, 0),
)
time_interval.match_style(time_interval_indicator)
time_interval_indicator.add(time_interval)
dt_mob = TexMobject("dt")
dt_mob.next_to(integrand.target, RIGHT, SMALL_BUFF, DOWN)
dt_mob.add_background_rectangle()
dots = self.show_center_of_mass_sampling(20)
self.wait()
self.play(
Write(sum_expr),
MoveToTarget(integrand),
)
#Add k subscript to t's
t1 = integrand[0][1][2]
t2 = integrand[1][1][-1]
t_mobs = VGroup(t1, t2)
t_mobs.save_state()
t_mobs.generate_target()
for i, t_mob in enumerate(t_mobs.target):
k = TexMobject("k")
k.match_style(t_mob)
k.match_height(t_mob)
k.scale(0.5)
k.move_to(t_mob.get_corner(DOWN+RIGHT), LEFT)
k.add_background_rectangle()
t_mob.add(k)
if i == 0:
t_mob.shift(0.5*SMALL_BUFF*LEFT)
self.play(MoveToTarget(t_mobs))
self.play(LaggedStartMap(
Indicate, dots[1],
rate_func = there_and_back,
color = TEAL,
))
self.show_center_of_mass_sampling(100)
dots = self.show_center_of_mass_sampling(500)
self.wait()
self.play(FadeOut(dots))
self.play(
ReplacementTransform(sum_expr, integral),
FadeIn(dt_mob),
t_mobs.restore,
)
self.wait()
self.play(ShowCreation(time_interval_indicator))
self.wait()
self.play(FadeOut(time_interval_indicator))
self.wait()
#Show confusion
randy = Randolph()
randy.flip()
randy.next_to(integrand, DOWN, LARGE_BUFF)
randy.to_edge(RIGHT)
full_expression_rect = SurroundingRectangle(
VGroup(integral, dt_mob), color = RED
)
com_dot = self.center_of_mass_dot
self.center_of_mass_dot_anim.update(0)
com_arrow = Arrow(
full_expression_rect.get_left(), com_dot,
buff = SMALL_BUFF
)
com_arrow.match_color(com_dot)
self.play(FadeIn(randy))
self.play(randy.change, "confused", integral)
self.play(Blink(randy))
self.wait(2)
self.play(ShowCreation(full_expression_rect))
self.play(
randy.change, "thinking", self.pol_graph,
GrowArrow(com_arrow),
GrowFromCenter(com_dot),
)
self.play(Blink(randy))
self.wait(2)
def show_center_of_mass_sampling(self, n_dots):
time_graph = self.graph
pol_graph = self.graph.polarized_mobject
axes = self.time_axes
dot = Dot(radius = 0.05, color = PINK)
pre_dots = VGroup(*[
dot.copy().move_to(axes.coords_to_point(t, 0))
for t in np.linspace(axes.x_min, axes.x_max, n_dots)
])
pre_dots.set_fill(opacity = 0)
for graph in time_graph, pol_graph:
if hasattr(graph, "dots"):
graph.dot_fade_anims = [FadeOut(graph.dots)]
else:
graph.dot_fade_anims = []
graph.save_state()
graph.generate_target()
if not hasattr(graph, "is_faded"):
graph.target.fade(0.7)
graph.is_faded = True
graph.dots = VGroup(*[
dot.copy().move_to(graph.point_from_proportion(a))
for a in np.linspace(0, 1, n_dots)
])
self.play(
ReplacementTransform(
pre_dots, time_graph.dots,
lag_ratio = 0.5,
run_time = 2,
),
MoveToTarget(time_graph),
*time_graph.dot_fade_anims
)
self.play(
ReplacementTransform(
time_graph.copy(), pol_graph.target
),
MoveToTarget(pol_graph),
ReplacementTransform(
time_graph.dots.copy(),
pol_graph.dots,
),
*pol_graph.dot_fade_anims,
run_time = 2
)
return VGroup(time_graph.dots, pol_graph.dots)
class EulersFormulaViaGroupTheoryWrapper(Scene):
def construct(self):
title = TextMobject("Euler's formula with introductory group theory")
title.to_edge(UP)
screen_rect = ScreenRectangle(height = 6)
screen_rect.next_to(title, DOWN)
self.add(title)
self.play(ShowCreation(screen_rect))
self.wait(2)
class WhyAreYouTellingUsThis(TeacherStudentsScene):
def construct(self):
self.student_says("Why are you \\\\ telling us this?")
self.play(self.teacher.change, "happy")
self.wait(2)
class BuildUpExpressionStepByStep(TeacherStudentsScene):
def construct(self):
expression = TexMobject(
"\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}",
"g(t)", "e", "^{-2\\pi i", "f", "t}", "dt"
)
frac, integral, g, e, two_pi_i, f, t, dt = expression
expression.next_to(self.teacher, UP+LEFT)
t.set_color(YELLOW)
g[2].set_color(YELLOW)
dt[1].set_color(YELLOW)
f.set_color(GREEN)
t.save_state()
t.move_to(f, LEFT)
self.play(
self.teacher.change, "raise_right_hand",
FadeIn(e),
FadeIn(two_pi_i),
)
self.play(
self.get_student_changes(*["pondering"]*3),
FadeIn(t),
)
self.play(
FadeIn(f),
t.restore,
)
self.wait()
self.play(FadeIn(g), Blink(self.students[1]))
self.wait()
self.play(
FadeIn(integral),
FadeIn(frac),
FadeIn(dt),
)
self.wait(3)
self.teacher_says(
"Just one final \\\\ distinction.",
bubble_kwargs = {"height" : 2.5, "width" : 3.5},
added_anims = [expression.to_corner, UP+RIGHT]
)
self.wait(3)
class ScaleUpCenterOfMass(WriteComplexExponentialExpression):
CONFIG = {
"time_axes_scale_val" : 0.6,
"initial_winding_frequency" : 2.05
}
def construct(self):
self.remove(self.pi_creature)
self.setup_plane()
self.setup_graph()
self.add_center_of_mass_dot()
self.add_expression()
self.cross_out_denominator()
self.scale_up_center_of_mass()
self.comment_on_current_signal()
def add_center_of_mass_dot(self):
self.center_of_mass_dot = self.get_center_of_mass_dot()
self.generate_center_of_mass_dot_update_anim()
self.add(self.center_of_mass_dot)
def add_expression(self):
expression = TexMobject(
"\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}",
"g(t)", "e", "^{-2\\pi i", "f", "t}", "dt"
)
frac, integral, g, e, two_pi_i, f, t, dt = expression
expression.to_corner(UP+RIGHT)
t.set_color(YELLOW)
g[2].set_color(YELLOW)
dt[1].set_color(YELLOW)
f.set_color(GREEN)
self.expression = expression
self.add(expression)
self.winding_freq_label.to_edge(RIGHT)
self.winding_freq_label[1].match_color(f)
self.winding_freq_label.align_to(
self.circle_plane.coords_to_point(0, 0.1), DOWN
)
def cross_out_denominator(self):
frac = self.expression[0]
integral = self.expression[1:]
for mob in frac, integral:
mob.add_to_back(BackgroundRectangle(mob))
self.add(mob)
cross = Cross(frac)
brace = Brace(integral, DOWN)
label = brace.get_text("The actual \\\\ Fourier transform")
label.add_background_rectangle()
label.shift_onto_screen()
rect = SurroundingRectangle(integral)
self.play(ShowCreation(cross))
self.wait()
self.play(ShowCreation(rect))
self.play(
GrowFromCenter(brace),
FadeIn(label)
)
self.wait(2)
self.integral = integral
self.frac = frac
self.frac_cross = cross
self.integral_rect = rect
self.integral_brace = brace
self.integral_label = label
def scale_up_center_of_mass(self):
plane = self.circle_plane
origin = plane.coords_to_point(0, 0)
com_dot = self.center_of_mass_dot
com_vector = Arrow(
origin, com_dot.get_center(),
buff = 0
)
com_vector.match_style(com_dot)
vector_to_scale = com_vector.copy()
def get_com_vector_copies(n):
com_vector_copies = VGroup(*[
com_vector.copy().shift(x*com_vector.get_vector())
for x in range(1, n+1)
])
com_vector_copies.set_color(TEAL)
return com_vector_copies
com_vector_update = UpdateFromFunc(
com_vector,
lambda v : v.put_start_and_end_on(origin, com_dot.get_center())
)
circle = Circle(color = TEAL)
circle.surround(com_dot, buffer_factor = 1.2)
time_span = Rectangle(
stroke_width = 0,
fill_color = TEAL,
fill_opacity = 0.4
)
axes = self.time_axes
time_span.replace(
Line(axes.coords_to_point(0, 0), axes.coords_to_point(3, 1.5)),
stretch = True
)
time_span.save_state()
time_span.stretch(0, 0, about_edge = LEFT)
graph = self.graph
short_graph, long_graph = [
axes.get_graph(
graph.underlying_function, x_min = 0, x_max = t_max,
).match_style(graph)
for t_max in (3, 6)
]
for g in short_graph, long_graph:
self.get_polarized_mobject(g, freq = self.initial_winding_frequency)
self.play(
FocusOn(circle, run_time = 2),
Succession(
ShowCreation, circle,
FadeOut, circle,
),
)
self.play(
com_dot.fade, 0.5,
FadeIn(vector_to_scale)
)
self.wait()
self.play(vector_to_scale.scale, 4, {"about_point" : origin})
self.wait()
self.play(
FadeOut(vector_to_scale),
FadeIn(com_vector),
)
self.remove(graph.polarized_mobject)
self.play(
com_dot.move_to,
center_of_mass(short_graph.polarized_mobject.points),
com_vector_update,
time_span.restore,
ShowCreation(short_graph.polarized_mobject),
)
self.wait()
# dot = Dot(fill_opacity = 0.5).move_to(time_span)
# self.play(
# dot.move_to, com_vector,
# dot.set_fill, {"opacity" : 0},
# remover = True
# )
com_vector_copies = get_com_vector_copies(2)
self.play(*[
ReplacementTransform(
com_vector.copy(), cvc,
path_arc = -TAU/10
)
for cvc in com_vector_copies
])
self.wait()
#Squish_graph
to_squish = VGroup(
axes, graph,
time_span,
)
to_squish.generate_target()
squish_factor = 0.75
to_squish.target.stretch(squish_factor, 0, about_edge = LEFT)
pairs = list(zip(
to_squish.family_members_with_points(),
to_squish.target.family_members_with_points()
))
to_unsquish = list(axes.x_axis.numbers) + list(axes.labels)
for sm, tsm in pairs:
if sm in to_unsquish:
tsm.stretch(1/squish_factor, 0)
if sm is axes.background_rectangle:
tsm.stretch(1/squish_factor, 0, about_edge = LEFT)
long_graph.stretch(squish_factor, 0)
self.play(
MoveToTarget(to_squish),
FadeOut(com_vector_copies)
)
long_graph.move_to(graph, LEFT)
self.play(
com_dot.move_to,
center_of_mass(long_graph.polarized_mobject.points),
com_vector_update,
time_span.stretch, 2, 0, {"about_edge" : LEFT},
*[
ShowCreation(
mob,
rate_func = lambda a : interpolate(
0.5, 1, smooth(a)
)
)
for mob in (long_graph, long_graph.polarized_mobject)
],
run_time = 2
)
self.remove(graph, short_graph.polarized_mobject)
self.graph = long_graph
self.wait()
self.play(FocusOn(com_dot))
com_vector_copies = get_com_vector_copies(5)
self.play(*[
ReplacementTransform(
com_vector.copy(), cvc,
path_arc = -TAU/10
)
for cvc in com_vector_copies
])
self.wait()
# Scale graph out even longer
to_shift = VGroup(self.integral, self.integral_rect)
to_fade = VGroup(
self.integral_brace, self.integral_label,
self.frac, self.frac_cross
)
self.play(
to_shift.shift, 2*DOWN,
FadeOut(to_fade),
axes.background_rectangle.stretch, 2, 0, {"about_edge" : LEFT},
Animation(axes),
Animation(self.graph),
FadeOut(com_vector_copies),
)
self.change_frequency(2.0, added_anims = [com_vector_update])
very_long_graph = axes.get_graph(
graph.underlying_function,
x_min = 0, x_max = 12,
)
very_long_graph.match_style(graph)
self.get_polarized_mobject(very_long_graph, freq = 2.0)
self.play(
com_dot.move_to,
center_of_mass(very_long_graph.polarized_mobject.points),
com_vector_update,
ShowCreation(
very_long_graph,
rate_func = lambda a : interpolate(0.5, 1, a)
),
ShowCreation(very_long_graph.polarized_mobject)
)
self.remove(graph, graph.polarized_mobject)
self.graph = very_long_graph
self.wait()
self.play(
com_vector.scale, 12, {"about_point" : origin},
run_time = 2
)
# com_vector_copies = get_com_vector_copies(11)
# self.play(ReplacementTransform(
# VGroup(com_vector.copy()),
# com_vector_copies,
# path_arc = TAU/10,
# run_time = 1.5,
# lag_ratio = 0.5
# ))
self.wait()
self.com_vector = com_vector
self.com_vector_update = com_vector_update
self.com_vector_copies = com_vector_copies
def comment_on_current_signal(self):
graph = self.graph
com_dot = self.center_of_mass_dot
com_vector = self.com_vector
com_vector_update = self.com_vector_update
axes = self.time_axes
origin = self.circle_plane.coords_to_point(0, 0)
wps_label = self.winding_freq_label
new_com_vector_update = UpdateFromFunc(
com_vector, lambda v : v.put_start_and_end_on(
origin, com_dot.get_center()
).scale(12, about_point = origin)
)
v_lines = self.get_v_lines_indicating_periods(
freq = 1.0, n_lines = 3
)[:2]
graph_portion = axes.get_graph(
graph.underlying_function, x_min = 1, x_max = 2
)
graph_portion.set_color(TEAL)
bps_label = TextMobject("2 beats per second")
bps_label.scale(0.75)
bps_label.next_to(graph_portion, UP, aligned_edge = LEFT)
bps_label.shift(SMALL_BUFF*RIGHT)
bps_label.add_background_rectangle()
self.play(
ShowCreation(v_lines, lag_ratio = 0),
ShowCreation(graph_portion),
FadeIn(bps_label),
)
self.wait()
self.play(ReplacementTransform(
bps_label[1][0].copy(), wps_label[1]
))
self.wait()
self.play(
com_vector.scale, 0.5, {"about_point" : origin},
rate_func = there_and_back,
run_time = 2
)
self.wait(2)
self.change_frequency(2.5,
added_anims = [new_com_vector_update],
run_time = 20,
rate_func=linear,
)
self.wait()
class TakeAStepBack(TeacherStudentsScene):
def construct(self):
self.student_says(
"Hang on, go over \\\\ that again?",
target_mode = "confused"
),
self.change_student_modes(*["confused"]*3)
self.play(self.teacher.change, "happy")
self.wait(3)
class SimpleCosineWrappingAroundCircle(WriteComplexExponentialExpression):
CONFIG = {
"initial_winding_frequency" : 0,
"circle_plane_config" : {
"unit_size" : 3,
},
}
def construct(self):
self.setup_plane()
self.setup_graph()
self.remove(self.pi_creature)
self.winding_freq_label.shift(7*LEFT)
VGroup(self.time_axes, self.graph).shift(4*UP)
VGroup(
self.circle_plane,
self.graph.polarized_mobject
).move_to(ORIGIN)
self.add(self.get_center_of_mass_dot())
self.generate_center_of_mass_dot_update_anim()
self.change_frequency(
2.0,
rate_func=linear,
run_time = 30
)
self.wait()
class SummarizeTheFullTransform(DrawFrequencyPlot):
CONFIG = {
"time_axes_config" : {
"x_max" : 4.5,
"x_axis_config" : {
"unit_size" : 1.2,
"tick_frequency" : 0.5,
# "numbers_with_elongated_ticks" : range(0, 10, 2),
# "numbers_to_show" : range(0, 10, 2),
}
},
"frequency_axes_config" : {
"x_max" : 5,
"x_axis_config" : {
"unit_size" : 1,
"numbers_to_show" : list(range(1, 5)),
},
"y_max" : 2,
"y_min" : -2,
"y_axis_config" : {
"unit_size" : 0.75,
"tick_frequency" : 1,
},
},
}
def construct(self):
self.setup_all_axes()
self.show_transform_function()
self.show_winding()
def setup_all_axes(self):
time_axes = self.get_time_axes()
time_label, intensity_label = time_axes.labels
time_label.next_to(
time_axes.x_axis.get_right(),
DOWN, SMALL_BUFF
)
intensity_label.next_to(time_axes.y_axis, UP, buff = SMALL_BUFF)
intensity_label.to_edge(LEFT)
frequency_axes = self.get_frequency_axes()
frequency_axes.to_corner(UP+RIGHT)
frequency_axes.shift(RIGHT)
fy_axis = frequency_axes.y_axis
for number in fy_axis.numbers:
number.add_background_rectangle()
fy_axis.remove(*fy_axis.numbers[1::2])
frequency_axes.remove(frequency_axes.box)
frequency_axes.label.shift_onto_screen()
circle_plane = self.get_circle_plane()
self.set_variables_as_attrs(time_axes, frequency_axes, circle_plane)
self.add(time_axes)
def show_transform_function(self):
time_axes = self.time_axes
frequency_axes = self.frequency_axes
def func(t):
return 0.5*(2+np.cos(2*TAU*t) + np.cos(3*TAU*t))
fourier_func = get_fourier_transform(
func,
t_min = time_axes.x_min,
t_max = time_axes.x_max,
use_almost_fourier = False,
)
graph = time_axes.get_graph(func)
graph.set_color(GREEN)
fourier_graph = frequency_axes.get_graph(fourier_func)
fourier_graph.set_color(RED)
g_t = TexMobject("g(t)")
g_t[-2].match_color(graph)
g_t.next_to(graph, UP)
g_hat_f = TexMobject("\\hat g(f)")
g_hat_f[-2].match_color(fourier_graph)
g_hat_f.next_to(
frequency_axes.input_to_graph_point(2, fourier_graph),
UP
)
morty = self.pi_creature
time_label = time_axes.labels[0]
frequency_label = frequency_axes.label
for label in time_label, frequency_label:
label.rect = SurroundingRectangle(label)
time_label.rect.match_style(graph)
frequency_label.rect.match_style(fourier_graph)
self.add(graph)
g_t.save_state()
g_t.move_to(morty, UP+LEFT)
g_t.fade(1)
self.play(
morty.change, "raise_right_hand",
g_t.restore,
)
self.wait()
self.play(Write(frequency_axes, run_time = 1))
self.play(
ReplacementTransform(graph.copy(), fourier_graph),
ReplacementTransform(g_t.copy(), g_hat_f),
)
self.wait(2)
for label in time_label, frequency_label:
self.play(
ShowCreation(label.rect),
morty.change, "thinking"
)
self.play(FadeOut(label.rect))
self.wait()
self.set_variables_as_attrs(
graph, fourier_graph,
g_t, g_hat_f
)
def show_winding(self):
plane = self.circle_plane
graph = self.graph
fourier_graph = self.fourier_graph
morty = self.pi_creature
g_hat_f = self.g_hat_f
g_hat_f_rect = SurroundingRectangle(g_hat_f)
g_hat_f_rect.set_color(TEAL)
g_hat_rect = SurroundingRectangle(g_hat_f[0])
g_hat_rect.match_style(g_hat_f_rect)
g_hat_f.generate_target()
g_hat_f.target.next_to(plane, RIGHT)
g_hat_f.target.shift(UP)
arrow = Arrow(
g_hat_f.target.get_left(),
plane.coords_to_point(0, 0),
color = self.center_of_mass_color,
)
frequency_axes = self.frequency_axes
imaginary_fourier_graph = frequency_axes.get_graph(
get_fourier_transform(
graph.underlying_function,
t_min = self.time_axes.x_min,
t_max = self.time_axes.x_max,
real_part = False,
use_almost_fourier = False,
)
)
imaginary_fourier_graph.set_color(BLUE)
imaginary_fourier_graph.shift(
frequency_axes.x_axis.get_right() - \
imaginary_fourier_graph.points[-1],
)
real_part = TextMobject(
"Real part of", "$\\hat g(f)$"
)
real_part[1].match_style(g_hat_f)
real_part.move_to(g_hat_f)
real_part.to_edge(RIGHT)
self.get_polarized_mobject(graph, freq = 0)
update_pol_graph = UpdateFromFunc(
graph.polarized_mobject,
lambda m : m.set_stroke(width = 2)
)
com_dot = self.get_center_of_mass_dot()
winding_run_time = 40.0
g_hat_f_indication = Succession(
Animation, Mobject(), {"run_time" : 4},
FocusOn, g_hat_f,
ShowCreation, g_hat_f_rect,
Animation, Mobject(),
Transform, g_hat_f_rect, g_hat_rect,
Animation, Mobject(),
FadeOut, g_hat_f_rect,
Animation, Mobject(),
MoveToTarget, g_hat_f,
UpdateFromAlphaFunc, com_dot, lambda m, a : m.set_fill(opacity = a),
Animation, Mobject(), {"run_time" : 2},
GrowArrow, arrow,
FadeOut, arrow,
Animation, Mobject(), {"run_time" : 5},
Write, real_part, {"run_time" : 2},
Animation, Mobject(), {"run_time" : 3},
ShowCreation, imaginary_fourier_graph, {"run_time" : 3},
rate_func = squish_rate_func(
lambda x : x, 0, 31./winding_run_time
),
run_time = winding_run_time
)
self.play(
FadeIn(plane),
ReplacementTransform(
graph.copy(), graph.polarized_mobject
),
morty.change, "happy",
)
self.generate_center_of_mass_dot_update_anim(multiplier = 4.5)
self.generate_fourier_dot_transform(fourier_graph)
self.change_frequency(
5.0,
rate_func=linear,
run_time = winding_run_time,
added_anims = [
g_hat_f_indication,
update_pol_graph,
Animation(frequency_axes.x_axis.numbers),
Animation(self.fourier_graph_dot),
]
)
self.wait()
class SummarizeFormula(Scene):
def construct(self):
expression = self.get_expression()
screen_rect = ScreenRectangle(height = 5)
screen_rect.to_edge(DOWN)
exp_rect, g_exp_rect, int_rect = [
SurroundingRectangle(VGroup(
expression.get_part_by_tex(p1),
expression.get_part_by_tex(p2),
))
for p1, p2 in [("e", "t}"), ("g({}", "t}"), ("\\int", "dt")]
]
self.add(expression)
self.wait()
self.play(
ShowCreation(screen_rect),
ShowCreation(exp_rect),
)
self.wait(2)
self.play(Transform(exp_rect, g_exp_rect))
self.wait(2)
self.play(Transform(exp_rect, int_rect))
self.wait(2)
def get_expression(self):
expression = TexMobject(
"\\hat g(", "f", ")", "=", "\\int", "_{t_1}", "^{t_2}",
"g({}", "t", ")", "e", "^{-2\\pi i", "f", "t}", "dt"
)
expression.set_color_by_tex(
"t", YELLOW, substring = False,
)
expression.set_color_by_tex("t}", YELLOW)
expression.set_color_by_tex(
"f", RED, substring = False,
)
expression.scale(1.2)
expression.to_edge(UP)
return expression
class OneSmallNote(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"Just one \\\\ small note...",
# target_mode =
)
self.change_student_modes("erm", "happy", "sassy")
self.wait(2)
class BoundsAtInfinity(SummarizeFormula):
def construct(self):
expression = self.get_expression()
self.add(expression)
self.add_graph()
axes = self.axes
graph = self.graph
time_interval = self.get_time_interval(-2, 2)
wide_interval = self.get_time_interval(-FRAME_X_RADIUS, FRAME_X_RADIUS)
bounds = VGroup(*reversed(expression.get_parts_by_tex("t_")))
bound_rects = VGroup(*[
SurroundingRectangle(b, buff = 0.5*SMALL_BUFF)
for b in bounds
])
bound_rects.set_color(TEAL)
inf_bounds = VGroup(*[
VGroup(TexMobject(s + "\\infty"))
for s in ("-", "+")
])
decimal_bounds = VGroup(*[DecimalNumber(0) for x in range(2)])
for bound, inf_bound, d_bound in zip(bounds, inf_bounds, decimal_bounds):
for new_bound in inf_bound, d_bound:
new_bound.scale(0.7)
new_bound.move_to(bound, LEFT)
new_bound.bound = bound
def get_db_num_update(vect):
return lambda a : axes.x_axis.point_to_number(
time_interval.get_edge_center(vect)
)
decimal_updates = [
ChangingDecimal(
db, get_db_num_update(vect),
position_update_func = lambda m : m.move_to(
m.bound, LEFT
)
)
for db, vect in zip(decimal_bounds, [LEFT, RIGHT])
]
for update in decimal_updates:
update.update(1)
time_interval.save_state()
self.wait()
self.play(ReplacementTransform(
self.get_time_interval(0, 0.01), time_interval
))
self.play(LaggedStartMap(ShowCreation, bound_rects))
self.wait()
self.play(FadeOut(bound_rects))
self.play(ReplacementTransform(bounds, inf_bounds))
self.play(Transform(
time_interval, wide_interval,
run_time = 4,
rate_func = there_and_back
))
self.play(
ReplacementTransform(inf_bounds, decimal_bounds),
time_interval.restore,
)
self.play(
VGroup(axes, graph).stretch, 0.05, 0,
Transform(time_interval, wide_interval),
UpdateFromAlphaFunc(
axes.x_axis.numbers,
lambda m, a : m.set_fill(opacity = 1-a)
),
*decimal_updates,
run_time = 12,
rate_func = bezier([0, 0, 1, 1])
)
self.wait()
def add_graph(self):
axes = Axes(
x_min = -140,
x_max = 140,
y_min = -2,
y_max = 2,
number_line_config = {
"include_tip" : False,
},
)
axes.x_axis.add_numbers(*list(filter(
lambda x : x != 0,
list(range(-8, 10, 2)),
)))
axes.shift(DOWN)
self.add(axes)
def func(x):
return np.exp(-0.1*x**2)*(1 + np.cos(TAU*x))
graph = axes.get_graph(func)
self.add(graph)
graph.set_color(YELLOW)
self.set_variables_as_attrs(axes, graph)
def get_time_interval(self, t1, t2):
line = Line(*[
self.axes.coords_to_point(t, 0)
for t in (t1, t2)
])
rect = Rectangle(
stroke_width = 0,
fill_color = TEAL,
fill_opacity = 0.5,
)
rect.match_width(line)
rect.stretch_to_fit_height(2.5)
rect.move_to(line, DOWN)
return rect
class MoreToCover(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"Much more to say...",
target_mode = "hooray",
run_time = 1,
)
self.wait()
self.teacher_says(
"SO MUCH!",
target_mode = "surprised",
added_anims = [self.get_student_changes(*3*["happy"])],
run_time = 0.5
)
self.wait(2)
class ShowUncertaintyPrinciple(Scene):
def construct(self):
title = TextMobject("Uncertainty principle")
self.add(title)
top_axes = Axes(
x_min = -FRAME_X_RADIUS,
x_max = FRAME_X_RADIUS,
y_min = 0,
y_max = 3,
y_axis_config = {
"unit_size" : 0.6,
"include_tip" : False,
}
)
bottom_axes = top_axes.deepcopy()
arrow = Vector(DOWN, color = WHITE)
group = VGroup(
title, top_axes, arrow, bottom_axes
)
group.arrange(DOWN)
title.shift(MED_SMALL_BUFF*UP)
group.to_edge(UP)
fourier_word = TextMobject("Fourier transform")
fourier_word.next_to(arrow, RIGHT)
self.add(group, fourier_word)
ghost_dot = Dot(RIGHT, fill_opacity = 0)
def get_bell_func(factor = 1):
return lambda x : 2*np.exp(-factor*x**2)
top_graph = top_axes.get_graph(get_bell_func())
top_graph.set_color(YELLOW)
bottom_graph = bottom_axes.get_graph(get_bell_func())
bottom_graph.set_color(RED)
def get_update_func(axes):
def update_graph(graph):
f = ghost_dot.get_center()[0]
if axes == bottom_axes:
f = 1./f
new_graph = axes.get_graph(get_bell_func(f))
graph.points = new_graph.points
return update_graph
factors = [0.3, 0.1, 2, 10, 100, 0.01, 0.5]
self.play(ShowCreation(top_graph))
self.play(ReplacementTransform(
top_graph.copy(),
bottom_graph,
))
self.wait(2)
self.add(*[
Mobject.add_updater(graph, get_update_func(axes))
for graph, axes in [(top_graph, top_axes), (bottom_graph, bottom_axes)]
])
for factor in factors:
self.play(
ghost_dot.move_to, factor*RIGHT,
run_time = 2
)
self.wait()
class XCoordinateLabelTypoFix(Scene):
def construct(self):
words = TextMobject("$x$-coordinate for center of mass")
words.set_color(RED)
self.add(words)
class NextVideoWrapper(Scene):
def construct(self):
title = TextMobject("Next video")
title.to_edge(UP)
screen_rect = ScreenRectangle(height = 6)
screen_rect.next_to(title, DOWN)
self.add(title)
self.play(ShowCreation(screen_rect))
self.wait(2)
class SubscribeOrBinge(PiCreatureScene):
def construct(self):
morty = self.pi_creature
morty.center().to_edge(DOWN, LARGE_BUFF)
subscribe = TextMobject("Subscribe")
subscribe.set_color(RED)
subscribe.next_to(morty, UP+RIGHT)
binge = TextMobject("Binge")
binge.set_color(BLUE)
binge.next_to(morty, UP+LEFT)
videos = VGroup(*[VideoIcon() for x in range(30)])
colors = it.cycle([BLUE_D, BLUE_E, BLUE_C, GREY_BROWN])
for video, color in zip(videos, colors):
video.set_color(color)
videos.move_to(binge.get_bottom(), UP)
video_anim = LaggedStartMap(
Succession, videos,
lambda v : (
FadeIn, v,
ApplyMethod, v.shift, 5*DOWN, {"run_time" : 6},
),
run_time = 10
)
sub_arrow = Arrow(
subscribe.get_bottom(),
Dot().to_corner(DOWN+RIGHT, buff = LARGE_BUFF),
color = RED
)
for word in subscribe, binge:
word.save_state()
word.shift(DOWN)
word.set_fill(opacity = 0)
self.play(
subscribe.restore,
morty.change, "raise_left_hand"
)
self.play(GrowArrow(sub_arrow))
self.wait()
self.play(
video_anim,
Succession(
AnimationGroup(
ApplyMethod(binge.restore),
ApplyMethod(morty.change, "raise_right_hand", binge),
),
Blink, morty,
ApplyMethod, morty.change, "shruggie", videos,
Animation, Mobject(), {"run_time" : 2},
Blink, morty,
Animation, Mobject(), {"run_time" : 4}
)
)
class CloseWithAPuzzle(TeacherStudentsScene):
def construct(self):
self.teacher_says("Close with a puzzle!", run_time = 1)
self.change_student_modes(*["hooray"]*3)
self.wait(3)
class PuzzleDescription(Scene):
def construct(self):
lines = VGroup(
TextMobject("Convex set", "$C$", "in $\\mathds{R}^3$"),
TextMobject("Boundary", "$B$", "$=$", "$\\partial C$"),
TextMobject("$D$", "$=\\{p+q | p, q \\in B\\}$"),
TextMobject("Prove that", "$D$", "is convex")
)
for line in lines:
line.set_color_by_tex_to_color_map({
"$C$" : BLUE_D,
"\\partial C" : BLUE_D,
"$B$" : BLUE_C,
"$D$" : YELLOW,
})
VGroup(lines[2][1][2], lines[2][1][6]).set_color(RED)
VGroup(lines[2][1][4], lines[2][1][8]).set_color(MAROON_B)
lines[2][1][10].set_color(BLUE_C)
lines.scale(1.25)
lines.arrange(DOWN, buff = LARGE_BUFF, aligned_edge = LEFT)
lines.to_corner(UP+RIGHT)
for line in lines:
self.play(Write(line))
self.wait(2)
class SponsorScreenGrab(PiCreatureScene):
def construct(self):
morty = self.pi_creature
screen = ScreenRectangle(height = 5)
screen.to_corner(UP+LEFT)
screen.shift(MED_LARGE_BUFF*DOWN)
url = TextMobject("janestreet.com/3b1b")
url.next_to(screen, UP)
self.play(
morty.change, "raise_right_hand",
ShowCreation(screen)
)
self.play(Write(url))
self.wait(2)
for mode in "happy", "thinking", "pondering", "thinking":
self.play(morty.change, mode, screen)
self.wait(4)
class FourierEndScreen(PatreonEndScreen):
CONFIG = {
"specific_patrons" : [
"CrypticSwarm",
"Ali Yahya",
"Juan Benet",
"Markus Persson",
"Damion Kistler",
"Burt Humburg",
"Yu Jun",
"Dave Nicponski",
"Kaustuv DeBiswas",
"Joseph John Cox",
"Luc Ritchie",
"Achille Brighton",
"Rish Kundalia",
"Yana Chernobilsky",
"Shìmín Kuang",
"Mathew Bramson",
"Jerry Ling",
"Mustafa Mahdi",
"Meshal Alshammari",
"Mayank M. Mehrotra",
"Lukas Biewald",
"Robert Teed",
"One on Epsilon",
"Samantha D. Suplee",
"Mark Govea",
"John Haley",
"Julian Pulgarin",
"Jeff Linse",
"Cooper Jones",
"Boris Veselinovich",
"Ryan Dahl",
"Ripta Pasay",
"Eric Lavault",
"Mads Elvheim",
"Andrew Busey",
"Randall Hunt",
"Desmos",
"Tianyu Ge",
"Awoo",
"Dr David G. Stork",
"Linh Tran",
"Jason Hise",
"Bernd Sing",
"Ankalagon",
"Mathias Jansson",
"David Clark",
"Ted Suzman",
"Eric Chow",
"Michael Gardner",
"Jonathan Eppele",
"Clark Gaebel",
"David Kedmey",
"Jordan Scales",
"Ryan Atallah",
"supershabam",
"1stViewMaths",
"Jacob Magnuson",
"Thomas Tarler",
"Isak Hietala",
"James Thornton",
"Egor Gumenuk",
"Waleed Hamied",
"Oliver Steele",
"Yaw Etse",
"David B",
"Julio Cesar Campo Neto",
"Delton Ding",
"George Chiesa",
"Chloe Zhou",
"Alexander Nye",
"Ross Garber",
"Wang HaoRan",
"Felix Tripier",
"Arthur Zey",
"Norton",
"Kevin Le",
"Alexander Feldman",
"David MacCumber",
],
}
class Thumbnail(Scene):
def construct(self):
title = TextMobject("Fourier\\\\", "Visualized")
title.set_color(YELLOW)
title.set_stroke(RED, 2)
title.scale(2.5)
title.add_background_rectangle()
def func(t):
return np.cos(2*TAU*t) + np.cos(3*TAU*t) + np.cos(5*t)
fourier = get_fourier_transform(func, -5, 5)
graph = FunctionGraph(func, x_min = -5, x_max = 5)
graph.set_color(BLUE)
fourier_graph = FunctionGraph(fourier, x_min = 0, x_max = 6)
fourier_graph.set_color(YELLOW)
for g in graph, fourier_graph:
g.stretch_to_fit_height(2)
g.stretch_to_fit_width(10)
g.set_stroke(width = 8)
pol_graphs = VGroup()
for f in np.linspace(1.98, 2.02, 7):
pol_graph = ParametricFunction(
lambda t : complex_to_R3(
(2+np.cos(2*TAU*t)+np.cos(3*TAU*t))*np.exp(-complex(0, TAU*f*t))
),
t_min = -5,
t_max = 5,
num_graph_points = 200,
)
pol_graph.match_color(graph)
pol_graph.set_height(2)
pol_graphs.add(pol_graph)
pol_graphs.arrange(RIGHT, buff = LARGE_BUFF)
pol_graphs.set_color_by_gradient(BLUE_C, YELLOW)
pol_graphs.match_width(graph)
pol_graphs.set_stroke(width = 2)
self.clear()
title.center().to_edge(UP)
pol_graphs.set_width(FRAME_WIDTH - 1)
pol_graphs.center()
title.move_to(pol_graphs)
title.shift(SMALL_BUFF*LEFT)
graph.next_to(title, UP)
fourier_graph.next_to(title, DOWN)
self.add(pol_graphs, title, graph, fourier_graph)