mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
1783 lines
51 KiB
Python
1783 lines
51 KiB
Python
from manimlib.imports import *
|
|
from old_projects.div_curl import PureAirfoilFlow
|
|
from old_projects.div_curl import move_submobjects_along_vector_field
|
|
from old_projects.div_curl import move_points_along_vector_field
|
|
from old_projects.div_curl import four_swirls_function
|
|
from old_projects.lost_lecture import ShowWord
|
|
|
|
|
|
class CreationDestructionMobject(VMobject):
|
|
CONFIG = {
|
|
"start_time": 0,
|
|
"frequency": 0.25,
|
|
"max_ratio_shown": 0.3,
|
|
"use_copy": True,
|
|
}
|
|
|
|
def __init__(self, template, **kwargs):
|
|
VMobject.__init__(self, **kwargs)
|
|
if self.use_copy:
|
|
self.ghost_mob = template.copy().fade(1)
|
|
self.add(self.ghost_mob)
|
|
else:
|
|
self.ghost_mob = template
|
|
# Don't add
|
|
self.shown_mob = template.deepcopy()
|
|
self.shown_mob.clear_updaters()
|
|
self.add(self.shown_mob)
|
|
self.total_time = self.start_time
|
|
|
|
def update(mob, dt):
|
|
mob.total_time += dt
|
|
period = 1.0 / mob.frequency
|
|
unsmooth_alpha = (mob.total_time % period) / period
|
|
alpha = bezier([0, 0, 1, 1])(unsmooth_alpha)
|
|
mrs = mob.max_ratio_shown
|
|
mob.shown_mob.pointwise_become_partial(
|
|
mob.ghost_mob,
|
|
max(interpolate(-mrs, 1, alpha), 0),
|
|
min(interpolate(0, 1 + mrs, alpha), 1),
|
|
)
|
|
|
|
self.add_updater(update)
|
|
|
|
|
|
class Eddy(VMobject):
|
|
CONFIG = {
|
|
"cd_mob_config": {
|
|
"frequency": 0.2,
|
|
"max_ratio_shown": 0.3
|
|
},
|
|
"n_spirils": 5,
|
|
"n_layers": 20,
|
|
"radius": 1,
|
|
"colors": [BLUE_A, BLUE_E],
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
VMobject.__init__(self, **kwargs)
|
|
lines = self.get_lines()
|
|
# self.add(lines)
|
|
self.add(*[
|
|
CreationDestructionMobject(line, **self.cd_mob_config)
|
|
for line in lines
|
|
])
|
|
self.randomize_times()
|
|
|
|
def randomize_times(self):
|
|
for submob in self.submobjects:
|
|
if hasattr(submob, "total_time"):
|
|
T = 1.0 / submob.frequency
|
|
submob.total_time = T * random.random()
|
|
|
|
def get_lines(self):
|
|
a = 0.2
|
|
return VGroup(*[
|
|
self.get_line(r=self.radius * (1 - a + 2 * a * random.random()))
|
|
for x in range(self.n_layers)
|
|
])
|
|
|
|
def get_line(self, r):
|
|
return ParametricFunction(
|
|
lambda t: r * (t + 1)**(-1) * np.array([
|
|
np.cos(TAU * t),
|
|
np.sin(TAU * t),
|
|
0,
|
|
]),
|
|
t_min=0.1 * random.random(),
|
|
t_max=self.n_spirils,
|
|
stroke_width=1,
|
|
color=interpolate_color(*self.colors, random.random())
|
|
)
|
|
|
|
|
|
class Chaos(Eddy):
|
|
CONFIG = {
|
|
"n_lines": 12,
|
|
"height": 1,
|
|
"width": 2,
|
|
"n_midpoints": 4,
|
|
"cd_mob_config": {
|
|
"use_copy": False,
|
|
"frequency": 1,
|
|
"max_ratio_shown": 0.8
|
|
}
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
VMobject.__init__(self, **kwargs)
|
|
rect = Rectangle(height=self.height, width=self.width)
|
|
rect.move_to(ORIGIN, DL)
|
|
rect.fade(1)
|
|
self.rect = rect
|
|
self.add(rect)
|
|
|
|
lines = self.get_lines()
|
|
self.add(*[
|
|
CreationDestructionMobject(line, **self.cd_mob_config)
|
|
for line in lines
|
|
])
|
|
self.randomize_times()
|
|
lines.fade(1)
|
|
self.add(lines)
|
|
|
|
def get_lines(self):
|
|
return VGroup(*[
|
|
self.get_line(y)
|
|
for y in np.linspace(0, self.height, self.n_lines)
|
|
])
|
|
|
|
def get_line(self, y):
|
|
frequencies = [0] + list(2 + 2 * np.random.random(self.n_midpoints)) + [0]
|
|
rect = self.rect
|
|
line = Line(
|
|
y * UP, y * UP + self.width * RIGHT,
|
|
stroke_width=1
|
|
)
|
|
line.insert_n_curves(self.n_midpoints)
|
|
line.total_time = random.random()
|
|
delta_h = self.height / (self.n_lines - 1)
|
|
|
|
def update(line, dt):
|
|
x0, y0 = rect.get_corner(DL)[:2]
|
|
x1, y1 = rect.get_corner(UR)[:2]
|
|
line.total_time += dt
|
|
xs = np.linspace(x0, x1, self.n_midpoints + 2)
|
|
new_anchors = [
|
|
np.array([
|
|
x + 1.0 * delta_h * np.cos(f * line.total_time),
|
|
y0 + y + 1.0 * delta_h * np.cos(f * line.total_time),
|
|
0
|
|
])
|
|
for (x, f) in zip(xs, frequencies)
|
|
]
|
|
line.set_points_smoothly(new_anchors)
|
|
|
|
line.add_updater(update)
|
|
return line
|
|
|
|
|
|
class DoublePendulum(VMobject):
|
|
CONFIG = {
|
|
"start_angles": [3 * PI / 7, 3 * PI / 4],
|
|
"color1": BLUE,
|
|
"color2": RED,
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
VMobject.__init__(self, **kwargs)
|
|
line1 = Line(ORIGIN, UP)
|
|
dot1 = Dot(color=self.color1)
|
|
dot1.add_updater(lambda d: d.move_to(line1.get_end()))
|
|
line2 = Line(UP, 2 * UP)
|
|
dot2 = Dot(color=self.color2)
|
|
dot2.add_updater(lambda d: d.move_to(line2.get_end()))
|
|
self.add(line1, line2, dot1, dot2)
|
|
|
|
# Largely copied from https://scipython.com/blog/the-double-pendulum/
|
|
# Pendulum rod lengths (m), bob masses (kg).
|
|
L1, L2 = 1, 1
|
|
m1, m2 = 1, 1
|
|
# The gravitational acceleration (m.s-2).
|
|
g = 9.81
|
|
|
|
self.state_vect = np.array([
|
|
self.start_angles[0], 0,
|
|
self.start_angles[1], 0,
|
|
])
|
|
self.state_vect += np.random.random(4) * 1e-7
|
|
|
|
def update(group, dt):
|
|
for x in range(2):
|
|
line1, line2 = group.submobjects[:2]
|
|
theta1, z1, theta2, z2 = group.state_vect
|
|
|
|
c, s = np.cos(theta1 - theta2), np.sin(theta1 - theta2)
|
|
|
|
theta1dot = z1
|
|
z1dot = (m2 * g * np.sin(theta2) * c - m2 * s * (L1 * (z1**2) * c + L2 * z2**2) -
|
|
(m1 + m2) * g * np.sin(theta1)) / L1 / (m1 + m2 * s**2)
|
|
theta2dot = z2
|
|
z2dot = ((m1 + m2) * (L1 * (z1**2) * s - g * np.sin(theta2) + g * np.sin(theta1) * c) +
|
|
m2 * L2 * (z2**2) * s * c) / L2 / (m1 + m2 * s**2)
|
|
|
|
group.state_vect += 0.5 * dt * np.array([
|
|
theta1dot, z1dot, theta2dot, z2dot,
|
|
])
|
|
group.state_vect[1::2] *= 0.9999
|
|
|
|
p1 = L1 * np.sin(theta1) * RIGHT - L1 * np.cos(theta1) * UP
|
|
p2 = p1 + L2 * np.sin(theta2) * RIGHT - L2 * np.cos(theta2) * UP
|
|
|
|
line1.put_start_and_end_on(ORIGIN, p1)
|
|
line2.put_start_and_end_on(p1, p2)
|
|
|
|
self.add_updater(update)
|
|
|
|
|
|
class DoublePendulums(VGroup):
|
|
def __init__(self, **kwargs):
|
|
colors = [BLUE, RED, YELLOW, PINK, MAROON_B, PURPLE, GREEN]
|
|
VGroup.__init__(
|
|
self,
|
|
*[
|
|
DoublePendulum(
|
|
color1=random.choice(colors),
|
|
color2=random.choice(colors),
|
|
)
|
|
for x in range(5)
|
|
],
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class Diffusion(VMobject):
|
|
CONFIG = {
|
|
"height": 1.5,
|
|
"n_dots": 1000,
|
|
"colors": [RED, BLUE]
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
VMobject.__init__(self, **kwargs)
|
|
self.add_dots()
|
|
self.add_invisible_circles()
|
|
|
|
def add_dots(self):
|
|
dots = VGroup(*[Dot() for x in range(self.n_dots)])
|
|
dots.arrange_in_grid(buff=SMALL_BUFF)
|
|
dots.center()
|
|
dots.set_height(self.height)
|
|
dots.sort(lambda p: p[0])
|
|
dots[:len(dots) // 2].set_color(self.colors[0])
|
|
dots[len(dots) // 2:].set_color(self.colors[1])
|
|
dots.set_fill(opacity=0.8)
|
|
self.dots = dots
|
|
self.add(dots)
|
|
|
|
def add_invisible_circles(self):
|
|
circles = VGroup()
|
|
for dot in self.dots:
|
|
point = dot.get_center()
|
|
radius = get_norm(point)
|
|
circle = Circle(radius=radius)
|
|
circle.rotate(angle_of_vector(point))
|
|
circle.fade(1)
|
|
circles.add(circle)
|
|
self.add_updater_to_dot(dot, circle)
|
|
self.add(circles)
|
|
|
|
def add_updater_to_dot(self, dot, circle):
|
|
dot.total_time = 0
|
|
radius = get_norm(dot.get_center())
|
|
freq = 0.1 + 0.05 * random.random() + 0.05 / radius
|
|
|
|
def update(dot, dt):
|
|
dot.total_time += dt
|
|
prop = (freq * dot.total_time) % 1
|
|
dot.move_to(circle.point_from_proportion(prop))
|
|
|
|
dot.add_updater(update)
|
|
|
|
|
|
class NavierStokesEquations(TexMobject):
|
|
CONFIG = {
|
|
"tex_to_color_map": {
|
|
"\\rho": YELLOW,
|
|
"\\mu": GREEN,
|
|
"\\textbf{v}": BLUE,
|
|
"p{}": RED,
|
|
},
|
|
"width": 10,
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
v_tex = "\\textbf{v}"
|
|
TexMobject.__init__(
|
|
self,
|
|
"\\rho",
|
|
"\\left("
|
|
"{\\partial", v_tex, "\\over",
|
|
"\\partial", "t}",
|
|
"+",
|
|
v_tex, "\\cdot", "\\nabla", v_tex,
|
|
"\\right)",
|
|
"=",
|
|
"-", "\\nabla", "p{}", "+",
|
|
"\\mu", "\\nabla^2", v_tex, "+",
|
|
# "\\frac{1}{3}", "\\mu", "\\nabla",
|
|
# "(", "\\nabla", "\\cdot", v_tex, ")", "+",
|
|
"\\textbf{F}",
|
|
"\\qquad\\qquad",
|
|
"\\nabla", "\\cdot", v_tex, "=", "0",
|
|
**kwargs
|
|
)
|
|
self.set_width(self.width)
|
|
|
|
def get_labels(self):
|
|
parts = self.get_parts()
|
|
words = [
|
|
"Analogous to \\\\ mass $\\times$ acceleration",
|
|
"Pressure\\\\forces",
|
|
"Viscous\\\\forces",
|
|
"External\\\\forces",
|
|
]
|
|
|
|
result = VGroup()
|
|
braces = VGroup()
|
|
word_mobs = VGroup()
|
|
for i, part, word in zip(it.count(), parts, words):
|
|
brace = Brace(part, DOWN, buff=SMALL_BUFF)
|
|
word_mob = brace.get_text(word)
|
|
word_mob.scale(0.7, about_edge=UP)
|
|
word_mobs.add(word_mob)
|
|
braces.add(brace)
|
|
result.add(VGroup(brace, word_mob))
|
|
word_mobs[1:].arrange(RIGHT, buff=MED_SMALL_BUFF)
|
|
word_mobs[1:].next_to(braces[2], DOWN, SMALL_BUFF)
|
|
word_mobs[1].set_color(RED)
|
|
word_mobs[2].set_color(GREEN)
|
|
return result
|
|
|
|
def get_parts(self):
|
|
return VGroup(
|
|
self[:12],
|
|
self[13:16],
|
|
self[17:20],
|
|
self[21:22],
|
|
)
|
|
|
|
|
|
class Test(Scene):
|
|
def construct(self):
|
|
self.add(DoublePendulums())
|
|
self.wait(30)
|
|
|
|
# Scenes
|
|
|
|
|
|
class EddyReference(Scene):
|
|
CONFIG = {
|
|
"radius": 1,
|
|
"label": "Eddy",
|
|
"label": "",
|
|
}
|
|
|
|
def construct(self):
|
|
eddy = Eddy(radius=self.radius)
|
|
new_eddy = eddy.get_lines()
|
|
for line in new_eddy:
|
|
line.set_stroke(
|
|
width=(3 + 3 * random.random())
|
|
)
|
|
label = TextMobject(self.label)
|
|
label.next_to(new_eddy, UP)
|
|
|
|
self.play(
|
|
LaggedStartMap(ShowCreationThenDestruction, new_eddy),
|
|
FadeIn(
|
|
label,
|
|
rate_func=there_and_back_with_pause,
|
|
),
|
|
run_time=3
|
|
)
|
|
|
|
|
|
class EddyReferenceWithLabel(EddyReference):
|
|
CONFIG = {
|
|
"label": "Eddy"
|
|
}
|
|
|
|
|
|
class EddyLabels(Scene):
|
|
def construct(self):
|
|
labels = VGroup(
|
|
TextMobject("Large eddy"),
|
|
TextMobject("Medium eddy"),
|
|
TextMobject("Small eddy"),
|
|
)
|
|
for label in labels:
|
|
self.play(FadeIn(
|
|
label,
|
|
rate_func=there_and_back_with_pause,
|
|
run_time=3
|
|
))
|
|
|
|
|
|
class LargeEddyReference(EddyReference):
|
|
CONFIG = {
|
|
"radius": 2,
|
|
"label": ""
|
|
}
|
|
|
|
|
|
class MediumEddyReference(EddyReference):
|
|
CONFIG = {
|
|
"radius": 0.8,
|
|
"label": "Medium eddy"
|
|
}
|
|
|
|
|
|
class SmallEddyReference(EddyReference):
|
|
CONFIG = {
|
|
"radius": 0.25,
|
|
"label": "Small eddy"
|
|
}
|
|
|
|
|
|
class SomeTurbulenceEquations(PiCreatureScene):
|
|
def construct(self):
|
|
randy, morty = self.pi_creatures
|
|
navier_stokes = NavierStokesEquations()
|
|
line = Line(randy.get_right(), morty.get_left())
|
|
navier_stokes.replace(line, dim_to_match=0)
|
|
navier_stokes.scale(1.2)
|
|
|
|
distribution = TexMobject(
|
|
"E(k) \\propto k^{-5/3}",
|
|
tex_to_color_map={
|
|
"k": GREEN,
|
|
"-5/3": YELLOW,
|
|
}
|
|
)
|
|
distribution.next_to(morty, UL)
|
|
brace = Brace(distribution, DOWN, buff=SMALL_BUFF)
|
|
brace_words = brace.get_text("Explained soon...")
|
|
brace_group = VGroup(brace, brace_words)
|
|
|
|
self.play(
|
|
Write(navier_stokes),
|
|
randy.change, "confused", navier_stokes,
|
|
morty.change, "confused", navier_stokes,
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
morty.change, "raise_right_hand", distribution,
|
|
randy.look_at, distribution,
|
|
FadeInFromDown(distribution),
|
|
navier_stokes.fade, 0.5,
|
|
)
|
|
self.play(GrowFromCenter(brace_group))
|
|
self.play(randy.change, "pondering", distribution)
|
|
self.wait(3)
|
|
dist_group = VGroup(distribution, brace_group)
|
|
self.play(
|
|
LaggedStartMap(FadeOut, VGroup(randy, morty, navier_stokes)),
|
|
dist_group.scale, 1.5,
|
|
dist_group.center,
|
|
dist_group.to_edge, UP,
|
|
)
|
|
self.wait()
|
|
|
|
def create_pi_creatures(self):
|
|
randy, morty = Randolph(), Mortimer()
|
|
randy.to_corner(DL)
|
|
morty.to_corner(DR)
|
|
return (randy, morty)
|
|
|
|
|
|
class JokeRingEquation(Scene):
|
|
def construct(self):
|
|
items = VGroup(
|
|
TextMobject("Container with a lip"),
|
|
TextMobject("Fill with smoke (or fog)"),
|
|
TextMobject("Hold awkwardly"),
|
|
)
|
|
line = Line(LEFT, RIGHT).set_width(items.get_width() + 1)
|
|
items.add(line)
|
|
items.add(TextMobject("Vortex ring"))
|
|
items.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
|
|
line.shift(LEFT)
|
|
plus = TexMobject("+")
|
|
plus.next_to(line.get_left(), UR, SMALL_BUFF)
|
|
line.add(plus)
|
|
items.to_edge(RIGHT)
|
|
|
|
point = 3.8 * LEFT + 0.2 * UP
|
|
arrow1 = Arrow(
|
|
items[0].get_left(), point + 0.8 * UP + 0.3 * RIGHT,
|
|
path_arc=90 * DEGREES,
|
|
)
|
|
arrow1.pointwise_become_partial(arrow1, 0, 0.99)
|
|
|
|
arrow2 = Arrow(
|
|
items[1].get_left(), point,
|
|
)
|
|
arrows = VGroup(arrow1, arrow2)
|
|
|
|
for i in 0, 1:
|
|
self.play(
|
|
FadeInFromDown(items[i]),
|
|
ShowCreation(arrows[i])
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStartMap(FadeIn, items[2:]))
|
|
self.wait()
|
|
self.play(FadeOut(arrows))
|
|
self.wait()
|
|
|
|
|
|
class VideoOnPhysicsGirlWrapper(Scene):
|
|
def construct(self):
|
|
rect = ScreenRectangle(height=6)
|
|
title = TextMobject("Video on Physics Girl")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
rect.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
|
|
|
|
class LightBouncingOffFogParticle(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"Light bouncing\\\\",
|
|
"off fog particles"
|
|
)
|
|
arrow = Vector(UP + 0.5 * RIGHT)
|
|
arrow.next_to(words, UP)
|
|
arrow.set_color(WHITE)
|
|
|
|
self.add(words)
|
|
self.play(GrowArrow(arrow))
|
|
self.wait()
|
|
|
|
|
|
class NightHawkInLightWrapper(Scene):
|
|
def construct(self):
|
|
title = TextMobject("NightHawkInLight")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
rect = ScreenRectangle(height=6)
|
|
rect.next_to(title, DOWN)
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
|
|
|
|
class CarefulWithLasers(TeacherStudentsScene):
|
|
def construct(self):
|
|
morty = self.teacher
|
|
randy = self.students[1]
|
|
randy2 = self.students[2]
|
|
# randy.change('hooray')
|
|
laser = VGroup(
|
|
Rectangle(
|
|
height=0.1,
|
|
width=0.3,
|
|
fill_color=LIGHT_GREY,
|
|
fill_opacity=1,
|
|
stroke_color=DARK_GREY,
|
|
stroke_width=1,
|
|
),
|
|
Line(ORIGIN, 10 * RIGHT, color=GREEN_SCREEN)
|
|
)
|
|
laser.arrange(RIGHT, buff=0)
|
|
laser.rotate(45 * DEGREES)
|
|
laser.shift(randy.get_corner(UR) - laser[0].get_center() + 0.1 * DR)
|
|
|
|
laser.time = 0
|
|
|
|
def update_laser(laser, dt):
|
|
laser.time += dt
|
|
laser.rotate(
|
|
0.5 * dt * np.sin(laser.time),
|
|
about_point=laser[0].get_center()
|
|
)
|
|
laser.add_updater(update_laser)
|
|
|
|
self.play(LaggedStartMap(FadeInFromDown, self.pi_creatures, run_time=1))
|
|
self.add(self.pi_creatures, laser)
|
|
for pi in self.pi_creatures:
|
|
pi.add_updater(lambda p: p.look_at(laser[1]))
|
|
self.play(
|
|
ShowCreation(laser),
|
|
self.get_student_changes(
|
|
"surprised", "hooray", "horrified",
|
|
look_at_arg=laser
|
|
)
|
|
)
|
|
self.teacher_says(
|
|
"Careful with \\\\ the laser!",
|
|
target_mode="angry"
|
|
)
|
|
self.wait(2.2)
|
|
morty.save_state()
|
|
randy2.save_state()
|
|
self.play(
|
|
morty.blink, randy2.blink,
|
|
run_time=0.3
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
morty.restore, randy2.restore,
|
|
run_time=0.3
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class SetAsideTurbulence(PiCreatureScene):
|
|
def construct(self):
|
|
self.pi_creature_says(
|
|
"Forget vortex rings",
|
|
target_mode="speaking"
|
|
)
|
|
self.wait()
|
|
self.pi_creature_says(
|
|
"look at that\\\\ turbulence!",
|
|
target_mode="surprised"
|
|
)
|
|
self.wait()
|
|
|
|
def create_pi_creature(self):
|
|
morty = Mortimer()
|
|
morty.to_corner(DR)
|
|
return morty
|
|
|
|
|
|
class WavingRodLabel(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"(Waving a small flag \\\\ through the air)"
|
|
)
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
|
|
class SeekOrderWords(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Seek order amidst chaos")
|
|
words.scale(1.5)
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
|
|
class LongEddy(Scene):
|
|
def construct(self):
|
|
self.add(Eddy())
|
|
self.wait(30)
|
|
|
|
|
|
class LongDoublePendulum(Scene):
|
|
def construct(self):
|
|
self.add(DoublePendulums())
|
|
self.wait(30)
|
|
|
|
|
|
class LongDiffusion(Scene):
|
|
def construct(self):
|
|
self.add(Diffusion())
|
|
self.wait(30)
|
|
|
|
|
|
class AskAboutTurbulence(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.pi_creatures_ask()
|
|
self.divide_by_qualitative_quantitative()
|
|
self.three_qualitative_descriptors()
|
|
self.rigorous_definition()
|
|
|
|
def pi_creatures_ask(self):
|
|
morty = self.teacher
|
|
randy = self.students[1]
|
|
morty.change("surprised")
|
|
|
|
words = TextMobject("Wait,", "what", "exactly \\\\", "is turbulence?")
|
|
question = TextMobject("What", "is turbulence?")
|
|
question.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH - 1)
|
|
h_line.next_to(question, DOWN, buff=MED_LARGE_BUFF)
|
|
|
|
self.student_says(
|
|
words,
|
|
target_mode='raise_left_hand',
|
|
added_anims=[morty.change, 'pondering']
|
|
)
|
|
self.change_student_modes(
|
|
"erm", "raise_left_hand", "confused",
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
morty.change, "raise_right_hand",
|
|
FadeOut(randy.bubble),
|
|
ReplacementTransform(VGroup(words[1], words[3]), question),
|
|
FadeOut(VGroup(words[0], words[2])),
|
|
self.get_student_changes(
|
|
*3 * ["pondering"],
|
|
look_at_arg=question
|
|
)
|
|
)
|
|
self.play(
|
|
ShowCreation(h_line),
|
|
LaggedStartMap(
|
|
FadeOutAndShiftDown, self.pi_creatures,
|
|
run_time=1,
|
|
lag_ratio=0.8
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
self.question = question
|
|
self.h_line = h_line
|
|
|
|
def divide_by_qualitative_quantitative(self):
|
|
v_line = Line(
|
|
self.h_line.get_center(),
|
|
FRAME_HEIGHT * DOWN / 2,
|
|
)
|
|
words = VGroup(
|
|
TextMobject("Features", color=YELLOW),
|
|
TextMobject("Rigorous definition", color=BLUE),
|
|
)
|
|
words.next_to(self.h_line, DOWN)
|
|
words[0].shift(FRAME_WIDTH * LEFT / 4)
|
|
words[1].shift(FRAME_WIDTH * RIGHT / 4)
|
|
self.play(
|
|
ShowCreation(v_line),
|
|
LaggedStartMap(FadeInFromDown, words)
|
|
)
|
|
self.wait()
|
|
|
|
self.words = words
|
|
|
|
def three_qualitative_descriptors(self):
|
|
words = VGroup(
|
|
TextMobject("- Eddies"),
|
|
TextMobject("- Chaos"),
|
|
TextMobject("- Diffusion"),
|
|
)
|
|
words.arrange(
|
|
DOWN, buff=1.25,
|
|
aligned_edge=LEFT
|
|
)
|
|
words.to_edge(LEFT)
|
|
words.shift(MED_LARGE_BUFF * DOWN)
|
|
|
|
# objects = VGroup(
|
|
# Eddy(),
|
|
# DoublePendulum(),
|
|
# Diffusion(),
|
|
# )
|
|
|
|
# for word, obj in zip(words, objects):
|
|
for word in words:
|
|
# obj.next_to(word, RIGHT)
|
|
self.play(
|
|
FadeInFromDown(word),
|
|
# VFadeIn(obj)
|
|
)
|
|
self.wait(3)
|
|
|
|
def rigorous_definition(self):
|
|
randy = Randolph()
|
|
randy.move_to(FRAME_WIDTH * RIGHT / 4)
|
|
randy.change("pondering", self.words[1])
|
|
|
|
self.play(FadeIn(randy))
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(randy.change, "shruggie")
|
|
for x in range(2):
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(randy.look, LEFT)
|
|
self.wait(2)
|
|
self.play(randy.look, UP)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
|
|
|
|
class BumpyPlaneRide(Scene):
|
|
def construct(self):
|
|
plane = SVGMobject(file_name="plane2")
|
|
self.add(plane)
|
|
|
|
total_time = 0
|
|
while total_time < 10:
|
|
point = 2 * np.append(np.random.random(2), 2) + DL
|
|
point *= 0.2
|
|
time = 0.2 * random.random()
|
|
total_time += time
|
|
arc = PI * random.random() - PI / 2
|
|
self.play(
|
|
plane.move_to, point,
|
|
run_time=time,
|
|
path_arc=arc
|
|
)
|
|
|
|
|
|
class PureAirfoilFlowCopy(PureAirfoilFlow):
|
|
def modify_vector_field(self, vector_field):
|
|
PureAirfoilFlow.modify_vector_field(self, vector_field)
|
|
vector_field.set_fill(opacity=0.1)
|
|
vector_field.set_stroke(opacity=0.1)
|
|
|
|
|
|
class LaminarFlowLabel(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Laminar flow")
|
|
words.scale(1.5)
|
|
words.to_edge(UP)
|
|
subwords = TextMobject(
|
|
"`Lamina', in Latin, means \\\\"
|
|
"``a thin sheet of material''",
|
|
tex_to_color_map={"Lamina": YELLOW},
|
|
arg_separator="",
|
|
)
|
|
subwords.next_to(words, DOWN, MED_LARGE_BUFF)
|
|
VGroup(words, subwords).set_background_stroke(width=4)
|
|
self.play(Write(words))
|
|
self.wait()
|
|
self.play(FadeInFromDown(subwords))
|
|
self.wait()
|
|
|
|
|
|
class HighCurlFieldBreakingLayers(Scene):
|
|
CONFIG = {
|
|
"flow_anim": move_submobjects_along_vector_field,
|
|
}
|
|
|
|
def construct(self):
|
|
lines = VGroup(*[
|
|
self.get_line()
|
|
for x in range(20)
|
|
])
|
|
lines.arrange(DOWN, buff=MED_SMALL_BUFF)
|
|
lines[0::2].set_color(BLUE)
|
|
lines[1::2].set_color(RED)
|
|
all_dots = VGroup(*it.chain(*lines))
|
|
|
|
def func(p):
|
|
vect = four_swirls_function(p)
|
|
norm = get_norm(vect)
|
|
if norm > 2:
|
|
vect *= 4.0 / get_norm(vect)**2
|
|
return vect
|
|
|
|
self.add(lines)
|
|
self.add(self.flow_anim(all_dots, func))
|
|
self.wait(16)
|
|
|
|
def get_line(self):
|
|
line = VGroup(*[Dot() for x in range(100)])
|
|
line.set_height(0.1)
|
|
line.arrange(RIGHT, buff=0)
|
|
line.set_width(10)
|
|
return line
|
|
|
|
|
|
class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers):
|
|
CONFIG = {
|
|
"flow_anim": move_points_along_vector_field
|
|
}
|
|
|
|
def get_line(self):
|
|
line = Line(LEFT, RIGHT)
|
|
line.insert_n_curves(500)
|
|
line.set_width(5)
|
|
return line
|
|
|
|
|
|
class VorticitySynonyms(Scene):
|
|
def construct(self):
|
|
words = VGroup(
|
|
TextMobject("High", "vorticity"),
|
|
TexMobject(
|
|
"\\text{a.k.a} \\,",
|
|
"|\\nabla \\times \\vec{\\textbf{v}}| > 0"
|
|
),
|
|
TextMobject("a.k.a", "high", "swirly-swirly", "factor"),
|
|
)
|
|
words[0].set_color_by_tex("vorticity", BLUE)
|
|
words[1].set_color_by_tex("nabla", BLUE)
|
|
words[2].set_color_by_tex("swirly", BLUE)
|
|
words.arrange(
|
|
DOWN,
|
|
aligned_edge=LEFT,
|
|
buff=MED_LARGE_BUFF
|
|
)
|
|
|
|
for word in words:
|
|
word.add_background_rectangle()
|
|
self.play(FadeInFromDown(word))
|
|
self.wait()
|
|
|
|
|
|
class VorticityDoesNotImplyTurbulence(TeacherStudentsScene):
|
|
def construct(self):
|
|
t_to_v = TextMobject(
|
|
"Turbulence", "$\\Rightarrow$", "Vorticity",
|
|
)
|
|
v_to_t = TextMobject(
|
|
"Vorticity", "$\\Rightarrow$", "Turbulence",
|
|
)
|
|
for words in t_to_v, v_to_t:
|
|
words.move_to(self.hold_up_spot, DR)
|
|
words.set_color_by_tex_to_color_map({
|
|
"Vorticity": BLUE,
|
|
"Turbulence": GREEN,
|
|
})
|
|
v_to_t.submobjects.reverse()
|
|
cross = Cross(v_to_t[1])
|
|
|
|
morty = self.teacher
|
|
self.play(
|
|
morty.change, "raise_right_hand",
|
|
FadeInFromDown(t_to_v)
|
|
)
|
|
self.wait()
|
|
self.play(t_to_v.shift, 2 * UP,)
|
|
self.play(
|
|
TransformFromCopy(t_to_v, v_to_t, path_arc=PI / 2),
|
|
self.get_student_changes(
|
|
"erm", "confused", "sassy",
|
|
run_time=1
|
|
),
|
|
ShowCreation(cross, run_time=2),
|
|
)
|
|
self.add(cross)
|
|
self.wait(4)
|
|
|
|
|
|
class SurroundingRectangleSnippet(Scene):
|
|
def construct(self):
|
|
rect = Rectangle()
|
|
rect.set_color(YELLOW)
|
|
rect.set_stroke(width=5)
|
|
self.play(ShowCreation(rect))
|
|
self.play(FadeOut(rect))
|
|
|
|
|
|
class FeynmanOnTurbulence(Scene):
|
|
def construct(self):
|
|
feynman = ImageMobject("Feynman_Woods", height=4)
|
|
name = TextMobject("Richard Feynman")
|
|
name.next_to(feynman, DOWN)
|
|
quote = TextMobject(
|
|
"``", "Turbulence", "is the most\\\\"
|
|
"important", "unsolved problem\\\\",
|
|
"of classical physics.''",
|
|
tex_to_color_map={
|
|
"Turbulence": BLUE,
|
|
"unsolved problem\\\\": YELLOW,
|
|
},
|
|
)
|
|
quote[0].shift(SMALL_BUFF * RIGHT)
|
|
quote.next_to(feynman, RIGHT)
|
|
Group(feynman, name, quote).center()
|
|
|
|
self.play(
|
|
FadeInFrom(feynman, UP),
|
|
FadeInFrom(name, DOWN),
|
|
Write(quote, run_time=4)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class ShowNavierStokesEquations(Scene):
|
|
def construct(self):
|
|
self.introduce_equations()
|
|
self.ask_about_evolution()
|
|
self.ask_about_reasonable()
|
|
self.ask_about_blowup()
|
|
self.show_money()
|
|
|
|
def introduce_equations(self):
|
|
name = TextMobject("Navier-Stokes equations (incompressible)")
|
|
equations = NavierStokesEquations()
|
|
name.to_edge(UP)
|
|
equations.next_to(name, DOWN, MED_LARGE_BUFF)
|
|
labels = equations.get_labels()
|
|
parts = equations.get_parts()
|
|
newtons_second = TextMobject(
|
|
"Newton's 2nd law \\\\ $ma = F$"
|
|
)
|
|
newtons_second.next_to(parts, DOWN)
|
|
variables = TexMobject(
|
|
"&\\textbf{v}", "\\text{ is velocity}\\\\",
|
|
"&\\rho", "\\text{ is density}\\\\",
|
|
"&p{}", "\\text{ is pressure}\\\\",
|
|
"&\\mu", "\\text{ is viscosity}\\\\",
|
|
tex_to_color_map=NavierStokesEquations.CONFIG["tex_to_color_map"]
|
|
)
|
|
variables.to_corner(DL)
|
|
|
|
self.play(FadeInFromDown(equations))
|
|
self.play(Write(name))
|
|
self.play(LaggedStartMap(
|
|
FadeInFrom, variables,
|
|
lambda m: (m, RIGHT),
|
|
))
|
|
self.wait()
|
|
self.play(Write(newtons_second))
|
|
self.wait()
|
|
self.play(
|
|
FadeInFromDown(labels[0]),
|
|
newtons_second.next_to, variables, RIGHT, LARGE_BUFF
|
|
)
|
|
self.play(ShowCreationThenFadeAround(parts[0]))
|
|
self.wait()
|
|
self.play(LaggedStartMap(FadeInFrom, labels[1:]))
|
|
self.wait(3)
|
|
self.play(LaggedStartMap(
|
|
FadeOut, VGroup(*it.chain(labels, variables, newtons_second))
|
|
))
|
|
|
|
self.equations = equations
|
|
|
|
def ask_about_evolution(self):
|
|
words = TextMobject(
|
|
"Given a start state...",
|
|
"...how does it evolve?"
|
|
)
|
|
words.arrange(RIGHT, buff=2)
|
|
|
|
words.next_to(self.equations, DOWN, LARGE_BUFF)
|
|
|
|
self.play(Write(words[0]))
|
|
self.wait()
|
|
self.play(Write(words[1]))
|
|
self.wait(2)
|
|
self.play(FadeOut(words))
|
|
|
|
def ask_about_reasonable(self):
|
|
question = TextMobject(
|
|
"Do ``reasonable'' \\\\"
|
|
"solutions always\\\\"
|
|
"exist?"
|
|
)
|
|
self.play(FadeInFromDown(question))
|
|
self.wait()
|
|
|
|
self.reasonable_question = question
|
|
|
|
def ask_about_blowup(self):
|
|
axes, graph = self.get_axes_and_graph()
|
|
question = TextMobject("Is this possible?")
|
|
question.set_color(YELLOW)
|
|
question.move_to(axes.get_corner(UR), LEFT)
|
|
question.align_to(axes, UP)
|
|
q_arrow = Arrow(
|
|
question.get_bottom(),
|
|
graph.point_from_proportion(0.8),
|
|
buff=SMALL_BUFF,
|
|
path_arc=-60 * DEGREES
|
|
)
|
|
q_arrow.set_stroke(WHITE, 3)
|
|
morty = Mortimer()
|
|
morty.to_corner(DR)
|
|
morty.change('confused', graph)
|
|
|
|
self.play(
|
|
Write(axes, run_time=1),
|
|
self.reasonable_question.to_edge, LEFT,
|
|
self.reasonable_question.shift, DOWN,
|
|
)
|
|
self.play(
|
|
Write(question),
|
|
ShowCreation(graph),
|
|
FadeIn(morty),
|
|
)
|
|
self.add(q_arrow, morty)
|
|
self.play(ShowCreation(q_arrow), Blink(morty))
|
|
self.wait()
|
|
self.play(morty.look_at, question)
|
|
self.wait()
|
|
self.play(morty.change, "maybe", graph)
|
|
self.wait(2)
|
|
to_fade = VGroup(question, q_arrow, axes, graph)
|
|
self.play(
|
|
LaggedStartMap(FadeOut, to_fade),
|
|
morty.change, "pondering"
|
|
)
|
|
self.wait(2)
|
|
self.play(Blink(morty))
|
|
self.wait(2)
|
|
|
|
self.morty = morty
|
|
|
|
def show_money(self):
|
|
# Million dollar problem
|
|
problem = TextMobject(
|
|
"Navier-Stokes existence \\\\ and smoothness problems"
|
|
)
|
|
money = TextMobject("\\$1{,}000{,}000")
|
|
money.set_color(GREEN)
|
|
money.next_to(problem, DOWN)
|
|
pi1 = Randolph()
|
|
pi2 = self.morty
|
|
pi1.to_corner(DL)
|
|
pis = VGroup(pi1, pi2)
|
|
for pi in pis:
|
|
pi.change("pondering")
|
|
pi.money_eyes = VGroup()
|
|
for eye in pi.eyes:
|
|
cash = TexMobject("\\$")
|
|
cash.set_color(GREEN)
|
|
cash.replace(eye, dim_to_match=1)
|
|
pi.money_eyes.add(cash)
|
|
|
|
self.play(
|
|
ReplacementTransform(
|
|
self.reasonable_question,
|
|
problem,
|
|
),
|
|
pi2.look_at, problem,
|
|
pi1.look_at, problem,
|
|
VFadeIn(pi1),
|
|
)
|
|
self.wait()
|
|
self.play(FadeInFromLarge(money))
|
|
self.play(
|
|
pi1.change, "hooray",
|
|
pi2.change, "hooray",
|
|
)
|
|
self.play(
|
|
ReplacementTransform(pi1.pupils, pi1.money_eyes),
|
|
ReplacementTransform(pi2.pupils, pi2.money_eyes),
|
|
)
|
|
self.wait()
|
|
|
|
# Helpers
|
|
def get_axes_and_graph(self):
|
|
axes = Axes(
|
|
x_min=-1,
|
|
x_max=5,
|
|
y_min=-1,
|
|
y_max=5,
|
|
)
|
|
time = TextMobject("Time")
|
|
time.next_to(axes.x_axis, RIGHT)
|
|
ke = TextMobject("Kinetic energy")
|
|
ke.next_to(axes.y_axis, UP)
|
|
axes.add(time, ke)
|
|
axes.set_height(4)
|
|
axes.center()
|
|
axes.to_edge(DOWN)
|
|
v_line = DashedLine(
|
|
axes.coords_to_point(4, 0),
|
|
axes.coords_to_point(4, 5),
|
|
)
|
|
axes.add(v_line)
|
|
graph = axes.get_graph(
|
|
lambda x: -1.0 / (x - 4),
|
|
x_min=0.01,
|
|
x_max=3.8,
|
|
)
|
|
graph.set_color(BLUE)
|
|
return axes, graph
|
|
|
|
|
|
class NewtonsSecond(Scene):
|
|
def construct(self):
|
|
square = Square(
|
|
stroke_color=WHITE,
|
|
fill_color=LIGHT_GREY,
|
|
fill_opacity=0.5,
|
|
side_length=1
|
|
)
|
|
label = TexMobject("m")
|
|
label.scale(1.5)
|
|
label.move_to(square)
|
|
square.add(label)
|
|
square.save_state()
|
|
arrows = VGroup(
|
|
Vector(0.5 * UP).next_to(square, UP, buff=0),
|
|
Vector(RIGHT).next_to(square, RIGHT, buff=0),
|
|
)
|
|
|
|
self.play(
|
|
square.shift, 4 * RIGHT + 2 * UP,
|
|
rate_func=lambda t: t**2,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
square.restore()
|
|
self.play(
|
|
LaggedStartMap(GrowArrow, arrows)
|
|
)
|
|
square.add(arrows)
|
|
self.play(
|
|
square.shift, 4 * RIGHT + 2 * UP,
|
|
rate_func=lambda t: t**2,
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class CandleLabel(Scene):
|
|
def construct(self):
|
|
word = TextMobject("Candle")
|
|
arrow = Vector(DR, color=WHITE)
|
|
arrow.move_to(word.get_bottom() + SMALL_BUFF * DOWN, UL)
|
|
self.play(
|
|
FadeInFromDown(word),
|
|
GrowArrow(arrow)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class FiguresOfFluidDynamics(Scene):
|
|
def construct(self):
|
|
names = [
|
|
"Leonhard Euler",
|
|
"George Stokes",
|
|
"Hermann von Helmholtz",
|
|
"Lewis Richardson",
|
|
"Geoffrey Taylor",
|
|
"Andrey Kolmogorov",
|
|
]
|
|
images = Group(*[
|
|
ImageMobject(name.replace(" ", "_"), height=3)
|
|
for name in names
|
|
])
|
|
images.arrange(RIGHT, buff=MED_SMALL_BUFF)
|
|
image_groups = Group()
|
|
for image, name in zip(images, names):
|
|
name_mob = TextMobject(name)
|
|
name_mob.scale(0.6)
|
|
name_mob.next_to(image, DOWN)
|
|
image_groups.add(Group(image, name_mob))
|
|
image_groups.arrange_in_grid(2, 3)
|
|
image_groups.set_height(FRAME_HEIGHT - 1)
|
|
|
|
self.play(LaggedStartMap(
|
|
FadeInFromDown, image_groups,
|
|
lag_ratio=0.5,
|
|
run_time=3
|
|
))
|
|
self.wait()
|
|
to_fade = image_groups[:-1]
|
|
to_fade.generate_target()
|
|
to_fade.target.space_out_submobjects(3)
|
|
to_fade.target.shift(3 * UL)
|
|
to_fade.target.fade(1)
|
|
self.play(
|
|
MoveToTarget(to_fade, remover=True),
|
|
image_groups[-1].set_height, 5,
|
|
image_groups[-1].center,
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class KineticEnergyBreakdown(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Kinetic energy breakdown")
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH)
|
|
h_line.next_to(title, DOWN)
|
|
v_line = Line(h_line.get_center(), FRAME_HEIGHT * DOWN / 2)
|
|
lc_title = TextMobject("Simpler physics")
|
|
lc_title.set_color(YELLOW)
|
|
rc_title = TextMobject("Turbulence physics")
|
|
rc_title.set_color(GREEN)
|
|
for word, vect in (lc_title, LEFT), (rc_title, RIGHT):
|
|
word.next_to(h_line, DOWN)
|
|
word.shift(FRAME_WIDTH * vect / 4)
|
|
|
|
left_items = VGroup(
|
|
TextMobject("- Big moving things"),
|
|
TextMobject("- Heat"),
|
|
)
|
|
left_items.arrange(DOWN, aligned_edge=LEFT)
|
|
left_items.next_to(lc_title, DOWN, MED_LARGE_BUFF)
|
|
left_items.to_edge(LEFT)
|
|
|
|
self.play(
|
|
Write(VGroup(*it.chain(
|
|
title, h_line, v_line, lc_title, rc_title
|
|
)))
|
|
)
|
|
self.wait()
|
|
for item in left_items:
|
|
self.play(FadeInFrom(item))
|
|
self.wait()
|
|
|
|
|
|
class MovingCar(Scene):
|
|
def construct(self):
|
|
car = Car()
|
|
x = 3
|
|
car.move_to(x * LEFT)
|
|
self.play(MoveCar(car, x * RIGHT, run_time=4))
|
|
|
|
|
|
class Heat(Scene):
|
|
def construct(self):
|
|
box = Square(
|
|
side_length=2,
|
|
stroke_color=WHITE,
|
|
)
|
|
balls = VGroup(*[
|
|
self.get_ball(box)
|
|
for x in range(20)
|
|
])
|
|
|
|
self.add(box, balls)
|
|
self.wait(20)
|
|
|
|
def get_ball(self, box):
|
|
speed_factor = random.random()
|
|
ball = Dot(
|
|
radius=0.05,
|
|
color=interpolate_color(BLUE, RED, speed_factor)
|
|
)
|
|
speed = 2 + 3 * speed_factor
|
|
direction = rotate_vector(RIGHT, TAU * random.random())
|
|
ball.velocity = speed * direction
|
|
x0, y0, z0 = box.get_corner(DL)
|
|
x1, y1, z1 = box.get_corner(UR)
|
|
ball.move_to(np.array([
|
|
interpolate(x0, x1, random.random()),
|
|
interpolate(y0, y1, random.random()),
|
|
0
|
|
]))
|
|
|
|
def update(ball, dt):
|
|
ball.shift(ball.velocity * dt)
|
|
if ball.get_left()[0] < box.get_left()[0]:
|
|
ball.velocity[0] = abs(ball.velocity[0])
|
|
if ball.get_right()[0] > box.get_right()[0]:
|
|
ball.velocity[0] = -abs(ball.velocity[0])
|
|
if ball.get_bottom()[1] < box.get_bottom()[1]:
|
|
ball.velocity[1] = abs(ball.velocity[1])
|
|
if ball.get_top()[1] > box.get_top()[1]:
|
|
ball.velocity[1] = -abs(ball.velocity[1])
|
|
return ball
|
|
|
|
ball.add_updater(update)
|
|
return ball
|
|
|
|
|
|
class GrowArrowScene(Scene):
|
|
def construct(self):
|
|
arrow = Arrow(UP, DOWN, color=WHITE)
|
|
self.play(GrowArrow(arrow))
|
|
self.wait()
|
|
|
|
|
|
class Poem(Scene):
|
|
def construct(self):
|
|
picture = ImageMobject("Lewis_Richardson")
|
|
picture.set_height(4)
|
|
picture.center().to_edge(LEFT, buff=LARGE_BUFF)
|
|
|
|
title = TextMobject("Poem by Lewis F. Richardson")
|
|
title.to_edge(UP)
|
|
|
|
poem_text = """
|
|
Big{\\,\\,}whirls have little{\\,\\,}whirls\\\\
|
|
which feed on their velocity,\\\\
|
|
And little{\\,\\,}whirls have lesser{\\,\\,}whirls\\\\
|
|
And so on to viscosity.\\\\
|
|
"""
|
|
poem_words = [s for s in poem_text.split(" ") if s]
|
|
poem = TextMobject(*poem_words, alignment="")
|
|
poem.next_to(picture, RIGHT, LARGE_BUFF)
|
|
|
|
self.add(picture)
|
|
self.play(FadeInFrom(title, DOWN))
|
|
self.wait()
|
|
for word in poem:
|
|
if "whirl" in word.get_tex_string():
|
|
word.set_color(BLUE)
|
|
self.play(ShowWord(word))
|
|
self.wait(0.005 * len(word)**1.5)
|
|
|
|
|
|
class SwirlDiameterD(Scene):
|
|
def construct(self):
|
|
kwargs = {
|
|
"path_arc": PI,
|
|
"buff": SMALL_BUFF,
|
|
"color": WHITE
|
|
}
|
|
swirl = VGroup(
|
|
Arrow(RIGHT, LEFT, **kwargs),
|
|
Arrow(LEFT, RIGHT, **kwargs),
|
|
)
|
|
swirl.set_stroke(width=5)
|
|
f = 1.5
|
|
swirl.scale(f)
|
|
|
|
h_line = DashedLine(
|
|
f * LEFT, f * RIGHT,
|
|
color=YELLOW,
|
|
)
|
|
D_label = TexMobject("D")
|
|
D_label.scale(2)
|
|
D_label.next_to(h_line, UP, SMALL_BUFF)
|
|
D_label.match_color(h_line)
|
|
# diam = VGroup(h_line, D_label)
|
|
|
|
self.play(*map(ShowCreation, swirl))
|
|
self.play(
|
|
GrowFromCenter(h_line),
|
|
FadeInFrom(D_label, UP),
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class KolmogorovGraph(Scene):
|
|
def construct(self):
|
|
axes = Axes(
|
|
x_min=-1,
|
|
y_min=-1,
|
|
x_max=7,
|
|
y_max=9,
|
|
y_axis_config={
|
|
"unit_size": 0.7,
|
|
}
|
|
)
|
|
axes.center().shift(1.5 * RIGHT)
|
|
x_label = TexMobject("\\log(D)")
|
|
x_label.next_to(axes.x_axis.get_right(), UP)
|
|
y_label = TexMobject("\\log(\\text{K.E. at length scale D})")
|
|
y_label.scale(0.8)
|
|
y_label.next_to(axes.y_axis.get_top(), LEFT)
|
|
y_label.shift_onto_screen()
|
|
axes.add(x_label, y_label)
|
|
|
|
v_lines = VGroup(*[
|
|
DashedLine(
|
|
axes.coords_to_point(x, 0),
|
|
axes.coords_to_point(x, 9),
|
|
color=YELLOW,
|
|
stroke_width=1
|
|
)
|
|
for x in [0.5, 5]
|
|
])
|
|
inertial_subrange = TextMobject("``Inertial subrange''")
|
|
inertial_subrange.scale(0.7)
|
|
inertial_subrange.next_to(v_lines.get_bottom(), UP)
|
|
|
|
def func(x):
|
|
if 0.5 < x < 5:
|
|
return (5 / 3) * x
|
|
elif x < 0.5:
|
|
return 5 * (x - 0.5) + 0.5 * (5 / 3)
|
|
elif x > 5:
|
|
return np.log(x) + (5 / 3) * 5 - np.log(5)
|
|
|
|
graph = axes.get_graph(func, x_min=0.3, x_max=7)
|
|
|
|
prop_label = TexMobject("\\text{K.E.} \\propto D^{5/3}")
|
|
prop_label.next_to(
|
|
graph.point_from_proportion(0.5), UL,
|
|
buff=0
|
|
)
|
|
|
|
self.add(axes)
|
|
self.play(ShowCreation(graph))
|
|
self.play(FadeInFromDown(prop_label))
|
|
self.wait()
|
|
self.add(v_lines)
|
|
self.play(Write(inertial_subrange))
|
|
self.wait()
|
|
|
|
|
|
class TechnicalNote(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Technical note:")
|
|
title.to_edge(UP)
|
|
title.set_color(RED)
|
|
self.add(title)
|
|
|
|
words = TextMobject("""
|
|
This idea of quantifying the energy held at different
|
|
length scales is typically defined
|
|
in terms of an ``energy spectrum'' involving the Fourier
|
|
transform of a function measuring the correlations
|
|
between the fluid's velocities at different points in space.
|
|
I know, yikes!
|
|
\\quad\\\\
|
|
\\quad\\\\
|
|
Building up the relevant background for that is a bit cumbersome,
|
|
so we'll be thinking about the energy at different scales in
|
|
terms of all eddy's with a given diameter. This is admittedly
|
|
a less well-defined notion, but it does capture the spirit
|
|
of Kolmogorov's result.
|
|
\\quad\\\\
|
|
\\quad\\\\
|
|
See the links in the description for more details,
|
|
if you're curious.
|
|
""", alignment="")
|
|
words.scale(0.75)
|
|
words.next_to(title, DOWN, LARGE_BUFF)
|
|
|
|
self.add(title, words)
|
|
|
|
|
|
class FiveThirds(TeacherStudentsScene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"5/3", "is a sort of fundamental\\\\ constant of turbulence"
|
|
)
|
|
self.teacher_says(words)
|
|
self.change_student_modes("pondering", "maybe", "erm")
|
|
self.play(
|
|
FadeOut(self.teacher.bubble),
|
|
FadeOut(words[1]),
|
|
self.teacher.change, "raise_right_hand",
|
|
words[0].scale, 1.5,
|
|
words[0].move_to, self.hold_up_spot
|
|
)
|
|
self.change_student_modes("thinking", "pondering", "hooray")
|
|
self.wait(3)
|
|
|
|
|
|
class TurbulenceGifLabel(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Turbulence in 2d")
|
|
title.to_edge(UP)
|
|
|
|
attribution = TextMobject(
|
|
"Animation by Gabe Weymouth (@gabrielweymouth)"
|
|
)
|
|
attribution.scale(0.5)
|
|
attribution.to_edge(DOWN)
|
|
|
|
self.play(Write(title))
|
|
self.play(FadeInFrom(attribution, UP))
|
|
self.wait()
|
|
|
|
|
|
class VortexStretchingLabel(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Vortex stretching")
|
|
self.play(Write(title))
|
|
self.wait()
|
|
|
|
|
|
class VortedStretching(ThreeDScene):
|
|
CONFIG = {
|
|
"n_circles": 200,
|
|
}
|
|
|
|
def construct(self):
|
|
axes = ThreeDAxes()
|
|
axes.set_stroke(width=1)
|
|
self.add(axes)
|
|
self.move_camera(
|
|
phi=70 * DEGREES,
|
|
theta=-145 * DEGREES,
|
|
run_time=0,
|
|
)
|
|
self.begin_ambient_camera_rotation()
|
|
|
|
short_circles = self.get_cylinder_circles(2, 0.5, 0.5)
|
|
tall_circles = short_circles.copy().scale(0.125)
|
|
tall_circles.stretch(16 * 4, 2)
|
|
torus_circles = tall_circles.copy()
|
|
for circle in torus_circles:
|
|
circle.shift(RIGHT)
|
|
z = circle.get_center()[2]
|
|
circle.shift(z * IN)
|
|
angle = PI * z / 2
|
|
circle.rotate(angle, axis=DOWN, about_point=ORIGIN)
|
|
|
|
circles = short_circles.copy()
|
|
flow_lines = self.get_flow_lines(circles)
|
|
|
|
self.add(circles, flow_lines)
|
|
self.play(LaggedStartMap(ShowCreation, circles))
|
|
self.wait(5)
|
|
self.play(Transform(circles, tall_circles, run_time=3))
|
|
self.wait(10)
|
|
self.play(Transform(
|
|
circles, torus_circles,
|
|
run_time=3
|
|
))
|
|
self.wait(10)
|
|
|
|
def get_cylinder_circles(self, radius, radius_var, max_z):
|
|
return VGroup(*[
|
|
ParametricFunction(
|
|
lambda t: np.array([
|
|
np.cos(TAU * t) * r,
|
|
np.sin(TAU * t) * r,
|
|
z
|
|
]),
|
|
**self.get_circle_kwargs()
|
|
)
|
|
for z in sorted(max_z * np.random.random(self.n_circles))
|
|
for r in [radius + radius_var * random.random()]
|
|
]).center()
|
|
|
|
def get_torus_circles(self, out_r, in_r, in_r_var):
|
|
result = VGroup()
|
|
for u in sorted(np.random.random(self.n_circles)):
|
|
r = in_r + in_r_var * random.random()
|
|
circle = ParametricFunction(
|
|
lambda t: r * np.array([
|
|
np.cos(TAU * t),
|
|
np.sin(TAU * t),
|
|
0,
|
|
]),
|
|
**self.get_circle_kwargs()
|
|
)
|
|
circle.shift(out_r * RIGHT)
|
|
circle.rotate(
|
|
TAU * u - PI,
|
|
about_point=ORIGIN,
|
|
axis=DOWN,
|
|
)
|
|
result.add(circle)
|
|
return result
|
|
|
|
def get_flow_lines(self, circle_group):
|
|
window = 0.3
|
|
|
|
def update_circle(circle, dt):
|
|
circle.total_time += dt
|
|
diameter = get_norm(
|
|
circle.template.point_from_proportion(0) -
|
|
circle.template.point_from_proportion(0.5)
|
|
)
|
|
modulus = np.sqrt(diameter) + 0.1
|
|
alpha = (circle.total_time % modulus) / modulus
|
|
circle.pointwise_become_partial(
|
|
circle.template,
|
|
max(interpolate(-window, 1, alpha), 0),
|
|
min(interpolate(0, 1 + window, alpha), 1),
|
|
)
|
|
|
|
result = VGroup()
|
|
for template in circle_group:
|
|
circle = template.deepcopy()
|
|
circle.set_stroke(
|
|
color=interpolate_color(BLUE_A, BLUE_E, random.random()),
|
|
# width=3 * random.random()
|
|
width=1,
|
|
)
|
|
circle.template = template
|
|
circle.total_time = 4 * random.random()
|
|
circle.add_updater(update_circle)
|
|
result.add(circle)
|
|
return result
|
|
|
|
def get_circle_kwargs(self):
|
|
return {
|
|
"stroke_color": BLACK,
|
|
"stroke_width": 0,
|
|
}
|
|
|
|
|
|
class TurbulenceEndScreen(PatreonEndScreen):
|
|
CONFIG = {
|
|
"specific_patrons": [
|
|
"1stViewMaths",
|
|
"Adrian Robinson",
|
|
"Alexis Olson",
|
|
"Andrew Busey",
|
|
"Ankalagon",
|
|
"Art Ianuzzi",
|
|
"Awoo",
|
|
"Ayan Doss",
|
|
"Bernd Sing",
|
|
"Boris Veselinovich",
|
|
"Brian Staroselsky",
|
|
"Britt Selvitelle",
|
|
"Carla Kirby",
|
|
"Charles Southerland",
|
|
"Chris Connett",
|
|
"Christian Kaiser",
|
|
"Clark Gaebel",
|
|
"Cooper Jones",
|
|
"Danger Dai",
|
|
"Dave B",
|
|
"Dave Kester",
|
|
"David Clark",
|
|
"Delton Ding",
|
|
"Devarsh Desai",
|
|
"eaglle",
|
|
"Eric Younge",
|
|
"Eryq Ouithaqueue",
|
|
"Federico Lebron",
|
|
"Florian Chudigiewitsch",
|
|
"Giovanni Filippi",
|
|
"Hal Hildebrand",
|
|
"Igor Napolskikh",
|
|
"Jacob Magnuson",
|
|
"Jameel Syed",
|
|
"James Hughes",
|
|
"Jan Pijpers",
|
|
"Jason Hise",
|
|
"Jeff Linse",
|
|
"Jeff Straathof",
|
|
"Jerry Ling",
|
|
"John Griffith",
|
|
"John Haley",
|
|
"John V Wertheim",
|
|
"Jonathan Eppele",
|
|
"Jonathan Wilson",
|
|
"Jordan Scales",
|
|
"Joseph John Cox",
|
|
"Julian Pulgarin",
|
|
"Kai-Siang Ang",
|
|
"Kanan Gill",
|
|
"L0j1k",
|
|
"Linh Tran",
|
|
"Luc Ritchie",
|
|
"Ludwig Schubert",
|
|
"Lukas -krtek.net- Novy",
|
|
"Magister Mugit",
|
|
"Magnus Dahlström",
|
|
"Mark B Bahu",
|
|
"Markus Persson",
|
|
"Mathew Bramson",
|
|
"Mathias Jansson",
|
|
"Matt Langford",
|
|
"Matt Roveto",
|
|
"Matthew Cocke",
|
|
"Mehdi Razavi",
|
|
"Michael Faust",
|
|
"Michael Hardel",
|
|
"Mustafa Mahdi",
|
|
"Márton Vaitkus",
|
|
"Nero Li",
|
|
"Oliver Steele",
|
|
"Omar Zrien",
|
|
"Peter Ehrnstrom",
|
|
"Prasant Jagannath",
|
|
"Randy C. Will",
|
|
"Richard Burgmann",
|
|
"Ripta Pasay",
|
|
"Rish Kundalia",
|
|
"Robert Teed",
|
|
"Roobie",
|
|
"Ryan Atallah",
|
|
"Ryan Williams",
|
|
"Sindre Reino Trosterud",
|
|
"Solara570",
|
|
"Song Gao",
|
|
"Steven Soloway",
|
|
"Steven Tomlinson",
|
|
"Stevie Metke",
|
|
"Ted Suzman",
|
|
"Valeriy Skobelev",
|
|
"Xavier Bernard",
|
|
"Yaw Etse",
|
|
"YinYangBalance.Asia",
|
|
"Zach Cardwell",
|
|
],
|
|
}
|
|
|
|
|
|
class LaserWord(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Laser").scale(2))
|
|
|
|
|
|
class TurbulenceWord(Scene):
|
|
def construct(self):
|
|
self.add(TextMobject("Turbulence").scale(2))
|
|
|
|
|
|
class ArrowScene(Scene):
|
|
def construct(self):
|
|
arrow = Arrow(LEFT, RIGHT, color=WHITE)
|
|
arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5))
|
|
self.add(arrow)
|