mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
4739 lines
150 KiB
Python
4739 lines
150 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import scipy
|
|
from manimlib.imports import *
|
|
from old_projects.fourier import *
|
|
|
|
import warnings
|
|
warnings.warn("""
|
|
Warning: This file makes use of
|
|
ContinualAnimation, which has since
|
|
been deprecated
|
|
""")
|
|
|
|
FREQUENCY_COLOR = RED
|
|
USE_ALMOST_FOURIER_BY_DEFAULT = False
|
|
|
|
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
|
|
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
|
|
"""
|
|
CONFIG = {
|
|
"stroke_width" : 0,
|
|
"mu" : ORIGIN,
|
|
"sigma" : RIGHT,
|
|
}
|
|
def __init__(self, **kwargs):
|
|
Line.__init__(self, ORIGIN, RIGHT, **kwargs)
|
|
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)
|
|
return self
|
|
|
|
def get_parameters(self):
|
|
""" Return mu_x, mu_y, sigma_x, sigma_y"""
|
|
center, end = self.get_center(), self.get_end()
|
|
return center, end-center
|
|
|
|
def get_random_points(self, size = 1):
|
|
mu, sigma = self.get_parameters()
|
|
return np.array([
|
|
np.array([
|
|
np.random.normal(mu_coord, sigma_coord)
|
|
for mu_coord, sigma_coord in zip(mu, sigma)
|
|
])
|
|
for x in range(size)
|
|
])
|
|
|
|
class ProbabalisticMobjectCloud(ContinualAnimation):
|
|
CONFIG = {
|
|
"fill_opacity" : 0.25,
|
|
"n_copies" : 100,
|
|
"gaussian_distribution_wrapper_config" : {},
|
|
"time_per_change" : 1./60,
|
|
"start_up_time" : 0,
|
|
}
|
|
def __init__(self, prototype, **kwargs):
|
|
digest_config(self, kwargs)
|
|
fill_opacity = self.fill_opacity or prototype.get_fill_opacity()
|
|
if "mu" not in self.gaussian_distribution_wrapper_config:
|
|
self.gaussian_distribution_wrapper_config["mu"] = prototype.get_center()
|
|
self.gaussian_distribution_wrapper = GaussianDistributionWrapper(
|
|
**self.gaussian_distribution_wrapper_config
|
|
)
|
|
self.time_since_last_change = np.inf
|
|
group = VGroup(*[
|
|
prototype.copy().set_fill(opacity = fill_opacity)
|
|
for x in range(self.n_copies)
|
|
])
|
|
ContinualAnimation.__init__(self, group, **kwargs)
|
|
self.update_mobject(0)
|
|
|
|
def update_mobject(self, dt):
|
|
self.time_since_last_change += dt
|
|
if self.time_since_last_change < self.time_per_change:
|
|
return
|
|
self.time_since_last_change = 0
|
|
|
|
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
|
|
)
|
|
|
|
class RadarDish(SVGMobject):
|
|
CONFIG = {
|
|
"file_name" : "radar_dish",
|
|
"fill_color" : LIGHT_GREY,
|
|
"stroke_color" : WHITE,
|
|
"stroke_width" : 1,
|
|
"height" : 1,
|
|
}
|
|
|
|
class Plane(SVGMobject):
|
|
CONFIG = {
|
|
"file_name" : "plane",
|
|
"color" : LIGHT_GREY,
|
|
"height" : 1,
|
|
}
|
|
def __init__(self, **kwargs):
|
|
SVGMobject.__init__(self, **kwargs)
|
|
self.rotate(-TAU/4)
|
|
|
|
class FalconHeavy(SVGMobject):
|
|
CONFIG = {
|
|
"file_name" : "falcon_heavy",
|
|
"color" : WHITE,
|
|
"logo_color" : BLUE_E,
|
|
"height" : 1.5,
|
|
}
|
|
def __init__(self, **kwargs):
|
|
SVGMobject.__init__(self, **kwargs)
|
|
self.logo = self[-9:]
|
|
self.logo.set_color(self.logo_color)
|
|
|
|
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/get_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.set_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)
|
|
|
|
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)
|
|
|
|
def is_finished(self):
|
|
return all([ps.is_finished() for ps in self.pulse_singletons])
|
|
|
|
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)
|
|
|
|
###################
|
|
|
|
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},
|
|
center_func = lambda : dot_cloud.gaussian_distribution_wrapper.get_parameters()[0],
|
|
)
|
|
for cloud in dot_cloud, vector_cloud:
|
|
cloud.gaussian_distribution_wrapper.next_to(
|
|
title, DOWN, 2*LARGE_BUFF
|
|
)
|
|
vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT)
|
|
|
|
def get_brace_text_group_update(gdw, vect, text, color):
|
|
brace = Brace(gdw, vect)
|
|
text = brace.get_tex("2\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF)
|
|
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)
|
|
group.set_color(color)
|
|
return Mobject.add_updater(group, update_group)
|
|
|
|
dot_brace_anim = get_brace_text_group_update(
|
|
dot_cloud.gaussian_distribution_wrapper,
|
|
DOWN, "position", dot_cloud.color
|
|
)
|
|
vector_brace_anim = get_brace_text_group_update(
|
|
vector_cloud.gaussian_distribution_wrapper,
|
|
UP, "momentum", vector_cloud.color
|
|
)
|
|
|
|
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,
|
|
{"sigma" : 0.1*RIGHT},
|
|
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,
|
|
{"sigma" : RIGHT},
|
|
self.get_student_changes(*3*["confused"]),
|
|
run_time = 3,
|
|
)
|
|
#Back and forth
|
|
for x in range(2):
|
|
self.play(
|
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
|
{"sigma" : 2*RIGHT},
|
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
|
{"sigma" : 0.1*RIGHT},
|
|
run_time = 3,
|
|
)
|
|
self.change_student_modes("thinking", "erm", "sassy")
|
|
self.play(
|
|
dot_cloud.gaussian_distribution_wrapper.change_parameters,
|
|
{"sigma" : 0.1*RIGHT},
|
|
vector_cloud.gaussian_distribution_wrapper.change_parameters,
|
|
{"sigma" : 1*RIGHT},
|
|
run_time = 3,
|
|
)
|
|
self.wait()
|
|
|
|
class FourierTradeoff(Scene):
|
|
CONFIG = {
|
|
"show_text" : True,
|
|
"complex_to_real_func" : lambda z : z.real,
|
|
"widths" : [6, 0.02, 1],
|
|
}
|
|
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.scale(1.5)
|
|
time_label.next_to(
|
|
time_axes.x_axis.get_right(), UP+LEFT,
|
|
buff = MED_SMALL_BUFF,
|
|
)
|
|
time_axes.add(time_label)
|
|
time_axes.center().to_edge(UP)
|
|
time_axes.x_axis.add_numbers(*list(range(1, 2*time_mean)))
|
|
|
|
frequency_axes = Axes(
|
|
x_min = 0,
|
|
x_max = 8,
|
|
x_axis_config = {"unit_size" : 1.5},
|
|
y_min = -0.025,
|
|
y_max = 0.075,
|
|
y_axis_config = {
|
|
"unit_size" : 30,
|
|
"tick_frequency" : 0.025,
|
|
},
|
|
color = TEAL,
|
|
)
|
|
frequency_label = TextMobject("Frequency")
|
|
frequency_label.scale(1.5)
|
|
frequency_label.next_to(
|
|
frequency_axes.x_axis.get_right(), UP+LEFT,
|
|
buff = MED_SMALL_BUFF,
|
|
)
|
|
frequency_label.set_color(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 = ExponentialValueTracker(0.5)
|
|
get_width = width_tracker.get_value
|
|
|
|
def get_wave_packet_function():
|
|
factor = 1./get_width()
|
|
return lambda t : (factor**0.25)*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.set_color(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,
|
|
complex_to_real_func = self.complex_to_real_func,
|
|
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, frequency_axes.y_max/2,
|
|
),
|
|
color = FREQUENCY_COLOR,
|
|
)
|
|
fourier_words = TextMobject("Fourier Transform")
|
|
fourier_words.next_to(arrow, LEFT, buff = MED_LARGE_BUFF)
|
|
sub_words = TextMobject("(To be explained shortly)")
|
|
sub_words.set_color(BLUE)
|
|
sub_words.scale(0.75)
|
|
sub_words.next_to(fourier_words, DOWN)
|
|
|
|
#Draw items
|
|
self.add(time_axes, frequency_axes)
|
|
self.play(ShowCreation(wave_packet, rate_func = double_smooth))
|
|
anims = [ReplacementTransform(
|
|
wave_packet.copy(), fourier_graph
|
|
)]
|
|
if self.show_text:
|
|
anims += [
|
|
GrowArrow(arrow),
|
|
Write(fourier_words, run_time = 1)
|
|
]
|
|
self.play(*anims)
|
|
# self.play(FadeOut(arrow))
|
|
self.wait()
|
|
for width in self.widths:
|
|
self.play(
|
|
width_tracker.set_value, width,
|
|
wave_packet_update,
|
|
fourier_graph_update,
|
|
run_time = 3
|
|
)
|
|
if sub_words not in self.mobjects and self.show_text:
|
|
self.play(FadeIn(sub_words))
|
|
else:
|
|
self.wait()
|
|
self.wait()
|
|
|
|
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])
|
|
self.play_quantum_anims(words[2])
|
|
|
|
def add_title(self):
|
|
title = TextMobject("The plan")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
|
|
h_line.next_to(title, DOWN)
|
|
self.add(title, h_line)
|
|
|
|
def get_words(self):
|
|
trips = [
|
|
("sound waves", "(time vs. frequency)", YELLOW),
|
|
("Doppler radar", "(distance vs. velocity)", GREEN),
|
|
("quantum particles", "(position vs. momentum)", BLUE),
|
|
]
|
|
words = VGroup()
|
|
for topic, tradeoff, color in trips:
|
|
word = TextMobject("Uncertainty for", topic, tradeoff)
|
|
word[1:].set_color(color)
|
|
word[2].scale(0.75)
|
|
word[2].next_to(word[1], DOWN, buff = 1.5*SMALL_BUFF)
|
|
words.add(word)
|
|
words.arrange(DOWN, aligned_edge = LEFT, buff = MED_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,
|
|
step_size = 0.001,
|
|
)
|
|
wave.next_to(word, RIGHT)
|
|
rect = BackgroundRectangle(wave, fill_opacity = 1)
|
|
rect.stretch(2, 1)
|
|
rect.next_to(wave, LEFT, buff = 0)
|
|
always_shift(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)
|
|
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):
|
|
morty = self.pi_creature
|
|
|
|
radar_dish = RadarDish()
|
|
radar_dish.next_to(word, DOWN, aligned_edge = LEFT)
|
|
target = Plane()
|
|
# target.match_height(radar_dish)
|
|
target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF)
|
|
always_shift(target, direction = RIGHT, rate = 1.25)
|
|
|
|
pulse = RadarPulse(radar_dish, target)
|
|
|
|
checkmark = self.get_checkmark(word)
|
|
|
|
self.add(target)
|
|
self.play(
|
|
Write(word),
|
|
DrawBorderThenFill(radar_dish),
|
|
UpdateFromAlphaFunc(
|
|
target, lambda m, a : m.set_fill(opacity = a)
|
|
),
|
|
morty.change, "pondering",
|
|
run_time = 1
|
|
)
|
|
self.add(pulse)
|
|
count = it.count() #TODO, this is not a great hack...
|
|
while not pulse.is_finished() and next(count) < 15:
|
|
self.play(
|
|
morty.look_at, pulse.mobject,
|
|
run_time = 0.5
|
|
)
|
|
self.play(
|
|
Write(checkmark),
|
|
UpdateFromAlphaFunc(
|
|
target, lambda m, a : m.set_fill(opacity = 1-a)
|
|
),
|
|
FadeOut(radar_dish),
|
|
morty.change, "happy"
|
|
)
|
|
self.wait()
|
|
|
|
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)
|
|
|
|
|
|
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)
|
|
|
|
|
|
##
|
|
|
|
def get_checkmark(self, word):
|
|
checkmark = TexMobject("\\checkmark")
|
|
checkmark.set_color(GREEN)
|
|
checkmark.scale(1.25)
|
|
checkmark.next_to(word[1], UP+RIGHT, buff = 0)
|
|
return checkmark
|
|
|
|
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)
|
|
|
|
class TwoCarsAtRedLight(Scene):
|
|
CONFIG = {
|
|
"text_scale_val" : 0.75,
|
|
}
|
|
def construct(self):
|
|
self.pull_up_behind()
|
|
self.flash_in_sync_short_time()
|
|
self.show_low_confidence()
|
|
self.flash_in_sync_long_time()
|
|
self.show_high_confidence()
|
|
|
|
def pull_up_behind(self):
|
|
#Setup Traffic light
|
|
traffic_light = TrafficLight()
|
|
traffic_light.move_to(6*RIGHT + 2.5*DOWN, DOWN)
|
|
source_point = VectorizedPoint(
|
|
traffic_light[2].get_right()
|
|
)
|
|
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)
|
|
|
|
#Setup cars
|
|
car1, car2 = cars = self.cars = VGroup(*[
|
|
Car() for x in range(2)
|
|
])
|
|
cars.arrange(RIGHT, buff = LARGE_BUFF)
|
|
cars.next_to(
|
|
traffic_light, LEFT,
|
|
buff = LARGE_BUFF, aligned_edge = DOWN
|
|
)
|
|
car2.pi_creature.set_color(GREY_BROWN)
|
|
car1.start_point = car1.get_corner(DOWN+RIGHT)
|
|
car1.shift(FRAME_X_RADIUS*LEFT)
|
|
|
|
#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,
|
|
},
|
|
)
|
|
axes.x_axis.add_numbers(1, 2, 3)
|
|
time_label = TextMobject("Time")
|
|
time_label.scale(self.text_scale_val)
|
|
time_label.next_to(axes.x_axis.get_right(), DOWN)
|
|
y_title = TextMobject("Signal")
|
|
y_title.scale(self.text_scale_val)
|
|
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(
|
|
self.get_multispike_function(list(range(1, 4))),
|
|
x_min = 0.8,
|
|
x_max = 3.8,
|
|
)
|
|
graph.set_color(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")
|
|
text.scale(self.text_scale_val)
|
|
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),
|
|
LaggedStartMap(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=linear, 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),
|
|
)
|
|
|
|
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
|
|
|
|
#Setup axes
|
|
frequency_axes = Axes(
|
|
x_min = 0,
|
|
x_max = 3,
|
|
y_min = 0,
|
|
y_max = 1.5,
|
|
y_axis_config = {
|
|
"tick_frequency" : 0.5,
|
|
}
|
|
)
|
|
frequency_axes.next_to(time_axes, DOWN, LARGE_BUFF)
|
|
frequency_axes.set_color(LIGHT_GREY)
|
|
frequency_label = TextMobject("Frequency")
|
|
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())
|
|
)
|
|
frequency_axes.x_axis.add_numbers(1, 2)
|
|
frequency_graph = frequency_axes.get_graph(
|
|
lambda x : np.exp(-4*(x-1)**2),
|
|
x_min = 0,
|
|
x_max = 2,
|
|
)
|
|
frequency_graph.set_color(RED)
|
|
peak_point = frequency_axes.input_to_graph_point(
|
|
1, frequency_graph
|
|
)
|
|
|
|
#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)
|
|
|
|
self.play(
|
|
ReplacementTransform(
|
|
self.time_axes.copy(), frequency_axes
|
|
),
|
|
ReplacementTransform(
|
|
self.time_graph.copy(), frequency_graph
|
|
),
|
|
)
|
|
self.play(
|
|
Write(label),
|
|
GrowArrow(arrow)
|
|
)
|
|
self.wait()
|
|
|
|
self.frequency_axes = frequency_axes
|
|
self.frequency_graph = frequency_graph
|
|
self.frequency_graph_label = VGroup(
|
|
label, arrow
|
|
)
|
|
|
|
def flash_in_sync_long_time(self):
|
|
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(list(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(LaggedStartMap(
|
|
FadeOut, VGroup(
|
|
self.time_graph_label,
|
|
self.frequency_graph_label,
|
|
self.time_graph,
|
|
)
|
|
))
|
|
self.play(
|
|
ApplyMethod(
|
|
self.time_axes.x_axis.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.get_right(),
|
|
LEFT
|
|
)
|
|
),
|
|
ShowCreation(
|
|
new_time_graph,
|
|
run_time = n_spikes,
|
|
rate_func=linear,
|
|
),
|
|
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
|
|
]
|
|
)
|
|
|
|
self.new_time_graph = new_time_graph
|
|
self.new_frequency_graph = new_frequency_graph
|
|
|
|
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),
|
|
*list(map(self.get_flashes, self.cars))
|
|
)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(text, run_time = 1),
|
|
*list(map(self.get_flashes, self.cars))
|
|
)
|
|
self.play(*[
|
|
self.get_flashes(car, num_flashes = 10)
|
|
for car in self.cars
|
|
])
|
|
|
|
###
|
|
|
|
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)
|
|
])
|
|
|
|
def get_multispike_function(self, spike_times):
|
|
return lambda x : sum([
|
|
1.25*np.exp(-100*(x-m)**2)
|
|
for m in spike_times
|
|
])
|
|
|
|
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 = ExponentialValueTracker(1)
|
|
def get_graph():
|
|
a = graph_width_tracker.get_value()
|
|
return FunctionGraph(
|
|
lambda x : np.exp(-a*x**2)*np.sin(freq*x)-0.5,
|
|
step_size = 0.001,
|
|
)
|
|
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.set_value, a),
|
|
graph_update_anim,
|
|
**kwargs
|
|
)
|
|
change_width_anim(FRAME_X_RADIUS).update(1)
|
|
graph_update_anim.update(0)
|
|
|
|
phrases = [
|
|
TextMobject(*words.split(" "))
|
|
for words in [
|
|
"Very clear frequency",
|
|
"Less clear frequency",
|
|
"Extremely unclear frequency",
|
|
]
|
|
]
|
|
|
|
|
|
#Show graphs and phrases
|
|
widths = [FRAME_X_RADIUS, 1, 0.2]
|
|
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=linear)),
|
|
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.set_color_by_tex_to_color_map({
|
|
"short" : RED,
|
|
"long" : GREEN,
|
|
"wide" : GREEN,
|
|
}, case_sensitive = False)
|
|
phrases.arrange(DOWN)
|
|
phrases.to_edge(UP)
|
|
|
|
long_graph = FunctionGraph(
|
|
lambda x : 0.5*np.sin(freq*x),
|
|
x_min = -FRAME_WIDTH,
|
|
x_max = FRAME_WIDTH,
|
|
n_components = 0.001
|
|
)
|
|
long_graph.set_color(BLUE)
|
|
long_graph.next_to(graph, UP, MED_LARGE_BUFF)
|
|
|
|
self.play(
|
|
ShowCreation(long_graph),
|
|
*list(map(FadeOut, [last_brace, last_phrase]))
|
|
)
|
|
self.play(
|
|
Write(short_signal_words),
|
|
change_width_anim(widths[2])
|
|
)
|
|
self.play(
|
|
long_graph.stretch, 0.35, 0,
|
|
long_graph.set_color, GREEN,
|
|
run_time = 5,
|
|
rate_func = wiggle
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(long_signal_words),
|
|
change_width_anim(widths[0]),
|
|
)
|
|
self.play(
|
|
long_graph.stretch, 0.95, 0,
|
|
long_graph.set_color, average_color(GREEN, BLUE),
|
|
run_time = 4,
|
|
rate_func = wiggle
|
|
)
|
|
self.wait()
|
|
|
|
class CrossOutDefinitenessAndCertainty(TeacherStudentsScene):
|
|
def construct(self):
|
|
words = VGroup(
|
|
TextMobject("Definiteness"),
|
|
TextMobject("Certainty"),
|
|
)
|
|
words.arrange(DOWN)
|
|
words.next_to(self.teacher, UP+LEFT)
|
|
crosses = VGroup(*list(map(Cross, words)))
|
|
|
|
self.add(words)
|
|
self.play(
|
|
self.teacher.change, "sassy",
|
|
ShowCreation(crosses[0])
|
|
)
|
|
self.play(
|
|
self.get_student_changes(*3*["erm"]),
|
|
ShowCreation(crosses[1])
|
|
)
|
|
self.wait(2)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(screen_rect))
|
|
self.wait()
|
|
|
|
class FourierRecapScene(DrawFrequencyPlot):
|
|
CONFIG = {
|
|
"frequency_axes_config" : {
|
|
"x_max" : 10.0,
|
|
"x_axis_config" : {
|
|
"unit_size" : 0.7,
|
|
"numbers_to_show" : list(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.set_color_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(
|
|
LaggedStartMap(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(LaggedStartMap(
|
|
Indicate, self.frequency_axes.x_axis.numbers,
|
|
run_time = 4,
|
|
rate_func = wiggle,
|
|
))
|
|
self.wait()
|
|
self.play(*list(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
|
|
),
|
|
run_time = 10
|
|
)
|
|
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 set_color_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.set_color(YELLOW)
|
|
return graph
|
|
|
|
class RealPartOfInsert(Scene):
|
|
def construct(self):
|
|
words = TextMobject("(Real part of the)")
|
|
words.set_color(RED)
|
|
self.add(words)
|
|
self.play(Write(words))
|
|
self.wait(5)
|
|
|
|
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.set_height(FRAME_HEIGHT)
|
|
circle_plane.target.center()
|
|
circle_plane.target.axes.set_stroke(width = 2)
|
|
circle_plane.targets.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):
|
|
CONFIG = {
|
|
"num_fourier_graph_points" : 1000,
|
|
}
|
|
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()
|
|
|
|
def setup_axes(self):
|
|
FourierRecapScene.setup_axes(self)
|
|
self.add(self.circle_plane)
|
|
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()
|
|
|
|
def extend_for_long_time(self):
|
|
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=linear),
|
|
ShowCreation(long_pol_graph, rate_func=linear),
|
|
run_time = 5
|
|
)
|
|
self.wait()
|
|
|
|
self.time_graph = self.graph = long_time_graph
|
|
|
|
def note_sharp_fourier_peak(self):
|
|
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)
|
|
|
|
def very_short_signal(self):
|
|
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)
|
|
|
|
def note_wide_fourier_peak(self):
|
|
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(*list(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/get_norm(vect)
|
|
arrow = Arrow(vect, ORIGIN, buff = SMALL_BUFF)
|
|
arrow.set_color(YELLOW)
|
|
arrow.shift(point)
|
|
dot.arrow = arrow
|
|
return dots
|
|
|
|
class FocusRectangleInsert(FourierRecapScene):
|
|
CONFIG = {
|
|
"target_width" : 0.5
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.clear()
|
|
point = self.frequency_axes.coords_to_point(5, 0.25)
|
|
rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS)
|
|
rect.set_stroke(YELLOW, 2)
|
|
self.add(rect)
|
|
self.wait()
|
|
self.play(
|
|
rect.stretch_to_fit_width, self.target_width,
|
|
rect.stretch_to_fit_height, 1.5,
|
|
rect.move_to, point,
|
|
run_time = 2
|
|
)
|
|
self.wait(3)
|
|
|
|
class BroadPeakFocusRectangleInsert(FocusRectangleInsert):
|
|
CONFIG = {
|
|
"target_width" : 1.5,
|
|
}
|
|
|
|
class CleanerFourierTradeoff(FourierTradeoff):
|
|
CONFIG = {
|
|
"show_text" : False,
|
|
"complex_to_real_func" : lambda z : z.real,
|
|
"widths" : [0.02, 6, 1],
|
|
}
|
|
|
|
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)
|
|
always_shift(plane, LEFT, 1)
|
|
plane.flip()
|
|
pulse = RadarPulse(dish, plane)
|
|
look_at_anims = [
|
|
Mobject.add_updater(
|
|
pi, lambda pi : pi.look_at(pulse.mobject)
|
|
)
|
|
for pi in self.get_pi_creatures()
|
|
]
|
|
|
|
self.add(dish, plane, pulse, *look_at_anims)
|
|
self.play(
|
|
self.teacher.change, "hooray",
|
|
words.restore
|
|
)
|
|
self.change_student_modes("pondering", "erm", "sassy")
|
|
self.wait(2)
|
|
self.play(
|
|
self.teacher.change, "happy",
|
|
self.get_student_changes(*["thinking"]*3)
|
|
)
|
|
self.wait()
|
|
dish.set_stroke(width = 0)
|
|
self.play(UpdateFromAlphaFunc(
|
|
VGroup(plane, dish),
|
|
lambda m, a : m.set_fill(opacity = 1 - a)
|
|
))
|
|
|
|
class IntroduceDopplerRadar(Scene):
|
|
CONFIG = {
|
|
"frequency_spread_factor" : 100,
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.measure_distance_with_time()
|
|
self.show_frequency_shift()
|
|
self.show_frequency_shift_in_fourier()
|
|
|
|
def setup_axes(self):
|
|
self.dish = RadarDish()
|
|
self.dish.to_corner(UP+LEFT)
|
|
axes = Axes(
|
|
x_min = 0,
|
|
x_max = 10,
|
|
y_min = -1.5,
|
|
y_max = 1.5
|
|
)
|
|
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, echo_graph, sum_graph = \
|
|
self.get_pulse_and_echo_graphs(
|
|
self.get_single_pulse_graph,
|
|
(1,), (1+time_diff,)
|
|
)
|
|
words = ["Original signal", "Echo"]
|
|
for graph, word in zip([pulse_graph, echo_graph], words):
|
|
arrow = Vector(DOWN)
|
|
arrow.next_to(graph.peak_point, UP, 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
|
|
|
|
double_arrow = DoubleArrow(
|
|
pulse_graph.peak_point,
|
|
echo_graph.peak_point,
|
|
color = WHITE
|
|
)
|
|
distance_text = TextMobject("$2 \\times$ distance/(signal speed)")
|
|
distance_text.set_width(0.9*double_arrow.get_width())
|
|
distance_text.next_to(double_arrow, UP, SMALL_BUFF)
|
|
|
|
#v_line anim?
|
|
|
|
pulse = RadarPulseSingleton(
|
|
dish, randy,
|
|
speed = 0.97*speed, #Just needs slightly better alignment
|
|
)
|
|
graph_draw = turn_animation_into_updater(
|
|
ShowCreation(
|
|
sum_graph,
|
|
rate_func=linear,
|
|
run_time = 0.97*axes.x_max
|
|
)
|
|
)
|
|
randy_look_at = Mobject.add_updater(
|
|
randy, lambda pi : pi.look_at(pulse.mobject)
|
|
)
|
|
axes_anim = ContinualAnimation(axes)
|
|
|
|
self.add(randy_look_at, axes_anim, graph_draw)
|
|
self.wait(0.5)
|
|
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()
|
|
self.play(
|
|
GrowFromCenter(double_arrow),
|
|
FadeIn(distance_text)
|
|
)
|
|
self.wait()
|
|
|
|
self.remove(graph_draw, pulse, randy_look_at, axes_anim)
|
|
self.add(axes)
|
|
self.play(LaggedStartMap(FadeOut, VGroup(
|
|
sum_graph, randy,
|
|
pulse_graph.arrow, pulse_graph.label,
|
|
echo_graph.arrow, echo_graph.label,
|
|
double_arrow, distance_text
|
|
)))
|
|
|
|
def show_frequency_shift(self):
|
|
axes = self.axes
|
|
dish = self.dish
|
|
plane = Plane()
|
|
plane.flip()
|
|
plane.move_to(dish)
|
|
plane.to_edge(RIGHT)
|
|
|
|
time_diff = 6
|
|
|
|
pulse_graph, echo_graph, sum_graph = graphs = \
|
|
self.get_pulse_and_echo_graphs(
|
|
self.get_frequency_pulse_graph,
|
|
(1,25), (1+time_diff,50)
|
|
)
|
|
for graph in graphs:
|
|
graph.set_stroke(width = 3)
|
|
signal_graph = self.get_frequency_pulse_graph(1)
|
|
|
|
pulse_brace = Brace(Line(ORIGIN, RIGHT), UP)
|
|
pulse_brace.move_to(axes.coords_to_point(1, 1.2))
|
|
echo_brace = pulse_brace.copy()
|
|
echo_brace.stretch(0.6, 0)
|
|
echo_brace.move_to(axes.coords_to_point(7, 1.2))
|
|
pulse_text = pulse_brace.get_text("Original signal")
|
|
pulse_text.add_background_rectangle()
|
|
echo_text = echo_brace.get_text("Echo")
|
|
echo_subtext = TextMobject("(Higher frequency)")
|
|
echo_subtext.next_to(echo_text, RIGHT)
|
|
echo_subtext.match_color(echo_graph)
|
|
|
|
graph_draw = turn_animation_into_updater(
|
|
ShowCreation(sum_graph, run_time = 8, rate_func=linear)
|
|
)
|
|
pulse = RadarPulse(dish, plane, n_pulse_singletons = 12)
|
|
always_shift(plane, LEFT, 1.5)
|
|
|
|
self.add(graph_draw, pulse, plane)
|
|
self.play(UpdateFromAlphaFunc(
|
|
plane, lambda m, a : m.set_fill(opacity = a)
|
|
))
|
|
self.play(
|
|
GrowFromCenter(pulse_brace),
|
|
FadeIn(pulse_text),
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
GrowFromCenter(echo_brace),
|
|
GrowFromCenter(echo_text),
|
|
)
|
|
self.play(UpdateFromAlphaFunc(
|
|
plane, lambda m, a : m.set_fill(opacity = 1-a)
|
|
))
|
|
#Only for when -s is run
|
|
graph_draw.update(10)
|
|
self.wait(0.1)
|
|
self.play(Write(echo_subtext, run_time = 1))
|
|
self.wait()
|
|
self.remove(graph_draw, pulse, plane)
|
|
|
|
pulse_graph.set_stroke(width = 0)
|
|
echo_graph.set_stroke(width = 0)
|
|
self.time_graph_group = VGroup(
|
|
axes, pulse_brace, pulse_text,
|
|
echo_brace, echo_text, echo_subtext,
|
|
pulse_graph, echo_graph, sum_graph,
|
|
)
|
|
self.set_variables_as_attrs(*self.time_graph_group)
|
|
|
|
def show_frequency_shift_in_fourier(self):
|
|
sum_graph = self.sum_graph
|
|
pulse_graph = self.pulse_graph
|
|
pulse_label = VGroup(self.pulse_brace, self.pulse_text)
|
|
echo_graph = self.echo_graph
|
|
echo_label = VGroup(
|
|
self.echo_brace, self.echo_text, self.echo_subtext
|
|
)
|
|
|
|
#Setup all fourier graph stuff
|
|
f_max = 0.02
|
|
frequency_axes = Axes(
|
|
x_min = 0, x_max = 20,
|
|
x_axis_config = {"unit_size" : 0.5},
|
|
y_min = -f_max, y_max = f_max,
|
|
y_axis_config = {
|
|
"unit_size" : 50,
|
|
"tick_frequency" : 0.01,
|
|
},
|
|
)
|
|
frequency_axes.move_to(self.axes, LEFT)
|
|
frequency_axes.to_edge(DOWN)
|
|
frequency_label = TextMobject("Frequency")
|
|
frequency_label.next_to(
|
|
frequency_axes.x_axis.get_right(), UP,
|
|
)
|
|
frequency_label.to_edge(RIGHT)
|
|
frequency_axes.add(frequency_label)
|
|
|
|
for graph in pulse_graph, echo_graph, sum_graph:
|
|
graph.fourier_transform = get_fourier_graph(
|
|
frequency_axes, graph.underlying_function,
|
|
frequency_axes.x_min, 25,
|
|
complex_to_real_func = abs,
|
|
)
|
|
|
|
#Braces labeling F.T.
|
|
original_fourier_brace = Brace(
|
|
Line(
|
|
frequency_axes.coords_to_point(7, 0.9*f_max),
|
|
frequency_axes.coords_to_point(9, 0.9*f_max),
|
|
),
|
|
UP,
|
|
).set_color(BLUE)
|
|
echo_fourier_brace = Brace(
|
|
Line(
|
|
frequency_axes.coords_to_point(14, 0.4*f_max),
|
|
frequency_axes.coords_to_point(18, 0.4*f_max),
|
|
),
|
|
UP,
|
|
).set_color(YELLOW)
|
|
# braces = [original_fourier_brace, echo_fourier_brace]
|
|
# words = ["original signal", "echo"]
|
|
# for brace, word in zip(braces, words):
|
|
# brace.add(brace.get_text("F.T. of \\\\ %s"%word))
|
|
fourier_label = TexMobject("||\\text{Fourier transform}||")
|
|
# fourier_label.next_to(sum_graph.fourier_transform, UP, MED_LARGE_BUFF)
|
|
fourier_label.next_to(frequency_axes.y_axis, UP, buff = SMALL_BUFF)
|
|
fourier_label.shift_onto_screen()
|
|
fourier_label.set_color(RED)
|
|
|
|
|
|
#v_lines
|
|
v_line = DashedLine(
|
|
frequency_axes.coords_to_point(8, 0),
|
|
frequency_axes.coords_to_point(8, 1.2*f_max),
|
|
color = YELLOW,
|
|
dash_length = 0.025,
|
|
)
|
|
v_line_pair = VGroup(*[
|
|
v_line.copy().shift(u*0.6*RIGHT)
|
|
for u in (-1, 1)
|
|
])
|
|
v_line = VGroup(v_line)
|
|
|
|
double_arrow = DoubleArrow(
|
|
frequency_axes.coords_to_point(8, 0.007),
|
|
frequency_axes.coords_to_point(16, 0.007),
|
|
buff = 0,
|
|
color = WHITE
|
|
)
|
|
|
|
self.play(
|
|
self.time_graph_group.to_edge, UP,
|
|
ApplyMethod(
|
|
self.dish.shift, 2*UP,
|
|
remover = True
|
|
),
|
|
FadeIn(frequency_axes)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(sum_graph),
|
|
FadeOut(echo_label),
|
|
pulse_graph.set_stroke, {"width" : 3},
|
|
)
|
|
self.play(
|
|
ReplacementTransform(
|
|
pulse_label[0].copy(),
|
|
original_fourier_brace
|
|
),
|
|
ShowCreation(pulse_graph.fourier_transform)
|
|
)
|
|
self.play(Write(fourier_label))
|
|
self.wait()
|
|
self.play(ShowCreation(v_line))
|
|
self.wait()
|
|
self.play(ReplacementTransform(v_line, v_line_pair))
|
|
self.wait()
|
|
self.play(FadeOut(v_line_pair))
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(pulse_graph),
|
|
FadeIn(sum_graph),
|
|
ReplacementTransform(
|
|
pulse_graph.fourier_transform,
|
|
sum_graph.fourier_transform
|
|
)
|
|
)
|
|
self.play(FadeIn(echo_label))
|
|
self.play(ReplacementTransform(
|
|
echo_label[0].copy(),
|
|
echo_fourier_brace,
|
|
))
|
|
self.wait(2)
|
|
self.play(GrowFromCenter(double_arrow))
|
|
self.wait()
|
|
|
|
|
|
###
|
|
|
|
def get_graph(self, func, **kwargs):
|
|
graph = self.axes.get_graph(func, **kwargs)
|
|
graph.peak_point = self.get_peak_point(graph)
|
|
return graph
|
|
|
|
def get_single_pulse_graph(self, x, **kwargs):
|
|
return self.get_graph(self.get_single_pulse_function(x), **kwargs)
|
|
|
|
def get_single_pulse_function(self, x):
|
|
return lambda t : -2*np.sin(10*(t-x))*np.exp(-100*(t-x)**2)
|
|
|
|
def get_frequency_pulse_graph(self, x, freq = 50, **kwargs):
|
|
return self.get_graph(
|
|
self.get_frequency_pulse_function(x, freq),
|
|
num_graph_points = 700,
|
|
**kwargs
|
|
)
|
|
|
|
def get_frequency_pulse_function(self, x, freq):
|
|
factor = self.frequency_spread_factor
|
|
return lambda t : op.mul(
|
|
2*np.cos(2*freq*(t-x)),
|
|
min(np.exp(-(freq**2/factor)*(t-x)**2), 0.5)
|
|
)
|
|
|
|
def get_peak_point(self, graph):
|
|
anchors = graph.get_anchors()
|
|
return anchors[np.argmax([p[1] for p in anchors])]
|
|
|
|
def get_pulse_and_echo_graphs(self, func, args1, args2):
|
|
pulse_graph = func(*args1, color = BLUE)
|
|
echo_graph = func(*args2, color = YELLOW)
|
|
sum_graph = self.axes.get_graph(
|
|
lambda x : sum([
|
|
pulse_graph.underlying_function(x),
|
|
echo_graph.underlying_function(x),
|
|
]),
|
|
num_graph_points = echo_graph.get_num_curves(),
|
|
color = WHITE
|
|
)
|
|
sum_graph.background_image_file = "blue_yellow_gradient"
|
|
return pulse_graph, echo_graph, sum_graph
|
|
|
|
class DopplerFormulaInsert(Scene):
|
|
def construct(self):
|
|
formula = TexMobject(
|
|
"f_{\\text{echo}", "=",
|
|
"\\left(1 + \\frac{v}{c}\\right)",
|
|
"f_{\\text{pulse}}"
|
|
)
|
|
formula[0].set_color(BLUE)
|
|
formula[3].set_color(YELLOW)
|
|
|
|
randy = Randolph(color = BLUE_C)
|
|
formula.scale(1.5)
|
|
formula.next_to(randy, UP+LEFT)
|
|
formula.shift_onto_screen()
|
|
|
|
self.add(randy)
|
|
self.play(
|
|
LaggedStartMap(FadeIn, formula),
|
|
randy.change, "pondering", randy.get_bottom(),
|
|
)
|
|
self.play(Blink(randy))
|
|
self.wait(2)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
|
|
class MentionPRFNuance(TeacherStudentsScene):
|
|
def construct(self):
|
|
title = TextMobject(
|
|
"Speed of light", "$\\gg$", "Speed of a plane"
|
|
)
|
|
title.to_edge(UP)
|
|
self.add(title)
|
|
|
|
axes = self.axes = Axes(
|
|
x_min = 0, x_max = 10,
|
|
y_min = 0, y_max = 2,
|
|
)
|
|
axes.next_to(title, DOWN, buff = MED_LARGE_BUFF)
|
|
frequency_label = TextMobject("Frequency")
|
|
frequency_label.scale(0.7)
|
|
frequency_label.next_to(axes.x_axis.get_right(), UP)
|
|
axes.add(frequency_label)
|
|
self.add(axes)
|
|
|
|
pulse_x, shift_x = 4, 6
|
|
pulse_graph = self.get_spike_graph(pulse_x)
|
|
shift_graph = self.get_spike_graph(shift_x)
|
|
shift_graph.set_stroke(YELLOW, 2)
|
|
peak_points = VGroup(pulse_graph.peak_point, shift_graph.peak_point)
|
|
self.add(pulse_graph)
|
|
|
|
brace = Brace(peak_points, UP, buff = SMALL_BUFF)
|
|
displayed_doppler_shift = TextMobject("How I'm showing the \\\\", "Doppler shift")
|
|
actual_doppler_shift = TextMobject("Actual\\\\", "Doppler shift")
|
|
doppler_shift_words = VGroup(displayed_doppler_shift, actual_doppler_shift)
|
|
doppler_shift_words.set_color(YELLOW)
|
|
doppler_shift_words.scale(0.75)
|
|
displayed_doppler_shift.next_to(brace, UP, buff = SMALL_BUFF)
|
|
actual_doppler_shift.move_to(pulse_graph.peak_point)
|
|
actual_doppler_shift.align_to(displayed_doppler_shift)
|
|
|
|
self.play(
|
|
Animation(pulse_graph),
|
|
self.teacher.change, "raise_right_hand",
|
|
run_time = 1
|
|
)
|
|
self.play(
|
|
ShowCreation(shift_graph),
|
|
FadeIn(brace),
|
|
Write(displayed_doppler_shift, run_time = 1),
|
|
self.get_student_changes(*3*["sassy"]),
|
|
)
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
shift_graph,
|
|
lambda g, a : Transform(
|
|
g, self.get_spike_graph(
|
|
interpolate(shift_x, pulse_x+0.01, a),
|
|
).match_style(shift_graph)
|
|
).update(1),
|
|
),
|
|
UpdateFromFunc(
|
|
brace,
|
|
lambda b : b.match_width(
|
|
peak_points, stretch = True
|
|
).next_to(peak_points, UP, SMALL_BUFF)
|
|
),
|
|
Transform(
|
|
displayed_doppler_shift, actual_doppler_shift,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.6)
|
|
),
|
|
run_time = 3
|
|
)
|
|
self.wait(2)
|
|
|
|
everything = VGroup(
|
|
title,
|
|
axes, pulse_graph, shift_graph,
|
|
brace, displayed_doppler_shift
|
|
)
|
|
rect = SurroundingRectangle(everything, color = WHITE)
|
|
everything.add(rect)
|
|
|
|
self.teacher_says(
|
|
"I'll ignore certain \\\\ nuances for now.",
|
|
target_mode = "shruggie",
|
|
added_anims = [
|
|
everything.scale, 0.4,
|
|
everything.to_corner, UP+LEFT,
|
|
UpdateFromAlphaFunc(
|
|
rect, lambda m, a : m.set_stroke(width = 2*a)
|
|
)
|
|
],
|
|
)
|
|
self.change_student_modes(*3*["hesitant"])
|
|
self.wait(2)
|
|
|
|
|
|
|
|
|
|
def get_spike_graph(self, x, color = RED, **kwargs):
|
|
graph = self.axes.get_graph(
|
|
lambda t : np.exp(-10*(t-x)**2)*np.cos(10*(t-x)),
|
|
color = color,
|
|
**kwargs
|
|
)
|
|
graph.peak_point = VectorizedPoint(self.axes.input_to_graph_point(x, graph))
|
|
graph.add(graph.peak_point)
|
|
return graph
|
|
|
|
class TimeAndFrequencyGivePositionAndVelocity(IntroduceDopplerRadar):
|
|
def construct(self):
|
|
x = 7
|
|
freq = 25
|
|
|
|
axes = self.axes = Axes(
|
|
x_min = 0, x_max = 10,
|
|
y_min = -2, y_max = 2,
|
|
)
|
|
axes.center()
|
|
title = TextMobject("Echo signal")
|
|
title.next_to(axes.y_axis, UP)
|
|
axes.add(title)
|
|
axes.to_edge(UP)
|
|
graph = self.get_frequency_pulse_graph(x = x, freq = freq)
|
|
graph.background_image_file = "blue_yellow_gradient"
|
|
|
|
arrow = Arrow(
|
|
axes.coords_to_point(0, -1.5),
|
|
axes.coords_to_point(x, -1.5),
|
|
color = WHITE,
|
|
buff = SMALL_BUFF,
|
|
)
|
|
time = TextMobject("Time")
|
|
time.next_to(arrow, DOWN, SMALL_BUFF)
|
|
|
|
delta_x = 0.7
|
|
brace = Brace(
|
|
Line(
|
|
axes.coords_to_point(x-delta_x, 1),
|
|
axes.coords_to_point(x+delta_x, 1)
|
|
),
|
|
UP
|
|
)
|
|
frequency = TextMobject("Frequency")
|
|
frequency.set_color(YELLOW)
|
|
frequency.next_to(brace, UP, SMALL_BUFF)
|
|
|
|
time_updown_arrow = TexMobject("\\Updownarrow")
|
|
time_updown_arrow.next_to(time, DOWN, SMALL_BUFF)
|
|
freq_updown_arrow = time_updown_arrow.copy()
|
|
freq_updown_arrow.next_to(frequency, UP, SMALL_BUFF)
|
|
distance = TextMobject("Distance")
|
|
distance.next_to(time_updown_arrow, DOWN, SMALL_BUFF)
|
|
velocity = TextMobject("Velocity")
|
|
velocity.next_to(freq_updown_arrow, UP, SMALL_BUFF)
|
|
VGroup(freq_updown_arrow, velocity).match_style(frequency)
|
|
|
|
self.add(axes)
|
|
self.play(ShowCreation(graph))
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
LaggedStartMap(FadeIn, time, run_time = 1)
|
|
)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
LaggedStartMap(FadeIn, frequency, run_time = 1)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromPoint(time_updown_arrow, time_updown_arrow.get_top()),
|
|
ReplacementTransform(
|
|
time.copy().fade(1),
|
|
distance
|
|
)
|
|
)
|
|
self.play(
|
|
GrowFromPoint(freq_updown_arrow, freq_updown_arrow.get_top()),
|
|
ReplacementTransform(
|
|
frequency.copy().fade(1),
|
|
velocity
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
class RadarOperatorUncertainty(Scene):
|
|
def construct(self):
|
|
dish = RadarDish()
|
|
dish.scale(3)
|
|
dish.move_to(4*RIGHT + 2*DOWN)
|
|
dish_words = TextMobject("3b1b industrial \\\\ enterprises")
|
|
dish_words.scale(0.25)
|
|
dish_words.set_stroke(BLACK, 0.5)
|
|
dish_words.set_color(BLACK)
|
|
dish_words.move_to(dish, DOWN)
|
|
dish_words.shift(SMALL_BUFF*(UP+2*LEFT))
|
|
dish.add(dish_words)
|
|
randy = Randolph()
|
|
randy.next_to(dish, LEFT, aligned_edge = DOWN)
|
|
bubble = randy.get_bubble(
|
|
width = 7,
|
|
height = 4,
|
|
)
|
|
|
|
echo_object = Square()
|
|
echo_object.move_to(dish)
|
|
echo_object.shift(FRAME_X_RADIUS*RIGHT)
|
|
pulse = RadarPulse(dish, echo_object, speed = 6)
|
|
|
|
plane = Plane().scale(0.5)
|
|
plane.move_to(bubble.get_bubble_center()+LEFT)
|
|
plane_cloud = ProbabalisticMobjectCloud(
|
|
plane,
|
|
fill_opacity = 0.3,
|
|
n_copies = 10,
|
|
)
|
|
plane_gdw = plane_cloud.gaussian_distribution_wrapper
|
|
|
|
vector_cloud = ProbabalisticVectorCloud(
|
|
center_func = plane_gdw.get_center,
|
|
)
|
|
vector_gdw = vector_cloud.gaussian_distribution_wrapper
|
|
vector_gdw.scale(0.05)
|
|
vector_gdw.move_to(plane_gdw)
|
|
vector_gdw.shift(2*RIGHT)
|
|
|
|
self.add(randy, dish, bubble, plane_cloud, pulse)
|
|
self.play(randy.change, "confused")
|
|
self.wait(3)
|
|
self.add(vector_cloud)
|
|
for i in range(3):
|
|
for plane_factor, vector_factor, freq in (0.05, 10, 0.01), (20, 0.1, 0.1):
|
|
pulse.internal_time = 0
|
|
pulse.frequency = freq
|
|
self.play(
|
|
randy.change, "pondering", plane,
|
|
plane_gdw.scale, plane_factor,
|
|
vector_gdw.scale, vector_factor,
|
|
)
|
|
self.wait(2)
|
|
|
|
class AmbiguityInLongEchos(IntroduceDopplerRadar, PiCreatureScene):
|
|
CONFIG = {
|
|
"object_x_coords" : [7, 4, 6, 9, 8],
|
|
"frequency_spread_factor" : 200,
|
|
"n_pulse_singletons" : 16,
|
|
"pulse_frequency" : 0.025,
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.setup_objects()
|
|
self.send_long_pulse_single_echo()
|
|
self.introduce_multiple_objects()
|
|
self.use_short_pulse()
|
|
self.fourier_transform_of_one_pulse()
|
|
self.show_echos_of_moving_objects()
|
|
self.overlapping_frequenies_of_various_objects()
|
|
self.echos_of_long_pure_signal_in_frequency_space()
|
|
self.concentrated_fourier_requires_long_time()
|
|
|
|
def setup_axes(self):
|
|
axes = self.axes = Axes(
|
|
x_min = 0, x_max = 10,
|
|
y_min = -1.5, y_max = 1.5,
|
|
)
|
|
time_label = TextMobject("Time")
|
|
time_label.next_to(axes.x_axis.get_right(), UP)
|
|
axes.add(time_label)
|
|
axes.center()
|
|
axes.shift(DOWN)
|
|
self.add(axes)
|
|
|
|
dish = self.dish = RadarDish()
|
|
dish.move_to(axes, LEFT)
|
|
dish.to_edge(UP, buff = LARGE_BUFF)
|
|
self.add(dish)
|
|
|
|
def setup_objects(self):
|
|
objects = self.objects = VGroup(
|
|
Plane().flip(),
|
|
SVGMobject(
|
|
file_name = "blimp",
|
|
color = BLUE_C,
|
|
height = 0.5,
|
|
),
|
|
SVGMobject(
|
|
file_name = "biplane",
|
|
color = RED_D,
|
|
height = 0.5,
|
|
),
|
|
SVGMobject(
|
|
file_name = "helicopter",
|
|
color = LIGHT_GREY,
|
|
height = 0.5,
|
|
).rotate(-TAU/24),
|
|
FalconHeavy(),
|
|
)
|
|
y_shifts = [0.25, 0, 0.5, 0.25, -0.5]
|
|
for x, y, obj in zip(self.object_x_coords, y_shifts, objects):
|
|
obj.move_to(self.axes.coords_to_point(x, 0))
|
|
obj.align_to(self.dish)
|
|
obj.shift(y*UP)
|
|
|
|
self.object_velocities = [
|
|
0.7*LEFT,
|
|
0.1*RIGHT,
|
|
0.4*LEFT,
|
|
0.4*RIGHT,
|
|
0.5*UP,
|
|
]
|
|
|
|
def send_long_pulse_single_echo(self):
|
|
x = self.object_x_coords[0]
|
|
plane = self.objects[0]
|
|
self.add(plane)
|
|
randy = self.pi_creature
|
|
self.remove(randy)
|
|
|
|
pulse_graph = self.get_frequency_pulse_graph(x)
|
|
pulse_graph.background_image_file = "blue_yellow_gradient"
|
|
|
|
pulse = self.get_pulse(self.dish, plane)
|
|
|
|
brace = Brace(
|
|
Line(
|
|
self.axes.coords_to_point(x-1, 1),
|
|
self.axes.coords_to_point(x+1, 1),
|
|
), UP
|
|
)
|
|
words = brace.get_text("Spread over time")
|
|
|
|
self.add(pulse)
|
|
self.wait()
|
|
squished_rate_func = squish_rate_func(smooth, 0.6, 0.9)
|
|
self.play(
|
|
ShowCreation(pulse_graph, rate_func=linear),
|
|
GrowFromCenter(brace, rate_func = squished_rate_func),
|
|
Write(words, rate_func = squished_rate_func),
|
|
run_time = 3,
|
|
)
|
|
self.remove(pulse)
|
|
self.play(FadeIn(randy))
|
|
self.play(PiCreatureBubbleIntroduction(
|
|
randy, "Who cares?",
|
|
bubble_class = ThoughtBubble,
|
|
bubble_kwargs = {
|
|
"direction" : LEFT,
|
|
"width" : 2,
|
|
"height": 1.5,
|
|
},
|
|
target_mode = "maybe",
|
|
look_at_arg = brace,
|
|
))
|
|
self.play(Blink(randy))
|
|
self.play(LaggedStartMap(
|
|
FadeOut, VGroup(
|
|
randy.bubble, randy.bubble.content,
|
|
brace, words,
|
|
)
|
|
))
|
|
|
|
self.curr_graph = pulse_graph
|
|
|
|
def introduce_multiple_objects(self):
|
|
objects = self.objects
|
|
x_coords = self.object_x_coords
|
|
curr_graph = self.curr_graph
|
|
randy = self.pi_creature
|
|
|
|
graphs = VGroup(*[
|
|
self.get_frequency_pulse_graph(x)
|
|
for x in x_coords
|
|
])
|
|
graphs.set_color_by_gradient(BLUE, YELLOW)
|
|
sum_graph = self.axes.get_graph(
|
|
lambda t : sum([
|
|
graph.underlying_function(t)
|
|
for graph in graphs
|
|
]),
|
|
num_graph_points = 1000
|
|
)
|
|
|
|
noise_function = lambda t : np.sum([
|
|
0.5*np.sin(f*t)/f
|
|
for f in (2, 3, 5, 7, 11, 13)
|
|
])
|
|
noisy_graph = self.axes.get_graph(
|
|
lambda t : sum_graph.underlying_function(t)*(1+noise_function(t)),
|
|
num_graph_points = 1000
|
|
)
|
|
for graph in sum_graph, noisy_graph:
|
|
graph.background_image_file = "blue_yellow_gradient"
|
|
|
|
pulses = self.get_pulses()
|
|
|
|
self.play(
|
|
LaggedStartMap(GrowFromCenter, objects[1:]),
|
|
FadeOut(curr_graph),
|
|
randy.change, "pondering"
|
|
)
|
|
self.add(*pulses)
|
|
self.wait(0.5)
|
|
self.play(
|
|
ShowCreation(
|
|
sum_graph,
|
|
rate_func=linear,
|
|
run_time = 3.5,
|
|
),
|
|
randy.change, "confused"
|
|
)
|
|
self.remove(*pulses)
|
|
self.play(randy.change, "pondering")
|
|
self.play(Transform(
|
|
sum_graph, noisy_graph,
|
|
rate_func = lambda t : wiggle(t, 4),
|
|
run_time = 3
|
|
))
|
|
self.wait(2)
|
|
|
|
self.curr_graph = sum_graph
|
|
|
|
def use_short_pulse(self):
|
|
curr_graph = self.curr_graph
|
|
objects = self.objects
|
|
x_coords = self.object_x_coords
|
|
randy = self.pi_creature
|
|
|
|
self.frequency_spread_factor = 10
|
|
self.n_pulse_singletons = 4
|
|
self.pulse_frequency = 0.015
|
|
|
|
graphs = VGroup(*[
|
|
self.get_frequency_pulse_graph(x)
|
|
for x in x_coords
|
|
])
|
|
sum_graph = self.axes.get_graph(
|
|
lambda t : sum([
|
|
graph.underlying_function(t)
|
|
for graph in graphs
|
|
]),
|
|
num_graph_points = 1000
|
|
)
|
|
sum_graph.background_image_file = "blue_yellow_gradient"
|
|
|
|
pulses = self.get_pulses()
|
|
|
|
self.play(FadeOut(curr_graph))
|
|
self.add(*pulses)
|
|
self.wait(0.5)
|
|
self.play(
|
|
ShowCreation(
|
|
sum_graph,
|
|
rate_func=linear,
|
|
run_time = 3.5,
|
|
),
|
|
randy.change, "happy"
|
|
)
|
|
self.wait()
|
|
|
|
self.curr_graph = sum_graph
|
|
self.first_echo_graph = graphs[0]
|
|
self.first_echo_graph.set_color(YELLOW)
|
|
|
|
def fourier_transform_of_one_pulse(self):
|
|
frequency_axes = Axes(
|
|
x_min = 0, x_max = 20,
|
|
x_axis_config = {
|
|
"unit_size" : 0.5,
|
|
"tick_frequency" : 2,
|
|
},
|
|
y_min = -.01, y_max = .01,
|
|
y_axis_config = {
|
|
"unit_size" : 110,
|
|
"tick_frequency" : 0.006
|
|
}
|
|
)
|
|
frequency_label = TextMobject("Frequency")
|
|
frequency_label.next_to(frequency_axes.x_axis.get_right(), UP)
|
|
frequency_axes.add(frequency_label)
|
|
first_echo_graph = self.first_echo_graph
|
|
|
|
self.play(
|
|
ApplyMethod(
|
|
VGroup(self.axes, first_echo_graph).to_edge, UP,
|
|
{"buff" : SMALL_BUFF},
|
|
rate_func = squish_rate_func(smooth, 0.5, 1)
|
|
),
|
|
LaggedStartMap(FadeOut, self.objects),
|
|
LaggedStartMap(FadeOut, VGroup(
|
|
self.curr_graph, self.dish, self.pi_creature
|
|
)),
|
|
run_time = 2
|
|
)
|
|
|
|
#
|
|
frequency_axes.next_to(self.axes, DOWN, LARGE_BUFF, LEFT)
|
|
fourier_graph = get_fourier_graph(
|
|
frequency_axes, first_echo_graph.underlying_function,
|
|
t_min = 0, t_max = 25,
|
|
complex_to_real_func = np.abs,
|
|
)
|
|
fourier_graph.save_state()
|
|
fourier_graph.move_to(first_echo_graph)
|
|
h_vect = 4*RIGHT
|
|
fourier_graph.shift(h_vect)
|
|
fourier_graph.fade(1)
|
|
|
|
f = 8
|
|
v_line = DashedLine(
|
|
frequency_axes.coords_to_point(f, 0),
|
|
frequency_axes.coords_to_point(f, frequency_axes.y_max),
|
|
)
|
|
v_lines = VGroup(
|
|
v_line.copy().shift(2*LEFT),
|
|
v_line.copy().shift(2*RIGHT),
|
|
)
|
|
rect = Rectangle(stroke_width = 0, fill_color = YELLOW, fill_opacity = 0.25)
|
|
rect.replace(v_lines, stretch = True)
|
|
rect.save_state()
|
|
rect.stretch(0, 0)
|
|
|
|
self.play(Write(frequency_axes, run_time = 1))
|
|
self.play(
|
|
ApplyFunction(
|
|
lambda m : m.move_to(fourier_graph.saved_state).shift(-h_vect).fade(1),
|
|
first_echo_graph.copy(),
|
|
remover = True,
|
|
),
|
|
fourier_graph.restore
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(v_line))
|
|
self.play(
|
|
ReplacementTransform(VGroup(v_line), v_lines),
|
|
rect.restore
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(v_lines), FadeOut(rect))
|
|
|
|
self.frequency_axes = frequency_axes
|
|
self.fourier_graph = fourier_graph
|
|
|
|
def show_echos_of_moving_objects(self):
|
|
objects = self.objects
|
|
objects.save_state()
|
|
object_velocities = self.object_velocities
|
|
|
|
movements = self.object_movements = [
|
|
always_shift(
|
|
obj,
|
|
direction = v/get_norm(v),
|
|
rate = get_norm(v)
|
|
)
|
|
for v, obj in zip(object_velocities, objects)
|
|
]
|
|
pulses = self.get_pulses()
|
|
continual_anims = pulses+movements
|
|
|
|
self.play(
|
|
FadeOut(self.axes),
|
|
FadeOut(self.first_echo_graph),
|
|
LaggedStartMap(FadeIn, objects),
|
|
FadeIn(self.dish)
|
|
)
|
|
self.add(*continual_anims)
|
|
self.wait(4)
|
|
self.play(*[
|
|
UpdateFromAlphaFunc(
|
|
obj,
|
|
lambda m, a : m.set_fill(opacity = 1-a),
|
|
)
|
|
for obj in objects
|
|
])
|
|
self.remove(*continual_anims)
|
|
self.wait()
|
|
|
|
def overlapping_frequenies_of_various_objects(self):
|
|
frequency_axes = self.frequency_axes
|
|
fourier_graph = self.fourier_graph
|
|
shifted_graphs = self.get_shifted_frequency_graphs(fourier_graph)
|
|
color = fourier_graph.get_color()
|
|
shifted_graphs.set_color_by_gradient(
|
|
average_color(color, WHITE),
|
|
color,
|
|
average_color(color, BLACK),
|
|
)
|
|
sum_graph = self.get_sum_graph(frequency_axes, shifted_graphs)
|
|
sum_graph.match_style(fourier_graph)
|
|
|
|
shifted_graphs.save_state()
|
|
|
|
self.play(ReplacementTransform(
|
|
VGroup(fourier_graph), shifted_graphs,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
shifted_graphs.arrange, DOWN,
|
|
shifted_graphs.move_to, fourier_graph, DOWN,
|
|
)
|
|
self.wait()
|
|
self.play(shifted_graphs.restore),
|
|
self.play(ReplacementTransform(
|
|
shifted_graphs, VGroup(sum_graph),
|
|
))
|
|
self.wait()
|
|
|
|
self.curr_fourier_graph = sum_graph
|
|
|
|
def echos_of_long_pure_signal_in_frequency_space(self):
|
|
curr_fourier_graph = self.curr_fourier_graph
|
|
f_max = self.frequency_axes.y_max
|
|
new_fourier_graph = self.frequency_axes.get_graph(
|
|
lambda x : f_max * np.exp(-100*(x-8)**2),
|
|
num_graph_points = 1000,
|
|
)
|
|
new_fourier_graph.set_color(PINK)
|
|
|
|
self.play(
|
|
FadeOut(curr_fourier_graph),
|
|
FadeIn(new_fourier_graph),
|
|
)
|
|
self.fourier_graph = new_fourier_graph
|
|
self.overlapping_frequenies_of_various_objects()
|
|
|
|
def concentrated_fourier_requires_long_time(self):
|
|
objects = self.objects
|
|
objects.restore()
|
|
object_movements = self.object_movements
|
|
self.n_pulse_singletons = 32
|
|
pulses = self.get_pulses()
|
|
randy = self.pi_creature
|
|
|
|
continual_anims = object_movements+pulses
|
|
self.play(FadeIn(randy))
|
|
self.add(*continual_anims)
|
|
self.play(randy.change, "angry", *[
|
|
UpdateFromAlphaFunc(obj, lambda m, a : m.set_fill(opacity = a))
|
|
for obj in objects
|
|
])
|
|
self.play(Blink(randy))
|
|
self.wait(2)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(randy.change, "plain", *[
|
|
UpdateFromAlphaFunc(obj, lambda m, a : m.set_fill(opacity = 1-a))
|
|
for obj in objects
|
|
])
|
|
self.wait()
|
|
|
|
|
|
###
|
|
|
|
def get_frequency_pulse_graph(self, x, freq = 25, **kwargs):
|
|
graph = IntroduceDopplerRadar.get_frequency_pulse_graph(
|
|
self, x, freq, **kwargs
|
|
)
|
|
return graph
|
|
|
|
def get_pulse(self, dish, echo_object):
|
|
return RadarPulse(
|
|
dish, echo_object,
|
|
n_pulse_singletons = self.n_pulse_singletons,
|
|
frequency = 0.025,
|
|
speed = 5.0,
|
|
)
|
|
|
|
def get_pulses(self):
|
|
return [
|
|
self.get_pulse(
|
|
self.dish.copy().shift(0.01*obj.get_center()[0]),
|
|
obj
|
|
)
|
|
for obj in self.objects
|
|
]
|
|
|
|
def create_pi_creature(self):
|
|
randy = Randolph()
|
|
randy.scale(0.5).flip()
|
|
randy.to_edge(RIGHT, buff = 1.7).shift(0.5*UP)
|
|
return randy
|
|
|
|
def get_shifted_frequency_graphs(self, fourier_graph):
|
|
frequency_axes = self.frequency_axes
|
|
def get_func(v):
|
|
return lambda f : fourier_graph.underlying_function(np.clip(
|
|
f-5*v[0],
|
|
frequency_axes.x_min,
|
|
frequency_axes.x_max,
|
|
))
|
|
def get_graph(func):
|
|
return frequency_axes.get_graph(func)
|
|
shifted_graphs = VGroup(*list(map(
|
|
get_graph, list(map(get_func, self.object_velocities))
|
|
)))
|
|
shifted_graphs.match_style(fourier_graph)
|
|
return shifted_graphs
|
|
|
|
def get_sum_graph(self, axes, graphs):
|
|
def get_func(graph):
|
|
return graph.underlying_function
|
|
funcs = list(map(get_func, graphs))
|
|
return axes.get_graph(
|
|
lambda t : sum([func(t) for func in funcs]),
|
|
)
|
|
|
|
class SummarizeFourierTradeoffForDoppler(Scene):
|
|
def construct(self):
|
|
time_axes = Axes(
|
|
x_min = 0, x_max = 12,
|
|
y_min = -0.5, y_max = 1,
|
|
)
|
|
time_axes.center().to_edge(UP, buff = LARGE_BUFF)
|
|
frequency_axes = time_axes.copy()
|
|
frequency_axes.next_to(time_axes, DOWN, buff = 2)
|
|
time_label = TextMobject("Time")
|
|
frequency_label = TextMobject("Frequency")
|
|
for label, axes in (time_label, time_axes), (frequency_label, frequency_axes):
|
|
label.next_to(axes.get_right(), UP, SMALL_BUFF)
|
|
axes.add(label)
|
|
frequency_label.shift_onto_screen()
|
|
title = TextMobject("Fourier Trade-off")
|
|
title.next_to(time_axes, DOWN)
|
|
self.add(title)
|
|
|
|
|
|
#Position determines log of scale value for exponentials
|
|
a_mob = VectorizedPoint()
|
|
x_values = [3, 5, 6, 7, 8]
|
|
v_values = [5, 5.5, 5.75, 6.5, 7]
|
|
def get_top_graphs():
|
|
a = np.exp(a_mob.get_center()[0])
|
|
graphs = VGroup(*[
|
|
time_axes.get_graph(lambda t : np.exp(-5*a*(t-x)**2))
|
|
for x in x_values
|
|
])
|
|
graphs.set_color(WHITE)
|
|
graphs.color_using_background_image("blue_yellow_gradient")
|
|
return graphs
|
|
def get_bottom_graphs():
|
|
a = np.exp(a_mob.get_center()[0])
|
|
graphs = VGroup(*[
|
|
frequency_axes.get_graph(lambda t : np.exp(-(5./a)*(t-v)**2))
|
|
for v in v_values
|
|
])
|
|
graphs.set_color(RED)
|
|
return graphs
|
|
|
|
top_graphs = get_top_graphs()
|
|
bottom_graphs = get_bottom_graphs()
|
|
update_top_graphs = Mobject.add_updater(
|
|
top_graphs,
|
|
lambda g : Transform(g, get_top_graphs()).update(1)
|
|
)
|
|
update_bottom_graphs = Mobject.add_updater(
|
|
bottom_graphs,
|
|
lambda g : Transform(g, get_bottom_graphs()).update(1)
|
|
)
|
|
|
|
self.add(time_axes, frequency_axes)
|
|
self.add(update_top_graphs, update_bottom_graphs)
|
|
|
|
shift_vect = 2*RIGHT
|
|
for s in 1, -2, 1:
|
|
self.play(a_mob.shift, s*shift_vect, run_time = 3)
|
|
|
|
class MentionUncertaintyPrincipleCopy(MentionUncertaintyPrinciple):
|
|
pass
|
|
|
|
class IntroduceDeBroglie(Scene):
|
|
CONFIG = {
|
|
"default_wave_frequency" : 1,
|
|
"wave_colors" : [BLUE_D, YELLOW],
|
|
"dispersion_factor" : 1,
|
|
"amplitude" : 1,
|
|
}
|
|
def construct(self):
|
|
text_scale_val = 0.8,
|
|
|
|
#Overlay real tower in video editor
|
|
eiffel_tower = Line(3*DOWN, 3*UP, stroke_width = 0)
|
|
picture = ImageMobject("de_Broglie")
|
|
picture.set_height(4)
|
|
picture.to_corner(UP+LEFT)
|
|
name = TextMobject("Louis de Broglie")
|
|
name.next_to(picture, DOWN)
|
|
|
|
picture.save_state()
|
|
picture.scale(0)
|
|
picture.move_to(eiffel_tower.get_top())
|
|
|
|
|
|
broadcasts = [
|
|
Broadcast(
|
|
eiffel_tower.get_top(),
|
|
big_radius = 10,
|
|
n_circles = 10,
|
|
lag_ratio = 0.9,
|
|
run_time = 7,
|
|
rate_func = squish_rate_func(smooth, a, a+0.3),
|
|
color = WHITE,
|
|
)
|
|
for a in np.linspace(0, 0.7, 3)
|
|
]
|
|
|
|
self.play(*broadcasts)
|
|
self.play(picture.restore)
|
|
self.play(Write(name))
|
|
self.wait()
|
|
|
|
#Time line
|
|
time_line = NumberLine(
|
|
x_min = 1900,
|
|
x_max = 1935,
|
|
tick_frequency = 1,
|
|
numbers_with_elongated_ticks = list(range(1900, 1941, 10)),
|
|
color = BLUE_D
|
|
)
|
|
time_line.stretch_to_fit_width(FRAME_WIDTH - picture.get_width() - 2)
|
|
time_line.add_numbers(*time_line.numbers_with_elongated_ticks)
|
|
time_line.next_to(picture, RIGHT, MED_LARGE_BUFF, DOWN)
|
|
|
|
year_to_words = {
|
|
1914 : "Wold War I begins",
|
|
1915 : "Einstein field equations",
|
|
1916 : "Lewis dot formulas",
|
|
1917 : "Not a lot of physics...because war",
|
|
1918 : "S'more Rutherford badassery",
|
|
1919 : "Eddington confirms general relativity predictions",
|
|
1920 : "World is generally stoked on general relativity",
|
|
1921 : "Einstein gets long overdue Nobel prize",
|
|
1922 : "Stern-Gerlach Experiment",
|
|
1923 : "Compton scattering observed",
|
|
1924 : "de Broglie's thesis"
|
|
}
|
|
arrow = Vector(DOWN, color = WHITE)
|
|
arrow.next_to(time_line.number_to_point(1914), UP)
|
|
words = TextMobject(year_to_words[1914])
|
|
words.scale(text_scale_val)
|
|
date = Integer(1914)
|
|
date.next_to(arrow, UP, LARGE_BUFF)
|
|
|
|
def get_year(alpha = 0):
|
|
return int(time_line.point_to_number(arrow.get_end()))
|
|
|
|
def update_words(words):
|
|
text = year_to_words.get(get_year(), "Hi there")
|
|
if text not in words.get_tex_string():
|
|
words.__init__(text)
|
|
words.scale(text_scale_val)
|
|
words.move_to(interpolate(
|
|
arrow.get_top(), date.get_bottom(), 0.5
|
|
))
|
|
update_words(words)
|
|
self.play(
|
|
FadeIn(time_line),
|
|
GrowArrow(arrow),
|
|
Write(words),
|
|
Write(date),
|
|
run_time = 1
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
arrow.next_to, time_line.number_to_point(1924), UP,
|
|
ChangingDecimal(
|
|
date, get_year,
|
|
position_update_func = lambda m : m.next_to(arrow, UP, LARGE_BUFF)
|
|
),
|
|
UpdateFromFunc(words, update_words),
|
|
run_time = 3,
|
|
)
|
|
self.wait()
|
|
|
|
#Transform time_line
|
|
line = time_line
|
|
self.play(
|
|
FadeOut(time_line.numbers),
|
|
VGroup(arrow, words, date).shift, MED_LARGE_BUFF*UP,
|
|
*[
|
|
ApplyFunction(
|
|
lambda m : m.rotate(TAU/4).set_stroke(width = 0),
|
|
mob,
|
|
remover = True
|
|
)
|
|
for mob in time_line.tick_marks
|
|
]
|
|
)
|
|
|
|
#Wave function
|
|
particle = VectorizedPoint()
|
|
axes = Axes(x_min = -1, x_max = 10)
|
|
axes.match_width(line)
|
|
axes.shift(line.get_center() - axes.x_axis.get_center())
|
|
im_line = line.copy()
|
|
im_line.set_color(YELLOW)
|
|
wave_update_animation = self.get_wave_update_animation(
|
|
axes, particle, line, im_line
|
|
)
|
|
|
|
for x in range(3):
|
|
particle.move_to(axes.coords_to_point(-10, 0))
|
|
self.play(
|
|
ApplyMethod(
|
|
particle.move_to, axes.coords_to_point(22, 0),
|
|
rate_func=linear
|
|
),
|
|
wave_update_animation,
|
|
run_time = 3
|
|
)
|
|
self.wait()
|
|
|
|
###
|
|
def get_wave_update_animation(self, axes, particle, re_line = None, im_line = None):
|
|
line = Line(
|
|
axes.x_axis.get_left(),
|
|
axes.x_axis.get_right(),
|
|
)
|
|
if re_line is None:
|
|
re_line = line.copy()
|
|
re_line.set_color(self.wave_colors[0])
|
|
if im_line is None:
|
|
im_line = line.copy()
|
|
im_line.set_color(self.wave_colors[1])
|
|
lines = VGroup(im_line, re_line)
|
|
def update_lines(lines):
|
|
waves = self.get_wave_pair(axes, particle)
|
|
for line, wave in zip(lines, waves):
|
|
wave.match_style(line)
|
|
Transform(line, wave).update(1)
|
|
return UpdateFromFunc(lines, update_lines)
|
|
|
|
def get_wave(
|
|
self, axes, particle,
|
|
complex_to_real_func = lambda z : z.real,
|
|
freq = None,
|
|
**kwargs):
|
|
freq = freq or self.default_wave_frequency
|
|
k0 = 1./freq
|
|
t0 = axes.x_axis.point_to_number(particle.get_center())
|
|
def func(x):
|
|
dispersion = fdiv(1., self.dispersion_factor)*(np.sqrt(1./(1+t0**2)))
|
|
wave_part = complex_to_real_func(np.exp(
|
|
complex(0, TAU*freq*(x-dispersion))
|
|
))
|
|
bell_part = np.exp(-dispersion*(x-t0)**2)
|
|
amplitude = self.amplitude
|
|
return amplitude*wave_part*bell_part
|
|
graph = axes.get_graph(func)
|
|
return graph
|
|
|
|
def get_wave_pair(self, axes, particle, colors = None, **kwargs):
|
|
if colors is None and "color" not in kwargs:
|
|
colors = self.wave_colors
|
|
return VGroup(*[
|
|
self.get_wave(
|
|
axes, particle,
|
|
C_to_R, color = color,
|
|
**kwargs
|
|
)
|
|
for C_to_R, color in zip(
|
|
[lambda z : z.imag, lambda z : z.real],
|
|
colors
|
|
)
|
|
])
|
|
|
|
class ShowMomentumFormula(IntroduceDeBroglie, TeacherStudentsScene):
|
|
CONFIG = {
|
|
"default_wave_frequency" : 2,
|
|
"dispersion_factor" : 0.25,
|
|
"p_color" : BLUE,
|
|
"xi_color" : YELLOW,
|
|
"amplitude" : 0.5,
|
|
}
|
|
def construct(self):
|
|
self.introduce_formula()
|
|
self.react_to_claim()
|
|
|
|
def introduce_formula(self):
|
|
formula = p, eq, h, xi = TexMobject("p", "=", "h", "\\xi")
|
|
formula.move_to(ORIGIN)
|
|
formula.scale(1.5)
|
|
|
|
word_shift_val = 1.75
|
|
p_words = TextMobject("Momentum")
|
|
p_words.next_to(p, UP, LARGE_BUFF).shift(word_shift_val*LEFT)
|
|
p_arrow = Arrow(
|
|
p_words.get_bottom(), p.get_corner(UP+LEFT),
|
|
buff = SMALL_BUFF
|
|
)
|
|
added_p_words = TextMobject("(Classically $m \\times v$)")
|
|
added_p_words.move_to(p_words, DOWN)
|
|
VGroup(p, p_words, added_p_words, p_arrow).set_color(self.p_color)
|
|
|
|
xi_words = TextMobject("Spatial frequency")
|
|
added_xi_words = TextMobject("(cycles per unit \\emph{distance})")
|
|
xi_words.next_to(xi, UP, LARGE_BUFF).shift(word_shift_val*RIGHT)
|
|
xi_words.align_to(p_words)
|
|
xi_arrow = Arrow(
|
|
xi_words.get_bottom(), xi.get_corner(UP+RIGHT),
|
|
buff = SMALL_BUFF
|
|
)
|
|
added_xi_words.move_to(xi_words, DOWN)
|
|
added_xi_words.align_to(added_p_words, DOWN)
|
|
VGroup(xi, xi_words, added_xi_words, xi_arrow).set_color(self.xi_color)
|
|
|
|
axes = Axes(
|
|
x_min = 0, x_max = FRAME_WIDTH,
|
|
y_min = -1, y_max = 1,
|
|
)
|
|
axes.center().to_edge(UP, buff = -0.5)
|
|
# axes.next_to(formula, RIGHT)
|
|
particle = VectorizedPoint()
|
|
wave_update_animation = self.get_wave_update_animation(axes, particle)
|
|
wave = wave_update_animation.mobject
|
|
wave[0].set_stroke(width = 0)
|
|
particle.next_to(wave, LEFT, buff = 2)
|
|
wave_propagation = AnimationGroup(
|
|
ApplyMethod(particle.move_to, axes.coords_to_point(30, 0)),
|
|
wave_update_animation,
|
|
run_time = 4,
|
|
rate_func=linear,
|
|
)
|
|
stopped_wave_propagation = AnimationGroup(
|
|
ApplyMethod(particle.move_to, xi_words),
|
|
wave_update_animation,
|
|
run_time = 3,
|
|
rate_func=linear,
|
|
)
|
|
n_v_lines = 10
|
|
v_lines = VGroup(*[
|
|
DashedLine(UP, DOWN)
|
|
for x in range(n_v_lines)
|
|
])
|
|
v_lines.match_color(xi)
|
|
v_lines.arrange(
|
|
RIGHT,
|
|
buff = float(axes.x_axis.unit_size)/self.default_wave_frequency
|
|
)
|
|
v_lines.move_to(stopped_wave_propagation.sub_anims[0].target_mobject)
|
|
v_lines.align_to(wave)
|
|
v_lines.shift(0.125*RIGHT)
|
|
|
|
self.add(formula, wave)
|
|
self.play(
|
|
self.teacher.change, "raise_right_hand",
|
|
GrowArrow(p_arrow),
|
|
Succession(
|
|
Write, p_words,
|
|
ApplyMethod, p_words.next_to, added_p_words, UP,
|
|
),
|
|
FadeIn(
|
|
added_p_words,
|
|
rate_func = squish_rate_func(smooth, 0.5, 1),
|
|
run_time = 2,
|
|
),
|
|
wave_propagation
|
|
)
|
|
self.play(
|
|
Write(xi_words),
|
|
GrowArrow(xi_arrow),
|
|
self.get_student_changes("confused", "erm", "sassy"),
|
|
stopped_wave_propagation
|
|
)
|
|
self.play(
|
|
FadeIn(added_xi_words),
|
|
xi_words.next_to, added_xi_words, UP,
|
|
)
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, v_lines),
|
|
self.get_student_changes(*["pondering"]*3)
|
|
)
|
|
self.play(LaggedStartMap(FadeOut, v_lines))
|
|
self.wait()
|
|
|
|
self.formula_labels = VGroup(
|
|
p_words, p_arrow, added_p_words,
|
|
xi_words, xi_arrow, added_xi_words,
|
|
)
|
|
self.set_variables_as_attrs(wave, wave_propagation, formula)
|
|
|
|
def react_to_claim(self):
|
|
formula_labels = self.formula_labels
|
|
full_formula = VGroup(self.formula, formula_labels)
|
|
full_formula.save_state()
|
|
wave_propagation = self.wave_propagation
|
|
|
|
student = self.students[2]
|
|
self.student_says(
|
|
"Hang on...",
|
|
bubble_kwargs = {"height" : 2, "width" : 2, "direction" : LEFT},
|
|
target_mode = "sassy",
|
|
student_index = 2,
|
|
added_anims = [self.teacher.change, "plain"]
|
|
)
|
|
student.bubble.add(student.bubble.content)
|
|
self.wait()
|
|
kwargs = {
|
|
"path_arc" : TAU/4,
|
|
"lag_ratio" : 0.5,
|
|
"lag_ratio" : 0.7,
|
|
"run_time" : 1.5,
|
|
}
|
|
self.play(
|
|
full_formula.scale, 0,
|
|
full_formula.move_to, student.eyes.get_bottom()+SMALL_BUFF*DOWN,
|
|
Animation(student.bubble),
|
|
**kwargs
|
|
)
|
|
self.play(full_formula.restore, Animation(student.bubble), **kwargs)
|
|
wave_propagation.update_config(
|
|
rate_func = lambda a : interpolate(0.35, 1, a)
|
|
)
|
|
self.play(
|
|
wave_propagation,
|
|
RemovePiCreatureBubble(student, target_mode = "confused"),
|
|
)
|
|
wave_propagation.update_config(rate_func = lambda t : t)
|
|
self.student_says(
|
|
"Physics is \\\\ just weird",
|
|
bubble_kwargs = {"height" : 2.5, "width" : 3},
|
|
target_mode = "shruggie",
|
|
student_index = 0,
|
|
added_anims = [ApplyMethod(full_formula.shift, UP)]
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
wave_propagation,
|
|
ApplyMethod(full_formula.shift, DOWN),
|
|
FadeOut(self.students[0].bubble),
|
|
FadeOut(self.students[0].bubble.content),
|
|
self.get_student_changes(*3*["pondering"]),
|
|
self.teacher.change, "pondering",
|
|
)
|
|
self.play(wave_propagation)
|
|
|
|
class AskPhysicists(PiCreatureScene):
|
|
def construct(self):
|
|
morty, physy1, physy2, physy3 = self.pi_creatures
|
|
formula = TexMobject("p", "=", "h", "\\xi")
|
|
formula.set_color_by_tex_to_color_map({
|
|
"p" : BLUE,
|
|
"\\xi" : YELLOW,
|
|
})
|
|
formula.scale(1.5)
|
|
|
|
formula.to_edge(UP)
|
|
formula.save_state()
|
|
formula.shift(DOWN)
|
|
formula.fade(1)
|
|
self.play(formula.restore)
|
|
self.pi_creature_says(
|
|
morty, "So...why?",
|
|
target_mode = "maybe"
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
RemovePiCreatureBubble(morty),
|
|
PiCreatureSays(
|
|
physy2,
|
|
"Take the Schrödinger equation \\\\ with $H = \\frac{p^2}{2m}+V(x)$",
|
|
bubble_kwargs = {"fill_opacity" : 0.9},
|
|
),
|
|
)
|
|
self.play(
|
|
PiCreatureSays(
|
|
physy1,
|
|
"Even classically position and \\\\ momentum are conjugate",
|
|
target_mode = "surprised",
|
|
bubble_kwargs = {"fill_opacity" : 0.9},
|
|
),
|
|
)
|
|
self.play(
|
|
PiCreatureSays(
|
|
physy3,
|
|
"Consider special relativity \\\\ together with $E = hf$",
|
|
target_mode = "hooray",
|
|
bubble_kwargs = {"fill_opacity" : 0.9},
|
|
),
|
|
morty.change, "guilty"
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
|
|
###
|
|
|
|
def create_pi_creatures(self):
|
|
scale_factor = 0.85
|
|
morty = Mortimer().flip()
|
|
morty.scale(scale_factor)
|
|
morty.to_corner(DOWN+LEFT)
|
|
|
|
physies = VGroup(*[
|
|
PiCreature(color = c).flip()
|
|
for c in (GREY, LIGHT_GREY, DARK_GREY)
|
|
])
|
|
physies.arrange(RIGHT, buff = MED_SMALL_BUFF)
|
|
physies.scale(scale_factor)
|
|
physies.to_corner(DOWN+RIGHT)
|
|
|
|
self.add(physies)
|
|
return VGroup(morty, *physies)
|
|
|
|
class SortOfDopplerEffect(PiCreatureScene):
|
|
CONFIG = {
|
|
"omega" : np.pi,
|
|
"arrow_spacing" : 0.25,
|
|
}
|
|
def setup(self):
|
|
PiCreatureScene.setup(self)
|
|
rect = self.screen_rect = ScreenRectangle(height = FRAME_HEIGHT)
|
|
rect.set_stroke(width = 0)
|
|
self.camera = MovingCamera(
|
|
rect, **self.camera_config
|
|
)
|
|
|
|
def construct(self):
|
|
screen_rect = self.screen_rect
|
|
|
|
#x-coordinate gives time
|
|
t_tracker = VectorizedPoint()
|
|
#x-coordinate gives wave number
|
|
k_tracker = VectorizedPoint(2*RIGHT)
|
|
always_shift(t_tracker, RIGHT, 1)
|
|
def get_wave():
|
|
t = t_tracker.get_center()[0]
|
|
k = k_tracker.get_center()[0]
|
|
omega = self.omega
|
|
color = interpolate_color(
|
|
BLUE, RED, (k-2)/2.0
|
|
)
|
|
func = lambda x : 0.5*np.cos(omega*t - k*x)
|
|
graph = FunctionGraph(
|
|
func,
|
|
x_min = -5*FRAME_X_RADIUS,
|
|
x_max = FRAME_X_RADIUS,
|
|
color = color,
|
|
)
|
|
return VGroup(graph, *[
|
|
Arrow(
|
|
x*RIGHT, x*RIGHT + func(x)*UP,
|
|
color = color
|
|
)
|
|
for x in np.arange(
|
|
-4*FRAME_X_RADIUS, FRAME_X_RADIUS,
|
|
self.arrow_spacing
|
|
)
|
|
])
|
|
return
|
|
wave = get_wave()
|
|
wave_update = Mobject.add_updater(
|
|
wave, lambda w : Transform(w, get_wave()).update(1)
|
|
)
|
|
|
|
rect = ScreenRectangle(height = 2)
|
|
rect.to_edge(RIGHT)
|
|
always_shift(rect, LEFT, 1)
|
|
rect_movement = rect
|
|
|
|
randy = self.pi_creature
|
|
randy_look_at = Mobject.add_updater(
|
|
randy, lambda r : r.look_at(rect)
|
|
)
|
|
|
|
ref_frame1 = TextMobject("Reference frame 1")
|
|
# ref_frame1.next_to(randy, UP, aligned_edge = LEFT)
|
|
ref_frame1.to_edge(UP)
|
|
ref_frame2 = TextMobject("Reference frame 2")
|
|
ref_frame2.next_to(rect, UP)
|
|
# ref_frame2.set_fill(opacity = 0)
|
|
ref_frame2_follow = Mobject.add_updater(
|
|
ref_frame2, lambda m : m.next_to(rect, UP)
|
|
)
|
|
ref_frame_1_continual_anim = ContinualAnimation(ref_frame1)
|
|
|
|
self.add(
|
|
t_tracker, wave_update, rect_movement, randy_look_at,
|
|
ref_frame2_follow, ref_frame_1_continual_anim
|
|
)
|
|
self.add(ref_frame1)
|
|
self.play(randy.change, "pondering")
|
|
self.wait(4)
|
|
start_height = screen_rect.get_height()
|
|
start_center = screen_rect.get_center()
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
screen_rect,
|
|
lambda m, a : m.move_to(
|
|
interpolate(start_center, rect.get_center(), a)
|
|
)
|
|
),
|
|
k_tracker.shift, 2*RIGHT,
|
|
)
|
|
self.play(
|
|
MaintainPositionRelativeTo(
|
|
screen_rect, rect,
|
|
run_time = 4
|
|
),
|
|
)
|
|
self.play(
|
|
screen_rect.move_to, rect.get_right()+FRAME_X_RADIUS*LEFT,
|
|
k_tracker.shift, 2*LEFT,
|
|
)
|
|
|
|
#Frequency words
|
|
temporal_frequency = TextMobject("Temporal", "frequency")
|
|
spatial_frequency = TextMobject("Spatial", "frequency")
|
|
temporal_frequency.move_to(screen_rect).to_edge(UP)
|
|
spatial_frequency.next_to(temporal_frequency, DOWN)
|
|
cross = Cross(temporal_frequency[0])
|
|
|
|
time = TextMobject("Time")
|
|
space = TextMobject("Space")
|
|
time.next_to(temporal_frequency, RIGHT, buff = 2)
|
|
space.next_to(time, DOWN)
|
|
space.align_to(spatial_frequency)
|
|
|
|
self.play(FadeIn(temporal_frequency))
|
|
self.play(ShowCreation(cross))
|
|
self.play(Write(spatial_frequency))
|
|
self.wait()
|
|
self.play(FadeIn(time), FadeIn(space))
|
|
self.play(
|
|
Transform(time, space),
|
|
Transform(space, time),
|
|
lag_ratio = 0.5,
|
|
run_time = 1,
|
|
)
|
|
self.play(FadeOut(time), FadeOut(space))
|
|
self.wait(3)
|
|
|
|
###
|
|
|
|
def create_pi_creature(self):
|
|
return Randolph().scale(0.5).to_corner(DOWN+LEFT)
|
|
|
|
class HangingWeightsScene(MovingCameraScene):
|
|
CONFIG = {
|
|
"frequency" : 0.5,
|
|
"ceiling_radius" : 3*FRAME_X_RADIUS,
|
|
"n_springs" : 72,
|
|
"amplitude" : 0.6,
|
|
"spring_radius" : 0.15,
|
|
}
|
|
def construct(self):
|
|
self.setup_springs()
|
|
self.setup_weights()
|
|
self.introduce()
|
|
self.show_analogy_with_electron()
|
|
self.metaphor_for_something()
|
|
self.moving_reference_frame()
|
|
|
|
def setup_springs(self):
|
|
ceiling = self.ceiling = Line(LEFT, RIGHT)
|
|
ceiling.scale(self.ceiling_radius)
|
|
ceiling.to_edge(UP, buff = LARGE_BUFF)
|
|
self.add(ceiling)
|
|
|
|
def get_spring(alpha, height = 2):
|
|
t_max = 6.5
|
|
r = self.spring_radius
|
|
s = (height - r)/(t_max**2)
|
|
spring = ParametricFunction(
|
|
lambda t : op.add(
|
|
r*(np.sin(TAU*t)*RIGHT+np.cos(TAU*t)*UP),
|
|
s*((t_max - t)**2)*DOWN,
|
|
),
|
|
t_min = 0, t_max = t_max,
|
|
color = WHITE,
|
|
stroke_width = 2,
|
|
)
|
|
spring.alpha = alpha
|
|
spring.move_to(ceiling.point_from_proportion(alpha), UP)
|
|
spring.color_using_background_image("grey_gradient")
|
|
return spring
|
|
alphas = np.linspace(0, 1, self.n_springs)
|
|
bezier([0, 1, 0, 1])
|
|
springs = self.springs = VGroup(*list(map(get_spring, alphas)))
|
|
|
|
k_tracker = self.k_tracker = VectorizedPoint()
|
|
t_tracker = self.t_tracker = VectorizedPoint()
|
|
always_shift(t_tracker, RIGHT, 1)
|
|
self.t_tracker_walk = t_tracker
|
|
equilibrium_height = springs.get_height()
|
|
def update_springs(springs):
|
|
for spring in springs:
|
|
k = k_tracker.get_center()[0]
|
|
t = t_tracker.get_center()[0]
|
|
f = self.frequency
|
|
x = spring.get_top()[0]
|
|
A = self.amplitude
|
|
d_height = A*np.cos(TAU*f*t - k*x)
|
|
new_spring = get_spring(spring.alpha, 2+d_height)
|
|
Transform(spring, new_spring).update(1)
|
|
spring_update_anim = Mobject.add_updater(springs, update_springs)
|
|
self.spring_update_anim = spring_update_anim
|
|
spring_update_anim.update(0)
|
|
|
|
self.play(
|
|
ShowCreation(ceiling),
|
|
LaggedStartMap(ShowCreation, springs)
|
|
)
|
|
|
|
def setup_weights(self):
|
|
weights = self.weights = VGroup()
|
|
weight_anims = weight_anims = []
|
|
for spring in self.springs:
|
|
x = spring.get_top()[0]
|
|
mass = np.exp(-0.1*x**2)
|
|
weight = Circle(radius = 0.15)
|
|
weight.start_radius = 0.15
|
|
weight.target_radius = 0.25*mass #For future update
|
|
weight.spring = spring
|
|
weight_anim = Mobject.add_updater(
|
|
weight, lambda w : w.move_to(w.spring.get_bottom())
|
|
)
|
|
weight_anim.update(0)
|
|
weight_anims.append(weight_anim)
|
|
weights.add(weight)
|
|
weights.set_fill(opacity = 1)
|
|
weights.set_color_by_gradient(BLUE_D, BLUE_E, BLUE_D)
|
|
weights.set_stroke(WHITE, 1)
|
|
|
|
self.play(LaggedStartMap(GrowFromCenter, weights))
|
|
self.add(self.t_tracker_walk)
|
|
self.add(self.spring_update_anim)
|
|
self.add(*weight_anims)
|
|
|
|
def introduce(self):
|
|
arrow = Arrow(4*LEFT, LEFT)
|
|
arrows = VGroup(arrow, arrow.copy().flip(about_point = ORIGIN))
|
|
arrows.set_color(WHITE)
|
|
|
|
self.wait(3)
|
|
self.play(*list(map(GrowArrow, arrows)))
|
|
self.play(*[
|
|
UpdateFromAlphaFunc(
|
|
weight, lambda w, a : w.set_width(
|
|
2*interpolate(w.start_radius, w.target_radius, a)
|
|
),
|
|
run_time = 2
|
|
)
|
|
for weight in self.weights
|
|
])
|
|
self.play(FadeOut(arrows))
|
|
self.wait(3)
|
|
|
|
def show_analogy_with_electron(self):
|
|
words = TextMobject(
|
|
"Analogous to the energy of a particle \\\\",
|
|
"(in the sense of $E=mc^2$)"
|
|
)
|
|
words.move_to(DOWN)
|
|
|
|
self.play(Write(words))
|
|
self.wait(3)
|
|
self.play(FadeOut(words))
|
|
|
|
def metaphor_for_something(self):
|
|
de_broglie = ImageMobject("de_Broglie")
|
|
de_broglie.set_height(3.5)
|
|
de_broglie.to_corner(DOWN+RIGHT)
|
|
words = TextMobject("""
|
|
If a photon's energy is carried as a wave \\\\
|
|
is this true for any particle?
|
|
""")
|
|
words.next_to(de_broglie, LEFT)
|
|
|
|
einstein = ImageMobject("Einstein")
|
|
einstein.match_height(de_broglie)
|
|
einstein.to_corner(DOWN+LEFT)
|
|
|
|
for picture in de_broglie, einstein:
|
|
picture.backdrop = Rectangle()
|
|
picture.backdrop.replace(picture, stretch = True)
|
|
picture.backdrop.set_fill(BLACK, 1)
|
|
picture.backdrop.set_stroke(BLACK, 0)
|
|
|
|
self.play(
|
|
Animation(de_broglie.backdrop, remover = True),
|
|
FadeIn(de_broglie)
|
|
)
|
|
self.play(Write(words))
|
|
self.wait(7)
|
|
self.play(
|
|
FadeOut(words),
|
|
Animation(einstein.backdrop, remover = True),
|
|
FadeIn(einstein)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.de_broglie = de_broglie
|
|
self.einstein = einstein
|
|
|
|
def moving_reference_frame(self):
|
|
rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS)
|
|
rect_movement = always_shift(rect, direction = LEFT, rate = 2)
|
|
camera_frame = self.camera_frame
|
|
|
|
self.add(rect)
|
|
self.play(
|
|
Animation(self.de_broglie.backdrop, remover = True),
|
|
FadeOut(self.de_broglie),
|
|
Animation(self.einstein.backdrop, remover = True),
|
|
FadeOut(self.einstein),
|
|
)
|
|
self.play(camera_frame.scale, 3, {"about_point" : 2*UP})
|
|
self.play(rect.shift, FRAME_WIDTH*RIGHT, path_arc = -TAU/2)
|
|
self.add(rect_movement)
|
|
self.wait(3)
|
|
|
|
def zoom_into_reference_frame():
|
|
original_height = camera_frame.get_height()
|
|
original_center = camera_frame.get_center()
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
camera_frame, lambda c, a : c.set_height(
|
|
interpolate(original_height, 0.95*rect.get_height(), a)
|
|
).move_to(
|
|
interpolate(original_center, rect.get_center(), a)
|
|
)
|
|
),
|
|
ApplyMethod(self.k_tracker.shift, RIGHT)
|
|
)
|
|
self.play(MaintainPositionRelativeTo(
|
|
camera_frame, rect,
|
|
run_time = 6
|
|
))
|
|
self.play(
|
|
camera_frame.set_height, original_height,
|
|
camera_frame.move_to, original_center,
|
|
ApplyMethod(self.k_tracker.shift, LEFT)
|
|
)
|
|
|
|
zoom_into_reference_frame()
|
|
self.wait()
|
|
self.play(
|
|
UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*(1-a)))
|
|
)
|
|
|
|
index = int(0.5*len(self.springs))
|
|
weights = VGroup(self.weights[index], self.weights[index+4])
|
|
flashes = list(map(self.get_peak_flash_anim, weights))
|
|
weights.save_state()
|
|
weights.set_fill(RED)
|
|
self.add(*flashes)
|
|
self.wait(5)
|
|
|
|
rect.align_to(camera_frame, RIGHT)
|
|
self.play(UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*a)))
|
|
|
|
randy = Randolph(mode = "pondering")
|
|
randy.look(UP+RIGHT)
|
|
de_broglie = ImageMobject("de_Broglie")
|
|
de_broglie.set_height(6)
|
|
de_broglie.next_to(4*DOWN, DOWN)
|
|
self.add(
|
|
Mobject.add_updater(
|
|
randy, lambda m : m.next_to(
|
|
rect.get_corner(DOWN+LEFT), UP+RIGHT, MED_LARGE_BUFF,
|
|
).look_at(weights)
|
|
),
|
|
de_broglie
|
|
)
|
|
self.wait(2)
|
|
|
|
zoom_into_reference_frame()
|
|
self.wait(8)
|
|
|
|
###
|
|
|
|
def get_peak_flash_anim(self, weight):
|
|
mobject = Mobject() #Dummy
|
|
mobject.last_y = 0
|
|
mobject.last_dy = 0
|
|
mobject.curr_anim = None
|
|
mobject.curr_anim_time = 0
|
|
mobject.time_since_last_flash = 0
|
|
def update(mob, dt):
|
|
mob.time_since_last_flash += dt
|
|
point = weight.get_center()
|
|
y = point[1]
|
|
mob.dy = y - mob.last_y
|
|
different_dy = np.sign(mob.dy) != np.sign(mob.last_dy)
|
|
if different_dy and mob.time_since_last_flash > 0.5:
|
|
mob.curr_anim = Flash(
|
|
VectorizedPoint(point),
|
|
flash_radius = 0.5,
|
|
line_length = 0.3,
|
|
run_time = 0.2,
|
|
)
|
|
mob.submobjects = [mob.curr_anim.mobject]
|
|
mob.time_since_last_flash = 0
|
|
mob.last_y = float(y)
|
|
mob.last_dy = float(mob.dy)
|
|
##
|
|
if mob.curr_anim:
|
|
mob.curr_anim_time += dt
|
|
if mob.curr_anim_time > mob.curr_anim.run_time:
|
|
mob.curr_anim = None
|
|
mob.submobjects = []
|
|
mob.curr_anim_time = 0
|
|
return
|
|
mob.curr_anim.update(mob.curr_anim_time/mob.curr_anim.run_time)
|
|
|
|
return Mobject.add_updater(mobject, update)
|
|
|
|
class MinutPhysicsWrapper(Scene):
|
|
def construct(self):
|
|
logo = ImageMobject("minute_physics_logo", invert = True)
|
|
logo.to_corner(UP+LEFT)
|
|
self.add(logo)
|
|
|
|
title = TextMobject("Minute Physics on special relativity")
|
|
title.to_edge(UP).shift(MED_LARGE_BUFF*RIGHT)
|
|
|
|
screen_rect = ScreenRectangle()
|
|
screen_rect.set_width(title.get_width() + LARGE_BUFF)
|
|
screen_rect.next_to(title, DOWN)
|
|
|
|
self.play(ShowCreation(screen_rect))
|
|
self.play(Write(title))
|
|
self.wait(2)
|
|
|
|
class WhatDoesTheFourierTradeoffTellUs(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says(
|
|
"So! What does \\\\ the Fourier trade-off \\\\ tell us?",
|
|
target_mode = "surprised",
|
|
bubble_kwargs = {"width" : 4, "height" : 3}
|
|
)
|
|
self.change_student_modes(*["thinking"]*3)
|
|
self.wait(4)
|
|
|
|
class FourierTransformOfWaveFunction(Scene):
|
|
CONFIG = {
|
|
"wave_stroke_width" : 3,
|
|
"wave_color" : BLUE,
|
|
}
|
|
def construct(self):
|
|
self.show_wave_packet()
|
|
self.take_fourier_transform()
|
|
self.show_correlations_with_pure_frequencies()
|
|
self.this_is_momentum()
|
|
self.show_tradeoff()
|
|
|
|
def setup(self):
|
|
self.x0_tracker = ValueTracker(-3)
|
|
self.k_tracker = ValueTracker(1)
|
|
self.a_tracker = ExponentialValueTracker(0.5)
|
|
|
|
def show_wave_packet(self):
|
|
axes = Axes(
|
|
x_min = 0, x_max = 12,
|
|
y_min = -1, y_max = 1,
|
|
y_axis_config = {
|
|
"tick_frequency" : 0.5
|
|
}
|
|
)
|
|
position_label = TextMobject("Position")
|
|
position_label.next_to(axes.x_axis.get_right(), UP)
|
|
axes.add(position_label)
|
|
axes.center().to_edge(UP, buff = LARGE_BUFF)
|
|
|
|
wave = self.get_wave(axes)
|
|
wave_update_animation = UpdateFromFunc(
|
|
wave, lambda w : Transform(w, self.get_wave(axes)).update(1)
|
|
)
|
|
|
|
self.add(axes, wave)
|
|
self.play(
|
|
self.x0_tracker.set_value, 5,
|
|
wave_update_animation,
|
|
run_time = 3,
|
|
)
|
|
self.wait()
|
|
|
|
self.wave_function = wave.underlying_function
|
|
self.wave_update_animation = wave_update_animation
|
|
self.wave = wave
|
|
self.axes = axes
|
|
|
|
def take_fourier_transform(self):
|
|
wave = self.wave
|
|
wave_update_animation = self.wave_update_animation
|
|
frequency_axes = Axes(
|
|
x_min = 0, x_max = 3,
|
|
x_axis_config = {
|
|
"unit_size" : 4,
|
|
"tick_frequency" : 0.25,
|
|
"numbers_with_elongated_ticks" : [1, 2]
|
|
},
|
|
y_min = -0.15,
|
|
y_max = 0.15,
|
|
y_axis_config = {
|
|
"unit_size" : 7.5,
|
|
"tick_frequency" : 0.05,
|
|
}
|
|
)
|
|
label = self.frequency_x_axis_label = TextMobject("Spatial frequency")
|
|
label.next_to(frequency_axes.x_axis.get_right(), UP)
|
|
frequency_axes.add(label)
|
|
frequency_axes.move_to(self.axes, LEFT)
|
|
frequency_axes.to_edge(DOWN, buff = LARGE_BUFF)
|
|
label.shift_onto_screen()
|
|
|
|
def get_wave_function_fourier_graph():
|
|
return get_fourier_graph(
|
|
frequency_axes, self.get_wave_func(),
|
|
t_min = 0, t_max = 15,
|
|
)
|
|
fourier_graph = get_wave_function_fourier_graph()
|
|
self.fourier_graph_update_animation = UpdateFromFunc(
|
|
fourier_graph, lambda m : Transform(
|
|
m, get_wave_function_fourier_graph()
|
|
).update(1)
|
|
)
|
|
|
|
wave_copy = wave.copy()
|
|
wave_copy.generate_target()
|
|
wave_copy.target.move_to(fourier_graph, LEFT)
|
|
wave_copy.target.fade(1)
|
|
fourier_graph.save_state()
|
|
fourier_graph.move_to(wave, LEFT)
|
|
fourier_graph.fade(1)
|
|
|
|
arrow = Arrow(
|
|
self.axes.coords_to_point(5, -1),
|
|
frequency_axes.coords_to_point(1, 0.1),
|
|
color = YELLOW,
|
|
)
|
|
fourier_label = TextMobject("Fourier Transform")
|
|
fourier_label.next_to(arrow.get_center(), RIGHT)
|
|
|
|
self.play(ReplacementTransform(
|
|
self.axes.copy(), frequency_axes
|
|
))
|
|
self.play(
|
|
MoveToTarget(wave_copy, remover = True),
|
|
fourier_graph.restore,
|
|
GrowArrow(arrow),
|
|
Write(fourier_label, run_time = 1),
|
|
)
|
|
self.wait()
|
|
|
|
self.frequency_axes = frequency_axes
|
|
self.fourier_graph = fourier_graph
|
|
self.fourier_label = VGroup(arrow, fourier_label)
|
|
|
|
def show_correlations_with_pure_frequencies(self):
|
|
frequency_axes = self.frequency_axes
|
|
axes = self.axes
|
|
|
|
sinusoid = axes.get_graph(
|
|
lambda x : 0.5*np.cos(TAU*x),
|
|
x_min = -FRAME_X_RADIUS, x_max = 3*FRAME_X_RADIUS,
|
|
)
|
|
sinusoid.to_edge(UP, buff = SMALL_BUFF)
|
|
|
|
v_line = DashedLine(1.5*UP, ORIGIN, color = YELLOW)
|
|
v_line.move_to(frequency_axes.coords_to_point(1, 0), DOWN)
|
|
|
|
f_equals = TexMobject("f = ")
|
|
freq_decimal = DecimalNumber(1)
|
|
freq_decimal.next_to(f_equals, RIGHT, buff = SMALL_BUFF)
|
|
freq_label = VGroup(f_equals, freq_decimal)
|
|
freq_label.next_to(
|
|
v_line, UP, SMALL_BUFF,
|
|
submobject_to_align = f_equals[0]
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(sinusoid),
|
|
ShowCreation(v_line),
|
|
Write(freq_label, run_time = 1),
|
|
FadeOut(self.fourier_label)
|
|
)
|
|
last_f = 1
|
|
for f in 1.4, 0.7, 1:
|
|
self.play(
|
|
sinusoid.stretch,f/last_f, 0,
|
|
{"about_point" : axes.coords_to_point(0, 0)},
|
|
v_line.move_to, frequency_axes.coords_to_point(f, 0), DOWN,
|
|
MaintainPositionRelativeTo(freq_label, v_line),
|
|
ChangeDecimalToValue(freq_decimal, f),
|
|
run_time = 3,
|
|
)
|
|
last_f = f
|
|
self.play(*list(map(FadeOut, [
|
|
sinusoid, v_line, freq_label
|
|
])))
|
|
|
|
def this_is_momentum(self):
|
|
formula = TexMobject("p", "=", "h", "\\xi")
|
|
formula.set_color_by_tex_to_color_map({
|
|
"p" : BLUE,
|
|
"xi" : YELLOW,
|
|
})
|
|
formula.next_to(
|
|
self.frequency_x_axis_label, UP
|
|
)
|
|
|
|
f_max = 0.12
|
|
brace = Brace(Line(2*LEFT, 2*RIGHT), UP)
|
|
brace.move_to(self.frequency_axes.coords_to_point(1, f_max), DOWN)
|
|
words = TextMobject("This wave \\\\ describes momentum")
|
|
words.next_to(brace, UP)
|
|
|
|
self.play(Write(formula))
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(words)
|
|
)
|
|
brace.add(words)
|
|
for k in 2, 0.5, 1:
|
|
self.play(
|
|
self.k_tracker.set_value, k,
|
|
self.wave_update_animation,
|
|
self.fourier_graph_update_animation,
|
|
UpdateFromFunc(
|
|
brace, lambda b : b.move_to(
|
|
self.frequency_axes.coords_to_point(
|
|
self.k_tracker.get_value(),
|
|
f_max,
|
|
),
|
|
DOWN
|
|
)
|
|
),
|
|
run_time = 2
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [brace, words, formula])))
|
|
|
|
def show_tradeoff(self):
|
|
for a in 5, 0.1, 0.01, 10, 0.5:
|
|
self.play(
|
|
ApplyMethod(
|
|
self.a_tracker.set_value, a,
|
|
run_time = 2
|
|
),
|
|
self.wave_update_animation,
|
|
self.fourier_graph_update_animation
|
|
)
|
|
self.wait()
|
|
|
|
##
|
|
|
|
def get_wave_func(self):
|
|
x0 = self.x0_tracker.get_value()
|
|
k = self.k_tracker.get_value()
|
|
a = self.a_tracker.get_value()
|
|
A = a**(0.25)
|
|
return lambda x : A*np.cos(TAU*k*x)*np.exp(-a*(x - x0)**2)
|
|
|
|
def get_wave(self, axes):
|
|
return axes.get_graph(
|
|
self.get_wave_func(),
|
|
color = self.wave_color,
|
|
stroke_width = self.wave_stroke_width
|
|
)
|
|
|
|
class DopplerComparisonTodos(TODOStub):
|
|
CONFIG = {
|
|
"message" : """
|
|
Insert some Doppler footage,
|
|
insert some hanging spring scene,
|
|
insert position-momentum Fourier trade-off
|
|
"""
|
|
}
|
|
|
|
class MusicalNote(AddingPureFrequencies):
|
|
def construct(self):
|
|
speaker = self.speaker = SVGMobject(file_name = "speaker")
|
|
speaker.move_to(2*DOWN)
|
|
randy = self.pi_creature
|
|
|
|
axes = Axes(
|
|
x_min = 0, x_max = 10,
|
|
y_min = -1.5, y_max = 1.5
|
|
)
|
|
axes.center().to_edge(UP)
|
|
time_label = TextMobject("Time")
|
|
time_label.next_to(axes.x_axis.get_right(), UP)
|
|
axes.add(time_label)
|
|
|
|
graph = axes.get_graph(
|
|
lambda x : op.mul(
|
|
np.exp(-0.2*(x-4)**2),
|
|
0.3*(np.cos(2*TAU*x) + np.cos(3*TAU*x) + np.cos(5*TAU*x)),
|
|
),
|
|
)
|
|
graph.set_color(BLUE)
|
|
v_line = DashedLine(ORIGIN, 0.5*UP)
|
|
v_line_update = UpdateFromFunc(
|
|
v_line, lambda l : l.put_start_and_end_on_with_projection(
|
|
graph.points[-1],
|
|
axes.x_axis.number_to_point(
|
|
axes.x_axis.point_to_number(graph.points[-1])
|
|
)
|
|
)
|
|
)
|
|
|
|
self.add(speaker, axes)
|
|
self.play(
|
|
randy.change, "pondering",
|
|
self.get_broadcast_animation(n_circles = 6, run_time = 5),
|
|
self.get_broadcast_animation(n_circles = 12, run_time = 5),
|
|
ShowCreation(graph, run_time = 5, rate_func=linear),
|
|
v_line_update
|
|
)
|
|
self.wait(2)
|
|
|
|
class AskAboutUncertainty(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"What does this have \\\\ to do with ``certainty''",
|
|
bubble_kwargs = {"direction" : LEFT},
|
|
student_index = 2
|
|
)
|
|
self.play(PiCreatureSays(
|
|
self.students[0],
|
|
"What even are \\\\ these waves?",
|
|
target_mode = "confused"
|
|
))
|
|
self.wait(2)
|
|
|
|
class ProbabalisticDetection(FourierTransformOfWaveFunction):
|
|
CONFIG = {
|
|
"wave_stroke_width" : 2,
|
|
}
|
|
def construct(self):
|
|
self.setup_wave()
|
|
self.detect_only_single_points()
|
|
self.show_probability_distribution()
|
|
self.show_concentration_of_the_wave()
|
|
|
|
def setup_wave(self):
|
|
axes = Axes(
|
|
x_min = 0, x_max = 10,
|
|
y_min = -0.5, y_max = 1.5,
|
|
y_axis_config = {
|
|
"unit_size" : 1.5,
|
|
"tick_frequency" : 0.25,
|
|
}
|
|
)
|
|
axes.set_stroke(width = 2)
|
|
axes.center()
|
|
self.x0_tracker.set_value(5)
|
|
self.k_tracker.set_value(1)
|
|
self.a_tracker.set_value(0.2)
|
|
wave = self.get_wave(axes)
|
|
self.wave_update_animation = UpdateFromFunc(
|
|
wave, lambda w : Transform(w, self.get_wave(axes)).update(1)
|
|
)
|
|
|
|
self.k_tracker.save_state()
|
|
self.k_tracker.set_value(0)
|
|
bell_curve = self.get_wave(axes)
|
|
self.k_tracker.restore()
|
|
bell_curve.set_stroke(width = 0)
|
|
bell_curve.set_fill(BLUE, opacity = 0.5)
|
|
squared_bell_curve = axes.get_graph(
|
|
lambda x : bell_curve.underlying_function(x)**2
|
|
).match_style(bell_curve)
|
|
|
|
self.set_variables_as_attrs(
|
|
axes, wave, bell_curve, squared_bell_curve
|
|
)
|
|
|
|
def detect_only_single_points(self):
|
|
particle = ProbabalisticDotCloud(
|
|
n_copies = 100,
|
|
fill_opacity = 0.05,
|
|
time_per_change = 0.05,
|
|
)
|
|
particle.mobject[0].set_fill(BLUE, opacity = 1)
|
|
gdw = particle.gaussian_distribution_wrapper
|
|
|
|
rect = Rectangle(
|
|
stroke_width = 0,
|
|
height = 0.5,
|
|
width = 2,
|
|
)
|
|
rect.set_fill(YELLOW, 0.3)
|
|
rect.move_to(self.axes.coords_to_point(self.x0_tracker.get_value(), 0))
|
|
brace = Brace(rect, UP, buff = 0)
|
|
question = TextMobject("Do we detect the particle \\\\ in this region?")
|
|
question.next_to(brace, UP)
|
|
question.add_background_rectangle()
|
|
rect.save_state()
|
|
rect.stretch(0, 0)
|
|
|
|
gdw_anim = Mobject.add_updater(
|
|
gdw, lambda m : m.set_width(
|
|
2.0/(self.a_tracker.get_value()**(0.5))
|
|
).move_to(rect)
|
|
)
|
|
|
|
self.add(rect, brace, question)
|
|
|
|
yes = TextMobject("Yes").set_color(GREEN)
|
|
no = TextMobject("No").set_color(RED)
|
|
for word in yes, no:
|
|
word.next_to(rect, DOWN)
|
|
# word.add_background_rectangle()
|
|
answer = VGroup()
|
|
def update_answer(answer):
|
|
px = particle.mobject[0].get_center()[0]
|
|
lx = rect.get_left()[0]
|
|
rx = rect.get_right()[0]
|
|
if lx < px < rx:
|
|
answer.submobjects = [yes]
|
|
else:
|
|
answer.submobjects = [no]
|
|
answer_anim = Mobject.add_updater(answer, update_answer)
|
|
|
|
self.add(gdw_anim, particle)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
rect.restore,
|
|
Write(question)
|
|
)
|
|
self.wait()
|
|
self.add(answer_anim)
|
|
self.wait(4)
|
|
self.add_foreground_mobjects(answer, particle.mobject)
|
|
|
|
self.question_group = VGroup(question, brace)
|
|
self.particle = particle
|
|
self.rect = rect
|
|
|
|
def show_probability_distribution(self):
|
|
axes = self.axes
|
|
wave = self.wave
|
|
bell_curve = self.bell_curve
|
|
question_group = self.question_group
|
|
gdw = self.particle.gaussian_distribution_wrapper
|
|
rect = self.rect
|
|
|
|
v_lines = VGroup(*[
|
|
DashedLine(ORIGIN, 3*UP).move_to(point, DOWN)
|
|
for point in (rect.get_left(), rect.get_right())
|
|
])
|
|
|
|
self.play(
|
|
FadeIn(VGroup(axes, wave)),
|
|
question_group.next_to, v_lines, UP, {"buff" : 0},
|
|
*list(map(ShowCreation, v_lines))
|
|
)
|
|
self.wait(10)
|
|
|
|
def show_concentration_of_the_wave(self):
|
|
self.play(
|
|
self.a_tracker.set_value, 5,
|
|
self.wave_update_animation,
|
|
)
|
|
self.wait(10)
|
|
|
|
class HeisenbergCommentTodos(TODOStub):
|
|
CONFIG = {
|
|
"message" : "Insert position-momentum trade-off"
|
|
}
|
|
|
|
class HeisenbergPetPeeve(PiCreatureScene):
|
|
def construct(self):
|
|
morty, other = self.pi_creatures
|
|
particle = ProbabalisticDotCloud()
|
|
gdw = particle.gaussian_distribution_wrapper
|
|
gdw.to_edge(UP, buff = LARGE_BUFF)
|
|
gdw.stretch_to_fit_width(3)
|
|
gdw.rotate(3*DEGREES)
|
|
|
|
self.add(particle)
|
|
self.wait()
|
|
self.play(PiCreatureSays(
|
|
other, """
|
|
According to the H.U.P., the \\\\
|
|
universe is unknowable!
|
|
""",
|
|
target_mode = "speaking"
|
|
))
|
|
self.play(morty.change, "angry")
|
|
self.wait(3)
|
|
self.play(
|
|
PiCreatureSays(
|
|
morty, "Well, yes and no",
|
|
target_mode = "sassy",
|
|
),
|
|
RemovePiCreatureBubble(
|
|
other, target_mode = "erm"
|
|
)
|
|
)
|
|
self.wait(4)
|
|
|
|
###
|
|
def create_pi_creatures(self):
|
|
morty = Mortimer()
|
|
morty.to_corner(DOWN+RIGHT)
|
|
other = PiCreature(color = MAROON_E)
|
|
other.to_edge(DOWN).shift(3*LEFT)
|
|
return VGroup(morty, other)
|
|
|
|
class OneLevelDeeper(Scene):
|
|
def construct(self):
|
|
heisenberg = ImageMobject("Heisenberg")
|
|
heisenberg.to_corner(UP+LEFT)
|
|
self.add(heisenberg)
|
|
|
|
hup_words = TextMobject("Heisenberg's uncertainty principle")
|
|
wave_words = TextMobject("Interpretation of the wave function")
|
|
arrow = Vector(UP)
|
|
group = VGroup(hup_words, arrow, wave_words)
|
|
group.arrange(DOWN)
|
|
|
|
randomness = ProbabalisticMobjectCloud(
|
|
TextMobject("Randomness"),
|
|
n_copies = 5,
|
|
time_per_change = 0.05
|
|
)
|
|
gdw = randomness.gaussian_distribution_wrapper
|
|
gdw.rotate(TAU/4)
|
|
gdw.set_height(1)
|
|
# gdw.set_width(4)
|
|
gdw.next_to(hup_words, UP, MED_LARGE_BUFF)
|
|
|
|
self.add(hup_words, randomness)
|
|
self.wait(4)
|
|
self.play(
|
|
FadeIn(wave_words),
|
|
GrowArrow(arrow),
|
|
ApplyMethod(
|
|
gdw.next_to, wave_words, DOWN, MED_LARGE_BUFF,
|
|
path_arc = TAU/2,
|
|
)
|
|
)
|
|
self.wait(6)
|
|
|
|
class BetterTranslation(TeacherStudentsScene):
|
|
def construct(self):
|
|
english_term = TextMobject("Uncertainty principle")
|
|
german_word = TextMobject("Unschärferelation")
|
|
translation = TextMobject("Unsharpness relation")
|
|
|
|
to_german_words = TextMobject("In German")
|
|
to_german_words.scale(0.5)
|
|
to_german_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF)
|
|
to_german_words.next_to(to_german_arrow, RIGHT, SMALL_BUFF)
|
|
to_german_words.set_color(YELLOW)
|
|
to_german_group = VGroup(to_german_arrow, to_german_words)
|
|
|
|
translation_words = TextMobject("Literal translation")
|
|
translation_words.scale(0.5)
|
|
translation_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF)
|
|
translation_words.next_to(translation_arrow, LEFT, SMALL_BUFF)
|
|
translation_words.set_color(YELLOW)
|
|
translation_group = VGroup(translation_arrow, translation_words)
|
|
|
|
english_term.next_to(self.teacher, UP+LEFT)
|
|
english_term.save_state()
|
|
english_term.shift(DOWN)
|
|
english_term.fade(1)
|
|
self.play(
|
|
english_term.restore,
|
|
self.get_student_changes(*["pondering"]*3)
|
|
)
|
|
self.wait()
|
|
|
|
german_word.move_to(english_term)
|
|
to_german_group.next_to(
|
|
german_word, UP,
|
|
submobject_to_align = to_german_arrow
|
|
)
|
|
self.play(
|
|
self.teacher.change, "raise_right_hand",
|
|
english_term.next_to, to_german_arrow, UP
|
|
)
|
|
self.play(
|
|
GrowArrow(to_german_arrow),
|
|
FadeIn(to_german_words),
|
|
ReplacementTransform(
|
|
english_term.copy().fade(1),
|
|
german_word
|
|
)
|
|
)
|
|
self.wait(2)
|
|
|
|
group = VGroup(english_term, to_german_group, german_word)
|
|
translation.move_to(german_word)
|
|
translation_group.next_to(
|
|
german_word, UP,
|
|
submobject_to_align = translation_arrow
|
|
)
|
|
self.play(
|
|
group.next_to, translation_arrow, UP,
|
|
)
|
|
self.play(
|
|
GrowArrow(translation_arrow),
|
|
FadeIn(translation_words),
|
|
ReplacementTransform(
|
|
german_word.copy().fade(1),
|
|
translation
|
|
)
|
|
)
|
|
self.change_student_modes(*["happy"]*3)
|
|
self.wait(2)
|
|
|
|
class ThinkOfHeisenbergUncertainty(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
morty.center().to_edge(DOWN).shift(LEFT)
|
|
|
|
dot_cloud = ProbabalisticDotCloud()
|
|
dot_gdw = dot_cloud.gaussian_distribution_wrapper
|
|
dot_gdw.set_width(1)
|
|
dot_gdw.rotate(TAU/8)
|
|
dot_gdw.move_to(FRAME_X_RADIUS*RIGHT/2),
|
|
|
|
vector_cloud = ProbabalisticVectorCloud(
|
|
center_func = dot_gdw.get_center
|
|
)
|
|
vector_gdw = vector_cloud.gaussian_distribution_wrapper
|
|
vector_gdw.set_width(0.1)
|
|
vector_gdw.rotate(TAU/8)
|
|
vector_gdw.next_to(dot_gdw, UP+LEFT, LARGE_BUFF)
|
|
|
|
time_tracker = ValueTracker(0)
|
|
self.add()
|
|
freq = 1
|
|
continual_anims = [
|
|
always_shift(time_tracker, direction = RIGHT, rate = 1),
|
|
Mobject.add_updater(
|
|
dot_gdw,
|
|
lambda d : d.set_width(
|
|
(np.cos(freq*time_tracker.get_value()) + 1.1)/2
|
|
)
|
|
),
|
|
Mobject.add_updater(
|
|
vector_gdw,
|
|
lambda d : d.set_width(
|
|
(-np.cos(freq*time_tracker.get_value()) + 1.1)/2
|
|
)
|
|
),
|
|
dot_cloud, vector_cloud
|
|
]
|
|
self.add(*continual_anims)
|
|
|
|
position, momentum, time, frequency = list(map(TextMobject, [
|
|
"Position", "Momentum", "Time", "Frequency"
|
|
]))
|
|
VGroup(position, time).set_color(BLUE)
|
|
VGroup(momentum, frequency).set_color(YELLOW)
|
|
groups = VGroup()
|
|
for m1, m2 in (position, momentum), (time, frequency):
|
|
arrow = TexMobject("\\updownarrow").scale(1.5)
|
|
group = VGroup(m1, arrow, m2)
|
|
group.arrange(DOWN)
|
|
lp, rp = parens = TexMobject("\\big(\\big)")
|
|
parens.stretch(1.5, 1)
|
|
parens.match_height(group)
|
|
lp.next_to(group, LEFT, buff = SMALL_BUFF)
|
|
rp.next_to(group, RIGHT, buff = SMALL_BUFF)
|
|
group.add(parens)
|
|
groups.add(group)
|
|
arrow = TexMobject("\\Leftrightarrow").scale(2)
|
|
groups.submobjects.insert(1, arrow)
|
|
groups.arrange(RIGHT)
|
|
groups.next_to(morty, UP+RIGHT, LARGE_BUFF)
|
|
groups.shift_onto_screen()
|
|
|
|
|
|
self.play(PiCreatureBubbleIntroduction(
|
|
morty, "Heisenberg \\\\ uncertainty \\\\ principle",
|
|
bubble_class = ThoughtBubble,
|
|
bubble_kwargs = {"height" : 4, "width" : 4, "direction" : RIGHT},
|
|
target_mode = "pondering"
|
|
))
|
|
self.wait()
|
|
self.play(morty.change, "confused", dot_gdw)
|
|
self.wait(10)
|
|
self.play(
|
|
ApplyMethod(
|
|
VGroup(dot_gdw, vector_gdw ).shift,
|
|
FRAME_X_RADIUS*RIGHT,
|
|
rate_func = running_start
|
|
)
|
|
)
|
|
self.remove(*continual_anims)
|
|
self.play(
|
|
morty.change, "raise_left_hand", groups,
|
|
FadeIn(
|
|
groups,
|
|
lag_ratio = 0.5,
|
|
run_time = 3,
|
|
)
|
|
)
|
|
self.wait(2)
|
|
|
|
# End things
|
|
|
|
class PatreonMention(PatreonThanks):
|
|
def construct(self):
|
|
morty = Mortimer()
|
|
morty.next_to(ORIGIN, DOWN)
|
|
|
|
patreon_logo = PatreonLogo()
|
|
patreon_logo.to_edge(UP)
|
|
|
|
thank_you = TextMobject("Thank you.")
|
|
thank_you.next_to(patreon_logo, DOWN)
|
|
|
|
self.play(
|
|
DrawBorderThenFill(patreon_logo),
|
|
morty.change, "gracious"
|
|
)
|
|
self.play(Write(thank_you))
|
|
self.wait(3)
|
|
|
|
class Promotion(PiCreatureScene):
|
|
CONFIG = {
|
|
"camera_class" : ThreeDCamera,
|
|
"seconds_to_blink" : 5,
|
|
}
|
|
def construct(self):
|
|
aops_logo = AoPSLogo()
|
|
aops_logo.next_to(self.pi_creature, UP+LEFT)
|
|
url = TextMobject(
|
|
"AoPS.com/", "3b1b",
|
|
arg_separator = ""
|
|
)
|
|
url.to_corner(UP+LEFT)
|
|
url_rect = Rectangle(color = BLUE)
|
|
url_rect.replace(
|
|
url.get_part_by_tex("3b1b"),
|
|
stretch = True
|
|
)
|
|
|
|
url_rect.stretch_in_place(1.1, dim = 1)
|
|
|
|
rect = Rectangle(height = 9, width = 16)
|
|
rect.set_height(4.5)
|
|
rect.next_to(url, DOWN)
|
|
rect.to_edge(LEFT)
|
|
rect.set_stroke(width = 0)
|
|
mathy = Mathematician()
|
|
mathy.flip()
|
|
mathy.to_corner(DOWN+RIGHT)
|
|
morty = self.pi_creature
|
|
morty.save_state()
|
|
book = ImageMobject("AoPS_volume_2")
|
|
book.set_height(2)
|
|
book.next_to(mathy, UP+LEFT).shift(MED_LARGE_BUFF*LEFT)
|
|
mathy.get_center = mathy.get_top
|
|
|
|
words = TextMobject("""
|
|
Interested in working for \\\\
|
|
one of my favorite math\\\\
|
|
education companies?
|
|
""", alignment = "")
|
|
words.to_edge(UP)
|
|
|
|
arrow = Arrow(
|
|
aops_logo.get_top(),
|
|
morty.get_top(),
|
|
path_arc = -0.4*TAU,
|
|
stroke_width = 5,
|
|
tip_length = 0.5,
|
|
)
|
|
arrow.tip.shift(SMALL_BUFF*DOWN)
|
|
|
|
self.add(words)
|
|
self.play(
|
|
self.pi_creature.change_mode, "raise_right_hand",
|
|
*[
|
|
DrawBorderThenFill(
|
|
submob,
|
|
run_time = 2,
|
|
rate_func = squish_rate_func(double_smooth, a, a+0.5)
|
|
)
|
|
for submob, a in zip(aops_logo, np.linspace(0, 0.5, len(aops_logo)))
|
|
]
|
|
)
|
|
self.play(
|
|
words.scale, 0.75,
|
|
words.next_to, url, DOWN, LARGE_BUFF,
|
|
words.shift_onto_screen,
|
|
Write(url),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
LaggedStartMap(
|
|
ApplyFunction, aops_logo,
|
|
lambda mob : (lambda m : m.shift(0.2*UP).set_color(YELLOW), mob),
|
|
rate_func = there_and_back,
|
|
run_time = 1,
|
|
),
|
|
morty.change, "thinking"
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(arrow))
|
|
self.play(FadeOut(arrow))
|
|
self.wait()
|
|
|
|
# To teacher
|
|
self.play(
|
|
morty.change_mode, "plain",
|
|
morty.flip,
|
|
morty.scale, 0.7,
|
|
morty.next_to, mathy, LEFT, LARGE_BUFF,
|
|
morty.to_edge, DOWN,
|
|
FadeIn(mathy),
|
|
)
|
|
self.play(
|
|
PiCreatureSays(
|
|
mathy, "",
|
|
bubble_kwargs = {"width" : 5},
|
|
look_at_arg = morty.eyes,
|
|
),
|
|
morty.change, "happy",
|
|
aops_logo.shift, 1.5*UP + 0.5*RIGHT
|
|
)
|
|
self.play(Blink(mathy))
|
|
self.wait()
|
|
self.play(
|
|
RemovePiCreatureBubble(
|
|
mathy, target_mode = "raise_right_hand"
|
|
),
|
|
aops_logo.to_corner, UP+RIGHT,
|
|
aops_logo.shift, MED_SMALL_BUFF*DOWN,
|
|
GrowFromPoint(book, mathy.get_corner(UP+LEFT)),
|
|
)
|
|
self.play(morty.change, "pondering", book)
|
|
self.wait(3)
|
|
self.play(Blink(mathy))
|
|
self.wait()
|
|
self.play(
|
|
Animation(
|
|
BackgroundRectangle(book, fill_opacity = 1),
|
|
remover = True
|
|
),
|
|
FadeOut(book),
|
|
)
|
|
print(self.num_plays)
|
|
self.play(
|
|
FadeOut(words),
|
|
ShowCreation(rect),
|
|
morty.restore,
|
|
morty.change, "happy", rect,
|
|
FadeOut(mathy),
|
|
)
|
|
self.wait(10)
|
|
self.play(ShowCreation(url_rect))
|
|
self.play(
|
|
FadeOut(url_rect),
|
|
url.get_part_by_tex("3b1b").set_color, BLUE,
|
|
)
|
|
self.wait(15)
|
|
|
|
class PuzzleStatement(Scene):
|
|
def construct(self):
|
|
aops_logo = AoPSLogo()
|
|
url = TextMobject("AoPS.com/3b1b")
|
|
url.next_to(aops_logo, UP)
|
|
group = VGroup(aops_logo, url)
|
|
group.to_edge(UP)
|
|
self.add(group)
|
|
|
|
words = TextMobject("""
|
|
AoPS must choose one of 20 people to send to a
|
|
tug-of-war tournament. We don't care who we send,
|
|
as long as we don't send our weakest person. \\\\ \\\\
|
|
|
|
Each person has a different strength, but we don't know
|
|
those strengths. We get 10 intramural 10-on-10 matches
|
|
to determine who we send. Can we make sure we don't send
|
|
the weakest person?
|
|
""", alignment = "")
|
|
words.set_width(FRAME_WIDTH - 2)
|
|
words.next_to(group, DOWN, LARGE_BUFF)
|
|
self.play(LaggedStartMap(FadeIn, words, run_time = 5, lag_ratio = 0.2))
|
|
self.wait(2)
|
|
|
|
class UncertaintyEndScreen(PatreonEndScreen):
|
|
CONFIG = {
|
|
"specific_patrons" : [
|
|
"CrypticSwarm",
|
|
"Ali Yahya",
|
|
"Juan Benet",
|
|
"Markus Persson",
|
|
"Damion Kistler",
|
|
"Burt Humburg",
|
|
"Yu Jun",
|
|
"Dave Nicponski",
|
|
"Kaustuv DeBiswas",
|
|
"Joseph John Cox",
|
|
"Luc Ritchie",
|
|
"Achille Brighton",
|
|
"Rish Kundalia",
|
|
"Yana Chernobilsky",
|
|
"Shìmín Kuang",
|
|
"Mathew Bramson",
|
|
"Jerry Ling",
|
|
"Mustafa Mahdi",
|
|
"Meshal Alshammari",
|
|
"Mayank M. Mehrotra",
|
|
"Lukas Biewald",
|
|
"Robert Teed",
|
|
"Samantha D. Suplee",
|
|
"Mark Govea",
|
|
"John Haley",
|
|
"Julian Pulgarin",
|
|
"Jeff Linse",
|
|
"Cooper Jones",
|
|
"Desmos ",
|
|
"Boris Veselinovich",
|
|
"Ryan Dahl",
|
|
"Ripta Pasay",
|
|
"Eric Lavault",
|
|
"Randall Hunt",
|
|
"Andrew Busey",
|
|
"Mads Elvheim",
|
|
"Tianyu Ge",
|
|
"Awoo",
|
|
"Dr. David G. Stork",
|
|
"Linh Tran",
|
|
"Jason Hise",
|
|
"Bernd Sing",
|
|
"James H. Park",
|
|
"Ankalagon ",
|
|
"Mathias Jansson",
|
|
"David Clark",
|
|
"Ted Suzman",
|
|
"Eric Chow",
|
|
"Michael Gardner",
|
|
"David Kedmey",
|
|
"Jonathan Eppele",
|
|
"Clark Gaebel",
|
|
"Jordan Scales",
|
|
"Ryan Atallah",
|
|
"supershabam ",
|
|
"1stViewMaths",
|
|
"Jacob Magnuson",
|
|
"Chloe Zhou",
|
|
"Ross Garber",
|
|
"Thomas Tarler",
|
|
"Isak Hietala",
|
|
"Egor Gumenuk",
|
|
"Waleed Hamied",
|
|
"Oliver Steele",
|
|
"Yaw Etse",
|
|
"David B",
|
|
"Delton Ding",
|
|
"James Thornton",
|
|
"Felix Tripier",
|
|
"Arthur Zey",
|
|
"George Chiesa",
|
|
"Norton Wang",
|
|
"Kevin Le",
|
|
"Alexander Feldman",
|
|
"David MacCumber",
|
|
"Jacob Kohl",
|
|
"Frank Secilia",
|
|
"George John",
|
|
"Akash Kumar",
|
|
"Britt Selvitelle",
|
|
"Jonathan Wilson",
|
|
"Michael Kunze",
|
|
"Giovanni Filippi",
|
|
"Eric Younge",
|
|
"Prasant Jagannath",
|
|
"Andrejs olins",
|
|
"Cody Brocious",
|
|
],
|
|
}
|
|
|
|
class Thumbnail(Scene):
|
|
def construct(self):
|
|
uncertainty_principle = TextMobject("Uncertainty \\\\", "principle")
|
|
uncertainty_principle[1].shift(SMALL_BUFF*UP)
|
|
quantum = TextMobject("Quantum")
|
|
VGroup(uncertainty_principle, quantum).scale(2.5)
|
|
uncertainty_principle.to_edge(UP, MED_LARGE_BUFF)
|
|
quantum.to_edge(DOWN, MED_LARGE_BUFF)
|
|
|
|
arrow = TexMobject("\\Downarrow")
|
|
arrow.scale(4)
|
|
arrow.move_to(Line(
|
|
uncertainty_principle.get_bottom(),
|
|
quantum.get_top(),
|
|
))
|
|
|
|
cross = Cross(arrow)
|
|
cross.set_stroke(RED, 20)
|
|
|
|
is_word, not_word = is_not = TextMobject("is", "\\emph{NOT}")
|
|
is_not.scale(3)
|
|
is_word.move_to(arrow)
|
|
# is_word.shift(0.6*UP)
|
|
not_word.set_color(RED)
|
|
not_word.set_stroke(RED, 3)
|
|
not_word.rotate(10*DEGREES, about_edge = DOWN+LEFT)
|
|
not_word.next_to(is_word, DOWN, 0.1*SMALL_BUFF)
|
|
|
|
dot_cloud = ProbabalisticDotCloud(
|
|
n_copies = 1000,
|
|
)
|
|
dot_gdw = dot_cloud.gaussian_distribution_wrapper
|
|
# dot_gdw.rotate(3*DEGREES)
|
|
dot_gdw.rotate(25*DEGREES)
|
|
# dot_gdw.scale(2)
|
|
dot_gdw.scale(2)
|
|
# dot_gdw.move_to(quantum.get_bottom()+SMALL_BUFF*DOWN)
|
|
dot_gdw.move_to(quantum)
|
|
|
|
|
|
|
|
def get_func(a):
|
|
return lambda t : 0.5*np.exp(-a*t**2)*np.cos(TAU*t)
|
|
axes = Axes(
|
|
x_min = -6, x_max = 6,
|
|
x_axis_config = {"unit_size" : 0.25}
|
|
)
|
|
graphs = VGroup(*[
|
|
axes.get_graph(get_func(a))
|
|
for a in (10, 3, 1, 0.3, 0.1,)
|
|
])
|
|
graphs.arrange(DOWN, buff = 0.6)
|
|
graphs.to_corner(UP+LEFT)
|
|
graphs.set_color_by_gradient(BLUE_B, BLUE_D)
|
|
|
|
frequency_axes = Axes(
|
|
x_min = 0, x_max = 2,
|
|
x_axis_config = {"unit_size" : 1}
|
|
)
|
|
fourier_graphs = VGroup(*[
|
|
get_fourier_graph(
|
|
frequency_axes, graph.underlying_function,
|
|
t_min = -10, t_max = 10,
|
|
)
|
|
for graph in graphs
|
|
])
|
|
for graph, fourier_graph in zip(graphs, fourier_graphs):
|
|
fourier_graph.pointwise_become_partial(fourier_graph, 0.02, 0.06)
|
|
fourier_graph.scale(3)
|
|
fourier_graph.stretch(3, 1)
|
|
fourier_graph.move_to(graph)
|
|
fourier_graph.to_edge(RIGHT)
|
|
|
|
self.add(graphs, fourier_graphs)
|
|
|
|
|
|
self.add(dot_cloud)
|
|
self.add(
|
|
uncertainty_principle, quantum,
|
|
)
|
|
self.add(arrow, cross)
|
|
# self.add(is_word)
|
|
# self.add(is_not)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|