2018-01-11 18:13:50 -08:00
|
|
|
from helpers import *
|
2018-01-18 13:33:48 -08:00
|
|
|
import scipy
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
from animation.animation import Animation
|
|
|
|
from animation.transform import *
|
|
|
|
from animation.simple_animations import *
|
|
|
|
from animation.playground import *
|
|
|
|
from animation.continual_animation import *
|
|
|
|
from topics.geometry import *
|
|
|
|
from topics.characters import *
|
|
|
|
from topics.functions import *
|
|
|
|
from topics.fractals import *
|
|
|
|
from topics.number_line import *
|
|
|
|
from topics.combinatorics import *
|
|
|
|
from topics.numerals import *
|
|
|
|
from topics.three_dimensions import *
|
|
|
|
from topics.objects import *
|
|
|
|
from topics.probability import *
|
|
|
|
from topics.complex_numbers import *
|
2018-01-20 19:31:43 -08:00
|
|
|
from topics.common_scenes import *
|
2018-01-11 18:13:50 -08:00
|
|
|
from scene import Scene
|
|
|
|
from scene.reconfigurable_scene import ReconfigurableScene
|
|
|
|
from scene.zoomed_scene import *
|
|
|
|
from camera import Camera
|
|
|
|
from mobject import *
|
|
|
|
from mobject.image_mobject import *
|
|
|
|
from mobject.vectorized_mobject import *
|
|
|
|
from mobject.svg_mobject import *
|
|
|
|
from mobject.tex_mobject import *
|
|
|
|
from topics.graph_scene import *
|
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
|
|
|
|
def get_fourier_transform(
|
|
|
|
func, t_min, t_max,
|
|
|
|
real_part = True,
|
|
|
|
use_almost_fourier = True,
|
|
|
|
):
|
|
|
|
# part = "real" if real_part else "imag"
|
|
|
|
trig = np.cos if real_part else np.sin
|
|
|
|
scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0
|
|
|
|
def fourier_transform(f):
|
|
|
|
return scalar*scipy.integrate.quad(
|
|
|
|
lambda t : func(t)*trig(-TAU*f*t),
|
|
|
|
t_min, t_max
|
|
|
|
)[0]
|
|
|
|
return fourier_transform
|
|
|
|
|
|
|
|
##
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-11 18:13:50 -08:00
|
|
|
class AddingPureFrequencies(PiCreatureScene):
|
2018-01-13 15:51:25 -08:00
|
|
|
CONFIG = {
|
|
|
|
"A_frequency" : 2.1,
|
|
|
|
"A_color" : YELLOW,
|
|
|
|
"D_color" : PINK,
|
2018-01-14 19:27:22 -08:00
|
|
|
"F_color" : TEAL,
|
|
|
|
"C_color" : RED,
|
2018-01-13 15:51:25 -08:00
|
|
|
"sum_color" : GREEN,
|
|
|
|
"equilibrium_height" : 1.5,
|
|
|
|
}
|
2018-01-11 18:13:50 -08:00
|
|
|
def construct(self):
|
|
|
|
self.add_speaker()
|
2018-01-13 15:51:25 -08:00
|
|
|
self.play_A440()
|
2018-01-11 18:13:50 -08:00
|
|
|
self.measure_air_pressure()
|
|
|
|
self.play_lower_pitch()
|
|
|
|
self.play_mix()
|
|
|
|
self.separate_out_parts()
|
2018-01-13 15:51:25 -08:00
|
|
|
self.draw_sum_at_single_point()
|
|
|
|
self.draw_full_sum()
|
2018-01-11 18:13:50 -08:00
|
|
|
self.add_more_notes()
|
|
|
|
|
|
|
|
def add_speaker(self):
|
|
|
|
speaker = SVGMobject(file_name = "speaker")
|
|
|
|
speaker.to_edge(DOWN)
|
|
|
|
|
|
|
|
self.add(speaker)
|
|
|
|
self.speaker = speaker
|
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
def play_A440(self):
|
2018-01-11 18:13:50 -08:00
|
|
|
randy = self.pi_creature
|
2018-01-13 15:51:25 -08:00
|
|
|
A_label = TextMobject("A440")
|
|
|
|
A_label.highlight(self.A_color)
|
|
|
|
A_label.next_to(self.speaker, UP)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
self.broadcast(
|
2018-01-13 15:51:25 -08:00
|
|
|
FadeIn(A_label),
|
2018-01-14 19:27:22 -08:00
|
|
|
Succession(
|
|
|
|
ApplyMethod, randy.change, "pondering",
|
|
|
|
Animation, randy,
|
|
|
|
Blink, randy
|
|
|
|
)
|
2018-01-11 18:13:50 -08:00
|
|
|
)
|
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
self.set_variables_as_attrs(A_label)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
def measure_air_pressure(self):
|
|
|
|
randy = self.pi_creature
|
|
|
|
axes = Axes(
|
2018-01-13 15:51:25 -08:00
|
|
|
y_min = -2, y_max = 2,
|
2018-01-11 18:13:50 -08:00
|
|
|
x_min = 0, x_max = 10,
|
|
|
|
number_line_config = {"include_tip" : False},
|
|
|
|
)
|
|
|
|
axes.stretch_to_fit_height(2)
|
|
|
|
axes.to_corner(UP+LEFT)
|
2018-01-13 15:51:25 -08:00
|
|
|
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
|
|
|
|
)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
frequency = self.A_frequency
|
|
|
|
graph = self.get_wave_graph(frequency, axes)
|
|
|
|
func = graph.underlying_function
|
|
|
|
graph.highlight(self.A_color)
|
2018-01-11 18:13:50 -08:00
|
|
|
pressure = TextMobject("Pressure")
|
|
|
|
time = TextMobject("Time")
|
2018-01-13 15:51:25 -08:00
|
|
|
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)
|
2018-01-11 18:13:50 -08:00
|
|
|
time.next_to(axes.x_axis.get_right(), DOWN+LEFT)
|
|
|
|
axes.labels = VGroup(pressure, time)
|
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
n = 10
|
2018-01-11 18:13:50 -08:00
|
|
|
brace = Brace(Line(
|
2018-01-13 15:51:25 -08:00
|
|
|
axes.coords_to_point(n/frequency, func(n/frequency)),
|
|
|
|
axes.coords_to_point((n+1)/frequency, func((n+1)/frequency)),
|
2018-01-11 18:13:50 -08:00
|
|
|
), UP)
|
|
|
|
words = brace.get_text("Imagine 440 per second", buff = SMALL_BUFF)
|
2018-01-13 15:51:25 -08:00
|
|
|
words.scale(0.8, about_point = words.get_bottom())
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
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 = None),
|
2018-01-13 15:51:25 -08:00
|
|
|
ShowCreation(equilibrium_line),
|
2018-01-11 18:13:50 -08:00
|
|
|
)
|
2018-01-13 15:51:25 -08:00
|
|
|
axes.add(equilibrium_line)
|
2018-01-11 18:13:50 -08:00
|
|
|
self.play(
|
2018-01-14 19:27:22 -08:00
|
|
|
randy.change, "erm", graph,
|
2018-01-11 18:13:50 -08:00
|
|
|
GrowFromCenter(brace),
|
|
|
|
Write(words)
|
|
|
|
)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-11 18:13:50 -08:00
|
|
|
graph.save_state()
|
|
|
|
self.play(
|
|
|
|
FadeOut(brace),
|
|
|
|
FadeOut(words),
|
2018-01-13 15:51:25 -08:00
|
|
|
VGroup(axes, graph, axes.labels).shift, 0.8*UP,
|
|
|
|
graph.fade, 0.85,
|
|
|
|
graph.shift, 0.8*UP,
|
2018-01-11 18:13:50 -08:00
|
|
|
)
|
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
graph.saved_state.move_to(graph)
|
|
|
|
self.set_variables_as_attrs(axes, A_graph = graph)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
def play_lower_pitch(self):
|
2018-01-13 15:51:25 -08:00
|
|
|
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.highlight(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 = None),
|
|
|
|
randy.change, "happy",
|
|
|
|
n_circles = 6,
|
|
|
|
)
|
|
|
|
self.play(randy.change, "confused", graph)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait(2)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
|
|
|
self.set_variables_as_attrs(
|
|
|
|
D_label,
|
|
|
|
D_graph = graph
|
|
|
|
)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
def play_mix(self):
|
2018-01-13 15:51:25 -08:00
|
|
|
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 = None)
|
|
|
|
for graph in self.A_graph, self.D_graph
|
|
|
|
]
|
|
|
|
)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
def separate_out_parts(self):
|
2018-01-13 15:51:25 -08:00
|
|
|
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(
|
|
|
|
map(MoveToTarget, movers),
|
|
|
|
[
|
|
|
|
ApplyMethod(mob.shift, SPACE_HEIGHT*DOWN, remover = True)
|
|
|
|
for mob in randy, speaker
|
|
|
|
]
|
|
|
|
))
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-13 15:51:25 -08:00
|
|
|
|
|
|
|
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
|
2018-01-14 19:27:22 -08:00
|
|
|
v_line = Line(UP, DOWN).scale(SPACE_HEIGHT).move_to(x_point)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.play(GrowFromCenter(v_line))
|
|
|
|
self.play(FadeOut(v_line))
|
2018-01-13 15:51:25 -08:00
|
|
|
self.play(*map(ShowCreation, lines))
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-13 15:51:25 -08:00
|
|
|
self.play(MoveToTarget(sum_lines, path_arc = np.pi/4))
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait(2)
|
2018-01-21 16:44:32 -08:00
|
|
|
# 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)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
sum_graph = axes.get_graph(new_func)
|
|
|
|
sum_graph.highlight(self.sum_color)
|
2018-01-21 16:44:32 -08:00
|
|
|
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*SPACE_HEIGHT,
|
|
|
|
width = MED_SMALL_BUFF,
|
|
|
|
stroke_width = 0,
|
|
|
|
fill_color = YELLOW,
|
|
|
|
fill_opacity = 0.4
|
|
|
|
)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-21 16:44:32 -08:00
|
|
|
self.play(
|
|
|
|
ReplacementTransform(A_graph.copy(), thin_sum_graph),
|
|
|
|
ReplacementTransform(D_graph.copy(), thin_sum_graph),
|
|
|
|
# FadeOut(self.lines_to_fade)
|
|
|
|
)
|
2018-01-13 15:51:25 -08:00
|
|
|
self.play(
|
2018-01-14 19:27:22 -08:00
|
|
|
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),
|
2018-01-13 15:51:25 -08:00
|
|
|
run_time = 15,
|
|
|
|
rate_func = None
|
|
|
|
)
|
2018-01-21 16:44:32 -08:00
|
|
|
self.remove(thin_sum_graph)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-21 16:44:32 -08:00
|
|
|
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))
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
self.sum_graph = sum_graph
|
|
|
|
|
2018-01-11 18:13:50 -08:00
|
|
|
def add_more_notes(self):
|
2018-01-13 15:51:25 -08:00
|
|
|
axes = self.axes
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
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)
|
|
|
|
|
2018-01-21 16:44:32 -08:00
|
|
|
self.play(
|
|
|
|
MoveToTarget(squish_group),
|
|
|
|
FadeOut(self.lines_to_fade),
|
|
|
|
)
|
2018-01-14 19:27:22 -08:00
|
|
|
|
|
|
|
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.highlight(self.F_color)
|
|
|
|
C_graph = self.get_wave_graph(self.A_frequency*6.0/5, C_axes)
|
|
|
|
C_graph.highlight(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.highlight(graph.get_stroke_color())
|
|
|
|
label.next_to(graph, UP, SMALL_BUFF)
|
|
|
|
|
2018-01-21 16:44:32 -08:00
|
|
|
graphs = VGroup(self.A_graph, self.D_graph, F_graph, C_graph)
|
2018-01-14 19:27:22 -08:00
|
|
|
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
|
2018-01-15 15:30:15 -08:00
|
|
|
new_sum_graph = self.axes.get_graph(
|
|
|
|
new_sum_func,
|
|
|
|
num_graph_points = 200
|
|
|
|
)
|
2018-01-14 19:27:22 -08:00
|
|
|
new_sum_graph.highlight(BLUE_C)
|
2018-01-21 16:44:32 -08:00
|
|
|
thin_new_sum_graph = new_sum_graph.copy().fade()
|
2018-01-14 19:27:22 -08:00
|
|
|
|
|
|
|
self.play(*it.chain(
|
2018-01-21 16:44:32 -08:00
|
|
|
map(ShowCreation, [F_axes, C_axes, F_graph, C_graph]),
|
2018-01-14 19:27:22 -08:00
|
|
|
map(Write, [F_label, C_label]),
|
2018-01-21 16:44:32 -08:00
|
|
|
map(FadeOut, [self.sum_graph])
|
|
|
|
))
|
|
|
|
self.play(ReplacementTransform(
|
|
|
|
graphs.copy(), thin_new_sum_graph
|
2018-01-14 19:27:22 -08:00
|
|
|
))
|
|
|
|
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),
|
|
|
|
]
|
|
|
|
])
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
####
|
|
|
|
|
|
|
|
def broadcast(self, *added_anims, **kwargs):
|
2018-01-13 15:51:25 -08:00
|
|
|
self.play(self.get_broadcast_animation(**kwargs), *added_anims)
|
|
|
|
|
|
|
|
def get_broadcast_animation(self, **kwargs):
|
2018-01-11 18:13:50 -08:00
|
|
|
kwargs["run_time"] = kwargs.get("run_time", 5)
|
2018-01-13 15:51:25 -08:00
|
|
|
kwargs["n_circles"] = kwargs.get("n_circles", 10)
|
|
|
|
return Broadcast(self.speaker[1], **kwargs)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
def get_wave_graph(self, frequency, axes):
|
2018-01-11 18:13:50 -08:00
|
|
|
tail_len = 3.0
|
|
|
|
x_min, x_max = axes.x_min, axes.x_max
|
|
|
|
def func(x):
|
2018-01-13 15:51:25 -08:00
|
|
|
value = 0.7*np.cos(2*np.pi*frequency*x)
|
2018-01-11 18:13:50 -08:00
|
|
|
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)
|
2018-01-13 15:51:25 -08:00
|
|
|
return value + self.equilibrium_height
|
|
|
|
ngp = 2*(x_max - x_min)*frequency + 1
|
2018-01-21 16:44:32 -08:00
|
|
|
graph = axes.get_graph(func, num_graph_points = int(ngp))
|
2018-01-13 15:51:25 -08:00
|
|
|
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
|
2018-01-19 12:50:05 -08:00
|
|
|
|
2018-01-13 15:51:25 -08:00
|
|
|
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.highlight(self.A_color)
|
|
|
|
D_line.highlight(self.D_color)
|
|
|
|
return lines
|
2018-01-11 18:13:50 -08:00
|
|
|
|
|
|
|
def create_pi_creature(self):
|
|
|
|
return Randolph().to_corner(DOWN+LEFT)
|
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
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
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-14 19:27:22 -08:00
|
|
|
return UpdateFromAlphaFunc(line, update_line, **kwargs)
|
2018-01-18 11:00:43 -08:00
|
|
|
|
2018-01-15 15:30:15 -08:00
|
|
|
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(2*SPACE_WIDTH - 2)
|
|
|
|
axes.stretch_to_fit_height(3)
|
|
|
|
axes.center()
|
|
|
|
axes.to_edge(LEFT)
|
|
|
|
graph = axes.get_graph(func, num_graph_points = 200)
|
|
|
|
graph.highlight(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 = None,
|
|
|
|
)
|
|
|
|
self.play(*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.gradient_highlight(BLUE, RED)
|
|
|
|
pure_graphs.arrange_submobjects(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))
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-15 15:30:15 -08:00
|
|
|
|
|
|
|
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)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-15 15:30:15 -08:00
|
|
|
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),
|
|
|
|
LaggedStart(
|
|
|
|
ApplyFunction, pure_graphs,
|
|
|
|
lambda g : (lambda m : m.shift(SMALL_BUFF*UP).highlight(YELLOW), g),
|
|
|
|
rate_func = wiggle
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.play(FadeOut(rect))
|
|
|
|
self.play(Blink(randy))
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
class Quadrant(VMobject):
|
2018-01-15 15:30:15 -08:00
|
|
|
CONFIG = {
|
|
|
|
"radius" : 2,
|
2018-01-16 09:45:43 -08:00
|
|
|
"stroke_width": 0,
|
|
|
|
"fill_opacity" : 1,
|
|
|
|
"density" : 50,
|
|
|
|
"density_exp" : 2.0,
|
2018-01-15 15:30:15 -08:00
|
|
|
}
|
|
|
|
def generate_points(self):
|
2018-01-16 09:45:43 -08:00
|
|
|
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)
|
2018-01-15 15:30:15 -08:00
|
|
|
|
|
|
|
class UnmixMixedPaint(Scene):
|
|
|
|
CONFIG = {
|
|
|
|
"colors" : [BLUE, RED, YELLOW, GREEN],
|
|
|
|
}
|
|
|
|
def construct(self):
|
|
|
|
angles = np.arange(4)*np.pi/2
|
2018-01-16 09:45:43 -08:00
|
|
|
quadrants = VGroup(*[
|
2018-01-20 13:19:43 -08:00
|
|
|
Quadrant().rotate(angle, about_point = ORIGIN).highlight(color)
|
2018-01-15 15:30:15 -08:00
|
|
|
for color, angle in zip(self.colors, angles)
|
|
|
|
])
|
2018-01-16 09:45:43 -08:00
|
|
|
quadrants.add(*it.chain(*[
|
|
|
|
quadrants.copy().rotate(angle)
|
|
|
|
for angle in np.linspace(0, 0.02*TAU, 10)
|
|
|
|
]))
|
|
|
|
quadrants.set_fill(opacity = 0.5)
|
2018-01-15 15:30:15 -08:00
|
|
|
|
2018-01-16 22:40:02 -08:00
|
|
|
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)
|
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
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(np.linalg.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)
|
2018-01-15 18:49:58 -08:00
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
new_norms = np.apply_along_axis(np.linalg.norm, 1, points)
|
2018-01-16 22:40:02 -08:00
|
|
|
new_norms = np.clip(new_norms, 0.001, np.inf)
|
2018-01-16 09:45:43 -08:00
|
|
|
radius = np.max(norms)
|
2018-01-16 22:40:02 -08:00
|
|
|
multiplier = norms/new_norms
|
2018-01-16 09:45:43 -08:00
|
|
|
multiplier = multiplier.reshape((len(multiplier), 1))
|
|
|
|
multiplier.repeat(points.shape[1], axis = 1)
|
|
|
|
points *= multiplier
|
|
|
|
quadrant.set_points_smoothly(points)
|
2018-01-15 18:49:58 -08:00
|
|
|
|
|
|
|
self.add(quadrants)
|
2018-01-16 09:45:43 -08:00
|
|
|
run_time = 30
|
2018-01-15 15:30:15 -08:00
|
|
|
self.play(
|
2018-01-16 09:45:43 -08:00
|
|
|
*[
|
|
|
|
UpdateFromAlphaFunc(quadrant, update_quadrant)
|
|
|
|
for quadrant in quadrants
|
2018-01-16 22:40:02 -08:00
|
|
|
] + [
|
|
|
|
ApplyMethod(mud_circle.restore, rate_func = None)
|
2018-01-16 09:45:43 -08:00
|
|
|
],
|
|
|
|
run_time = run_time
|
2018-01-15 15:30:15 -08:00
|
|
|
)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-15 18:49:58 -08:00
|
|
|
#Incomplete, and probably not useful
|
|
|
|
class MachineThatTreatsOneFrequencyDifferently(Scene):
|
|
|
|
def construct(self):
|
|
|
|
graph = self.get_cosine_graph(0.5)
|
|
|
|
frequency_mob = DecimalNumber(220, num_decimal_points = 0)
|
|
|
|
frequency_mob.next_to(graph, UP, buff = MED_LARGE_BUFF)
|
|
|
|
|
|
|
|
self.graph = graph
|
|
|
|
self.frequency_mob = frequency_mob
|
|
|
|
self.add(graph, frequency_mob)
|
2018-01-11 18:13:50 -08:00
|
|
|
|
2018-01-15 18:49:58 -08:00
|
|
|
arrow1, q_marks, arrow2 = group = VGroup(
|
|
|
|
Vector(DOWN), TextMobject("???").scale(1.5), Vector(DOWN)
|
|
|
|
)
|
|
|
|
group.highlight(WHITE)
|
|
|
|
group.arrange_submobjects(DOWN)
|
|
|
|
group.next_to(graph, DOWN)
|
|
|
|
self.add(group)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-15 18:49:58 -08:00
|
|
|
self.change_graph_frequency(1)
|
|
|
|
graph.highlight(GREEN)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-15 18:49:58 -08:00
|
|
|
graph.highlight(YELLOW)
|
|
|
|
self.change_graph_frequency(2)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait()
|
2018-01-13 15:51:25 -08:00
|
|
|
|
|
|
|
|
2018-01-15 18:49:58 -08:00
|
|
|
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
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-15 18:49:58 -08:00
|
|
|
class FourierMachineScene(Scene):
|
|
|
|
CONFIG = {
|
2018-01-16 09:45:43 -08:00
|
|
|
"time_axes_config" : {
|
|
|
|
"x_min" : 0,
|
2018-01-17 12:19:16 -08:00
|
|
|
"x_max" : 4.4,
|
2018-01-16 09:45:43 -08:00
|
|
|
"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},
|
|
|
|
},
|
2018-01-21 10:10:42 -08:00
|
|
|
"time_label_t" : 3.4,
|
2018-01-16 09:45:43 -08:00
|
|
|
"circle_plane_config" : {
|
2018-01-20 13:44:28 -08:00
|
|
|
"x_radius" : 2.5,
|
|
|
|
"y_radius" : 2.5,
|
|
|
|
"x_unit_size" : 0.8,
|
|
|
|
"y_unit_size" : 0.8,
|
2018-01-16 09:45:43 -08:00
|
|
|
},
|
2018-01-18 11:00:43 -08:00
|
|
|
"frequency_axes_config" : {
|
2018-01-18 13:33:48 -08:00
|
|
|
"number_line_config" : {
|
|
|
|
"color" : TEAL,
|
|
|
|
},
|
2018-01-18 11:00:43 -08:00
|
|
|
"x_min" : 0,
|
|
|
|
"x_max" : 5.0,
|
|
|
|
"x_axis_config" : {
|
2018-01-18 13:33:48 -08:00
|
|
|
"unit_size" : 1.4,
|
2018-01-18 11:00:43 -08:00
|
|
|
},
|
2018-01-18 13:33:48 -08:00
|
|
|
"y_min" : -1.0,
|
|
|
|
"y_max" : 1.0,
|
2018-01-18 11:00:43 -08:00
|
|
|
"y_axis_config" : {
|
2018-01-18 13:33:48 -08:00
|
|
|
"unit_size" : 1.8,
|
|
|
|
"tick_frequency" : 0.5,
|
2018-01-18 11:00:43 -08:00
|
|
|
"line_to_number_vect" : LEFT,
|
|
|
|
},
|
2018-01-18 13:33:48 -08:00
|
|
|
"color" : TEAL,
|
2018-01-18 11:00:43 -08:00
|
|
|
},
|
2018-01-18 13:33:48 -08:00
|
|
|
"frequency_axes_box_color" : TEAL_E,
|
2018-01-16 09:45:43 -08:00
|
|
|
"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,
|
2018-01-16 22:40:02 -08:00
|
|
|
"remover" : True,
|
|
|
|
},
|
|
|
|
"default_time_sweep_config" : {
|
|
|
|
"rate_func" : None,
|
|
|
|
"run_time" : 5,
|
|
|
|
},
|
2018-01-15 18:49:58 -08:00
|
|
|
}
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
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)
|
2018-01-21 10:10:42 -08:00
|
|
|
time_label.next_to(
|
|
|
|
time_axes.coords_to_point(self.time_label_t,0),
|
|
|
|
DOWN
|
|
|
|
)
|
2018-01-19 18:35:58 -08:00
|
|
|
intensity_label.next_to(time_axes.y_axis.get_top(), RIGHT)
|
2018-01-16 09:45:43 -08:00
|
|
|
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.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
|
|
|
|
|
2018-01-18 11:00:43 -08:00
|
|
|
def get_frequency_axes(self):
|
|
|
|
frequency_axes = Axes(**self.frequency_axes_config)
|
|
|
|
frequency_axes.x_axis.add_numbers(*range(1, 6))
|
|
|
|
frequency_axes.y_axis.add_numbers(
|
2018-01-18 13:33:48 -08:00
|
|
|
*frequency_axes.y_axis.get_tick_numbers()
|
2018-01-18 11:00:43 -08:00
|
|
|
)
|
|
|
|
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)
|
|
|
|
|
2018-01-18 13:33:48 -08:00
|
|
|
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
|
2018-01-18 11:00:43 -08:00
|
|
|
return frequency_axes
|
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
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
|
|
|
|
|
2018-01-19 18:35:58 -08:00
|
|
|
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)
|
|
|
|
)
|
2018-01-16 09:45:43 -08:00
|
|
|
|
2018-01-18 13:33:48 -08:00
|
|
|
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_min = self.time_axes.x_min
|
|
|
|
t_max = self.time_axes.x_max
|
|
|
|
return self.frequency_axes.get_graph(
|
2018-01-21 23:32:57 -08:00
|
|
|
get_fourier_transform(func, t_min, t_max, **kwargs),
|
2018-01-18 13:33:48 -08:00
|
|
|
color = self.center_of_mass_color,
|
|
|
|
)
|
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
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))
|
2018-01-18 11:00:43 -08:00
|
|
|
# polarized_mobject.make_smooth()
|
2018-01-16 09:45:43 -08:00
|
|
|
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)
|
|
|
|
|
2018-01-16 22:40:02 -08:00
|
|
|
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)
|
|
|
|
|
2018-01-16 09:45:43 -08:00
|
|
|
def animate_frequency_change(self, mobjects, new_freq, **kwargs):
|
|
|
|
kwargs["run_time"] = kwargs.get("run_time", 3.0)
|
2018-01-16 22:40:02 -08:00
|
|
|
added_anims = kwargs.get("added_anims", [])
|
2018-01-16 09:45:43 -08:00
|
|
|
self.play(*[
|
|
|
|
self.get_frequency_change_animation(mob, new_freq, **kwargs)
|
|
|
|
for mob in mobjects
|
2018-01-16 22:40:02 -08:00
|
|
|
] + added_anims)
|
2018-01-16 09:45:43 -08:00
|
|
|
|
|
|
|
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)
|
2018-01-16 22:40:02 -08:00
|
|
|
mobject.polarized_mobject = pm
|
|
|
|
mobject.polarized_mobject.frequency = freq
|
2018-01-16 09:45:43 -08:00
|
|
|
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_max = x_axis.x_min, x_axis.x_max
|
|
|
|
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
|
2018-01-16 22:40:02 -08:00
|
|
|
return UpdateFromAlphaFunc(vector, update_vector, **config)
|
2018-01-16 09:45:43 -08:00
|
|
|
|
|
|
|
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)
|
2018-01-16 22:40:02 -08:00
|
|
|
origin = self.circle_plane.coords_to_point(0, 0)
|
|
|
|
graph_copy = polarized_graph.copy()
|
2018-01-16 09:45:43 -08:00
|
|
|
def update_vector(vector, alpha):
|
2018-01-16 22:40:02 -08:00
|
|
|
point = graph_copy.point_from_proportion(alpha)
|
2018-01-17 12:19:16 -08:00
|
|
|
vector.put_start_and_end_on_with_projection(origin, point)
|
2018-01-16 22:40:02 -08:00
|
|
|
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),
|
2018-01-21 16:44:32 -08:00
|
|
|
stroke_width = 6,
|
2018-01-16 22:40:02 -08:00
|
|
|
)
|
|
|
|
v_line.highlight(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)))
|
|
|
|
|
2018-01-19 12:50:05 -08:00
|
|
|
def get_v_lines_indicating_periods(self, freq, n_lines = 20):
|
|
|
|
period = np.divide(1., max(freq, 0.01))
|
2018-01-16 22:40:02 -08:00
|
|
|
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
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-19 12:50:05 -08:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2018-01-16 22:40:02 -08:00
|
|
|
class WrapCosineGraphAroundCircle(FourierMachineScene):
|
|
|
|
CONFIG = {
|
|
|
|
"initial_winding_frequency" : 0.5,
|
|
|
|
"signal_frequency" : 3.0,
|
|
|
|
}
|
2018-01-15 18:49:58 -08:00
|
|
|
def construct(self):
|
2018-01-16 22:40:02 -08:00
|
|
|
self.show_initial_signal()
|
|
|
|
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)
|
2018-01-17 12:19:16 -08:00
|
|
|
self.graph = graph
|
|
|
|
braces = VGroup(*self.get_peak_braces()[3:6])
|
2018-01-16 22:40:02 -08:00
|
|
|
v_lines = VGroup(*[
|
|
|
|
DashedLine(
|
|
|
|
ORIGIN, 2*UP, color = RED
|
|
|
|
).move_to(axes.coords_to_point(x, 0), DOWN)
|
|
|
|
for x in 1, 2
|
|
|
|
])
|
2018-01-17 12:19:16 -08:00
|
|
|
words = self.get_bps_label()
|
2018-01-19 18:35:58 -08:00
|
|
|
words.save_state()
|
|
|
|
words.next_to(axes.coords_to_point(1.5, 0), DOWN, MED_LARGE_BUFF)
|
2018-01-16 22:40:02 -08:00
|
|
|
|
|
|
|
self.add(axes)
|
2018-01-17 12:19:16 -08:00
|
|
|
self.play(ShowCreation(graph, run_time = 2, rate_func = None))
|
2018-01-16 22:40:02 -08:00
|
|
|
self.play(
|
|
|
|
FadeIn(words),
|
2018-01-17 12:19:16 -08:00
|
|
|
LaggedStart(FadeIn, braces),
|
2018-01-16 22:40:02 -08:00
|
|
|
*map(ShowCreation, v_lines)
|
|
|
|
)
|
|
|
|
self.wait()
|
2018-01-19 18:35:58 -08:00
|
|
|
self.play(
|
|
|
|
FadeOut(VGroup(braces, v_lines)),
|
|
|
|
words.restore,
|
|
|
|
)
|
2018-01-16 22:40:02 -08:00
|
|
|
self.wait()
|
|
|
|
|
|
|
|
self.beats_per_second_label = words
|
|
|
|
self.graph = graph
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
self.play(ShowCreation(circle_plane, submobject_mode = "all_at_once"))
|
2018-01-16 22:40:02 -08:00
|
|
|
self.play(ReplacementTransform(
|
|
|
|
moving_graph,
|
|
|
|
polarized_graph,
|
|
|
|
run_time = 3,
|
|
|
|
path_arc = -TAU/2
|
|
|
|
))
|
|
|
|
self.animate_frequency_change([graph], freq)
|
|
|
|
self.wait()
|
2018-01-17 12:19:16 -08:00
|
|
|
pg_copy = polarized_graph.copy()
|
|
|
|
self.remove(polarized_graph)
|
|
|
|
self.play(pg_copy.fade, 0.75)
|
2018-01-16 22:40:02 -08:00
|
|
|
self.play(*self.get_vector_animations(graph), run_time = 15)
|
2018-01-17 12:19:16 -08:00
|
|
|
self.remove(pg_copy)
|
2018-01-16 22:40:02 -08:00
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def show_time_sweeps(self):
|
|
|
|
freq = self.initial_winding_frequency
|
|
|
|
graph = self.graph
|
|
|
|
|
|
|
|
v_lines = self.get_v_lines_indicating_periods(freq)
|
2018-01-17 12:19:16 -08:00
|
|
|
winding_freq_label = self.get_winding_frequency_label()
|
2018-01-16 22:40:02 -08:00
|
|
|
|
|
|
|
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
|
2018-01-17 12:19:16 -08:00
|
|
|
braces = self.get_peak_braces(buff = 0)
|
2018-01-16 22:40:02 -08:00
|
|
|
|
|
|
|
self.play(ShowCreation(bps_label.rect))
|
|
|
|
self.play(FadeOut(bps_label.rect))
|
|
|
|
self.play(LaggedStart(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
|
2018-01-17 12:19:16 -08:00
|
|
|
for target_freq in [1.23, 0.2, 1.55, self.signal_frequency]:
|
2018-01-16 22:40:02 -08:00
|
|
|
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(LaggedStart(
|
|
|
|
ApplyFunction, v_lines,
|
|
|
|
lambda mob : (
|
|
|
|
lambda m : m.shift(0.25*UP).highlight(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
|
|
|
|
)
|
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
##
|
|
|
|
|
|
|
|
def get_winding_frequency_label(self):
|
|
|
|
freq = self.initial_winding_frequency
|
|
|
|
winding_freq_label = VGroup(
|
|
|
|
DecimalNumber(freq, num_decimal_points = 2),
|
|
|
|
TextMobject("cycles/second")
|
|
|
|
)
|
|
|
|
winding_freq_label.arrange_submobjects(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:])
|
|
|
|
])
|
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
def get_bps_label(self, freq = 3):
|
|
|
|
braces = VGroup(*self.get_peak_braces()[freq:2*freq])
|
|
|
|
words = TextMobject("%d beats/second"%freq)
|
2018-01-17 12:19:16 -08:00
|
|
|
words.scale_to_fit_width(0.9*braces.get_width())
|
2018-01-19 18:35:58 -08:00
|
|
|
words.move_to(braces, DOWN)
|
2018-01-17 12:19:16 -08:00
|
|
|
return words
|
|
|
|
|
|
|
|
class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
|
|
|
CONFIG = {
|
|
|
|
"initial_winding_frequency" : 3.0,
|
|
|
|
"center_of_mass_color" : RED,
|
|
|
|
}
|
|
|
|
def construct(self):
|
|
|
|
self.remove(self.pi_creature)
|
|
|
|
self.setup_graph()
|
2018-01-19 18:35:58 -08:00
|
|
|
self.indicate_weight_of_wire()
|
2018-01-17 12:19:16 -08:00
|
|
|
self.show_center_of_mass_dot()
|
2018-01-19 18:35:58 -08:00
|
|
|
self.change_to_various_frequencies()
|
2018-01-17 12:19:16 -08:00
|
|
|
self.introduce_frequency_plot()
|
|
|
|
self.draw_full_frequency_plot()
|
2018-01-20 13:44:28 -08:00
|
|
|
self.recap_objects_on_screen()
|
|
|
|
self.lower_graph()
|
|
|
|
self.label_as_almost_fourier()
|
2018-01-17 12:19:16 -08:00
|
|
|
|
|
|
|
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 = Dot(self.get_pol_graph_center_of_mass())
|
|
|
|
dot.highlight(color)
|
|
|
|
dot.save_state()
|
|
|
|
arrow = Vector(DOWN+2*LEFT, color = color)
|
2018-01-18 11:00:43 -08:00
|
|
|
arrow.next_to(dot.get_center(), UP+RIGHT, buff = SMALL_BUFF)
|
2018-01-17 12:19:16 -08:00
|
|
|
dot.move_to(arrow.get_start())
|
|
|
|
words = TextMobject("Center of mass")
|
|
|
|
words.next_to(arrow.get_start(), RIGHT)
|
|
|
|
words.highlight(color)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
GrowArrow(arrow),
|
|
|
|
dot.restore,
|
|
|
|
)
|
|
|
|
self.play(Write(words))
|
2018-01-18 11:00:43 -08:00
|
|
|
self.play(FadeOut(arrow), FadeOut(self.pi_creature))
|
2018-01-17 12:19:16 -08:00
|
|
|
self.wait()
|
|
|
|
|
2018-01-18 11:00:43 -08:00
|
|
|
self.center_of_mass_dot = dot
|
2018-01-20 19:31:43 -08:00
|
|
|
self.generate_center_of_mass_dot_update_anim()
|
2018-01-18 11:00:43 -08:00
|
|
|
self.center_of_mass_label = words
|
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def change_to_various_frequencies(self):
|
2018-01-18 11:00:43 -08:00
|
|
|
for new_freq in [0.5, 0.2, 1.04, 2.21, 3.0]:
|
|
|
|
self.change_frequency(new_freq)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
*self.get_vector_animations(self.graph),
|
|
|
|
run_time = 15
|
|
|
|
)
|
2018-01-17 12:19:16 -08:00
|
|
|
|
|
|
|
def introduce_frequency_plot(self):
|
2018-01-18 11:00:43 -08:00
|
|
|
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()
|
2018-01-18 18:20:51 -08:00
|
|
|
x_coord_label = TextMobject("$x$-coordiante for center of mass")
|
|
|
|
x_coord_label.highlight(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,
|
|
|
|
)
|
2018-01-19 18:35:58 -08:00
|
|
|
flower_path.move_to(self.center_of_mass_dot)
|
2018-01-18 11:00:43 -08:00
|
|
|
|
|
|
|
self.play(
|
2018-01-18 13:33:48 -08:00
|
|
|
wps_label.move_to, self.circle_plane.get_top(),
|
2018-01-18 11:00:43 -08:00
|
|
|
com_label.move_to, self.circle_plane, DOWN,
|
|
|
|
)
|
2018-01-18 18:20:51 -08:00
|
|
|
self.play(LaggedStart(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
|
|
|
|
))
|
2018-01-18 11:00:43 -08:00
|
|
|
self.wait()
|
|
|
|
|
2018-01-19 18:35:58 -08:00
|
|
|
self.x_coord_label = x_coord_label
|
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def draw_full_frequency_plot(self):
|
2018-01-18 18:20:51 -08:00
|
|
|
graph = self.graph
|
2018-01-21 23:32:57 -08:00
|
|
|
fourier_graph = get_fourier_transform_graph(graph)
|
2018-01-19 12:50:05 -08:00
|
|
|
fourier_graph.save_state()
|
2018-01-20 19:31:43 -08:00
|
|
|
fourier_graph_update = self.get_fouier_graph_drawing_update_anim(
|
|
|
|
fourier_graph
|
|
|
|
)
|
2018-01-19 12:50:05 -08:00
|
|
|
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()
|
|
|
|
)
|
2018-01-18 18:20:51 -08:00
|
|
|
|
|
|
|
self.change_frequency(0.0)
|
2018-01-19 18:35:58 -08:00
|
|
|
self.generate_fourier_dot_transform(fourier_graph)
|
2018-01-19 12:50:05 -08:00
|
|
|
self.wait()
|
|
|
|
self.play(ShowCreation(v_line))
|
2018-01-19 18:35:58 -08:00
|
|
|
self.play(
|
|
|
|
GrowFromCenter(self.fourier_graph_dot),
|
|
|
|
FadeOut(v_line)
|
|
|
|
)
|
2018-01-19 12:50:05 -08:00
|
|
|
f_max = int(self.frequency_axes.x_max)
|
2018-01-19 18:35:58 -08:00
|
|
|
for freq in [0.2, 1.5, 3.0, 4.0, 5.0]:
|
2018-01-19 12:50:05 -08:00
|
|
|
fourier_graph.restore()
|
|
|
|
self.change_frequency(
|
|
|
|
freq,
|
2018-01-20 19:31:43 -08:00
|
|
|
added_anims = [fourier_graph_update],
|
|
|
|
run_time = 8,
|
2018-01-19 12:50:05 -08:00
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.fourier_graph = fourier_graph
|
|
|
|
|
|
|
|
def recap_objects_on_screen(self):
|
|
|
|
rect = FullScreenFadeRectangle()
|
2018-01-19 18:35:58 -08:00
|
|
|
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]
|
2018-01-19 12:50:05 -08:00
|
|
|
|
|
|
|
self.play(FadeIn(rect))
|
2018-01-19 18:35:58 -08:00
|
|
|
self.wait()
|
|
|
|
for group in groups:
|
2018-01-20 19:31:43 -08:00
|
|
|
graph_copy = group[0].copy().highlight(PINK)
|
2018-01-19 18:35:58 -08:00
|
|
|
self.play(FadeIn(group))
|
2018-01-20 19:31:43 -08:00
|
|
|
self.play(ShowCreation(graph_copy))
|
|
|
|
self.play(FadeOut(graph_copy))
|
2018-01-19 18:35:58 -08:00
|
|
|
self.wait()
|
|
|
|
self.play(FadeOut(group))
|
|
|
|
self.wait()
|
|
|
|
self.play(FadeOut(rect))
|
2018-01-19 12:50:05 -08:00
|
|
|
|
|
|
|
def lower_graph(self):
|
2018-01-19 18:35:58 -08:00
|
|
|
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
|
|
|
|
)
|
2018-01-21 23:32:57 -08:00
|
|
|
new_fourier_graph = get_fourier_transform_graph(new_graph)
|
2018-01-19 18:35:58 -08:00
|
|
|
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 = None)
|
|
|
|
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()
|
2018-01-16 22:40:02 -08:00
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def label_as_almost_fourier(self):
|
2018-01-19 18:35:58 -08:00
|
|
|
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)
|
2018-01-16 22:40:02 -08:00
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
##
|
2018-01-16 09:45:43 -08:00
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def get_pol_graph_center_of_mass(self):
|
2018-01-18 11:00:43 -08:00
|
|
|
pg = self.graph.polarized_mobject
|
|
|
|
result = center_of_mass([
|
|
|
|
pg.point_from_proportion(alpha)
|
|
|
|
for alpha in np.linspace(0, 1, 1000)
|
|
|
|
])
|
|
|
|
return result
|
2018-01-16 09:45:43 -08:00
|
|
|
|
2018-01-19 18:35:58 -08:00
|
|
|
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)
|
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
def get_fouier_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
|
|
|
|
return UpdateFromFunc(fourier_graph, update_fourier_graph)
|
|
|
|
|
|
|
|
def generate_center_of_mass_dot_update_anim(self):
|
|
|
|
self.center_of_mass_dot_anim = UpdateFromFunc(
|
|
|
|
self.center_of_mass_dot,
|
|
|
|
lambda d : d.move_to(self.get_pol_graph_center_of_mass())
|
|
|
|
)
|
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def change_frequency(self, new_freq, **kwargs):
|
|
|
|
kwargs["run_time"] = kwargs.get("run_time", 3)
|
2018-01-19 12:50:05 -08:00
|
|
|
added_anims = kwargs.get("added_anims", [])
|
2018-01-18 18:20:51 -08:00
|
|
|
freq_label = filter(
|
|
|
|
lambda sm : isinstance(sm, DecimalNumber),
|
|
|
|
self.winding_freq_label
|
|
|
|
)[0]
|
2018-01-17 12:19:16 -08:00
|
|
|
anims = [
|
|
|
|
ChangeDecimalToValue(freq_label, new_freq),
|
|
|
|
self.get_frequency_change_animation(
|
|
|
|
self.graph, new_freq
|
|
|
|
),
|
2018-01-19 12:50:05 -08:00
|
|
|
self.get_period_v_lines_update_anim(),
|
2018-01-17 12:19:16 -08:00
|
|
|
]
|
2018-01-19 12:50:05 -08:00
|
|
|
anims += added_anims
|
2018-01-18 11:00:43 -08:00
|
|
|
if hasattr(self, "center_of_mass_dot"):
|
|
|
|
anims.append(self.center_of_mass_dot_anim)
|
2018-01-19 18:35:58 -08:00
|
|
|
if hasattr(self, "fourier_graph_dot"):
|
|
|
|
anims.append(self.fourier_graph_dot_anim)
|
2018-01-17 12:19:16 -08:00
|
|
|
self.play(*anims, **kwargs)
|
2018-01-16 09:45:43 -08:00
|
|
|
|
2018-01-17 12:19:16 -08:00
|
|
|
def create_pi_creature(self):
|
|
|
|
return Mortimer().to_corner(DOWN+RIGHT)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-19 18:35:58 -08:00
|
|
|
class StudentsHorrifiedAtScene(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
self.change_student_modes(
|
|
|
|
*3*["horrified"],
|
|
|
|
look_at_arg = 2*UP + 3*LEFT
|
|
|
|
)
|
|
|
|
self.wait(4)
|
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
class ShowLowerFrequency(DrawFrequencyPlot):
|
2018-01-20 13:19:43 -08:00
|
|
|
CONFIG = {
|
2018-01-20 19:31:43 -08:00
|
|
|
"signal_frequency" : 2.0,
|
|
|
|
"higher_signal_frequency" : 3.0,
|
2018-01-20 13:19:43 -08:00
|
|
|
"lower_signal_color" : PINK,
|
|
|
|
}
|
2018-01-19 18:35:58 -08:00
|
|
|
def construct(self):
|
2018-01-20 13:19:43 -08:00
|
|
|
self.setup_all_axes()
|
2018-01-19 18:35:58 -08:00
|
|
|
self.show_lower_frequency_signal()
|
|
|
|
self.play_with_lower_frequency_signal()
|
2018-01-20 13:19:43 -08:00
|
|
|
|
|
|
|
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
|
2018-01-20 19:31:43 -08:00
|
|
|
start_graph = self.get_cosine_wave(freq = self.higher_signal_frequency)
|
|
|
|
graph = self.get_cosine_wave(
|
|
|
|
freq = self.signal_frequency,
|
|
|
|
)
|
2018-01-20 13:19:43 -08:00
|
|
|
graph.highlight(self.lower_signal_color)
|
2018-01-20 19:31:43 -08:00
|
|
|
self.graph = graph
|
|
|
|
ratio = float(self.higher_signal_frequency)/self.signal_frequency
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
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)
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
|
|
|
|
# self.add(start_graph)
|
|
|
|
self.play(
|
|
|
|
start_graph.stretch, ratio, 0, {"about_edge" : LEFT},
|
|
|
|
start_graph.highlight, graph.get_color(),
|
|
|
|
)
|
|
|
|
self.play(FadeOut(start_graph), Animation(graph))
|
|
|
|
self.remove(start_graph)
|
|
|
|
self.play(
|
|
|
|
Write(bps_label),
|
|
|
|
LaggedStart(FadeIn, braces),
|
|
|
|
*map(ShowCreation, v_lines),
|
|
|
|
run_time = 1
|
|
|
|
)
|
2018-01-20 13:19:43 -08:00
|
|
|
self.wait()
|
2018-01-20 19:31:43 -08:00
|
|
|
self.play(
|
|
|
|
FadeOut(v_lines),
|
|
|
|
FadeOut(braces),
|
|
|
|
bps_label.restore,
|
|
|
|
)
|
2018-01-20 13:19:43 -08:00
|
|
|
|
|
|
|
def play_with_lower_frequency_signal(self):
|
2018-01-20 19:31:43 -08:00
|
|
|
freq = 0.1
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
#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()
|
2018-01-21 10:10:42 -08:00
|
|
|
ChangeDecimalToValue(wps_label[0], freq).update(1)
|
2018-01-20 19:31:43 -08:00
|
|
|
wps_label.add_to_back(BackgroundRectangle(wps_label))
|
|
|
|
wps_label.move_to(self.circle_plane, UP)
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
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()
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
#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()
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
self.play(
|
|
|
|
GrowArrow(arrow),
|
|
|
|
dot.restore,
|
|
|
|
Write(com_words)
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(*map(FadeOut, [arrow, com_words]))
|
|
|
|
self.change_frequency(0.0)
|
|
|
|
self.wait()
|
2018-01-20 13:19:43 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
#Show fourier graph
|
2018-01-21 23:32:57 -08:00
|
|
|
fourier_graph = get_fourier_transform_graph(graph)
|
2018-01-20 19:31:43 -08:00
|
|
|
fourier_graph_update = self.get_fouier_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.highlight(self.center_of_mass_color)
|
|
|
|
self.generate_fourier_dot_transform(fourier_graph)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
self.play(Write(x_coord_label))
|
|
|
|
self.change_frequency(
|
|
|
|
self.signal_frequency,
|
|
|
|
added_anims = [fourier_graph_update],
|
|
|
|
run_time = 10,
|
|
|
|
rate_func = smooth,
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.change_frequency(
|
|
|
|
self.frequency_axes.x_max,
|
|
|
|
added_anims = [fourier_graph_update],
|
|
|
|
run_time = 15,
|
|
|
|
rate_func = smooth,
|
|
|
|
)
|
|
|
|
self.wait()
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
self.set_variables_as_attrs(
|
|
|
|
fourier_graph,
|
|
|
|
fourier_graph_update,
|
|
|
|
)
|
|
|
|
|
|
|
|
class MixingUnmixingTODOStub(TODOStub):
|
|
|
|
CONFIG = {
|
|
|
|
"message" : "Insert mixing and unmixing of signals"
|
|
|
|
}
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-20 19:31:43 -08:00
|
|
|
class ShowLinearity(DrawFrequencyPlot):
|
|
|
|
CONFIG = {
|
|
|
|
"high_freq_color": YELLOW,
|
|
|
|
"low_freq_color": PINK,
|
|
|
|
"sum_color": GREEN,
|
2018-01-21 10:10:42 -08:00
|
|
|
"low_freq" : 2.0,
|
|
|
|
"high_freq" : 3.0,
|
2018-01-20 19:31:43 -08:00
|
|
|
}
|
|
|
|
def construct(self):
|
|
|
|
self.remove(self.pi_creature)
|
|
|
|
self.show_sum_of_signals()
|
|
|
|
self.show_winding_with_sum_graph()
|
|
|
|
self.point_out_two_spikes()
|
|
|
|
|
|
|
|
def show_sum_of_signals(self):
|
2018-01-21 10:10:42 -08:00
|
|
|
low_freq, high_freq = self.low_freq, self.high_freq
|
2018-01-20 19:31:43 -08:00
|
|
|
axes = self.get_time_axes()
|
|
|
|
axes_copy = axes.copy()
|
2018-01-21 10:10:42 -08:00
|
|
|
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
|
|
|
|
]
|
2018-01-20 19:31:43 -08:00
|
|
|
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.highlight(color)
|
|
|
|
label.highlight(color)
|
|
|
|
sum_label[0].match_color(low_freq_graph)
|
2018-01-21 10:10:42 -08:00
|
|
|
sum_label[2].match_color(high_freq_graph)
|
2018-01-20 19:31:43 -08:00
|
|
|
|
|
|
|
self.add(axes, low_freq_graph)
|
|
|
|
self.play(
|
|
|
|
FadeIn(axes_copy),
|
|
|
|
ShowCreation(high_freq_graph),
|
|
|
|
)
|
|
|
|
self.play(LaggedStart(
|
|
|
|
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),
|
2018-01-21 10:10:42 -08:00
|
|
|
ReplacementTransform(
|
|
|
|
VGroup(low_freq_label, high_freq_label),
|
|
|
|
sum_label
|
|
|
|
)
|
2018-01-20 19:31:43 -08:00
|
|
|
)
|
2018-01-21 10:10:42 -08:00
|
|
|
self.wait()
|
|
|
|
self.graph = graph
|
2018-01-20 19:31:43 -08:00
|
|
|
|
|
|
|
def show_winding_with_sum_graph(self):
|
2018-01-21 10:10:42 -08:00
|
|
|
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()
|
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
fourier_graph = get_fourier_transform_graph(graph)
|
2018-01-21 10:10:42 -08:00
|
|
|
fourier_graph_update = self.get_fouier_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.highlight(self.center_of_mass_color)
|
|
|
|
almost_fourier_label = TextMobject(
|
|
|
|
"``Almost-Fourier transform''"
|
|
|
|
)
|
|
|
|
|
|
|
|
self.generate_fourier_dot_transform(fourier_graph)
|
|
|
|
|
|
|
|
self.play(LaggedStart(
|
|
|
|
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,
|
|
|
|
added_anims = [fourier_graph_update],
|
|
|
|
run_time = 8,
|
|
|
|
rate_func = bezier([0, 0, 1, 1]),
|
|
|
|
)
|
2018-01-20 19:31:43 -08:00
|
|
|
|
|
|
|
def point_out_two_spikes(self):
|
|
|
|
pass
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 10:10:42 -08:00
|
|
|
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):
|
2018-01-21 16:44:32 -08:00
|
|
|
self.show_diagram()
|
|
|
|
self.point_out_spikes()
|
|
|
|
|
|
|
|
def show_diagram(self):
|
2018-01-21 10:10:42 -08:00
|
|
|
self.remove(self.pi_creature)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 10:10:42 -08:00
|
|
|
#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_submobjects(DOWN, buff = MED_LARGE_BUFF)
|
|
|
|
ta_group.to_corner(UP+LEFT, buff = MED_SMALL_BUFF)
|
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
frequency_axes = Axes(**self.frequency_axes_config)
|
|
|
|
frequency_axes.highlight(TEAL)
|
2018-01-21 10:10:42 -08:00
|
|
|
freq_label = TextMobject("Frequency")
|
|
|
|
freq_label.scale(self.text_scale_val)
|
2018-01-21 23:32:57 -08:00
|
|
|
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)
|
2018-01-21 10:10:42 -08:00
|
|
|
fa_group = VGroup(
|
2018-01-21 23:32:57 -08:00
|
|
|
frequency_axes, frequency_axes.deepcopy(), frequency_axes.deepcopy()
|
2018-01-21 10:10:42 -08:00
|
|
|
)
|
|
|
|
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.main_line, RIGHT,
|
|
|
|
submobject_to_align = fa.x_axis.main_line
|
|
|
|
)
|
|
|
|
fa.to_edge(RIGHT)
|
2018-01-21 10:45:26 -08:00
|
|
|
ta.remove(ta.labels)
|
|
|
|
fa.remove(fa.label)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 10:10:42 -08:00
|
|
|
## 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.highlight(color)
|
|
|
|
label.highlight(color)
|
|
|
|
label.scale(0.75)
|
|
|
|
label.next_to(time_graph, UP, SMALL_BUFF)
|
2018-01-21 23:32:57 -08:00
|
|
|
fourier = get_fourier_transform(
|
2018-01-21 10:10:42 -08:00
|
|
|
func, ta.x_min, 4*ta.x_max
|
|
|
|
)
|
|
|
|
fourier_graph = fa.get_graph(fourier)
|
|
|
|
fourier_graph.highlight(self.center_of_mass_color)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 10:10:42 -08:00
|
|
|
arrow = Arrow(
|
|
|
|
ta.x_axis.main_line, fa.x_axis.main_line,
|
|
|
|
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)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 10:45:26 -08:00
|
|
|
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)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
|
|
|
|
2018-01-21 10:45:26 -08:00
|
|
|
#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()
|
|
|
|
|
2018-01-21 16:44:32 -08:00
|
|
|
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(LaggedStart(GrowFromCenter, flat_rects))
|
|
|
|
self.wait()
|
|
|
|
self.play(LaggedStart(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(LaggedStart(
|
|
|
|
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
|
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
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)
|
2018-01-19 18:35:58 -08:00
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
self.play(
|
|
|
|
Write(formula),
|
|
|
|
self.teacher.change, "raise_right_hand",
|
|
|
|
self.get_student_changes(*["confused"]*3)
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
ApplyMethod(
|
|
|
|
formula.next_to, SPACE_WIDTH*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)
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
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()
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
def play_sound(self):
|
|
|
|
randy = self.pi_creature
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
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
|
2018-01-13 15:51:25 -08:00
|
|
|
|
2018-01-21 23:32:57 -08:00
|
|
|
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.highlight(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.highlight(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(
|
|
|
|
alt_fourier_func, num_graph_points = 150,
|
|
|
|
)
|
|
|
|
fourier_graph.highlight(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, SPACE_HEIGHT*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
|
|
|
|
|
|
|
|
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 = 200
|
|
|
|
)
|
|
|
|
new_graph.highlight(RED)
|
|
|
|
|
|
|
|
self.play(spike_rect.stretch, 4)
|
|
|
|
self.play(
|
|
|
|
Transform(fourier_graph, new_graph),
|
|
|
|
spike_rect.stretch, 0.01, {
|
|
|
|
"about_point" : frequency_axes.coords_to_point(0, 0)
|
|
|
|
},
|
|
|
|
run_time = 2
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def mention_inverse_transform(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
##
|
|
|
|
|
|
|
|
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
|
|
|
|
]
|
2018-01-13 15:51:25 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|