Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber

This commit is contained in:
Sridhar Ramesh
2018-02-20 09:12:03 -08:00
5 changed files with 330 additions and 72 deletions

View File

@ -45,7 +45,7 @@ def get_fourier_graph(
# T = time_range/n_samples
time_range = float(t_max - t_min)
time_step_size = time_range/n_samples
time_samples = time_func(np.linspace(t_min, t_max, n_samples))
time_samples = np.vectorize(time_func)(np.linspace(t_min, t_max, n_samples))
fft_output = np.fft.fft(time_samples)
frequencies = np.linspace(0.0, n_samples/(2.0*time_range), n_samples//2)
# #Cycles per second of fouier_samples[1]

View File

@ -1686,7 +1686,8 @@ class IntroduceDopplerRadar(Scene):
def construct(self):
self.setup_axes()
self.measure_distance_with_time()
self.measure_velocity_with_frequency()
self.show_frequency_shift()
self.show_frequency_shift_in_fourier()
def setup_axes(self):
self.dish = RadarDish()
@ -1694,8 +1695,8 @@ class IntroduceDopplerRadar(Scene):
axes = Axes(
x_min = 0,
x_max = 10,
y_min = -2,
y_max = 2
y_min = -1.5,
y_max = 1.5
)
axes.move_to(DOWN)
time_label = TextMobject("Time")
@ -1718,20 +1719,15 @@ class IntroduceDopplerRadar(Scene):
randy.move_to(dish.get_right(), LEFT)
randy.shift(distance*RIGHT)
pulse_graph = self.get_single_pulse_graph(1, color = BLUE)
echo_graph = self.get_single_pulse_graph(1+time_diff, color = YELLOW)
sum_graph = axes.get_graph(
lambda x : sum([
pulse_graph.underlying_function(x),
echo_graph.underlying_function(x),
]),
color = WHITE
)
sum_graph.background_image_file = "blue_yellow_gradient"
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+LEFT)
arrow.next_to(graph.peak_point, UP+RIGHT, SMALL_BUFF)
arrow = Vector(DOWN)
arrow.next_to(graph.peak_point, UP, SMALL_BUFF)
arrow.match_color(graph)
graph.arrow = arrow
label = TextMobject(word)
@ -1739,22 +1735,35 @@ class IntroduceDopplerRadar(Scene):
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.scale_to_fit_width(0.9*double_arrow.get_width())
distance_text.next_to(double_arrow, UP, SMALL_BUFF)
#v_line anim?
pulse = RadarPulseSingleton(dish, randy, speed = speed)
pulse = RadarPulseSingleton(
dish, randy,
speed = 0.97*speed, #Just needs slightly better alignment
)
graph_draw = NormalAnimationAsContinualAnimation(
ShowCreation(
sum_graph,
rate_func = None,
run_time = axes.x_max
run_time = 0.97*axes.x_max
)
)
randy_look_at = ContinualUpdateFromFunc(
randy, lambda pi : pi.look_at(pulse.mobject)
)
axes_anim = ContinualAnimation(axes)
self.add(randy_look_at, ContinualAnimation(axes), graph_draw)
self.wait()
self.add(randy_look_at, axes_anim, graph_draw)
self.wait(0.5)
self.add(pulse)
self.play(
Write(pulse_graph.label),
@ -1768,35 +1777,263 @@ class IntroduceDopplerRadar(Scene):
GrowArrow(echo_graph.arrow),
run_time = 1
)
self.wait(3)
graph_draw.update(10)
self.add(sum_graph)
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(LaggedStart(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 = NormalAnimationAsContinualAnimation(
ShowCreation(sum_graph, run_time = 8, rate_func = None)
)
pulse = RadarPulse(dish, plane, n_pulse_singletons = 12)
plane_flight = AmbientMovement(
plane, direction = LEFT, rate = 1.5
)
self.add(graph_draw, pulse, plane_flight)
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_flight)
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,
).highlight(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,
).highlight(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.highlight(RED)
def measure_velocity_with_frequency(self):
pass
#v_lines
v_line = DashedLine(
frequency_axes.coords_to_point(8, 0),
frequency_axes.coords_to_point(8, 1.2*f_max),
color = YELLOW,
dashed_segment_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_single_pulse_graph(self, x, **kwargs):
graph = self.axes.get_graph(
self.get_single_pulse_function(x),
**kwargs
)
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):
return lambda t : 2*np.cos(2*freq*(t-x))*min(np.exp(-(freq**2/100)*(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_anchor_points(),
color = WHITE
)
sum_graph.background_image_file = "blue_yellow_gradient"
return pulse_graph, echo_graph, sum_graph

View File

@ -129,19 +129,6 @@ class Animation(object):
return self
def sync_animation_run_times_and_rate_funcs(*animations, **kwargs):
for animation in animations:
animation.update_config(**kwargs)
max_run_time = max([a.run_time for a in animations])
for animation in animations:
if animation.run_time != max_run_time:
new_rate_func = squish_rate_func(
animation.get_rate_func(),
0, float(animation.run_time)/max_run_time
)
animation.set_rate_func(new_rate_func)
animation.set_run_time(max_run_time)

View File

@ -7,7 +7,6 @@ from mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TextMobject
from animation import Animation
from animation import sync_animation_run_times_and_rate_funcs
from transform import Transform
class Rotating(Animation):
@ -498,15 +497,17 @@ class AnimationGroup(Animation):
self.empty = True
self.run_time = 0
else:
# Should really make copies of animations, instead of messing with originals...
sync_animation_run_times_and_rate_funcs(*sub_anims, **kwargs)
for anim in sub_anims:
# If AnimationGroup is called with any configuration,
# it is propagated to the sub_animations
anim.update_config(**kwargs)
self.run_time = max([a.run_time for a in sub_anims])
everything = Mobject(*[a.mobject for a in sub_anims])
Animation.__init__(self, everything, **kwargs)
def update_mobject(self, alpha):
def update(self, alpha):
for anim in self.sub_anims:
anim.update(alpha)
anim.update(alpha * self.run_time / anim.run_time)
def clean_up(self, *args, **kwargs):
for anim in self.sub_anims:

View File

@ -17,7 +17,6 @@ from camera import Camera
from tk_scene import TkSceneRoot
from mobject import Mobject, VMobject
from animation import Animation
from animation.animation import sync_animation_run_times_and_rate_funcs
from animation.transform import MoveToTarget
from animation.continual_animation import ContinualAnimation
from container import *
@ -65,7 +64,10 @@ class Scene(Container):
self.setup()
if self.write_to_movie:
self.open_movie_pipe()
self.construct(*self.construct_args)
try:
self.construct(*self.construct_args)
except EndSceneEarlyException:
pass
if self.write_to_movie:
self.close_movie_pipe()
print("Played a total of %d animations"%self.num_plays)
@ -138,7 +140,10 @@ class Scene(Container):
mobjects = None,
background = None,
include_submobjects = True,
dont_update_when_skipping = True,
**kwargs):
if self.skip_animations and dont_update_when_skipping:
return
if mobjects is None:
mobjects = list_update(
self.mobjects,
@ -331,12 +336,16 @@ class Scene(Container):
return moving_mobjects
def get_time_progression(self, run_time):
times = np.arange(0, run_time, self.frame_duration)
if self.skip_animations:
times = [run_time]
else:
step = self.frame_duration
times = np.arange(0, run_time + step, step)
time_progression = ProgressDisplay(times)
return time_progression
def get_animation_time_progression(self, animations):
run_time = animations[0].run_time
run_time = np.max([animation.run_time for animation in animations])
time_progression = self.get_time_progression(run_time)
time_progression.set_description("".join([
"Animation %d: "%self.num_plays,
@ -406,24 +415,29 @@ class Scene(Container):
compile_method(state)
return animations
def handle_animation_skipping(self):
if self.start_at_animation_number:
if self.num_plays == self.start_at_animation_number:
self.skip_animations = self.original_skipping_status
if self.end_at_animation_number:
if self.num_plays >= self.end_at_animation_number:
self.skip_animations = True
raise EndSceneEarlyException()
def play(self, *args, **kwargs):
if len(args) == 0:
warnings.warn("Called Scene.play with no animations")
return
if self.start_at_animation_number:
if self.num_plays == self.start_at_animation_number:
self.skip_animations = False
if self.end_at_animation_number:
if self.num_plays >= self.end_at_animation_number:
self.skip_animations = True
return self #Don't even both with the rest...
if self.skip_animations:
kwargs["run_time"] = 0
self.handle_animation_skipping()
animations = self.compile_play_args_to_animation_list(*args)
sync_animation_run_times_and_rate_funcs(*animations, **kwargs)
for animation in animations:
# This is where kwargs to play like run_time and rate_func
# get applied to all animations
animation.update_config(**kwargs)
moving_mobjects = self.get_moving_mobjects(*animations)
# Paint all non-moving objects onto the screen, so they don't
# have to be rendered every frame
self.update_frame(excluded_mobjects = moving_mobjects)
static_image = self.get_frame()
for t in self.get_animation_time_progression(animations):
@ -435,7 +449,12 @@ class Scene(Container):
self.add(*moving_mobjects)
self.mobjects_from_last_animation = moving_mobjects
self.clean_up_animations(*animations)
self.continual_update(0)
if self.skip_animations:
# Todo, not great that this uses a variable from
# a previous loop...
self.continual_update(t)
else:
self.continual_update(0)
self.num_plays += 1
return self
@ -451,17 +470,17 @@ class Scene(Container):
return []
def wait(self, duration = DEFAULT_WAIT_TIME):
if self.skip_animations:
return self
if self.should_continually_update():
for t in self.get_time_progression(duration):
self.continual_update()
self.update_frame()
self.add_frames(self.get_frame())
else:
elif not self.skip_animations:
self.update_frame()
self.add_frames(*[self.get_frame()]*int(duration / self.frame_duration))
else:
#If self.skip_animations is set, do nothing
pass
return self
@ -500,7 +519,7 @@ class Scene(Container):
#Display methods
def show_frame(self):
self.update_frame()
self.update_frame(dont_update_when_skipping = False)
self.get_image().show()
def preview(self):
@ -520,7 +539,7 @@ class Scene(Container):
if not os.path.exists(directory_path):
os.makedirs(directory_path)
if not dont_update:
self.update_frame()
self.update_frame(dont_update_when_skipping = False)
image = self.get_image()
image = image.convert(mode)
image.save(path)
@ -573,3 +592,17 @@ class Scene(Container):
shutil.move(*self.args_to_rename_file)
else:
os.rename(*self.args_to_rename_file)
class EndSceneEarlyException(Exception):
pass