2018-02-09 15:26:12 -08:00
|
|
|
from helpers import *
|
|
|
|
import scipy
|
|
|
|
|
|
|
|
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 *
|
|
|
|
from topics.common_scenes import *
|
|
|
|
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-02-13 21:38:38 -08:00
|
|
|
from topics.light import *
|
2018-02-09 15:26:12 -08:00
|
|
|
|
|
|
|
from active_projects.fourier import *
|
|
|
|
|
|
|
|
|
2018-02-10 18:32:17 -08:00
|
|
|
FREQUENCY_COLOR = RED
|
|
|
|
USE_ALMOST_FOURIER_BY_DEFAULT = False
|
|
|
|
|
2018-02-09 15:26:12 -08:00
|
|
|
class GaussianDistributionWrapper(Line):
|
|
|
|
"""
|
|
|
|
This is meant to encode a 2d normal distribution as
|
|
|
|
a mobject (so as to be able to have it be interpolated
|
2018-02-12 22:49:15 -08:00
|
|
|
during animations). It is a line whose center is the mean
|
|
|
|
mu of a distribution, and whose radial vector (center to end)
|
|
|
|
is the distribution's standard deviation
|
2018-02-09 15:26:12 -08:00
|
|
|
"""
|
|
|
|
CONFIG = {
|
|
|
|
"stroke_width" : 0,
|
2018-02-12 22:49:15 -08:00
|
|
|
"mu" : ORIGIN,
|
|
|
|
"sigma" : RIGHT,
|
2018-02-09 15:26:12 -08:00
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
Line.__init__(self, ORIGIN, RIGHT, **kwargs)
|
2018-02-12 22:49:15 -08:00
|
|
|
self.change_parameters(self.mu, self.sigma)
|
|
|
|
|
|
|
|
def change_parameters(self, mu = None, sigma = None):
|
|
|
|
curr_mu, curr_sigma = self.get_parameters()
|
|
|
|
mu = mu if mu is not None else curr_mu
|
|
|
|
sigma = sigma if sigma is not None else curr_sigma
|
|
|
|
self.put_start_and_end_on(mu - sigma, mu + sigma)
|
2018-02-09 15:26:12 -08:00
|
|
|
return self
|
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
def get_parameters(self):
|
2018-02-09 15:26:12 -08:00
|
|
|
""" Return mu_x, mu_y, sigma_x, sigma_y"""
|
2018-02-12 22:49:15 -08:00
|
|
|
center, end = self.get_center(), self.get_end()
|
|
|
|
return center, end-center
|
2018-02-09 15:26:12 -08:00
|
|
|
|
|
|
|
def get_random_points(self, size = 1):
|
2018-02-12 22:49:15 -08:00
|
|
|
mu, sigma = self.get_parameters()
|
2018-02-09 15:26:12 -08:00
|
|
|
return np.array([
|
2018-02-12 22:49:15 -08:00
|
|
|
np.array([
|
|
|
|
np.random.normal(mu_coord, sigma_coord)
|
|
|
|
for mu_coord, sigma_coord in zip(mu, sigma)
|
|
|
|
])
|
|
|
|
for x in range(size)
|
2018-02-09 15:26:12 -08:00
|
|
|
])
|
|
|
|
|
|
|
|
class ProbabalisticMobjectCloud(ContinualAnimation):
|
|
|
|
CONFIG = {
|
|
|
|
"fill_opacity" : 0.25,
|
|
|
|
"n_copies" : 100,
|
2018-02-12 22:49:15 -08:00
|
|
|
"gaussian_distribution_wrapper_config" : {}
|
2018-02-09 15:26:12 -08:00
|
|
|
}
|
|
|
|
def __init__(self, prototype, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
fill_opacity = self.fill_opacity or prototype.get_fill_opacity()
|
|
|
|
self.gaussian_distribution_wrapper = GaussianDistributionWrapper(
|
|
|
|
**self.gaussian_distribution_wrapper_config
|
|
|
|
)
|
|
|
|
group = VGroup(*[
|
|
|
|
prototype.copy().set_fill(opacity = fill_opacity)
|
|
|
|
for x in range(self.n_copies)
|
|
|
|
])
|
|
|
|
ContinualAnimation.__init__(self, group, **kwargs)
|
|
|
|
|
|
|
|
def update_mobject(self, dt):
|
|
|
|
group = self.mobject
|
|
|
|
points = self.gaussian_distribution_wrapper.get_random_points(len(group))
|
|
|
|
for mob, point in zip(group, points):
|
|
|
|
self.update_mobject_by_point(mob, point)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def update_mobject_by_point(self, mobject, point):
|
|
|
|
mobject.move_to(point)
|
|
|
|
return self
|
|
|
|
|
|
|
|
class ProbabalisticDotCloud(ProbabalisticMobjectCloud):
|
|
|
|
CONFIG = {
|
|
|
|
"color" : BLUE,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
dot = Dot(color = self.color)
|
|
|
|
ProbabalisticMobjectCloud.__init__(self, dot)
|
|
|
|
|
|
|
|
class ProbabalisticVectorCloud(ProbabalisticMobjectCloud):
|
|
|
|
CONFIG = {
|
|
|
|
"color" : RED,
|
|
|
|
"n_copies" : 20,
|
|
|
|
"fill_opacity" : 0.5,
|
|
|
|
"center_func" : lambda : ORIGIN,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
vector = Vector(
|
|
|
|
RIGHT, color = self.color,
|
|
|
|
max_tip_length_to_length_ratio = 1,
|
|
|
|
)
|
|
|
|
ProbabalisticMobjectCloud.__init__(self, vector)
|
|
|
|
|
|
|
|
def update_mobject_by_point(self, vector, point):
|
|
|
|
vector.put_start_and_end_on(
|
|
|
|
self.center_func(),
|
|
|
|
point
|
|
|
|
)
|
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
class RadarDish(SVGMobject):
|
|
|
|
CONFIG = {
|
|
|
|
"file_name" : "radar_dish",
|
2018-02-13 11:44:36 -08:00
|
|
|
"fill_color" : LIGHT_GREY,
|
|
|
|
"stroke_color" : WHITE,
|
|
|
|
"stroke_width" : 1,
|
|
|
|
"height" : 1,
|
2018-02-12 22:49:15 -08:00
|
|
|
}
|
2018-02-13 11:44:36 -08:00
|
|
|
|
2018-02-13 15:39:57 -08:00
|
|
|
class Plane(SVGMobject):
|
|
|
|
CONFIG = {
|
|
|
|
"file_name" : "plane",
|
|
|
|
"color" : GREY,
|
|
|
|
"height" : 1,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
SVGMobject.__init__(self, **kwargs)
|
|
|
|
self.rotate(-TAU/8)
|
|
|
|
|
2018-02-13 11:44:36 -08:00
|
|
|
class RadarPulseSingleton(ContinualAnimation):
|
|
|
|
CONFIG = {
|
|
|
|
"speed" : 3.0,
|
|
|
|
"direction" : RIGHT,
|
|
|
|
"start_up_time" : 0,
|
|
|
|
"fade_in_time" : 0.5,
|
|
|
|
"color" : WHITE,
|
|
|
|
"stroke_width" : 3,
|
|
|
|
}
|
|
|
|
def __init__(self, radar_dish, target, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
self.direction = self.direction/np.linalg.norm(self.direction)
|
|
|
|
self.radar_dish = radar_dish
|
|
|
|
self.target = target
|
|
|
|
self.reflection_distance = None
|
|
|
|
self.arc = Arc(
|
|
|
|
start_angle = -30*DEGREES,
|
|
|
|
angle = 60*DEGREES,
|
|
|
|
)
|
|
|
|
self.arc.scale_to_fit_height(0.75*radar_dish.get_height())
|
|
|
|
self.arc.move_to(radar_dish, UP+RIGHT)
|
|
|
|
self.start_points = np.array(self.arc.points)
|
|
|
|
self.start_center = self.arc.get_center()
|
|
|
|
self.finished = False
|
|
|
|
|
|
|
|
ContinualAnimation.__init__(self, self.arc, **kwargs)
|
2018-02-12 22:49:15 -08:00
|
|
|
|
2018-02-13 11:44:36 -08:00
|
|
|
def update_mobject(self, dt):
|
|
|
|
arc = self.arc
|
|
|
|
total_distance = self.speed*self.internal_time
|
|
|
|
arc.points = np.array(self.start_points)
|
|
|
|
arc.shift(total_distance*self.direction)
|
|
|
|
|
|
|
|
if self.internal_time < self.fade_in_time:
|
|
|
|
alpha = np.clip(self.internal_time/self.fade_in_time, 0, 1)
|
|
|
|
arc.set_stroke(self.color, alpha*self.stroke_width)
|
|
|
|
|
|
|
|
if self.reflection_distance is None:
|
|
|
|
#Check if reflection is happening
|
|
|
|
arc_point = arc.get_edge_center(self.direction)
|
|
|
|
target_point = self.target.get_edge_center(-self.direction)
|
|
|
|
arc_distance = np.dot(arc_point, self.direction)
|
|
|
|
target_distance = np.dot(target_point, self.direction)
|
|
|
|
if arc_distance > target_distance:
|
|
|
|
self.reflection_distance = target_distance
|
|
|
|
#Don't use elif in case the above code creates reflection_distance
|
|
|
|
if self.reflection_distance is not None:
|
|
|
|
delta_distance = total_distance - self.reflection_distance
|
|
|
|
point_distances = np.dot(self.direction, arc.points.T)
|
|
|
|
diffs = point_distances - self.reflection_distance
|
|
|
|
shift_vals = np.outer(-2*np.maximum(diffs, 0), self.direction)
|
|
|
|
arc.points += shift_vals
|
|
|
|
|
|
|
|
#Check if done
|
|
|
|
arc_point = arc.get_edge_center(-self.direction)
|
|
|
|
if np.dot(arc_point, self.direction) < np.dot(self.start_center, self.direction):
|
|
|
|
self.finished = True
|
|
|
|
self.arc.fade(1)
|
|
|
|
|
|
|
|
def is_finished(self):
|
|
|
|
return self.finished
|
|
|
|
|
|
|
|
class RadarPulse(ContinualAnimation):
|
|
|
|
CONFIG = {
|
|
|
|
"n_pulse_singletons" : 8,
|
|
|
|
"frequency" : 0.05,
|
|
|
|
"colors" : [BLUE, YELLOW]
|
|
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
colors = color_gradient(self.colors, self.n_pulse_singletons)
|
|
|
|
self.pulse_singletons = [
|
|
|
|
RadarPulseSingleton(*args, color = color, **kwargs)
|
|
|
|
for color in colors
|
|
|
|
]
|
|
|
|
pluse_mobjects = VGroup(*[ps.mobject for ps in self.pulse_singletons])
|
|
|
|
ContinualAnimation.__init__(self, pluse_mobjects, **kwargs)
|
|
|
|
|
|
|
|
def update_mobject(self, dt):
|
|
|
|
for i, ps in enumerate(self.pulse_singletons):
|
|
|
|
ps.internal_time = self.internal_time - i*self.frequency
|
|
|
|
ps.update_mobject(dt)
|
2018-02-12 22:49:15 -08:00
|
|
|
|
2018-02-13 11:44:36 -08:00
|
|
|
def is_finished(self):
|
|
|
|
return all([ps.is_finished() for ps in self.pulse_singletons])
|
2018-02-12 22:49:15 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
class Flash(AnimationGroup):
|
|
|
|
CONFIG = {
|
|
|
|
"line_length" : 0.2,
|
|
|
|
"num_lines" : 12,
|
|
|
|
"flash_radius" : 0.3,
|
|
|
|
"line_stroke_width" : 3,
|
|
|
|
}
|
|
|
|
def __init__(self, mobject, color = YELLOW, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
original_color = mobject.get_color()
|
|
|
|
on_and_off = UpdateFromAlphaFunc(
|
|
|
|
mobject.copy(), lambda m, a : m.highlight(
|
|
|
|
color if a < 0.5 else original_color
|
|
|
|
),
|
|
|
|
remover = True
|
|
|
|
)
|
|
|
|
lines = VGroup()
|
|
|
|
for angle in np.arange(0, TAU, TAU/self.num_lines):
|
|
|
|
line = Line(ORIGIN, self.line_length*RIGHT)
|
|
|
|
line.shift((self.flash_radius - self.line_length)*RIGHT)
|
|
|
|
line.rotate(angle, about_point = ORIGIN)
|
|
|
|
lines.add(line)
|
|
|
|
lines.move_to(mobject)
|
|
|
|
lines.highlight(color)
|
|
|
|
line_anims = [
|
|
|
|
ShowCreationThenDestruction(
|
|
|
|
line, rate_func = squish_rate_func(smooth, 0, 0.5)
|
|
|
|
)
|
|
|
|
for line in lines
|
|
|
|
]
|
|
|
|
fade_anims = [
|
|
|
|
UpdateFromAlphaFunc(
|
|
|
|
line, lambda m, a : m.set_stroke(
|
|
|
|
width = self.line_stroke_width*(1-a)
|
|
|
|
),
|
|
|
|
rate_func = squish_rate_func(smooth, 0, 0.75)
|
|
|
|
)
|
|
|
|
for line in lines
|
|
|
|
]
|
|
|
|
|
|
|
|
AnimationGroup.__init__(
|
|
|
|
self, on_and_off, *line_anims+fade_anims, **kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
class MultipleFlashes(Succession):
|
|
|
|
CONFIG = {
|
|
|
|
"run_time_per_flash" : 1.0,
|
|
|
|
"num_flashes" : 3,
|
|
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
kwargs["run_time"] = self.run_time_per_flash
|
|
|
|
Succession.__init__(self, *[
|
|
|
|
Flash(*args, **kwargs)
|
|
|
|
for x in range(self.num_flashes)
|
|
|
|
])
|
|
|
|
|
|
|
|
class TrafficLight(SVGMobject):
|
|
|
|
CONFIG = {
|
|
|
|
"file_name" : "traffic_light",
|
|
|
|
"height" : 0.7,
|
|
|
|
"post_height" : 2,
|
|
|
|
"post_width" : 0.05,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
SVGMobject.__init__(self, **kwargs)
|
|
|
|
post = Rectangle(
|
|
|
|
height = self.post_height,
|
|
|
|
width = self.post_width,
|
|
|
|
stroke_width = 0,
|
|
|
|
fill_color = WHITE,
|
|
|
|
fill_opacity = 1,
|
|
|
|
)
|
|
|
|
self.move_to(post.get_top(), DOWN)
|
|
|
|
self.add_to_back(post)
|
|
|
|
|
2018-02-09 15:26:12 -08:00
|
|
|
###################
|
|
|
|
|
|
|
|
class MentionUncertaintyPrinciple(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
title = TextMobject("Heisenberg Uncertainty Principle")
|
|
|
|
title.to_edge(UP)
|
|
|
|
|
|
|
|
dot_cloud = ProbabalisticDotCloud()
|
|
|
|
vector_cloud = ProbabalisticVectorCloud(
|
|
|
|
gaussian_distribution_wrapper_config = {"sigma_x" : 0.2},
|
2018-02-12 22:49:15 -08:00
|
|
|
center_func = lambda : dot_cloud.gaussian_distribution_wrapper.get_parameters()[0],
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
|
|
|
for cloud in dot_cloud, vector_cloud:
|
2018-02-12 22:49:15 -08:00
|
|
|
cloud.gaussian_distribution_wrapper.next_to(
|
|
|
|
title, DOWN, 2*LARGE_BUFF
|
|
|
|
)
|
2018-02-09 15:26:12 -08:00
|
|
|
vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT)
|
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
def get_brace_text_group_update(gdw, vect, text, color):
|
2018-02-09 15:26:12 -08:00
|
|
|
brace = Brace(gdw, vect)
|
2018-02-12 22:49:15 -08:00
|
|
|
text = brace.get_tex("2\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF)
|
2018-02-09 15:26:12 -08:00
|
|
|
group = VGroup(brace, text)
|
|
|
|
def update_group(group):
|
|
|
|
brace, text = group
|
|
|
|
brace.match_width(gdw, stretch = True)
|
|
|
|
brace.next_to(gdw, vect)
|
|
|
|
text.next_to(brace, vect, buff = SMALL_BUFF)
|
2018-02-12 22:49:15 -08:00
|
|
|
group.highlight(color)
|
2018-02-09 15:26:12 -08:00
|
|
|
return ContinualUpdateFromFunc(group, update_group)
|
|
|
|
|
|
|
|
dot_brace_anim = get_brace_text_group_update(
|
|
|
|
dot_cloud.gaussian_distribution_wrapper,
|
2018-02-12 22:49:15 -08:00
|
|
|
DOWN, "position", dot_cloud.color
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
|
|
|
vector_brace_anim = get_brace_text_group_update(
|
|
|
|
vector_cloud.gaussian_distribution_wrapper,
|
2018-02-12 22:49:15 -08:00
|
|
|
UP, "momentum", vector_cloud.color
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
self.add(title)
|
|
|
|
self.add(dot_cloud)
|
|
|
|
self.play(
|
|
|
|
Write(title),
|
|
|
|
self.teacher.change, "raise_right_hand",
|
|
|
|
self.get_student_changes(*["pondering"]*3)
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
Write(dot_brace_anim.mobject, run_time = 1)
|
|
|
|
)
|
|
|
|
self.add(dot_brace_anim)
|
|
|
|
self.wait()
|
|
|
|
# self.wait(2)
|
|
|
|
self.play(
|
|
|
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : 0.1*RIGHT},
|
2018-02-09 15:26:12 -08:00
|
|
|
run_time = 2,
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.add(vector_cloud)
|
|
|
|
self.play(
|
|
|
|
FadeIn(vector_brace_anim.mobject)
|
|
|
|
)
|
|
|
|
self.add(vector_brace_anim)
|
|
|
|
self.play(
|
|
|
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : RIGHT},
|
2018-02-09 15:26:12 -08:00
|
|
|
self.get_student_changes(*3*["confused"]),
|
|
|
|
run_time = 3,
|
|
|
|
)
|
|
|
|
#Back and forth
|
2018-02-10 18:32:17 -08:00
|
|
|
for x in range(2):
|
|
|
|
self.play(
|
|
|
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : 2*RIGHT},
|
2018-02-10 18:32:17 -08:00
|
|
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : 0.1*RIGHT},
|
2018-02-10 18:32:17 -08:00
|
|
|
run_time = 3,
|
|
|
|
)
|
|
|
|
self.change_student_modes("thinking", "erm", "sassy")
|
|
|
|
self.play(
|
|
|
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : 0.1*RIGHT},
|
2018-02-10 18:32:17 -08:00
|
|
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
2018-02-12 22:49:15 -08:00
|
|
|
{"sigma" : 1*RIGHT},
|
2018-02-10 18:32:17 -08:00
|
|
|
run_time = 3,
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
class FourierTradeoff(Scene):
|
2018-02-15 18:36:29 -08:00
|
|
|
CONFIG = {
|
|
|
|
"show_text" : True,
|
|
|
|
"complex_to_real_func" : abs,
|
|
|
|
}
|
2018-02-10 18:32:17 -08:00
|
|
|
def construct(self):
|
|
|
|
#Setup axes
|
|
|
|
time_mean = 4
|
|
|
|
time_axes = Axes(
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 2*time_mean,
|
|
|
|
x_axis_config = {"unit_size" : 1.5},
|
|
|
|
y_min = -2,
|
|
|
|
y_max = 2,
|
|
|
|
y_axis_config = {"unit_size" : 0.5}
|
|
|
|
)
|
|
|
|
time_label = TextMobject("Time")
|
2018-02-13 21:38:38 -08:00
|
|
|
time_label.scale(1.5)
|
2018-02-10 18:32:17 -08:00
|
|
|
time_label.next_to(
|
2018-02-13 21:38:38 -08:00
|
|
|
time_axes.x_axis.get_right(), UP+LEFT,
|
2018-02-10 18:32:17 -08:00
|
|
|
buff = MED_SMALL_BUFF,
|
|
|
|
)
|
|
|
|
time_axes.add(time_label)
|
|
|
|
time_axes.center().to_edge(UP)
|
|
|
|
time_axes.x_axis.add_numbers(*range(1, 2*time_mean))
|
|
|
|
|
|
|
|
frequency_axes = Axes(
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 8,
|
|
|
|
x_axis_config = {"unit_size" : 1.5},
|
|
|
|
y_min = 0,
|
2018-02-15 16:13:47 -08:00
|
|
|
y_max = 0.1,
|
2018-02-10 18:32:17 -08:00
|
|
|
y_axis_config = {
|
2018-02-15 16:13:47 -08:00
|
|
|
"unit_size" : 30,
|
|
|
|
"tick_frequency" : 0.025,
|
2018-02-10 18:32:17 -08:00
|
|
|
},
|
|
|
|
color = TEAL,
|
|
|
|
)
|
|
|
|
frequency_label = TextMobject("Frequency")
|
2018-02-13 21:38:38 -08:00
|
|
|
frequency_label.scale(1.5)
|
2018-02-10 18:32:17 -08:00
|
|
|
frequency_label.next_to(
|
2018-02-13 21:38:38 -08:00
|
|
|
frequency_axes.x_axis.get_right(), UP+LEFT,
|
2018-02-10 18:32:17 -08:00
|
|
|
buff = MED_SMALL_BUFF,
|
|
|
|
)
|
|
|
|
frequency_label.highlight(FREQUENCY_COLOR)
|
|
|
|
frequency_axes.add(frequency_label)
|
|
|
|
frequency_axes.move_to(time_axes, LEFT)
|
|
|
|
frequency_axes.to_edge(DOWN, buff = LARGE_BUFF)
|
|
|
|
frequency_axes.x_axis.add_numbers()
|
|
|
|
|
|
|
|
# Graph information
|
|
|
|
|
|
|
|
#x-coordinate of this point determines width of wave_packet graph
|
|
|
|
width_tracker = VectorizedPoint(0.5*RIGHT)
|
|
|
|
def get_width():
|
|
|
|
return width_tracker.get_center()[0]
|
|
|
|
|
|
|
|
def get_wave_packet_function():
|
|
|
|
factor = 1./get_width()
|
|
|
|
return lambda t : np.sqrt(factor)*np.cos(4*TAU*t)*np.exp(-factor*(t-time_mean)**2)
|
|
|
|
|
|
|
|
def get_wave_packet():
|
|
|
|
graph = time_axes.get_graph(
|
|
|
|
get_wave_packet_function(),
|
|
|
|
num_graph_points = 200,
|
|
|
|
)
|
|
|
|
graph.highlight(YELLOW)
|
|
|
|
return graph
|
|
|
|
|
|
|
|
time_radius = 10
|
|
|
|
def get_wave_packet_fourier_transform():
|
|
|
|
return get_fourier_graph(
|
2018-02-15 16:13:47 -08:00
|
|
|
frequency_axes,
|
|
|
|
get_wave_packet_function(),
|
2018-02-10 18:32:17 -08:00
|
|
|
t_min = time_mean - time_radius,
|
|
|
|
t_max = time_mean + time_radius,
|
|
|
|
n_samples = 2*time_radius*17,
|
2018-02-15 18:36:29 -08:00
|
|
|
complex_to_real_func = self.complex_to_real_func,
|
2018-02-10 18:32:17 -08:00
|
|
|
color = FREQUENCY_COLOR,
|
|
|
|
)
|
|
|
|
|
|
|
|
wave_packet = get_wave_packet()
|
|
|
|
wave_packet_update = UpdateFromFunc(
|
|
|
|
wave_packet,
|
|
|
|
lambda g : Transform(g, get_wave_packet()).update(1)
|
|
|
|
)
|
|
|
|
fourier_graph = get_wave_packet_fourier_transform()
|
|
|
|
fourier_graph_update = UpdateFromFunc(
|
|
|
|
fourier_graph,
|
|
|
|
lambda g : Transform(g, get_wave_packet_fourier_transform()).update(1)
|
|
|
|
)
|
|
|
|
|
|
|
|
arrow = Arrow(
|
2018-02-15 16:13:47 -08:00
|
|
|
wave_packet, frequency_axes.coords_to_point(
|
|
|
|
4, frequency_axes.y_max/2,
|
|
|
|
),
|
2018-02-10 18:32:17 -08:00
|
|
|
color = FREQUENCY_COLOR,
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
2018-02-13 21:38:38 -08:00
|
|
|
fourier_words = TextMobject("$|$Fourier Transform$|$")
|
|
|
|
fourier_words.next_to(arrow, LEFT, buff = MED_LARGE_BUFF)
|
2018-02-10 18:32:17 -08:00
|
|
|
sub_words = TextMobject("(To be explained shortly)")
|
|
|
|
sub_words.highlight(BLUE)
|
|
|
|
sub_words.scale(0.75)
|
|
|
|
sub_words.next_to(fourier_words, DOWN)
|
|
|
|
|
|
|
|
#Draw items
|
|
|
|
self.add(time_axes, frequency_axes)
|
2018-02-10 22:46:53 -08:00
|
|
|
self.play(ShowCreation(wave_packet, rate_func = double_smooth))
|
2018-02-15 18:36:29 -08:00
|
|
|
anims = [ReplacementTransform(
|
|
|
|
wave_packet.copy(), fourier_graph
|
|
|
|
)]
|
|
|
|
if self.show_text:
|
|
|
|
anims += [
|
|
|
|
GrowArrow(arrow),
|
|
|
|
Write(fourier_words, run_time = 1)
|
|
|
|
]
|
|
|
|
self.play(*anims)
|
2018-02-10 18:32:17 -08:00
|
|
|
# self.play(FadeOut(arrow))
|
|
|
|
self.wait()
|
2018-02-15 18:36:29 -08:00
|
|
|
for width in 6, 0.02, 1:
|
2018-02-10 18:32:17 -08:00
|
|
|
self.play(
|
|
|
|
width_tracker.move_to, width*RIGHT,
|
|
|
|
wave_packet_update,
|
|
|
|
fourier_graph_update,
|
|
|
|
run_time = 3
|
|
|
|
)
|
2018-02-15 18:36:29 -08:00
|
|
|
if sub_words not in self.mobjects and self.show_text:
|
2018-02-10 18:32:17 -08:00
|
|
|
self.play(FadeIn(sub_words))
|
|
|
|
else:
|
|
|
|
self.wait()
|
|
|
|
self.wait()
|
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
class ShowPlan(PiCreatureScene):
|
|
|
|
def construct(self):
|
|
|
|
self.add_title()
|
|
|
|
words = self.get_words()
|
|
|
|
self.play_sound_anims(words[0])
|
2018-02-13 21:38:38 -08:00
|
|
|
self.play_doppler_anims(words[1])
|
|
|
|
self.play_quantum_anims(words[2])
|
2018-02-12 22:49:15 -08:00
|
|
|
|
|
|
|
def add_title(self):
|
|
|
|
title = TextMobject("The plan")
|
|
|
|
title.scale(1.5)
|
|
|
|
title.to_edge(UP)
|
|
|
|
h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
|
|
|
|
h_line.next_to(title, DOWN)
|
|
|
|
self.add(title, h_line)
|
|
|
|
|
|
|
|
def get_words(self):
|
2018-02-13 21:38:38 -08:00
|
|
|
trips = [
|
|
|
|
("sound waves", "(time vs. frequency)", YELLOW),
|
|
|
|
("Doppler radar", "(distance vs. velocity)", GREEN),
|
|
|
|
("quantum particles", "(position vs. momentum)", BLUE),
|
|
|
|
]
|
2018-02-12 22:49:15 -08:00
|
|
|
words = VGroup()
|
2018-02-13 21:38:38 -08:00
|
|
|
for topic, tradeoff, color in trips:
|
|
|
|
word = TextMobject("Uncertainty for", topic, tradeoff)
|
|
|
|
word[1:].highlight(color)
|
|
|
|
word[2].scale(0.75)
|
|
|
|
word[2].next_to(word[1], DOWN, buff = 1.5*SMALL_BUFF)
|
2018-02-12 22:49:15 -08:00
|
|
|
words.add(word)
|
2018-02-13 21:38:38 -08:00
|
|
|
words.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF)
|
2018-02-12 22:49:15 -08:00
|
|
|
words.to_edge(LEFT)
|
|
|
|
|
|
|
|
return words
|
|
|
|
|
|
|
|
def play_sound_anims(self, word):
|
|
|
|
morty = self.pi_creature
|
|
|
|
wave = FunctionGraph(
|
|
|
|
lambda x : 0.3*np.sin(15*x)*np.sin(0.5*x),
|
|
|
|
x_min = 0, x_max = 30,
|
|
|
|
num_anchor_points = 500,
|
|
|
|
)
|
|
|
|
wave.next_to(word, RIGHT)
|
|
|
|
rect = BackgroundRectangle(wave, fill_opacity = 1)
|
|
|
|
rect.stretch(2, 1)
|
|
|
|
rect.next_to(wave, LEFT, buff = 0)
|
|
|
|
wave_shift = AmbientMovement(
|
|
|
|
wave, direction = LEFT, rate = 5
|
|
|
|
)
|
|
|
|
wave_fader = UpdateFromAlphaFunc(
|
|
|
|
wave,
|
|
|
|
lambda w, a : w.set_stroke(width = 3*a)
|
|
|
|
)
|
|
|
|
checkmark = self.get_checkmark(word)
|
|
|
|
|
|
|
|
self.add(wave_shift)
|
|
|
|
self.add_foreground_mobjects(rect, word)
|
|
|
|
self.play(
|
|
|
|
Animation(word),
|
|
|
|
wave_fader,
|
|
|
|
morty.change, "raise_right_hand", word
|
|
|
|
)
|
|
|
|
self.wait(2)
|
|
|
|
wave_fader.rate_func = lambda a : 1-smooth(a)
|
|
|
|
self.add_foreground_mobjects(checkmark)
|
|
|
|
self.play(
|
|
|
|
Write(checkmark),
|
|
|
|
morty.change, "happy",
|
|
|
|
wave_fader,
|
|
|
|
)
|
|
|
|
self.remove_foreground_mobjects(rect, word)
|
|
|
|
self.add(word)
|
|
|
|
self.wait()
|
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
def play_doppler_anims(self, word):
|
2018-02-12 22:49:15 -08:00
|
|
|
morty = self.pi_creature
|
|
|
|
|
|
|
|
radar_dish = RadarDish()
|
2018-02-13 11:44:36 -08:00
|
|
|
radar_dish.next_to(word, DOWN, aligned_edge = LEFT)
|
2018-02-13 15:39:57 -08:00
|
|
|
target = Plane()
|
|
|
|
# target.match_height(radar_dish)
|
|
|
|
target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF)
|
|
|
|
target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.25)
|
2018-02-12 22:49:15 -08:00
|
|
|
|
2018-02-13 11:44:36 -08:00
|
|
|
pulse = RadarPulse(radar_dish, target)
|
|
|
|
|
|
|
|
checkmark = self.get_checkmark(word)
|
|
|
|
|
|
|
|
self.add(target_movement)
|
2018-02-12 22:49:15 -08:00
|
|
|
self.play(
|
|
|
|
Write(word),
|
2018-02-13 11:44:36 -08:00
|
|
|
DrawBorderThenFill(radar_dish),
|
2018-02-13 15:39:57 -08:00
|
|
|
UpdateFromAlphaFunc(
|
|
|
|
target, lambda m, a : m.set_fill(opacity = a)
|
|
|
|
),
|
2018-02-12 22:49:15 -08:00
|
|
|
morty.change, "pondering",
|
|
|
|
run_time = 1
|
|
|
|
)
|
2018-02-13 11:44:36 -08:00
|
|
|
self.add(pulse)
|
2018-02-13 15:39:57 -08:00
|
|
|
count = it.count() #TODO, this is not a great hack...
|
|
|
|
while not pulse.is_finished() and count.next() < 15:
|
2018-02-13 11:44:36 -08:00
|
|
|
self.play(
|
|
|
|
morty.look_at, pulse.mobject,
|
|
|
|
run_time = 0.5
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
Write(checkmark),
|
2018-02-13 15:39:57 -08:00
|
|
|
UpdateFromAlphaFunc(
|
|
|
|
target, lambda m, a : m.set_fill(opacity = 1-a)
|
|
|
|
),
|
|
|
|
FadeOut(radar_dish),
|
2018-02-13 11:44:36 -08:00
|
|
|
morty.change, "happy"
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
def play_quantum_anims(self, word):
|
|
|
|
morty = self.pi_creature
|
|
|
|
dot_cloud = ProbabalisticDotCloud()
|
|
|
|
gdw = dot_cloud.gaussian_distribution_wrapper
|
|
|
|
gdw.next_to(word, DOWN, MED_LARGE_BUFF)
|
|
|
|
gdw.rotate(5*DEGREES)
|
|
|
|
gdw.save_state()
|
|
|
|
gdw.scale(0)
|
2018-02-13 11:44:36 -08:00
|
|
|
|
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
checkmark = self.get_checkmark(word)
|
|
|
|
ish = TextMobject("$\\dots$ish")
|
|
|
|
ish.next_to(checkmark, RIGHT, -SMALL_BUFF, DOWN)
|
|
|
|
|
|
|
|
self.add(dot_cloud)
|
|
|
|
self.play(
|
|
|
|
Write(word),
|
|
|
|
FadeIn(dot_cloud.mobject),
|
|
|
|
morty.change, "confused",
|
|
|
|
)
|
|
|
|
self.play(gdw.restore, run_time = 2)
|
|
|
|
self.play(Write(checkmark))
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
Write(ish),
|
|
|
|
morty.change, 'maybe'
|
|
|
|
)
|
|
|
|
self.wait(6)
|
2018-02-12 22:49:15 -08:00
|
|
|
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
##
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-12 22:49:15 -08:00
|
|
|
def get_checkmark(self, word):
|
|
|
|
checkmark = TexMobject("\\checkmark")
|
|
|
|
checkmark.highlight(GREEN)
|
2018-02-13 21:38:38 -08:00
|
|
|
checkmark.scale(1.25)
|
|
|
|
checkmark.next_to(word[1], UP+RIGHT, buff = 0)
|
2018-02-12 22:49:15 -08:00
|
|
|
return checkmark
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
class StartWithIntuition(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
self.teacher_says(
|
|
|
|
"You already \\\\ have this \\\\ intuition",
|
|
|
|
bubble_kwargs = {
|
|
|
|
"height" : 3.5,
|
|
|
|
"width" : 3,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.change_student_modes("pondering", "erm", "maybe")
|
|
|
|
self.look_at(VectorizedPoint(4*LEFT + 2*UP))
|
|
|
|
self.wait(5)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
class TwoCarsAtRedLight(Scene):
|
2018-02-14 12:33:53 -08:00
|
|
|
CONFIG = {
|
|
|
|
"text_scale_val" : 0.75,
|
|
|
|
}
|
2018-02-13 21:38:38 -08:00
|
|
|
def construct(self):
|
|
|
|
self.pull_up_behind()
|
2018-02-15 16:13:47 -08:00
|
|
|
self.flash_in_sync_short_time()
|
|
|
|
self.show_low_confidence()
|
|
|
|
self.flash_in_sync_long_time()
|
|
|
|
self.show_high_confidence()
|
2018-02-13 21:38:38 -08:00
|
|
|
|
|
|
|
def pull_up_behind(self):
|
|
|
|
#Setup Traffic light
|
|
|
|
traffic_light = TrafficLight()
|
2018-02-14 12:33:53 -08:00
|
|
|
traffic_light.move_to(6*RIGHT + 2.5*DOWN, DOWN)
|
2018-02-13 21:38:38 -08:00
|
|
|
source_point = VectorizedPoint(
|
2018-02-14 12:33:53 -08:00
|
|
|
traffic_light[2].get_right()
|
2018-02-13 21:38:38 -08:00
|
|
|
)
|
|
|
|
screen = Line(ORIGIN, UP)
|
|
|
|
screen.next_to(source_point, RIGHT, LARGE_BUFF)
|
|
|
|
red_light = Spotlight(
|
|
|
|
color = RED,
|
|
|
|
source_point = source_point,
|
|
|
|
radius = 0.5,
|
|
|
|
screen = screen,
|
|
|
|
num_levels = 20,
|
|
|
|
opacity_function = lambda r : 1/(10*r**2+1)
|
|
|
|
)
|
|
|
|
red_light.fade(0.5)
|
|
|
|
red_light.rotate(TAU/2, about_edge = LEFT)
|
|
|
|
self.add(red_light, traffic_light)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
#Setup cars
|
|
|
|
car1, car2 = cars = self.cars = VGroup(*[
|
|
|
|
Car() for x in range(2)
|
|
|
|
])
|
|
|
|
cars.arrange_submobjects(RIGHT, buff = LARGE_BUFF)
|
|
|
|
cars.next_to(
|
|
|
|
traffic_light, LEFT,
|
|
|
|
buff = LARGE_BUFF, aligned_edge = DOWN
|
|
|
|
)
|
|
|
|
car2.pi_creature.highlight(GREY_BROWN)
|
|
|
|
car1.start_point = car1.get_corner(DOWN+RIGHT)
|
|
|
|
car1.shift(SPACE_WIDTH*LEFT)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
#Pull up car
|
|
|
|
self.add(cars)
|
|
|
|
self.play(
|
|
|
|
SwitchOn(
|
|
|
|
red_light,
|
|
|
|
rate_func = squish_rate_func(smooth, 0, 0.3),
|
|
|
|
),
|
|
|
|
Animation(traffic_light),
|
|
|
|
self.get_flashes(car2, num_flashes = 3),
|
|
|
|
MoveCar(
|
|
|
|
car1, car1.start_point,
|
|
|
|
run_time = 3,
|
|
|
|
rate_func = rush_from,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def flash_in_sync_short_time(self):
|
|
|
|
car1, car2 = cars = self.cars
|
|
|
|
|
|
|
|
#Setup axes
|
|
|
|
axes = Axes(
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 5,
|
|
|
|
y_min = 0,
|
|
|
|
y_max = 2,
|
|
|
|
y_axis_config = {
|
|
|
|
"tick_frequency" : 0.5,
|
|
|
|
},
|
|
|
|
)
|
2018-02-14 12:33:53 -08:00
|
|
|
axes.x_axis.add_numbers(1, 2, 3)
|
2018-02-13 21:38:38 -08:00
|
|
|
time_label = TextMobject("Time")
|
2018-02-14 12:33:53 -08:00
|
|
|
time_label.scale(self.text_scale_val)
|
|
|
|
time_label.next_to(axes.x_axis.get_right(), DOWN)
|
2018-02-13 21:38:38 -08:00
|
|
|
y_title = TextMobject("Signal")
|
2018-02-14 12:33:53 -08:00
|
|
|
y_title.scale(self.text_scale_val)
|
2018-02-13 21:38:38 -08:00
|
|
|
y_title.next_to(axes.y_axis, UP, SMALL_BUFF)
|
|
|
|
axes.add(time_label, y_title)
|
|
|
|
axes.to_corner(UP+LEFT, buff = MED_SMALL_BUFF)
|
|
|
|
graph = axes.get_graph(
|
2018-02-14 12:33:53 -08:00
|
|
|
self.get_multispike_function(range(1, 4)),
|
2018-02-13 21:38:38 -08:00
|
|
|
x_min = 0.8,
|
|
|
|
x_max = 3.8,
|
|
|
|
)
|
|
|
|
graph.highlight(YELLOW)
|
|
|
|
|
|
|
|
#Label short duration
|
|
|
|
brace = Brace(Line(
|
|
|
|
axes.input_to_graph_point(1, graph),
|
|
|
|
axes.input_to_graph_point(3, graph),
|
|
|
|
), UP)
|
|
|
|
text = TextMobject("Short duration observation")
|
2018-02-14 12:33:53 -08:00
|
|
|
text.scale(self.text_scale_val)
|
2018-02-13 21:38:38 -08:00
|
|
|
text.next_to(brace, UP, SMALL_BUFF)
|
|
|
|
text.align_to(
|
|
|
|
axes.coords_to_point(0.25, 0), LEFT
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
self.get_flashes(car1, num_flashes = 2),
|
|
|
|
self.get_flashes(car2, num_flashes = 2),
|
|
|
|
LaggedStart(FadeIn, VGroup(
|
|
|
|
axes, time_label, y_title,
|
|
|
|
))
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
self.get_flashes(car1, num_flashes = 3),
|
|
|
|
self.get_flashes(car2, num_flashes = 3),
|
|
|
|
ShowCreation(graph, rate_func = None, run_time = 3)
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
self.get_flashes(car1, num_flashes = 10),
|
|
|
|
self.get_flashes(car2, num_flashes = 10, run_time_per_flash = 0.98),
|
|
|
|
GrowFromCenter(brace),
|
|
|
|
Write(text),
|
|
|
|
)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-13 21:38:38 -08:00
|
|
|
self.time_axes = axes
|
|
|
|
self.time_graph = graph
|
|
|
|
self.time_graph_label = VGroup(
|
|
|
|
brace, text
|
|
|
|
)
|
|
|
|
|
|
|
|
def show_low_confidence(self):
|
|
|
|
car1, car2 = cars = self.cars
|
|
|
|
time_axes = self.time_axes
|
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
#Setup axes
|
2018-02-13 21:38:38 -08:00
|
|
|
frequency_axes = Axes(
|
|
|
|
x_min = 0,
|
2018-02-14 12:33:53 -08:00
|
|
|
x_max = 3,
|
2018-02-13 21:38:38 -08:00
|
|
|
y_min = 0,
|
|
|
|
y_max = 1.5,
|
|
|
|
y_axis_config = {
|
|
|
|
"tick_frequency" : 0.5,
|
|
|
|
}
|
|
|
|
)
|
2018-02-14 12:33:53 -08:00
|
|
|
frequency_axes.next_to(time_axes, DOWN, LARGE_BUFF)
|
2018-02-13 21:38:38 -08:00
|
|
|
frequency_axes.highlight(LIGHT_GREY)
|
|
|
|
frequency_label = TextMobject("Frequency")
|
2018-02-14 12:33:53 -08:00
|
|
|
frequency_label.scale(self.text_scale_val)
|
|
|
|
frequency_label.next_to(frequency_axes.x_axis.get_right(), DOWN)
|
|
|
|
frequency_axes.add(
|
|
|
|
frequency_label,
|
|
|
|
VectorizedPoint(frequency_axes.y_axis.get_top())
|
2018-02-13 21:38:38 -08:00
|
|
|
)
|
|
|
|
frequency_axes.x_axis.add_numbers(1, 2)
|
2018-02-14 12:33:53 -08:00
|
|
|
frequency_graph = frequency_axes.get_graph(
|
|
|
|
lambda x : np.exp(-4*(x-1)**2),
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 2,
|
|
|
|
)
|
|
|
|
frequency_graph.highlight(RED)
|
|
|
|
peak_point = frequency_axes.input_to_graph_point(
|
|
|
|
1, frequency_graph
|
2018-02-13 21:38:38 -08:00
|
|
|
)
|
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
#Setup label
|
|
|
|
label = TextMobject("Low confidence")
|
|
|
|
label.scale(self.text_scale_val)
|
|
|
|
label.move_to(peak_point + UP+RIGHT, DOWN)
|
|
|
|
label.match_color(frequency_graph)
|
|
|
|
arrow = Arrow(label.get_bottom(), peak_point, buff = 2*SMALL_BUFF)
|
|
|
|
arrow.match_color(frequency_graph)
|
2018-02-13 21:38:38 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
self.play(
|
|
|
|
ReplacementTransform(
|
|
|
|
self.time_axes.copy(), frequency_axes
|
|
|
|
),
|
|
|
|
ReplacementTransform(
|
|
|
|
self.time_graph.copy(), frequency_graph
|
|
|
|
),
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
Write(label),
|
|
|
|
GrowArrow(arrow)
|
|
|
|
)
|
|
|
|
self.wait()
|
2018-02-13 21:38:38 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
self.frequency_axes = frequency_axes
|
|
|
|
self.frequency_graph = frequency_graph
|
|
|
|
self.frequency_graph_label = VGroup(
|
|
|
|
label, arrow
|
|
|
|
)
|
2018-02-13 21:38:38 -08:00
|
|
|
|
|
|
|
def flash_in_sync_long_time(self):
|
2018-02-14 12:33:53 -08:00
|
|
|
time_graph = self.time_graph
|
|
|
|
time_axes = self.time_axes
|
|
|
|
frequency_graph = self.frequency_graph
|
|
|
|
frequency_axes = self.frequency_axes
|
|
|
|
|
|
|
|
n_spikes = 12
|
|
|
|
new_time_graph = time_axes.get_graph(
|
|
|
|
self.get_multispike_function(range(1, n_spikes+1)),
|
|
|
|
x_min = 0.8,
|
|
|
|
x_max = n_spikes + 0.8,
|
|
|
|
)
|
|
|
|
new_time_graph.match_color(time_graph)
|
|
|
|
|
|
|
|
new_frequency_graph = frequency_axes.get_graph(
|
|
|
|
lambda x : np.exp(-500*(x-1)**2),
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 2,
|
|
|
|
num_anchors = 500,
|
|
|
|
)
|
|
|
|
new_frequency_graph.match_color(self.frequency_graph)
|
|
|
|
|
|
|
|
def pin_freq_graph_end_points(freq_graph):
|
|
|
|
freq_graph.points[0] = frequency_axes.coords_to_point(0, 0)
|
|
|
|
freq_graph.points[-1] = frequency_axes.coords_to_point(2, 0)
|
|
|
|
|
|
|
|
self.play(LaggedStart(
|
|
|
|
FadeOut, VGroup(
|
|
|
|
self.time_graph_label,
|
|
|
|
self.frequency_graph_label,
|
|
|
|
self.time_graph,
|
|
|
|
)
|
|
|
|
))
|
|
|
|
self.play(
|
|
|
|
ApplyMethod(
|
|
|
|
self.time_axes.x_axis.main_line.stretch, 2.5, 0,
|
|
|
|
{"about_edge" : LEFT},
|
|
|
|
run_time = 4,
|
|
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.6),
|
|
|
|
),
|
|
|
|
UpdateFromFunc(
|
|
|
|
self.time_axes.x_axis.tip,
|
|
|
|
lambda m : m.move_to(
|
|
|
|
self.time_axes.x_axis.main_line.get_right(),
|
|
|
|
LEFT
|
|
|
|
)
|
|
|
|
),
|
|
|
|
ShowCreation(
|
|
|
|
new_time_graph,
|
|
|
|
run_time = n_spikes,
|
|
|
|
rate_func = None,
|
|
|
|
),
|
|
|
|
ApplyMethod(
|
|
|
|
frequency_graph.stretch, 0.1, 0,
|
|
|
|
run_time = n_spikes,
|
|
|
|
),
|
|
|
|
UpdateFromFunc(frequency_graph, pin_freq_graph_end_points),
|
|
|
|
*[
|
|
|
|
self.get_flashes(car, num_flashes = n_spikes)
|
|
|
|
for car in self.cars
|
|
|
|
]
|
|
|
|
)
|
2018-02-13 21:38:38 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
self.new_time_graph = new_time_graph
|
|
|
|
self.new_frequency_graph = new_frequency_graph
|
2018-02-13 21:38:38 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
def show_high_confidence(self):
|
|
|
|
#Frequency stuff
|
|
|
|
arrow = self.frequency_graph_label[1]
|
|
|
|
label = TextMobject("High confidence")
|
|
|
|
label.scale(self.text_scale_val)
|
|
|
|
label.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
|
|
label.match_color(arrow)
|
|
|
|
|
|
|
|
frequency_axes = self.frequency_axes
|
|
|
|
|
|
|
|
#Time stuff
|
|
|
|
new_time_graph = self.new_time_graph
|
|
|
|
brace = Brace(new_time_graph, UP, buff = SMALL_BUFF)
|
|
|
|
text = TextMobject("Long duration observation")
|
|
|
|
text.scale(self.text_scale_val)
|
|
|
|
text.next_to(brace, UP, buff = SMALL_BUFF)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
FadeIn(label),
|
|
|
|
GrowArrow(arrow),
|
|
|
|
*map(self.get_flashes, self.cars)
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
GrowFromCenter(brace),
|
|
|
|
Write(text, run_time = 1),
|
|
|
|
*map(self.get_flashes, self.cars)
|
|
|
|
)
|
|
|
|
self.play(*[
|
|
|
|
self.get_flashes(car, num_flashes = 10)
|
|
|
|
for car in self.cars
|
|
|
|
])
|
2018-02-13 21:38:38 -08:00
|
|
|
|
|
|
|
###
|
|
|
|
|
|
|
|
def get_flashes(self, car, colors = [YELLOW, RED], num_flashes = 1, **kwargs):
|
|
|
|
return AnimationGroup(*[
|
|
|
|
MultipleFlashes(light, color, num_flashes = num_flashes, **kwargs)
|
|
|
|
for light, color in zip(car.get_lights(), colors)
|
|
|
|
])
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
def get_multispike_function(self, spike_times):
|
|
|
|
return lambda x : sum([
|
|
|
|
1.25*np.exp(-100*(x-m)**2)
|
|
|
|
for m in spike_times
|
|
|
|
])
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
class VariousMusicalNotes(Scene):
|
|
|
|
def construct(self):
|
|
|
|
freq = 20
|
|
|
|
# x-coordinate of this point represents log(a)
|
|
|
|
# where the bell curve component of the signal
|
|
|
|
# is exp(-a*(x**2))
|
|
|
|
graph_width_tracker = VectorizedPoint()
|
|
|
|
graph_width_tracker.move_to(np.log(2)*RIGHT)
|
|
|
|
def get_graph():
|
|
|
|
a = np.exp(graph_width_tracker.get_center()[0])
|
|
|
|
return FunctionGraph(
|
|
|
|
lambda x : np.exp(-a*x**2)*np.sin(freq*x)-0.5,
|
|
|
|
num_anchor_points = 500,
|
|
|
|
)
|
|
|
|
graph = get_graph()
|
|
|
|
def graph_update(graph):
|
|
|
|
graph.points = get_graph().points
|
|
|
|
graph_update_anim = UpdateFromFunc(graph, graph_update)
|
|
|
|
def change_width_anim(width, **kwargs):
|
|
|
|
a = 2.0/(width**2)
|
|
|
|
return AnimationGroup(
|
|
|
|
ApplyMethod(
|
|
|
|
graph_width_tracker.move_to,
|
|
|
|
np.log(a)*RIGHT
|
|
|
|
),
|
|
|
|
graph_update_anim,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
phrases = [
|
|
|
|
TextMobject(*words.split(" "))
|
|
|
|
for words in [
|
|
|
|
"Less clear frequency",
|
|
|
|
"Extremely unclear frequency",
|
|
|
|
"Very clear frequency",
|
|
|
|
]
|
|
|
|
]
|
2018-02-10 18:32:17 -08:00
|
|
|
|
|
|
|
|
2018-02-14 12:33:53 -08:00
|
|
|
#Show graphs and phrases
|
|
|
|
widths = [1, 0.2, SPACE_WIDTH]
|
|
|
|
for width, phrase in zip(widths, phrases):
|
|
|
|
brace = Brace(Line(LEFT, RIGHT), UP)
|
|
|
|
brace.stretch(width, 0)
|
|
|
|
brace.next_to(graph.get_center(), UP, buff = 1.2)
|
|
|
|
phrase.next_to(brace, UP)
|
|
|
|
|
|
|
|
if width is widths[0]:
|
|
|
|
self.play(ShowCreation(graph, rate_func = None)),
|
|
|
|
self.play(
|
|
|
|
GrowFromCenter(brace),
|
|
|
|
Write(phrase, run_time = 1)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self.play(
|
|
|
|
change_width_anim(width),
|
|
|
|
ReplacementTransform(
|
|
|
|
VGroup(last_phrase, last_brace),
|
|
|
|
VGroup(phrase, brace),
|
|
|
|
rate_func = squish_rate_func(smooth, 0.5, 1),
|
|
|
|
),
|
|
|
|
run_time = 2
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
# self.play(*map(FadeOut, [graph, brace, phrase]))
|
|
|
|
last_phrase = phrase
|
|
|
|
last_brace = brace
|
|
|
|
|
|
|
|
#Talk about correlations
|
|
|
|
short_signal_words = TextMobject(
|
|
|
|
"Short", "signal", "correlates",
|
|
|
|
"with", "wide range", "of frequencies"
|
|
|
|
)
|
|
|
|
long_signal_words = TextMobject(
|
|
|
|
"Only", "wide", "signals", "correlate",
|
|
|
|
"with a", "short range", "of frequencies"
|
|
|
|
)
|
|
|
|
phrases = VGroup(short_signal_words, long_signal_words)
|
|
|
|
for phrase in phrases:
|
|
|
|
phrase.scale(0.8)
|
|
|
|
phrase.highlight_by_tex_to_color_map({
|
|
|
|
"short" : RED,
|
|
|
|
"long" : GREEN,
|
|
|
|
"wide" : GREEN,
|
|
|
|
}, case_sensitive = False)
|
|
|
|
phrases.arrange_submobjects(DOWN)
|
|
|
|
phrases.to_edge(UP)
|
|
|
|
|
|
|
|
long_graph = FunctionGraph(
|
|
|
|
lambda x : 0.5*np.sin(freq*x),
|
|
|
|
x_min = -2*SPACE_WIDTH,
|
|
|
|
x_max = 2*SPACE_WIDTH,
|
|
|
|
num_anchor_points = 1000
|
|
|
|
)
|
|
|
|
long_graph.highlight(BLUE)
|
|
|
|
long_graph.next_to(graph, UP, MED_LARGE_BUFF)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
ShowCreation(long_graph),
|
|
|
|
*map(FadeOut, [last_brace, last_phrase])
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
Write(short_signal_words),
|
|
|
|
change_width_anim(widths[1])
|
|
|
|
)
|
|
|
|
self.play(
|
2018-02-15 16:13:47 -08:00
|
|
|
long_graph.stretch, 0.35, 0,
|
2018-02-14 12:33:53 -08:00
|
|
|
long_graph.highlight, GREEN,
|
|
|
|
run_time = 5,
|
|
|
|
rate_func = wiggle
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
Write(long_signal_words),
|
|
|
|
change_width_anim(widths[2]),
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
long_graph.stretch, 0.95, 0,
|
|
|
|
long_graph.highlight, average_color(GREEN, BLUE),
|
|
|
|
run_time = 4,
|
|
|
|
rate_func = wiggle
|
|
|
|
)
|
|
|
|
self.wait()
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
class BringInFourierTranform(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
fourier = TextMobject("Fourier")
|
|
|
|
fourier.scale(1.5)
|
|
|
|
fourier.next_to(self.teacher.get_corner(UP+LEFT), UP, LARGE_BUFF)
|
|
|
|
fourier.save_state()
|
|
|
|
fourier.shift(DOWN)
|
|
|
|
fourier.fade(1)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
self.teacher.change, "raise_right_hand",
|
|
|
|
fourier.restore
|
|
|
|
)
|
|
|
|
self.change_student_modes("happy", "erm", "confused")
|
|
|
|
self.look_at(3*LEFT + 2*UP)
|
|
|
|
self.wait(3)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
class LastVideoWrapper(Scene):
|
|
|
|
def construct(self):
|
|
|
|
title = TextMobject("Visualizing the Fourier Transform")
|
|
|
|
title.to_edge(UP)
|
|
|
|
screen_rect = ScreenRectangle(height = 6)
|
|
|
|
screen_rect.next_to(title, DOWN)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
self.add(title)
|
|
|
|
self.play(ShowCreation(screen_rect))
|
|
|
|
self.wait()
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
class FourierRecapScene(DrawFrequencyPlot):
|
|
|
|
CONFIG = {
|
|
|
|
"frequency_axes_config" : {
|
|
|
|
"x_max" : 10.0,
|
|
|
|
"x_axis_config" : {
|
|
|
|
"unit_size" : 0.7,
|
|
|
|
"numbers_to_show" : range(1, 10, 1),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"initial_winding_frequency" : 0.1,
|
|
|
|
}
|
|
|
|
def construct(self):
|
|
|
|
self.setup_axes()
|
|
|
|
self.preview_fourier_plot()
|
|
|
|
self.wrap_signal_around_circle()
|
|
|
|
self.match_winding_to_beat_frequency()
|
|
|
|
self.follow_center_of_mass()
|
|
|
|
self.draw_fourier_plot()
|
|
|
|
self.highlight_spike()
|
|
|
|
|
|
|
|
def setup_axes(self):
|
|
|
|
self.remove(self.pi_creature)
|
|
|
|
time_axes = self.get_time_axes()
|
|
|
|
time_axes.to_edge(UP, buff = MED_SMALL_BUFF)
|
|
|
|
time_axes.scale(0.9, about_edge = UP)
|
|
|
|
frequency_axes = self.get_frequency_axes()
|
|
|
|
circle_plane = self.get_circle_plane()
|
|
|
|
|
|
|
|
self.add(time_axes)
|
|
|
|
|
|
|
|
self.set_variables_as_attrs(
|
|
|
|
time_axes, frequency_axes,
|
|
|
|
circle_plane
|
|
|
|
)
|
|
|
|
|
|
|
|
def preview_fourier_plot(self):
|
|
|
|
time_graph = self.graph = self.get_time_graph(
|
|
|
|
width = 2,
|
|
|
|
num_graph_points = 200,
|
|
|
|
)
|
|
|
|
fourier_graph = self.get_fourier_transform_graph(
|
|
|
|
time_graph
|
|
|
|
)
|
|
|
|
fourier_graph.pointwise_become_partial(fourier_graph, 0.1, 1)
|
|
|
|
|
|
|
|
#labels
|
|
|
|
signal_label = TextMobject("Signal")
|
|
|
|
fourier_label = TextMobject("Fourier transform")
|
|
|
|
signal_label.next_to(time_graph, UP, buff = SMALL_BUFF)
|
|
|
|
fourier_label.next_to(fourier_graph, UP)
|
|
|
|
fourier_label.match_color(fourier_graph)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
ShowCreation(time_graph, run_time = 2),
|
|
|
|
Write(signal_label),
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
LaggedStart(FadeIn, self.frequency_axes),
|
|
|
|
ReplacementTransform(
|
|
|
|
time_graph.copy(),
|
|
|
|
fourier_graph,
|
|
|
|
run_time = 2
|
|
|
|
),
|
|
|
|
ReplacementTransform(
|
|
|
|
signal_label.copy(),
|
|
|
|
fourier_label,
|
|
|
|
run_time = 2,
|
|
|
|
rate_func = squish_rate_func(smooth, 0.5, 1)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(LaggedStart(
|
|
|
|
Indicate, self.frequency_axes.x_axis.numbers,
|
|
|
|
run_time = 4,
|
|
|
|
rate_func = wiggle,
|
|
|
|
))
|
|
|
|
self.wait()
|
|
|
|
self.play(*map(FadeOut, [
|
|
|
|
self.frequency_axes, fourier_graph,
|
|
|
|
signal_label, fourier_label,
|
|
|
|
]))
|
|
|
|
|
|
|
|
self.time_graph = time_graph
|
|
|
|
self.set_variables_as_attrs(time_graph, fourier_label)
|
|
|
|
|
|
|
|
def wrap_signal_around_circle(self):
|
|
|
|
time_graph = self.time_graph
|
|
|
|
circle_plane = self.circle_plane
|
|
|
|
freq = self.initial_winding_frequency
|
|
|
|
pol_graph = self.get_polarized_mobject(time_graph, freq)
|
|
|
|
winding_freq_label = self.get_winding_frequency_label()
|
|
|
|
winding_freq_label.add_to_back(BackgroundRectangle(winding_freq_label))
|
|
|
|
winding_freq_label.move_to(circle_plane.get_top(), DOWN)
|
|
|
|
|
|
|
|
self.add_foreground_mobjects(winding_freq_label)
|
|
|
|
self.play(
|
|
|
|
Write(circle_plane, run_time = 1),
|
|
|
|
ReplacementTransform(
|
|
|
|
time_graph.copy(), pol_graph,
|
|
|
|
path_arc = -TAU/4,
|
|
|
|
run_time_per_flash = 2,
|
|
|
|
run_time = 2,
|
|
|
|
),
|
|
|
|
FadeIn(winding_freq_label),
|
|
|
|
)
|
|
|
|
freq = 0.3
|
|
|
|
self.change_frequency(freq, run_time = 2)
|
|
|
|
ghost_pol_graph = pol_graph.copy()
|
|
|
|
self.remove(pol_graph)
|
|
|
|
self.play(ghost_pol_graph.set_stroke, {"width" : 0.5})
|
|
|
|
self.play(
|
|
|
|
*self.get_vector_animations(time_graph),
|
|
|
|
run_time = 15
|
|
|
|
)
|
|
|
|
self.remove(ghost_pol_graph)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def match_winding_to_beat_frequency(self):
|
|
|
|
self.v_lines_indicating_periods = self.get_v_lines_indicating_periods(0.3)
|
|
|
|
self.add(self.v_lines_indicating_periods)
|
|
|
|
for freq in range(1, 6):
|
|
|
|
self.change_frequency(freq, run_time = 5)
|
|
|
|
self.play(
|
|
|
|
*self.get_vector_animations(
|
|
|
|
self.time_graph,
|
|
|
|
draw_polarized_graph = False
|
|
|
|
),
|
|
|
|
rate_func = lambda t : 0.3*t,
|
|
|
|
run_time = 5
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def follow_center_of_mass(self):
|
|
|
|
com_dot = self.get_center_of_mass_dot()
|
|
|
|
self.generate_center_of_mass_dot_update_anim()
|
|
|
|
com_arrow = Arrow(UP+3*RIGHT, ORIGIN)
|
|
|
|
com_arrow.shift(com_dot.get_center())
|
|
|
|
com_arrow.match_color(com_dot)
|
|
|
|
com_words = TextMobject("Center of mass")
|
|
|
|
com_words.next_to(com_arrow.get_start(), UP)
|
|
|
|
com_words.match_color(com_arrow)
|
|
|
|
com_words.add_background_rectangle()
|
|
|
|
|
|
|
|
com_dot.save_state()
|
|
|
|
com_dot.move_to(com_arrow.get_start())
|
|
|
|
com_dot.fade(1)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
com_dot.restore,
|
|
|
|
GrowArrow(com_arrow, rate_func = squish_rate_func(smooth, 0.2, 1)),
|
|
|
|
Write(com_words),
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
squished_func = squish_rate_func(smooth, 0, 0.2)
|
|
|
|
self.change_frequency(
|
|
|
|
4,
|
|
|
|
added_anims = [
|
|
|
|
FadeOut(com_arrow, rate_func = squished_func),
|
|
|
|
FadeOut(com_words, rate_func = squished_func),
|
|
|
|
],
|
|
|
|
run_time = 5
|
|
|
|
)
|
|
|
|
|
|
|
|
def draw_fourier_plot(self):
|
|
|
|
frequency_axes = self.frequency_axes
|
|
|
|
fourier_label = self.fourier_label
|
|
|
|
|
|
|
|
self.change_frequency(0, run_time = 2)
|
|
|
|
self.play(
|
|
|
|
FadeIn(frequency_axes),
|
|
|
|
FadeIn(fourier_label),
|
|
|
|
)
|
|
|
|
|
|
|
|
fourier_graph = self.get_fourier_transform_graph(self.time_graph)
|
|
|
|
self.get_fourier_graph_drawing_update_anim(fourier_graph)
|
|
|
|
self.generate_fourier_dot_transform(fourier_graph)
|
|
|
|
|
|
|
|
self.change_frequency(5, run_time = 20)
|
|
|
|
self.wait()
|
|
|
|
self.change_frequency(7.5, run_time = 10)
|
|
|
|
self.fourier_graph_drawing_update_anim = Animation(Mobject())
|
|
|
|
self.fourier_graph = fourier_graph
|
|
|
|
|
|
|
|
def highlight_spike(self):
|
|
|
|
spike_point = self.frequency_axes.input_to_graph_point(
|
|
|
|
5, self.fourier_graph
|
|
|
|
)
|
|
|
|
circle = Circle(color = YELLOW, radius = 0.25)
|
|
|
|
circle.move_to(spike_point)
|
|
|
|
circle.save_state()
|
|
|
|
circle.scale(5)
|
|
|
|
circle.fade(1)
|
|
|
|
|
|
|
|
self.change_frequency(5)
|
|
|
|
self.play(circle.restore)
|
|
|
|
self.play(FadeOut(circle))
|
|
|
|
self.wait()
|
|
|
|
for x in range(2):
|
|
|
|
self.change_frequency(5.2, run_time = 3)
|
|
|
|
self.change_frequency(4.8, run_time = 3)
|
|
|
|
self.change_frequency(5, run_time = 1.5)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
#########
|
|
|
|
|
|
|
|
def get_time_graph(self, frequency = 5, width = 2, **kwargs):
|
|
|
|
# low_x = center-width/2
|
|
|
|
# high_x = center+width/2
|
|
|
|
# new_smooth = lambda x : np.clip(smooth((x+0.5)), 0, 1)
|
|
|
|
# def func(x):
|
|
|
|
# pure_signal = 0.9*np.cos(TAU*frequency*x)
|
|
|
|
# factor = new_smooth(x - low_x) - new_smooth(x-high_x)
|
|
|
|
# return 1 + factor*pure_signal
|
|
|
|
graph = self.time_axes.get_graph(
|
|
|
|
lambda x : 1+0.9*np.cos(TAU*frequency*x),
|
|
|
|
x_min = 0, x_max = width,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
graph.highlight(YELLOW)
|
|
|
|
return graph
|
|
|
|
|
|
|
|
class CenterOfMassDescription(FourierRecapScene):
|
|
|
|
def construct(self):
|
|
|
|
self.remove(self.pi_creature)
|
|
|
|
circle_plane = self.get_circle_plane()
|
|
|
|
circle_plane.save_state()
|
|
|
|
circle_plane.generate_target()
|
|
|
|
circle_plane.target.scale_to_fit_height(2*SPACE_HEIGHT)
|
|
|
|
circle_plane.target.center()
|
|
|
|
circle_plane.target.axes.set_stroke(width = 2)
|
|
|
|
circle_plane.target.main_lines.set_stroke(width = 2)
|
|
|
|
circle_plane.target.secondary_lines.set_stroke(width = 1)
|
|
|
|
|
|
|
|
start_coords = (0.5, 0.5)
|
|
|
|
alt_coords = (0.8, 0.8)
|
|
|
|
|
|
|
|
com_dot = Dot(color = self.center_of_mass_color)
|
|
|
|
com_dot.move_to(circle_plane.coords_to_point(*start_coords))
|
|
|
|
|
|
|
|
self.add(circle_plane, com_dot)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
MoveToTarget(circle_plane),
|
|
|
|
com_dot.move_to,
|
|
|
|
circle_plane.target.coords_to_point(*start_coords)
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
alt_com_dot = com_dot.copy().move_to(
|
|
|
|
circle_plane.coords_to_point(*alt_coords)
|
|
|
|
)
|
|
|
|
|
|
|
|
for dot in com_dot, alt_com_dot:
|
|
|
|
line = Line(ORIGIN, dot.get_center())
|
|
|
|
line.match_color(com_dot)
|
|
|
|
angle = line.get_angle()
|
|
|
|
line.rotate(-angle, about_point = ORIGIN)
|
|
|
|
brace = Brace(line, UP)
|
|
|
|
words = brace.get_text("Strength of frequency")
|
|
|
|
words.add_background_rectangle()
|
|
|
|
dot.length_label_group = VGroup(line, brace, words)
|
|
|
|
dot.length_label_group.rotate(angle, about_point = ORIGIN)
|
|
|
|
|
|
|
|
line, brace, words = com_dot.length_label_group
|
|
|
|
self.play(
|
|
|
|
GrowFromCenter(line),
|
|
|
|
GrowFromCenter(brace),
|
|
|
|
FadeIn(words),
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
|
|
Transform(
|
|
|
|
com_dot.length_label_group,
|
|
|
|
alt_com_dot.length_label_group,
|
|
|
|
),
|
|
|
|
Transform(com_dot, alt_com_dot),
|
|
|
|
rate_func = there_and_back,
|
|
|
|
run_time = 4,
|
|
|
|
)
|
|
|
|
|
|
|
|
#Do rotation
|
|
|
|
line = com_dot.length_label_group[0]
|
|
|
|
com_dot.length_label_group.remove(line)
|
|
|
|
angle = line.get_angle()
|
|
|
|
arc, alt_arc = [
|
|
|
|
Arc(
|
|
|
|
start_angle = 0,
|
|
|
|
angle = factor*angle,
|
|
|
|
radius = 0.5,
|
|
|
|
)
|
|
|
|
for factor in 1, 2
|
|
|
|
]
|
|
|
|
theta = TexMobject("\\theta")
|
|
|
|
theta.shift(1.5*arc.point_from_proportion(0.5))
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
FadeOut(com_dot.length_label_group),
|
|
|
|
Animation(line),
|
|
|
|
ShowCreation(arc),
|
|
|
|
Write(theta)
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
Rotate(
|
|
|
|
VGroup(line, com_dot),
|
|
|
|
angle, about_point = ORIGIN
|
|
|
|
),
|
|
|
|
Transform(arc, alt_arc),
|
|
|
|
theta.move_to, 1.5*alt_arc.point_from_proportion(0.5),
|
|
|
|
rate_func = there_and_back,
|
|
|
|
run_time = 4
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
class AskAboutLongVsShort(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
self.student_says(
|
|
|
|
"What happens if we \\\\ change the length of \\\\ the signal?",
|
|
|
|
student_index = 2,
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
self.teacher.change, "happy",
|
|
|
|
self.get_student_changes("pondering", "confused", "raise_right_hand")
|
|
|
|
)
|
|
|
|
self.wait(5)
|
|
|
|
|
|
|
|
class LongAndShortSignalsInWindingMachine(FourierRecapScene):
|
2018-02-15 18:36:29 -08:00
|
|
|
CONFIG = {
|
|
|
|
"num_fourier_graph_points" : 1000,
|
|
|
|
}
|
2018-02-15 16:13:47 -08:00
|
|
|
def construct(self):
|
|
|
|
self.setup_axes()
|
|
|
|
self.extend_for_long_time()
|
|
|
|
self.note_sharp_fourier_peak()
|
|
|
|
self.very_short_signal()
|
|
|
|
self.note_wide_fourier_peak()
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
def setup_axes(self):
|
|
|
|
FourierRecapScene.setup_axes(self)
|
|
|
|
self.add(self.circle_plane)
|
2018-02-15 18:36:29 -08:00
|
|
|
self.add(self.frequency_axes)
|
|
|
|
self.time_graph = self.graph = self.get_time_graph(width = 2)
|
|
|
|
self.add(self.time_graph)
|
|
|
|
|
|
|
|
self.force_skipping()
|
|
|
|
self.wrap_signal_around_circle()
|
|
|
|
|
|
|
|
fourier_graph = self.get_fourier_transform_graph(self.time_graph)
|
|
|
|
self.fourier_graph = fourier_graph
|
|
|
|
self.add(fourier_graph)
|
|
|
|
self.change_frequency(5)
|
|
|
|
|
|
|
|
self.revert_to_original_skipping_status()
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
def extend_for_long_time(self):
|
2018-02-15 18:36:29 -08:00
|
|
|
short_time_graph = self.time_graph
|
|
|
|
long_time_graph = self.get_time_graph(
|
|
|
|
width = 10,
|
|
|
|
num_graph_points = 500,
|
|
|
|
)
|
|
|
|
long_time_graph.set_stroke(width = 2)
|
|
|
|
new_freq = 5.1
|
|
|
|
long_pol_graph = self.get_polarized_mobject(
|
|
|
|
long_time_graph,
|
|
|
|
freq = new_freq
|
|
|
|
)
|
|
|
|
fourier_graph = self.fourier_graph
|
|
|
|
|
|
|
|
self.change_frequency(new_freq)
|
|
|
|
self.play(
|
|
|
|
FadeOut(self.graph),
|
|
|
|
FadeOut(self.graph.polarized_mobject),
|
|
|
|
FadeOut(fourier_graph)
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
ShowCreation(long_time_graph, rate_func = None),
|
|
|
|
ShowCreation(long_pol_graph, rate_func = None),
|
|
|
|
run_time = 5
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
self.time_graph = self.graph = long_time_graph
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
def note_sharp_fourier_peak(self):
|
2018-02-15 18:36:29 -08:00
|
|
|
fourier_graph = self.get_fourier_transform_graph(
|
|
|
|
self.time_graph,
|
|
|
|
num_graph_points = self.num_fourier_graph_points
|
|
|
|
)
|
|
|
|
self.fourier_graph = fourier_graph
|
|
|
|
self.note_fourier_peak(fourier_graph, 5, 5.1)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
def very_short_signal(self):
|
2018-02-15 18:36:29 -08:00
|
|
|
time_graph = self.time_graph
|
|
|
|
fourier_graph = self.fourier_graph
|
|
|
|
short_time_graph = self.get_time_graph(width = 0.6)
|
|
|
|
new_freq = 5.1
|
|
|
|
short_pol_graph = self.get_polarized_mobject(
|
|
|
|
short_time_graph,
|
|
|
|
freq = new_freq
|
|
|
|
)
|
|
|
|
|
|
|
|
self.play(
|
|
|
|
FadeOut(fourier_graph),
|
|
|
|
FadeOut(time_graph),
|
|
|
|
FadeOut(time_graph.polarized_mobject),
|
|
|
|
)
|
|
|
|
self.play(
|
|
|
|
ShowCreation(short_time_graph),
|
|
|
|
ShowCreation(short_time_graph.polarized_mobject),
|
|
|
|
)
|
|
|
|
self.graph = self.time_graph = short_time_graph
|
|
|
|
self.change_frequency(6.66, run_time = 5)
|
2018-02-10 18:32:17 -08:00
|
|
|
|
2018-02-15 16:13:47 -08:00
|
|
|
def note_wide_fourier_peak(self):
|
2018-02-15 18:36:29 -08:00
|
|
|
fourier_graph = self.get_fourier_transform_graph(
|
|
|
|
self.graph,
|
|
|
|
num_graph_points = self.num_fourier_graph_points
|
|
|
|
)
|
|
|
|
self.fourier_graph = fourier_graph
|
|
|
|
self.note_fourier_peak(fourier_graph, 5, 6.66)
|
|
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
|
|
|
def note_fourier_peak(self, fourier_graph, freq1, freq2):
|
|
|
|
fourier_graph = self.fourier_graph
|
|
|
|
dots = self.get_fourier_graph_dots(fourier_graph, freq1, freq2)
|
|
|
|
self.get_center_of_mass_dot()
|
|
|
|
self.generate_center_of_mass_dot_update_anim()
|
|
|
|
self.generate_fourier_dot_transform(fourier_graph)
|
|
|
|
dot = self.fourier_graph_dot
|
|
|
|
arrow = Arrow(UP, ORIGIN, buff = SMALL_BUFF)
|
|
|
|
arrow.next_to(dot, UP, buff = SMALL_BUFF)
|
|
|
|
|
|
|
|
self.play(ShowCreation(fourier_graph))
|
|
|
|
self.change_frequency(freq1,
|
|
|
|
added_anims = [
|
|
|
|
MaintainPositionRelativeTo(arrow, dot),
|
|
|
|
UpdateFromAlphaFunc(
|
|
|
|
arrow,
|
|
|
|
lambda m, a : m.set_fill(opacity = a)
|
|
|
|
),
|
|
|
|
],
|
|
|
|
run_time = 3,
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.change_frequency(freq2,
|
|
|
|
added_anims = [
|
|
|
|
MaintainPositionRelativeTo(arrow, dot)
|
|
|
|
],
|
|
|
|
run_time = 3
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(*map(FadeOut, [
|
|
|
|
dot, arrow, self.center_of_mass_dot
|
|
|
|
]))
|
|
|
|
#This is not great...
|
|
|
|
for attr in "center_of_mass_dot", "fourier_graph_dot":
|
|
|
|
self.__dict__.pop(attr)
|
|
|
|
|
|
|
|
|
|
|
|
def get_fourier_graph_dots(self, fourier_graph, *freqs):
|
|
|
|
axis_point = self.frequency_axes.coords_to_point(4.5, -0.25)
|
|
|
|
dots = VGroup()
|
|
|
|
for freq in freqs:
|
|
|
|
point = self.frequency_axes.input_to_graph_point(freq, fourier_graph)
|
|
|
|
dot = Dot(point)
|
|
|
|
dot.scale(0.5)
|
|
|
|
dots.add(dot)
|
|
|
|
vect = point - axis_point
|
|
|
|
vect *= 1.3/np.linalg.norm(vect)
|
|
|
|
arrow = Arrow(vect, ORIGIN, buff = SMALL_BUFF)
|
|
|
|
arrow.highlight(YELLOW)
|
|
|
|
arrow.shift(point)
|
|
|
|
dot.arrow = arrow
|
|
|
|
return dots
|
|
|
|
|
|
|
|
class CleanerFourierTradeoff(FourierTradeoff):
|
|
|
|
CONFIG = {
|
|
|
|
"show_text" : False,
|
|
|
|
"complex_to_real_func" : lambda z : z.real,
|
|
|
|
}
|
|
|
|
|
2018-02-15 23:00:18 -08:00
|
|
|
class MentionDopplerRadar(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
words = TextMobject("Doppler Radar")
|
|
|
|
words.next_to(self.teacher, UP)
|
|
|
|
words.save_state()
|
|
|
|
words.shift(DOWN).fade(1)
|
|
|
|
dish = RadarDish()
|
|
|
|
dish.next_to(self.students, UP, buff = 2, aligned_edge = LEFT)
|
|
|
|
plane = Plane()
|
|
|
|
plane.to_edge(RIGHT)
|
|
|
|
plane.align_to(dish)
|
|
|
|
plane_flight = AmbientMovement(
|
|
|
|
plane,
|
|
|
|
direction = LEFT,
|
|
|
|
rate = 1,
|
|
|
|
)
|
|
|
|
plane.flip()
|
|
|
|
pulse = RadarPulse(dish, plane)
|
|
|
|
look_at_anims = [
|
|
|
|
ContinualUpdateFromFunc(
|
|
|
|
pi, lambda pi : pi.look_at(pulse.mobject)
|
|
|
|
)
|
|
|
|
for pi in self.get_pi_creatures()
|
|
|
|
]
|
2018-02-15 18:36:29 -08:00
|
|
|
|
2018-02-15 23:00:18 -08:00
|
|
|
self.add(dish, plane_flight, pulse, *look_at_anims)
|
|
|
|
self.play(
|
|
|
|
self.teacher.change, "hooray",
|
|
|
|
words.restore
|
|
|
|
)
|
|
|
|
self.change_student_modes("pondering", "erm", "sassy")
|
2018-02-16 12:15:27 -08:00
|
|
|
self.wait(2)
|
2018-02-15 23:00:18 -08:00
|
|
|
self.play(
|
|
|
|
self.teacher.change, "happy",
|
|
|
|
self.get_student_changes(*["thinking"]*3)
|
|
|
|
)
|
2018-02-16 12:15:27 -08:00
|
|
|
self.wait()
|
2018-02-15 23:00:18 -08:00
|
|
|
dish.set_stroke(width = 0)
|
|
|
|
self.play(UpdateFromAlphaFunc(
|
|
|
|
VGroup(plane, dish),
|
|
|
|
lambda m, a : m.set_fill(opacity = 1 - a)
|
|
|
|
))
|
2018-02-15 18:36:29 -08:00
|
|
|
|
2018-02-16 12:15:27 -08:00
|
|
|
class IntroduceDopplerRadar(Scene):
|
|
|
|
def construct(self):
|
|
|
|
self.setup_axes()
|
|
|
|
self.measure_distance_with_time()
|
|
|
|
self.measure_velocity_with_frequency()
|
|
|
|
|
|
|
|
def setup_axes(self):
|
|
|
|
self.dish = RadarDish()
|
|
|
|
self.dish.to_corner(UP+LEFT)
|
|
|
|
axes = Axes(
|
|
|
|
x_min = 0,
|
|
|
|
x_max = 10,
|
|
|
|
y_min = -2,
|
|
|
|
y_max = 2
|
|
|
|
)
|
|
|
|
axes.move_to(DOWN)
|
|
|
|
time_label = TextMobject("Time")
|
|
|
|
time_label.next_to(axes.x_axis.get_right(), UP)
|
|
|
|
axes.time_label = time_label
|
|
|
|
axes.add(time_label)
|
|
|
|
self.axes = axes
|
|
|
|
|
|
|
|
self.add(self.dish)
|
|
|
|
self.add(axes)
|
|
|
|
|
|
|
|
def measure_distance_with_time(self):
|
|
|
|
dish = self.dish
|
|
|
|
axes = self.axes
|
|
|
|
distance = 5
|
|
|
|
time_diff = 5
|
|
|
|
speed = (2*distance)/time_diff
|
|
|
|
randy = Randolph().flip()
|
|
|
|
randy.match_height(dish)
|
|
|
|
randy.move_to(dish.get_right(), LEFT)
|
|
|
|
randy.shift(distance*RIGHT)
|
|
|
|
|
|
|
|
pulse_graph = self.get_single_pulse_graph(1, color = BLUE)
|
|
|
|
echo_graph = self.get_single_pulse_graph(1+time_diff, color = YELLOW)
|
|
|
|
sum_graph = axes.get_graph(
|
|
|
|
lambda x : sum([
|
|
|
|
pulse_graph.underlying_function(x),
|
|
|
|
echo_graph.underlying_function(x),
|
|
|
|
]),
|
|
|
|
color = WHITE
|
|
|
|
)
|
|
|
|
sum_graph.background_image_file = "blue_yellow_gradient"
|
|
|
|
words = ["Original signal", "Echo"]
|
|
|
|
for graph, word in zip([pulse_graph, echo_graph], words):
|
|
|
|
arrow = Vector(DOWN+LEFT)
|
|
|
|
arrow.next_to(graph.peak_point, UP+RIGHT, SMALL_BUFF)
|
|
|
|
arrow.match_color(graph)
|
|
|
|
graph.arrow = arrow
|
|
|
|
label = TextMobject(word)
|
|
|
|
label.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
|
|
label.match_color(graph)
|
|
|
|
graph.label = label
|
|
|
|
|
|
|
|
#v_line anim?
|
|
|
|
|
|
|
|
pulse = RadarPulseSingleton(dish, randy, speed = speed)
|
|
|
|
graph_draw = NormalAnimationAsContinualAnimation(
|
|
|
|
ShowCreation(
|
|
|
|
sum_graph,
|
|
|
|
rate_func = None,
|
|
|
|
run_time = axes.x_max
|
|
|
|
)
|
|
|
|
)
|
|
|
|
randy_look_at = ContinualUpdateFromFunc(
|
|
|
|
randy, lambda pi : pi.look_at(pulse.mobject)
|
|
|
|
)
|
|
|
|
|
|
|
|
self.add(randy_look_at, ContinualAnimation(axes), graph_draw)
|
|
|
|
self.wait()
|
|
|
|
self.add(pulse)
|
|
|
|
self.play(
|
|
|
|
Write(pulse_graph.label),
|
|
|
|
GrowArrow(pulse_graph.arrow),
|
|
|
|
run_time = 1,
|
|
|
|
)
|
|
|
|
self.play(randy.change, "pondering")
|
|
|
|
self.wait(time_diff - 2)
|
|
|
|
self.play(
|
|
|
|
Write(echo_graph.label),
|
|
|
|
GrowArrow(echo_graph.arrow),
|
|
|
|
run_time = 1
|
|
|
|
)
|
|
|
|
self.wait(3)
|
|
|
|
graph_draw.update(10)
|
|
|
|
self.add(sum_graph)
|
|
|
|
self.wait()
|
2018-02-15 18:36:29 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-16 12:15:27 -08:00
|
|
|
def measure_velocity_with_frequency(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
|
|
|
def get_single_pulse_graph(self, x, **kwargs):
|
|
|
|
graph = self.axes.get_graph(
|
|
|
|
self.get_single_pulse_function(x),
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
graph.peak_point = self.get_peak_point(graph)
|
|
|
|
return graph
|
2018-02-15 18:36:29 -08:00
|
|
|
|
2018-02-16 12:15:27 -08:00
|
|
|
def get_single_pulse_function(self, x):
|
|
|
|
return lambda t : -2*np.sin(10*(t-x))*np.exp(-100*(t-x)**2)
|
2018-02-15 18:36:29 -08:00
|
|
|
|
2018-02-16 12:15:27 -08:00
|
|
|
def get_peak_point(self, graph):
|
|
|
|
anchors = graph.get_anchors()
|
|
|
|
return anchors[np.argmax([p[1] for p in anchors])]
|
2018-02-15 18:36:29 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-10 18:32:17 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-09 15:26:12 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|