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 *
|
|
|
|
|
|
|
|
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-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):
|
|
|
|
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")
|
|
|
|
time_label.next_to(
|
|
|
|
time_axes.x_axis.get_right(), UP,
|
|
|
|
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,
|
|
|
|
y_max = 15,
|
|
|
|
y_axis_config = {
|
|
|
|
"unit_size" : 0.15,
|
|
|
|
"tick_frequency" : 5,
|
|
|
|
},
|
|
|
|
color = TEAL,
|
|
|
|
)
|
|
|
|
frequency_label = TextMobject("Frequency")
|
|
|
|
frequency_label.next_to(
|
|
|
|
frequency_axes.x_axis.get_right(), UP,
|
|
|
|
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(
|
|
|
|
frequency_axes, get_wave_packet_function(),
|
|
|
|
t_min = time_mean - time_radius,
|
|
|
|
t_max = time_mean + time_radius,
|
|
|
|
n_samples = 2*time_radius*17,
|
2018-02-12 22:49:15 -08:00
|
|
|
# complex_to_real_func = abs,
|
|
|
|
complex_to_real_func = lambda z : z.real,
|
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(
|
|
|
|
wave_packet, frequency_axes.coords_to_point(4, 10),
|
|
|
|
color = FREQUENCY_COLOR,
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
2018-02-10 18:32:17 -08:00
|
|
|
fourier_words = TextMobject("Fourier Transform")
|
|
|
|
fourier_words.next_to(arrow, RIGHT, buff = MED_LARGE_BUFF)
|
|
|
|
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-09 15:26:12 -08:00
|
|
|
self.play(
|
2018-02-10 18:32:17 -08:00
|
|
|
ReplacementTransform(
|
|
|
|
wave_packet.copy(),
|
|
|
|
fourier_graph,
|
|
|
|
),
|
|
|
|
GrowArrow(arrow),
|
|
|
|
Write(fourier_words, run_time = 1)
|
2018-02-09 15:26:12 -08:00
|
|
|
)
|
2018-02-10 18:32:17 -08:00
|
|
|
# self.play(FadeOut(arrow))
|
|
|
|
self.wait()
|
|
|
|
for width in 6, 0.1, 1:
|
|
|
|
self.play(
|
|
|
|
width_tracker.move_to, width*RIGHT,
|
|
|
|
wave_packet_update,
|
|
|
|
fourier_graph_update,
|
|
|
|
run_time = 3
|
|
|
|
)
|
|
|
|
if sub_words not in self.mobjects:
|
|
|
|
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])
|
|
|
|
self.play_doppler_anims(words[1], words[0])
|
|
|
|
self.play_quantum_anims(words[2], words[1])
|
|
|
|
|
|
|
|
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):
|
|
|
|
colors = [YELLOW, GREEN, BLUE]
|
|
|
|
topics = ["sound waves", "Doppler radar", "quantum particles"]
|
|
|
|
words = VGroup()
|
|
|
|
for topic, color in zip(topics, colors):
|
|
|
|
word = TextMobject("Uncertainty for", topic)
|
|
|
|
word[1].highlight(color)
|
|
|
|
words.add(word)
|
|
|
|
words.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = LARGE_BUFF)
|
|
|
|
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()
|
|
|
|
|
|
|
|
def play_doppler_anims(self, word, to_fade):
|
|
|
|
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(
|
|
|
|
to_fade.fade, 0.5,
|
|
|
|
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 15:39:57 -08:00
|
|
|
self.wait()
|
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-12 22:49:15 -08:00
|
|
|
|
|
|
|
def play_quantum_anims(self, word, to_fade):
|
|
|
|
pass
|
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)
|
|
|
|
checkmark.scale(1.5)
|
|
|
|
checkmark.next_to(word, UP+RIGHT, buff = 0)
|
|
|
|
return checkmark
|
2018-02-10 18:32:17 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-09 15:26:12 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|