Merge pull request #606 from 3b1b/diffyq

Diffyq
This commit is contained in:
Grant Sanderson 2019-06-19 16:14:56 -07:00 committed by GitHub
commit 7d596d0840
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 760 additions and 104 deletions

View file

@ -0,0 +1,18 @@
from active_projects.ode.part4.staging import *
from active_projects.ode.part4.fourier_series_scenes import *
from active_projects.ode.part4.pi_creature_scenes import *
OUTPUT_DIRECTORY = "ode/part4"
SCENES_IN_ORDER = [
ComplexFourierSeriesExample,
ComplexFourierSeriesExampleEnd,
FourierSeriesExampleWithRectForZoom,
ZoomedInFourierSeriesExample,
RelationToOtherVideos,
WhyWouldYouCare,
# Oldies
# FourierSeriesIllustraiton,
# FourierNameIntro,
# CircleAnimationOfF,
]

View file

@ -4,7 +4,7 @@ from manimlib.imports import *
class FourierCirclesScene(Scene):
CONFIG = {
"n_circles": 10,
"n_vectors": 10,
"big_radius": 2,
"colors": [
BLUE_D,
@ -15,14 +15,16 @@ class FourierCirclesScene(Scene):
"circle_style": {
"stroke_width": 2,
},
"arrow_config": {
"vector_config": {
"buff": 0,
"max_tip_length_to_length_ratio": 0.35,
"tip_length": 0.15,
"max_stroke_width_to_length_ratio": 10,
"stroke_width": 2,
},
"use_vectors": True,
"circle_config": {
"stroke_width": 1,
},
"base_frequency": 1,
"slow_factor": 0.25,
"center_point": ORIGIN,
@ -33,26 +35,35 @@ class FourierCirclesScene(Scene):
self.slow_factor_tracker = ValueTracker(
self.slow_factor
)
self.vector_clock = ValueTracker(0)
self.vector_clock.add_updater(
lambda m, dt: m.increment_value(
self.get_slow_factor() * dt
)
)
self.add(self.vector_clock)
def get_slow_factor(self):
return self.slow_factor_tracker.get_value()
def get_vector_time(self):
return self.vector_clock.get_value()
#
def get_freqs(self):
n = self.n_circles
n = self.n_vectors
all_freqs = list(range(n // 2, -n // 2, -1))
all_freqs.sort(key=abs)
return all_freqs
def get_coefficients(self):
return [complex(0) for x in range(self.n_circles)]
return [complex(0) for x in range(self.n_vectors)]
def get_color_iterator(self):
return it.cycle(self.colors)
def get_circles(self, freqs=None, coefficients=None):
circles = VGroup()
color_iterator = self.get_color_iterator()
def get_rotating_vectors(self, freqs=None, coefficients=None):
vectors = VGroup()
self.center_tracker = VectorizedPoint(self.center_point)
if freqs is None:
@ -60,80 +71,74 @@ class FourierCirclesScene(Scene):
if coefficients is None:
coefficients = self.get_coefficients()
last_circle = None
last_vector = None
for freq, coefficient in zip(freqs, coefficients):
if last_circle:
center_func = last_circle.get_start
if last_vector:
center_func = last_vector.get_end
else:
center_func = self.center_tracker.get_location
circle = self.get_circle(
vector = self.get_rotating_vector(
coefficient=coefficient,
freq=freq,
color=next(color_iterator),
center_func=center_func,
)
circles.add(circle)
last_circle = circle
return circles
vectors.add(vector)
last_vector = vector
return vectors
def get_circle(self, coefficient, freq, color, center_func):
radius = abs(coefficient)
phase = np.log(coefficient).imag
circle = Circle(
radius=radius,
color=color,
**self.circle_style,
)
line_points = (
circle.get_center(),
circle.get_start(),
)
if self.use_vectors:
circle.radial_line = Arrow(
*line_points,
**self.arrow_config,
)
def get_rotating_vector(self, coefficient, freq, center_func):
vector = Vector(RIGHT, **self.vector_config)
vector.scale(abs(coefficient))
if abs(coefficient) == 0:
phase = 0
else:
circle.radial_line = Line(
*line_points,
color=WHITE,
**self.circle_style,
phase = np.log(coefficient).imag
vector.rotate(phase, about_point=ORIGIN)
vector.freq = freq
vector.phase = phase
vector.coefficient = coefficient
vector.center_func = center_func
vector.add_updater(self.update_vector)
return vector
def update_vector(self, vector, dt):
time = self.get_vector_time()
vector.set_angle(
vector.phase + time * vector.freq * TAU
)
vector.shift(
vector.center_func() - vector.get_start()
)
return vector
def get_circles(self, vectors):
return VGroup(*[
self.get_circle(
vector,
color=color
)
circle.add(circle.radial_line)
circle.freq = freq
circle.phase = phase
circle.rotate(phase)
circle.coefficient = coefficient
circle.center_func = center_func
for vector, color in zip(
vectors,
self.get_color_iterator()
)
])
def get_circle(self, vector, color=BLUE):
circle = Circle(color=color, **self.circle_config)
circle.center_func = vector.get_start
circle.radius_func = vector.get_length
circle.add_updater(self.update_circle)
return circle
def update_circle(self, circle, dt):
circle.rotate(
self.get_slow_factor() * circle.freq * dt * TAU
)
def update_circle(self, circle):
circle.set_width(2 * circle.radius_func())
circle.move_to(circle.center_func())
return circle
def get_rotating_vectors(self, circles):
return VGroup(*[
self.get_rotating_vector(circle)
for circle in circles
])
def get_rotating_vector(self, circle):
vector = Vector(RIGHT, color=WHITE)
vector.add_updater(lambda v, dt: v.put_start_and_end_on(
circle.get_center(),
circle.get_start(),
))
circle.vector = vector
return vector
def get_circle_end_path(self, circles, color=YELLOW):
coefs = [c.coefficient for c in circles]
freqs = [c.freq for c in circles]
center = circles[0].get_center()
def get_vector_sum_path(self, vectors, color=YELLOW):
coefs = [v.coefficient for v in vectors]
freqs = [v.freq for v in vectors]
center = vectors[0].get_start()
path = ParametricFunction(
lambda t: center + reduce(op.add, [
@ -150,13 +155,14 @@ class FourierCirclesScene(Scene):
return path
# TODO, this should be a general animated mobect
def get_drawn_path(self, circles, stroke_width=2, **kwargs):
path = self.get_circle_end_path(circles, **kwargs)
def get_drawn_path(self, vectors, stroke_width=2, **kwargs):
path = self.get_vector_sum_path(vectors, **kwargs)
broken_path = CurvesAsSubmobjects(path)
broken_path.curr_time = 0
def update_path(path, dt):
alpha = path.curr_time * self.get_slow_factor()
# alpha = path.curr_time * self.get_slow_factor()
alpha = self.get_vector_time()
n_curves = len(path)
for a, sp in zip(np.linspace(0, 1, n_curves), path):
b = alpha - a
@ -173,12 +179,12 @@ class FourierCirclesScene(Scene):
return broken_path
def get_y_component_wave(self,
circles,
vectors,
left_x=1,
color=PINK,
n_copies=2,
right_shift_rate=5):
path = self.get_circle_end_path(circles)
path = self.get_vector_sum_path(vectors)
wave = ParametricFunction(
lambda t: op.add(
right_shift_rate * t * LEFT,
@ -216,15 +222,16 @@ class FourierCirclesScene(Scene):
return VGroup(wave, wave_copies)
def get_wave_y_line(self, circles, wave):
def get_wave_y_line(self, vectors, wave):
return DashedLine(
circles[-1].get_start(),
vectors[-1].get_end(),
wave[0].get_end(),
stroke_width=1,
dash_length=DEFAULT_DASH_LENGTH * 0.5,
)
# Computing Fourier series
# i.e. where all the math happens
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
if freqs is None:
freqs = self.get_freqs()
@ -250,7 +257,7 @@ class FourierCirclesScene(Scene):
class FourierSeriesIntroBackground4(FourierCirclesScene):
CONFIG = {
"n_circles": 4,
"n_vectors": 4,
"center_point": 4 * LEFT,
"run_time": 30,
"big_radius": 1.5,
@ -271,7 +278,7 @@ class FourierSeriesIntroBackground4(FourierCirclesScene):
self.wait(self.run_time)
def get_ks(self):
return np.arange(1, 2 * self.n_circles + 1, 2)
return np.arange(1, 2 * self.n_vectors + 1, 2)
def get_freqs(self):
return self.base_frequency * self.get_ks()
@ -282,53 +289,70 @@ class FourierSeriesIntroBackground4(FourierCirclesScene):
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 8,
"n_vectors": 8,
}
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 12,
"n_vectors": 12,
}
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 20,
"n_vectors": 20,
}
class FourierOfPiSymbol(FourierCirclesScene):
CONFIG = {
"n_circles": 50,
"n_vectors": 51,
"center_point": ORIGIN,
"slow_factor": 0.1,
"run_time": 30,
"n_cycles": 1,
"tex": "\\pi",
"start_drawn": False,
"max_circle_stroke_width": 1,
}
def construct(self):
self.add_vectors_circles_path()
for n in range(self.n_cycles):
self.run_one_cycle()
def add_vectors_circles_path(self):
path = self.get_path()
coefs = self.get_coefficients_of_path(path)
circles = self.get_circles(coefficients=coefs)
vectors = self.get_rotating_vectors(coefficients=coefs)
circles = self.get_circles(vectors)
self.set_decreasing_stroke_widths(circles)
# approx_path = self.get_circle_end_path(circles)
drawn_path = self.get_drawn_path(circles)
# approx_path = self.get_vector_sum_path(circles)
drawn_path = self.get_drawn_path(vectors)
if self.start_drawn:
drawn_path.curr_time = 1 / self.slow_factor
self.vector_clock.increment_value(1)
self.add(path)
self.add(vectors)
self.add(circles)
self.add(drawn_path)
self.wait(self.run_time)
self.vectors = vectors
self.circles = circles
self.path = path
self.drawn_path = drawn_path
def run_one_cycle(self):
time = 1 / self.slow_factor
self.wait(time)
def set_decreasing_stroke_widths(self, circles):
mcsw = self.max_circle_stroke_width
for k, circle in zip(it.count(1), circles):
circle.set_stroke(width=max(
1 / np.sqrt(k),
1,
# mcsw / np.sqrt(k),
mcsw / k,
mcsw,
))
return circles
@ -343,7 +367,7 @@ class FourierOfPiSymbol(FourierCirclesScene):
class FourierOfName(FourierOfPiSymbol):
CONFIG = {
"n_circles": 100,
"n_vectors": 100,
"name_color": WHITE,
"name_text": "Abc",
"time_per_symbol": 5,
@ -388,14 +412,14 @@ class FourierOfName(FourierOfPiSymbol):
class FourierOfPiSymbol5(FourierOfPiSymbol):
CONFIG = {
"n_circles": 5,
"n_vectors": 5,
"run_time": 10,
}
class FourierOfTrebleClef(FourierOfPiSymbol):
CONFIG = {
"n_circles": 100,
"n_vectors": 101,
"run_time": 10,
"start_drawn": True,
"file_name": "TrebleClef",
@ -419,7 +443,7 @@ class FourierOfIP(FourierOfTrebleClef):
CONFIG = {
"file_name": "IP_logo2",
"height": 6,
"n_circles": 100,
"n_vectors": 100,
}
# def construct(self):
@ -451,7 +475,7 @@ class FourierOfEighthNote(FourierOfTrebleClef):
class FourierOfN(FourierOfTrebleClef):
CONFIG = {
"height": 6,
"n_circles": 1000,
"n_vectors": 1000,
}
def get_shape(self):
@ -461,7 +485,7 @@ class FourierOfN(FourierOfTrebleClef):
class FourierNailAndGear(FourierOfTrebleClef):
CONFIG = {
"height": 6,
"n_circles": 200,
"n_vectors": 200,
"run_time": 100,
"slow_factor": 0.01,
"parametric_function_step_size": 0.0001,
@ -479,7 +503,7 @@ class FourierNailAndGear(FourierOfTrebleClef):
class FourierBatman(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 100,
"n_vectors": 100,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@ -495,7 +519,7 @@ class FourierBatman(FourierOfTrebleClef):
class FourierHeart(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 100,
"n_vectors": 100,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@ -517,7 +541,7 @@ class FourierHeart(FourierOfTrebleClef):
class FourierNDQ(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 1000,
"n_vectors": 1000,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@ -535,7 +559,7 @@ class FourierNDQ(FourierOfTrebleClef):
class FourierGoogleG(FourierOfTrebleClef):
CONFIG = {
"n_circles": 10,
"n_vectors": 10,
"height": 5,
"g_colors": [
"#4285F4",
@ -570,7 +594,7 @@ class FourierGoogleG(FourierOfTrebleClef):
class ExplainCircleAnimations(FourierCirclesScene):
CONFIG = {
"n_circles": 100,
"n_vectors": 100,
"center_point": 2 * DOWN,
"n_top_circles": 9,
"path_height": 3,

View file

@ -31,6 +31,7 @@ class TemperatureGraphScene(SpecialThreeDScene):
"stroke_color": WHITE,
"stroke_opacity": 0.5,
},
"temp_text": "Temperature",
}
def get_three_d_axes(self, include_labels=True, include_numbers=False, **kwargs):
@ -103,7 +104,7 @@ class TemperatureGraphScene(SpecialThreeDScene):
t_label.next_to(axes.y_axis.get_end(), UP)
axes.y_axis.label = t_label
temp_label = TextMobject("Temperature")
temp_label = TextMobject(self.temp_text)
temp_label.rotate(90 * DEGREES, RIGHT)
temp_label.next_to(axes.z_axis.get_zenith(), RIGHT)
axes.z_axis.label = temp_label

View file

@ -0,0 +1,242 @@
from manimlib.imports import *
from active_projects.ode.part2.fourier_series import FourierOfTrebleClef
class ComplexFourierSeriesExample(FourierOfTrebleClef):
CONFIG = {
"file_name": "EighthNote",
"run_time": 10,
"n_vectors": 200,
"n_cycles": 2,
"max_circle_stroke_width": 0.75,
"drawing_height": 5,
"center_point": DOWN,
"top_row_y": 3,
"top_row_label_y": 2,
"top_row_x_spacing": 1.75,
"top_row_copy_scale_factor": 0.9,
"start_drawn": False,
}
def construct(self):
self.add_vectors_circles_path()
self.add_top_row(self.vectors, self.circles)
self.write_title()
self.highlight_vectors_one_by_one()
self.change_shape()
def write_title(self):
title = TextMobject("Complex\\\\Fourier series")
title.scale(1.5)
title.to_edge(LEFT)
title.match_y(self.path)
self.wait(5)
self.play(FadeInFromDown(title))
self.wait(2)
self.title = title
def highlight_vectors_one_by_one(self):
# Don't know why these vectors can't get copied.
# That seems like a problem that will come up again.
labels = self.top_row[-1]
next_anims = []
for vector, circle, label in zip(self.vectors, self.circles, labels):
# v_color = vector.get_color()
c_color = circle.get_color()
c_stroke_width = circle.get_stroke_width()
rect = SurroundingRectangle(label, color=PINK)
self.play(
# vector.set_color, PINK,
circle.set_stroke, RED, 3,
FadeIn(rect),
*next_anims
)
self.wait()
next_anims = [
# vector.set_color, v_color,
circle.set_stroke, c_color, c_stroke_width,
FadeOut(rect),
]
self.play(*next_anims)
def change_shape(self):
# path_mob = TexMobject("\\pi")
path_mob = SVGMobject("Nail_And_Gear")
new_path = path_mob.family_members_with_points()[0]
new_path.set_height(4)
new_path.move_to(self.path, DOWN)
new_path.shift(0.5 * UP)
new_coefs = self.get_coefficients_of_path(new_path)
new_vectors = self.get_rotating_vectors(
coefficients=new_coefs
)
new_drawn_path = self.get_drawn_path(new_vectors)
self.vector_clock.set_value(0)
self.vector_clock.suspend_updating(0)
vectors = self.vectors
anims = []
for vect, new_vect in zip(vectors, new_vectors):
new_vect.update()
new_vect.clear_updaters()
line = Line(stroke_width=0)
line.put_start_and_end_on(*vect.get_start_and_end())
anims.append(ApplyMethod(
line.put_start_and_end_on,
*new_vect.get_start_and_end()
))
vect.freq = new_vect.freq
vect.phase = new_vect.phase
vect.coefficient = new_vect.coefficient
vect.line = line
vect.add_updater(
lambda v: v.put_start_and_end_on(
*v.line.get_start_and_end()
)
)
anims += [
FadeOut(self.drawn_path)
]
self.play(*anims, run_time=3)
self.vector_clock.resume_updating()
for vect in self.vectors:
vect.remove_updater(vect.updaters[-1])
self.add(new_drawn_path)
for n in range(self.n_cycles):
self.run_one_cycle()
#
def get_path(self):
path = super().get_path()
path.set_height(self.drawing_height)
path.to_edge(DOWN)
return path
def add_top_row(self, vectors, circles, max_freq=3):
self.top_row = self.get_top_row(
vectors, circles, max_freq
)
self.add(self.top_row)
def get_top_row(self, vectors, circles, max_freq=3):
vector_copies = VGroup()
circle_copies = VGroup()
for vector, circle in zip(vectors, circles):
if vector.freq > max_freq:
break
vcopy = vector.copy()
vcopy.clear_updaters()
ccopy = circle.copy()
ccopy.clear_updaters()
ccopy.original = circle
vcopy.original = vector
vcopy.center_point = np.array([
vector.freq * self.top_row_x_spacing,
self.top_row_y,
0
])
ccopy.center_point = vcopy.center_point
vcopy.add_updater(self.update_top_row_vector_copy)
ccopy.add_updater(self.update_top_row_circle_copy)
vector_copies.add(vcopy)
circle_copies.add(ccopy)
dots = VGroup(*[
TexMobject("\\dots").next_to(
circle_copies, direction,
MED_LARGE_BUFF,
)
for direction in [LEFT, RIGHT]
])
labels = self.get_top_row_labels(vector_copies)
return VGroup(
vector_copies,
circle_copies,
dots,
labels,
)
def update_top_row_vector_copy(self, vcopy):
vcopy.become(vcopy.original)
vcopy.scale(self.top_row_copy_scale_factor)
vcopy.shift(vcopy.center_point - vcopy.get_start())
return vcopy
def update_top_row_circle_copy(self, ccopy):
ccopy.become(ccopy.original)
ccopy.scale(self.top_row_copy_scale_factor)
ccopy.move_to(ccopy.center_point)
return ccopy
def get_top_row_labels(self, vector_copies):
labels = VGroup()
for vector_copy in vector_copies:
freq = vector_copy.freq
label = Integer(freq)
label.move_to(np.array([
freq * self.top_row_x_spacing,
self.top_row_label_y,
0
]))
labels.add(label)
return labels
class ComplexFourierSeriesExampleEnd(ExternallyAnimatedScene):
pass
class FourierSeriesExampleWithRectForZoom(ComplexFourierSeriesExample):
CONFIG = {
"n_vectors": 100,
"slow_factor": 0.01,
"rect_scale_factor": 0.15,
"parametric_function_step_size": 0.0001,
"start_drawn": True,
}
def construct(self):
self.add_vectors_circles_path()
self.circles.set_stroke(opacity=0.5)
rect = self.get_rect()
rect.set_height(self.rect_scale_factor * FRAME_HEIGHT)
rect.add_updater(lambda m: m.move_to(
center_of_mass([
v.get_end()
for v in self.vectors
])
))
self.add(rect)
self.run_one_cycle()
def get_rect(self):
return ScreenRectangle(
color=WHITE,
stroke_width=2,
)
class ZoomedInFourierSeriesExample(FourierSeriesExampleWithRectForZoom, MovingCameraScene):
CONFIG = {
"vector_config": {
"max_tip_length_to_length_ratio": 0.15,
"tip_length": 0.05,
}
}
def setup(self):
ComplexFourierSeriesExample.setup(self)
MovingCameraScene.setup(self)
def get_rect(self):
return self.camera_frame

View file

@ -0,0 +1,22 @@
from manimlib.imports import *
class WhyWouldYouCare(TeacherStudentsScene):
def construct(self):
self.student_says(
"Who cares!",
target_mode="sassy",
student_index=2,
added_anims=[self.teacher.change, "guilty"],
)
self.wait()
self.play(
RemovePiCreatureBubble(self.students[2]),
self.teacher.change, "raise_right_hand",
self.get_student_changes(
"pondering", "erm", "thinking",
look_at_arg=self.screen,
)
)
self.look_at(self.screen)
self.wait(5)

View file

@ -0,0 +1,345 @@
from manimlib.imports import *
from active_projects.ode.part3.temperature_graphs import TemperatureGraphScene
from active_projects.ode.part2.wordy_scenes import WriteHeatEquationTemplate
class RelationToOtherVideos(Scene):
CONFIG = {
"camera_config": {
"background_color": DARK_GREY,
},
}
def construct(self):
# Show three videos
videos = self.get_video_thumbnails()
brace = Brace(videos, UP)
text = TextMobject("Heat equation")
text.scale(2)
text.next_to(brace, UP)
self.play(
LaggedStartMap(
FadeInFrom, videos,
lambda m: (m, LEFT),
lag_ratio=0.4,
run_time=2,
),
GrowFromCenter(brace),
FadeInFromDown(text),
)
self.wait()
group = Group(text, brace, videos)
# Show Fourier thinking
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(4)
fourier.to_edge(RIGHT)
group.generate_target()
group.target.to_edge(DOWN)
fourier.align_to(group.target[0], DOWN)
bubble = ThoughtBubble(
direction=RIGHT,
width=3,
height=2,
fill_opacity=0.5,
stroke_color=WHITE,
)
bubble[-1].shift(0.25 * DOWN + 0.5 * LEFT)
bubble[:-1].rotate(20 * DEGREES)
for mob in bubble[:-1]:
mob.rotate(-20 * DEGREES)
bubble.move_tip_to(
fourier.get_corner(UL) + DOWN
)
bubble.to_edge(UP, buff=SMALL_BUFF)
self.play(
MoveToTarget(group),
FadeInFrom(fourier, LEFT)
)
self.play(Write(bubble, run_time=1))
self.wait()
# Discount first two
first_two = videos[:2]
first_two.generate_target()
first_two.target.scale(0.5)
first_two.target.to_corner(DL)
new_brace = Brace(first_two.target, UP)
self.play(
# fourier.scale, 0.8,
fourier.match_x, new_brace,
fourier.to_edge, UP,
MoveToTarget(first_two),
Transform(brace, new_brace),
text.scale, 0.7,
text.next_to, new_brace, UP,
FadeOutAndShift(bubble, LEFT),
)
self.play(
videos[2].scale, 1.7,
videos[2].to_corner, UR,
)
self.wait()
#
def get_video_thumbnails(self):
thumbnails = Group(
ImageMobject("diffyq_part2_thumbnail"),
ImageMobject("diffyq_part3_thumbnail"),
ImageMobject("diffyq_part4_thumbnail"),
)
for thumbnail in thumbnails:
thumbnail.set_height(4)
thumbnail.add(SurroundingRectangle(
thumbnail,
color=WHITE,
stroke_width=2,
buff=0
))
thumbnails.arrange(RIGHT, buff=LARGE_BUFF)
thumbnails.set_width(FRAME_WIDTH - 1)
return thumbnails
class ShowLinearity(WriteHeatEquationTemplate, TemperatureGraphScene):
CONFIG = {
"temp_text": "Temp",
"alpha": 0.1,
"axes_config": {
"z_max": 2,
"z_min": -2,
"z_axis_config": {
"tick_frequency": 0.5,
"unit_size": 1.5,
},
},
"default_surface_config": {
"resolution": (16, 16)
# "resolution": (4, 4)
},
"freqs": [2, 5],
}
def setup(self):
TemperatureGraphScene.setup(self)
WriteHeatEquationTemplate.setup(self)
def construct(self):
self.init_camera()
self.add_three_graphs()
self.show_words()
self.add_function_labels()
self.change_scalars()
def init_camera(self):
self.camera.set_distance(1000)
def add_three_graphs(self):
axes_group = self.get_axes_group()
axes0, axes1, axes2 = axes_group
freqs = self.freqs
scalar_trackers = Group(
ValueTracker(1),
ValueTracker(1),
)
graphs = VGroup(
self.get_graph(axes0, [freqs[0]], [scalar_trackers[0]]),
self.get_graph(axes1, [freqs[1]], [scalar_trackers[1]]),
self.get_graph(axes2, freqs, scalar_trackers),
)
plus = TexMobject("+").scale(2)
equals = TexMobject("=").scale(2)
plus.move_to(midpoint(
axes0.get_right(),
axes1.get_left(),
))
equals.move_to(midpoint(
axes1.get_right(),
axes2.get_left(),
))
self.add(axes_group)
self.add(graphs)
self.add(plus)
self.add(equals)
self.axes_group = axes_group
self.graphs = graphs
self.scalar_trackers = scalar_trackers
self.plus = plus
self.equals = equals
def show_words(self):
equation = self.get_d1_equation()
name = TextMobject("Heat equation")
name.next_to(equation, DOWN)
name.set_color_by_gradient(RED, YELLOW)
group = VGroup(equation, name)
group.to_edge(UP)
shift_val = 0.5 * RIGHT
arrow = Vector(1.5 * RIGHT)
arrow.move_to(group)
arrow.shift(shift_val)
linear_word = TextMobject("``Linear''")
linear_word.scale(2)
linear_word.next_to(arrow, RIGHT)
self.add(group)
self.wait()
self.play(
ShowCreation(arrow),
group.next_to, arrow, LEFT
)
self.play(FadeInFrom(linear_word, LEFT))
self.wait()
def add_function_labels(self):
axes_group = self.axes_group
graphs = self.graphs
solution_labels = VGroup()
for axes in axes_group:
label = TextMobject("Solution", "$\\checkmark$")
label.set_color_by_tex("checkmark", GREEN)
label.next_to(axes, DOWN)
solution_labels.add(label)
kw = {
"tex_to_color_map": {
"T_1": BLUE,
"T_2": GREEN,
}
}
T1 = TexMobject("a", "T_1", **kw)
T2 = TexMobject("b", "T_2", **kw)
T_sum = TexMobject("T_1", "+", "T_2", **kw)
T_sum_with_scalars = TexMobject(
"a", "T_1", "+", "b", "T_2", **kw
)
T1.next_to(graphs[0], UP)
T2.next_to(graphs[1], UP)
T_sum.next_to(graphs[2], UP)
T_sum.shift(SMALL_BUFF * DOWN)
T_sum_with_scalars.move_to(T_sum)
a_brace = Brace(T1[0], UP, buff=SMALL_BUFF)
b_brace = Brace(T2[0], UP, buff=SMALL_BUFF)
s1_decimal = DecimalNumber()
s1_decimal.match_color(T1[1])
s1_decimal.next_to(a_brace, UP, SMALL_BUFF)
s1_decimal.add_updater(lambda m: m.set_value(
self.scalar_trackers[0].get_value()
))
s2_decimal = DecimalNumber()
s2_decimal.match_color(T2[1])
s2_decimal.next_to(b_brace, UP, SMALL_BUFF)
s2_decimal.add_updater(lambda m: m.set_value(
self.scalar_trackers[1].get_value()
))
self.play(
FadeInFrom(T1[1], DOWN),
FadeInFrom(solution_labels[0], UP),
)
self.play(
FadeInFrom(T2[1], DOWN),
FadeInFrom(solution_labels[1], UP),
)
self.wait()
self.play(
TransformFromCopy(T1[1], T_sum[0]),
TransformFromCopy(T2[1], T_sum[2]),
TransformFromCopy(self.plus, T_sum[1]),
*[
Transform(
graph.copy().set_fill(opacity=0),
graphs[2].copy().set_fill(opacity=0),
remover=True
)
for graph in graphs[:2]
]
)
self.wait()
self.play(FadeInFrom(solution_labels[2], UP))
self.wait()
# Show constants
self.play(
FadeIn(T1[0]),
FadeIn(T2[0]),
FadeIn(a_brace),
FadeIn(b_brace),
FadeIn(s1_decimal),
FadeIn(s2_decimal),
FadeOut(T_sum),
FadeIn(T_sum_with_scalars),
)
def change_scalars(self):
s1, s2 = self.scalar_trackers
kw = {
"run_time": 2,
}
for graph in self.graphs:
graph.resume_updating()
self.play(s2.set_value, -0.5, **kw)
self.play(s1.set_value, -0.2, **kw)
self.play(s2.set_value, 1.5, **kw)
self.play(s1.set_value, 1.2)
self.play(s2.set_value, 0.3)
self.wait()
#
def get_axes_group(self):
axes_group = VGroup(*[
self.get_axes()
for x in range(3)
])
axes_group.arrange(RIGHT, buff=2)
axes_group.set_width(FRAME_WIDTH - 1)
axes_group.to_edge(DOWN, buff=1)
return axes_group
def get_axes(self):
axes = self.get_three_d_axes()
# axes.input_plane.set_fill(opacity=0)
# axes.input_plane.set_stroke(width=0.5)
# axes.add(axes.input_plane)
self.orient_three_d_mobject(axes)
axes.rotate(-5 * DEGREES, UP)
axes.set_width(4)
axes.x_axis.label.next_to(
axes.x_axis.get_end(), DOWN,
buff=2 * SMALL_BUFF
)
return axes
def get_graph(self, axes, freqs, scalar_trackers):
L = axes.x_max
a = self.alpha
def func(x, t):
scalars = [st.get_value() for st in scalar_trackers]
return np.sum([
s * np.cos(k * x) * np.exp(-a * (k**2) * t)
for freq, s in zip(freqs, scalars)
for k in [freq * PI / L]
])
def get_surface_graph_group():
return VGroup(
self.get_surface(axes, func),
self.get_time_slice_graph(axes, func, t=0),
)
result = always_redraw(get_surface_graph_group)
result.suspend_updating()
return result

View file

@ -333,7 +333,7 @@ class Camera(object):
vmobject, vmobject.points
)
# TODO, shouldn't this be handled in transform_points_pre_display?
points = points - self.get_frame_center()
# points = points - self.get_frame_center()
if len(points) == 0:
return

View file

@ -514,6 +514,9 @@ class Line(TipableVMobject):
about_point=self.get_start(),
)
def set_length(self, length):
self.scale(length / self.get_length())
def set_opacity(self, opacity, family=True):
# Overwrite default, which would set
# the fill opacity

View file

@ -455,9 +455,10 @@ class Bubble(SVGMobject):
mover.shift(point - self.get_tip())
return self
def flip(self):
Mobject.flip(self)
self.direction = -np.array(self.direction)
def flip(self, axis=UP):
Mobject.flip(self, axis=axis)
if abs(axis[0]) > 0:
self.direction = -np.array(self.direction)
return self
def pin_to(self, mobject):