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

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)