Tiny nudges for of div and curl

This commit is contained in:
Grant Sanderson 2018-06-18 13:04:58 -07:00
parent d72483e909
commit 71400a3941

View file

@ -4,7 +4,7 @@ DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED]
# Quick note to anyone coming to this file with the
# intent of recreating animations from the video. Some
# of these, espeically those involving StreamLineAnimation,
# of these, especially those involving StreamLineAnimation,
# can take an extremely long time to run, but much of the
# computational cost is just for giving subtle little effects
# which don't matter too much. Switching the line_anim_class
@ -14,6 +14,8 @@ DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED]
# run at production quality.
FOX_COLOR = "#DF7F20"
RABBIT_COLOR = "#C6D6EF"
# Helper functions
def get_flow_start_points(x_min=-8, x_max=8,
@ -301,6 +303,7 @@ class VectorField(VGroup):
"stroke_color": BLACK,
"stroke_width": 0.5,
"fill_opacity": 1.0,
"vector_config": {},
}
def __init__(self, func, **kwargs):
@ -317,19 +320,22 @@ class VectorField(VGroup):
point = x * RIGHT + y * UP
self.add(self.get_vector(point))
def get_vector(self, point):
def get_vector(self, point, **kwargs):
output = np.array(self.func(point))
norm = np.linalg.norm(output)
if norm == 0:
output *= 0
else:
output *= self.length_func(norm) / norm
vect = Vector(output)
vector_config = dict(self.vector_config)
vector_config.update(kwargs)
vect = Vector(output, **vector_config)
vect.shift(point)
fill_color = rgb_to_color(
self.rgb_gradient_function(np.array([norm]))[0]
)
vect.set_fill(fill_color, self.fill_opacity)
vect.set_color(fill_color)
vect.set_fill(opacity=self.fill_opacity)
vect.set_stroke(
self.stroke_color,
self.stroke_width
@ -456,6 +462,22 @@ class JigglingSubmobjects(ContinualAnimation):
# Scenes
class Introduction(MovingCameraScene):
def construct(self):
self.show_divergence()
self.show_curl()
self.show_context()
def show_divergence(self):
pass
def show_curl(self):
pass
def show_context(self):
pass
class TestVectorField(Scene):
CONFIG = {
"func": cylinder_flow_vector_field,
@ -476,7 +498,7 @@ class TestVectorField(Scene):
self.wait(10)
class Introduction(Scene):
class CylinderModel(Scene):
CONFIG = {
"production_quality_flow": True,
"vector_field_func": cylinder_flow_vector_field,
@ -735,7 +757,7 @@ class Introduction(Scene):
)
class ElectricField(Introduction, MovingCameraScene):
class ElectricField(CylinderModel, MovingCameraScene):
def construct(self):
self.add_plane()
self.add_title()
@ -1232,7 +1254,7 @@ class TotallyToScale(Scene):
# TODO: Revisit this
class FluidFlowAsHillGradient(Introduction, ThreeDScene):
class FluidFlowAsHillGradient(CylinderModel, ThreeDScene):
CONFIG = {
"production_quality_flow": False,
}
@ -1756,13 +1778,6 @@ class DivergenceAsNewFunction(Scene):
class DivergenceZeroCondition(Scene):
def construct(self):
self.add_vector_field()
self.add_title()
self.begin_flow()
self.add_circle()
self.wait(5)
def add_title(self):
title = TextMobject(
"For actual (incompressible) fluid flow:"
)
@ -1775,6 +1790,15 @@ class DivergenceZeroCondition(Scene):
for mob in title, equation:
mob.add_background_rectangle(buff=MED_SMALL_BUFF / 2)
self.add_foreground_mobjects(mob)
self.wait(1)
class PureCylinderFlow(Scene):
def construct(self):
self.add_vector_field()
self.begin_flow()
self.add_circle()
self.wait(5)
def add_vector_field(self):
vector_field = VectorField(
@ -1800,18 +1824,35 @@ class DivergenceZeroCondition(Scene):
if np.linalg.norm(stream_line.points[0]) < 1:
stream_lines.remove(stream_line)
self.modify_flow(stream_lines)
stream_line_animation = StreamLineAnimation(stream_lines)
stream_line_animation.update(3)
self.add(stream_line_animation)
def add_circle(self):
self.add_foreground_mobjects(Circle(
circle = Circle(
radius=1,
stroke_color=YELLOW,
fill_color=BLACK,
fill_opacity=1,
))
num_anchors=24,
)
self.modify_flow(circle)
self.add_foreground_mobjects()
def modify_flow(self, mobject):
pass
class PureAirfoilFlow(PureCylinderFlow):
def modify_flow(self, mobject):
vect = 0.1 * UP + 0.2 * LEFT
mobject.scale(np.linalg.norm(vect - RIGHT))
mobject.shift(vect)
mobject.apply_complex_function(joukowsky_map)
return mobject
class IntroduceCurl(IntroduceVectorField):
@ -2502,7 +2543,7 @@ class ShowTwoPopulations(Scene):
examples.arrange_submobjects(LEFT, buff=2)
preditor, prey = words = VGroup(
TextMobject("Preditor"),
TextMobject("Predator"),
TextMobject("Prey")
)
for mob, word in zip(examples, words):
@ -2524,11 +2565,9 @@ class ShowTwoPopulations(Scene):
group[1:],
run_time=4,
lag_ratio=0.1,
rate_func=lambda t: np.clip(smooth(2 * t), 0, 1)
)
for group in [
foxes[:self.start_num_foxes],
rabbits[:self.start_num_rabbits],
]
for group in [foxes, rabbits]
]
)
@ -2536,8 +2575,8 @@ class ShowTwoPopulations(Scene):
foxes = self.foxes
rabbits = self.rabbits
phase_point = VectorizedPoint(
self.start_num_foxes * RIGHT +
self.start_num_rabbits * UP
self.start_num_rabbits * RIGHT +
self.start_num_foxes * UP
)
self.add(VectorFieldFlow(
phase_point,
@ -2629,7 +2668,8 @@ class ShowTwoPopulations(Scene):
return self.get_animal("fox", FOX_COLOR)
def get_rabbit(self):
return self.get_animal("rabbit", WHITE)
# return self.get_animal("rabbit", WHITE)
return self.get_animal("bunny", RABBIT_COLOR)
def get_pop_labels(self):
labels = VGroup(
@ -2773,18 +2813,18 @@ class PhaseSpaceOfPopulationModel(ShowTwoPopulations, PiCreatureScene, MovingCam
self.pop_sizes_updates = pop_sizes_updates
def write_differential_equations(self):
variables = ["XX", "YY"]
variables = ["X", "YY"]
equations = TexMobject(
"""
{dXX \\over dt} =
30 \\cdot XX - XX \\cdot YY \\\\
{dX \\over dt} =
X \\cdot (\\alpha - \\beta YY \\,) \\\\
\\quad \\\\
{dYY \\over dt} =
XX \\cdot YY - 30 \\cdot YY
YY \\cdot (\\delta X - \\gamma)
""",
substrings_to_isolate=variables
)
animals = [self.get_fox().flip(), self.get_rabbit()]
animals = [self.get_rabbit(), self.get_fox().flip()]
for char, animal in zip(variables, animals):
for part in equations.get_parts_by_tex(char):
animal_copy = animal.copy()
@ -3191,7 +3231,7 @@ class ShowDotProduct(MovingCameraScene):
def get_product(self, v1, v2):
return np.dot(v1.get_vector(), v2.get_vector())
def add_additional_continual_animations(self):
def add_additional_continual_animations(self, v1, v2):
pass
@ -3224,6 +3264,479 @@ class ShowCrossProduct(ShowDotProduct):
))
class NewSceneName(Scene):
class DivergenceTinyNudgesView(MovingCameraScene):
CONFIG = {
"scale_factor": 0.25
}
def construct(self):
pass
self.add_vector_field()
self.zoom_in()
self.take_tiny_step()
self.show_dot_product()
self.show_circle_of_values()
self.switch_to_curl_words()
self.rotate_difference_vectors()
def add_vector_field(self):
plane = self.plane = NumberPlane()
vector_field = self.vector_field = VectorField(
lambda p: 0.4 * np.array([
np.cos(3 * p[0] * p[1] / 4),
np.sin(TAU * p[1] / 4),
0
]),
length_func=lambda n: max(n, 0.5),
max_magnitude=1.0,
)
self.add(plane)
self.add(vector_field)
def zoom_in(self):
point = self.point = 2 * LEFT + UP
vector_field = self.vector_field
sf = self.scale_factor
self.play(
self.camera_frame.scale, sf,
self.camera_frame.move_to, point,
FadeOut(vector_field),
run_time=2
)
vector_field.vector_config.update({
"rectangular_stem_width": 0.02,
"tip_length": 0.1
})
vector = vector_field.get_vector(point)
input_dot = Dot(point).scale(sf)
input_words = TextMobject("$(x_0, y_0)$").scale(sf)
input_words.next_to(input_dot, DL, SMALL_BUFF * sf)
output_words = TextMobject("Output").scale(sf)
output_words.add_background_rectangle()
output_words.next_to(vector.get_top(), UP, sf * SMALL_BUFF)
output_words.match_color(vector)
self.add_foreground_mobjects(input_dot)
self.play(
FadeInAndShiftFromDirection(input_dot, SMALL_BUFF * DL),
Write(input_words),
)
self.play(
GrowArrow(vector),
Write(output_words),
)
self.wait()
self.set_variables_as_attrs(
point, vector, input_dot,
input_words, output_words,
)
def take_tiny_step(self):
sf = self.scale_factor
vector_field = self.vector_field
point = self.point
vector = self.vector
output_words = self.output_words
input_dot = self.input_dot
nudge = 0.5 * RIGHT
nudged_point = point + nudge
new_vector = vector_field.get_vector(nudged_point)
new_vector.set_color(YELLOW)
new_dot = Dot(nudged_point).scale(sf)
step_vector = Arrow(
point, nudged_point,
buff=0,
color=TEAL,
**vector_field.vector_config
)
step_vector.set_stroke(BLACK, 0.5)
new_output_words = TextMobject("New output").scale(sf)
new_output_words.add_background_rectangle()
new_output_words.next_to(new_vector.get_end(), UP, sf * SMALL_BUFF)
new_output_words.match_color(new_vector)
step_words = TextMobject("Step").scale(sf)
step_words.next_to(step_vector, UP, buff=0)
step_words.set_color(step_vector.get_fill_color())
step_words.add_background_rectangle()
small_step_words = TextMobject("(think tiny step)").scale(sf)
small_step_words.next_to(
step_words, RIGHT,
buff=sf * MED_SMALL_BUFF,
)
small_step_words.add_background_rectangle()
small_step_words.match_style(step_words)
shifted_vector = vector.copy().shift(nudge)
diff_vector = Arrow(
shifted_vector.get_end(),
new_vector.get_end(),
buff=0,
color=RED,
**vector_field.vector_config
)
diff_words = TextMobject("Difference").scale(sf)
diff_words.add_background_rectangle()
diff_words.next_to(diff_vector.get_start(), UR, buff=2 * sf * SMALL_BUFF)
diff_words.match_color(diff_vector)
diff_words.rotate(
diff_vector.get_angle(),
about_point=diff_vector.get_start()
)
self.play(
GrowArrow(step_vector),
Write(step_words),
ReplacementTransform(input_dot.copy(), new_dot)
)
self.add_foreground_mobjects(new_dot)
self.play(FadeIn(small_step_words))
self.play(FadeOut(small_step_words))
self.play(
ReplacementTransform(vector.copy(), new_vector),
ReplacementTransform(output_words.copy(), new_output_words),
)
self.wait()
self.play(ReplacementTransform(
vector.copy(), shifted_vector,
path_arc=-TAU / 4
))
self.wait()
self.play(
FadeOut(output_words),
FadeOut(new_output_words),
GrowArrow(diff_vector),
Write(diff_words)
)
self.wait()
self.play(
vector.scale, 0, {"about_point": vector.get_start()},
shifted_vector.scale, 0, {"about_point": shifted_vector.get_start()},
ReplacementTransform(
new_vector,
diff_vector.copy().shift(-vector.get_vector()),
remover=True
),
diff_vector.shift, -vector.get_vector(),
MaintainPositionRelativeTo(diff_words, diff_vector),
run_time=2
)
self.wait()
self.set_variables_as_attrs(
step_vector, step_words,
diff_vector, diff_words,
)
def show_dot_product(self):
sf = self.scale_factor
point = self.point
step_vector = self.step_vector
step_words = self.step_words
diff_vector = self.diff_vector
diff_words = self.diff_words
vects = VGroup(step_vector, diff_vector)
moving_step_vector = step_vector.copy()
moving_diff_vector = diff_vector.copy()
def update_moving_diff_vector(dv):
step = moving_step_vector.get_vector()
o1 = self.vector_field.get_vector(point).get_vector()
o2 = self.vector_field.get_vector(point + step).get_vector()
diff = o2 - o1
dv.put_start_and_end_on(
moving_step_vector.get_end(),
moving_step_vector.get_end() + diff,
)
self.moving_diff_vector_update = ContinualUpdateFromFunc(
moving_diff_vector,
update_moving_diff_vector
)
self.add(self.moving_diff_vector_update)
div_text = self.get_operator_text("div")
step_words_copy = step_words.copy()
diff_words_copy = diff_words.copy()
copies = VGroup(step_words_copy, diff_words_copy)
substrings = ["Step", "Difference"]
dot_product = TextMobject(
"(Step) $\\cdot$ (Difference)",
substrings_to_isolate=substrings,
arg_separator="",
).scale(sf)
group = VGroup(div_text, dot_product)
group.arrange_submobjects(RIGHT, buff=sf * MED_SMALL_BUFF)
group.next_to(
self.camera_frame.get_top(), DOWN,
buff=sf * MED_SMALL_BUFF
)
for substring, mob, vect in zip(substrings, copies, vects):
part = dot_product.get_part_by_tex(substring)
mob.generate_target()
mob.target.rotate(-vect.get_angle())
mob.target.replace(part)
# part.set_fill(opacity=0)
part.match_color(mob)
dot_product.add_background_rectangle()
brace = Brace(
dot_product.copy().scale(1 / sf, about_point=ORIGIN), DOWN,
buff=SMALL_BUFF
).scale(sf, about_point=ORIGIN)
dp_kwargs = {
"include_sign": True,
}
dot_product_value = DecimalNumber(1.0, **dp_kwargs)
dot_product_value.scale(sf)
dot_product_value.next_to(brace, DOWN, sf * SMALL_BUFF)
dot_product_value_update = ContinualChangingDecimal(
dot_product_value,
lambda a: np.dot(
moving_step_vector.get_vector(),
moving_diff_vector.get_vector(),
),
**dp_kwargs
)
self.play(
Write(dot_product),
LaggedStart(MoveToTarget, copies)
)
self.remove(copies)
self.play(FadeIn(div_text))
self.play(ShowPassingFlashAround(
div_text[1:3],
surrounding_rectangle_config={"buff": sf * SMALL_BUFF}
))
self.add(BackgroundRectangle(dot_product_value))
self.play(
GrowFromCenter(brace),
Write(dot_product_value),
)
self.add(dot_product_value_update)
self.wait()
self.set_variables_as_attrs(
div_text, dot_product,
moving_step_vector,
moving_diff_vector,
dot_product_value,
dot_product_value_update,
brace,
)
def show_circle_of_values(self):
point = self.point
moving_step_vector = self.moving_step_vector
moving_diff_vector = self.moving_diff_vector
all_diff_vectors = VGroup()
all_step_vectors = VGroup()
# Loop around
n_samples = 12
angle = TAU / n_samples
self.add_foreground_mobjects(self.step_words)
for n in range(n_samples):
self.play(
Rotating(
moving_step_vector,
radians=angle,
about_point=point,
run_time=15.0 / n_samples,
rate_func=None,
)
)
step_vector_copy = moving_step_vector.copy()
diff_vector_copy = moving_diff_vector.copy()
diff_vector_copy.set_stroke(BLACK, 0.5)
self.add(step_vector_copy, diff_vector_copy)
all_step_vectors.add(step_vector_copy)
all_diff_vectors.add(diff_vector_copy)
self.remove(
self.step_vector, self.diff_vector,
self.moving_step_vector, self.moving_diff_vector,
self.moving_diff_vector_update,
self.dot_product_value_update
)
self.remove_foreground_mobjects(self.step_words)
self.play(
FadeOut(self.brace),
FadeOut(self.dot_product_value),
FadeOut(self.step_words),
FadeOut(self.diff_words),
)
self.wait()
for s in 0.6, -0.6:
for step, diff in zip(all_step_vectors, all_diff_vectors):
diff.generate_target()
diff.target.put_start_and_end_on(
step.get_end(),
step.get_end() + s * step.get_vector()
)
self.play(
all_step_vectors.set_fill, {"opacity": 0.5},
LaggedStart(
MoveToTarget, all_diff_vectors,
run_time=3
),
)
self.wait()
self.show_stream_lines(lambda p: s * (p - point))
self.wait()
self.set_variables_as_attrs(
all_step_vectors, all_diff_vectors,
)
def switch_to_curl_words(self):
sf = self.scale_factor
div_text = self.div_text
dot_product = self.dot_product
curl_text = self.get_operator_text("curl")
cross_product = TextMobject(
"(Step) $\\times$ (Difference)",
tex_to_color_map={
"Step": TEAL,
"Difference": RED
},
arg_separator="",
).scale(sf)
cross_product.add_background_rectangle(opacity=1)
group = VGroup(curl_text, cross_product)
group.arrange_submobjects(RIGHT, buff=sf * MED_SMALL_BUFF)
group.next_to(self.camera_frame.get_top(), sf * DOWN)
self.play(
dot_product.shift, sf * DOWN,
dot_product.fade, 1,
remover=True
)
self.play(FadeInAndShiftFromDirection(cross_product, sf * DOWN))
self.play(
div_text.shift, sf * DOWN,
div_text.fade, 1,
remover=True
)
self.play(FadeInAndShiftFromDirection(curl_text, sf * DOWN))
self.wait()
def rotate_difference_vectors(self):
point = self.point
all_step_vectors = self.all_step_vectors
all_diff_vectors = self.all_diff_vectors
for s in 0.6, -0.6:
for step, diff in zip(all_step_vectors, all_diff_vectors):
diff.generate_target()
diff.target.put_start_and_end_on(
step.get_end(),
step.get_end() + s * rotate_vector(
step.get_vector(),
90 * DEGREES
)
)
self.play(
LaggedStart(
MoveToTarget, all_diff_vectors,
run_time=2
),
)
self.wait()
self.show_stream_lines(
lambda p: s * rotate_vector((p - point), 90 * DEGREES)
)
self.wait()
self.set_variables_as_attrs(
all_step_vectors, all_diff_vectors,
)
# Helpers
def get_operator_text(self, operator):
text = TextMobject(
operator + "\\,",
"$\\textbf{F}(x_0, y_0)\\,$",
"corresponds to average of",
arg_separator=""
).scale(self.scale_factor)
text.set_color_by_tex(operator, YELLOW)
text.add_background_rectangle()
return text
def show_stream_lines(self, func):
point = self.point
stream_lines = StreamLines(
func,
start_points_generator_config={
"x_min": point[0] - 2,
"x_max": point[0] + 2,
"y_min": point[1] - 1,
"y_max": point[1] + 1,
"delta_x": 0.025,
"delta_y": 0.025,
},
virtual_time=1,
)
random.shuffle(stream_lines.submobjects)
self.play(LaggedStart(
ShowPassingFlash,
stream_lines,
run_time=4,
))
class IncmpressibleAndIrrotational(Scene):
def construct(self):
div_0 = TextMobject("div$\\textbf{F} = 0$")
curl_0 = TextMobject("curl$\\textbf{F}$ = 0")
incompressible = TextMobject("Incompressible")
irrotational = TextMobject("Irrotational")
for text in [div_0, curl_0, incompressible, irrotational]:
text.add_background_rectangle(buff=SMALL_BUFF)
div_0.to_edge(UP)
curl_0.next_to(div_0, DOWN, MED_LARGE_BUFF)
for op, word in (div_0, incompressible), (curl_0, irrotational):
op.generate_target()
group = VGroup(op.target, word)
group.arrange_submobjects(RIGHT, buff=LARGE_BUFF)
group.move_to(op)
self.play(FadeInFromDown(div_0))
self.play(FadeInFromDown(curl_0))
self.wait()
self.play(
MoveToTarget(div_0),
FadeInFromDown(incompressible),
)
self.wait()
self.play(
MoveToTarget(curl_0),
FadeInFromDown(irrotational),
)
self.wait()
rect = SurroundingRectangle(VGroup(curl_0, irrotational))
question = TextMobject("Does this actually happen?")
question.next_to(rect, DOWN)
question.match_color(rect)
question.add_background_rectangle()
self.play(ShowCreation(rect))
self.play(Write(question))
self.wait()