mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 22:13:30 +08:00
1208 lines
39 KiB
Python
1208 lines
39 KiB
Python
from mobject.tex_mobject import TexMobject
|
|
from mobject import Mobject
|
|
from mobject.image_mobject import ImageMobject
|
|
from mobject.vectorized_mobject import VMobject
|
|
|
|
from animation.animation import Animation
|
|
from animation.transform import *
|
|
from animation.simple_animations import *
|
|
from topics.geometry import *
|
|
from topics.characters import *
|
|
from topics.functions import *
|
|
from topics.number_line import *
|
|
from topics.numerals import *
|
|
from scene import Scene
|
|
from camera import Camera
|
|
from mobject.svg_mobject import *
|
|
from mobject.tex_mobject import *
|
|
from mobject.vectorized_mobject import *
|
|
|
|
from eola.matrix import *
|
|
from eola.two_d_space import *
|
|
from eola.footnote2 import TwoDTo1DTransformWithDots
|
|
|
|
from ka_playgrounds.circuits import Resistor, Source, LongResistor
|
|
|
|
V_COLOR = YELLOW
|
|
W_COLOR = MAROON_B
|
|
SUM_COLOR = PINK
|
|
|
|
def get_projection(stable_vector, vector_to_project):
|
|
dot_product = np.dot(*[
|
|
v.get_end()
|
|
for v in stable_vector, vector_to_project
|
|
])
|
|
stable_square_norm = stable_vector.get_length()**2
|
|
result = Vector(
|
|
stable_vector.get_end()*dot_product/stable_square_norm,
|
|
color = vector_to_project.get_color()
|
|
)
|
|
result.fade()
|
|
return result
|
|
|
|
class OpeningQuote(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"\\small Calvin:",
|
|
"You know, I don't think math is a science, I think it's a religion.",
|
|
"\\\\Hobbes:",
|
|
"A religion?",
|
|
"\\\\Calvin:" ,
|
|
"Yeah. All these equations are like miracles."
|
|
"You take two numbers and when you add them, "
|
|
"they magically become one NEW number!"
|
|
)
|
|
words.scale_to_fit_width(2*SPACE_WIDTH - 1)
|
|
words.to_edge(UP)
|
|
words[0].highlight(YELLOW)
|
|
words[2].highlight("#fd9c2b")
|
|
words[4].highlight(YELLOW)
|
|
|
|
for i in range(3):
|
|
self.play(Write(VMobject(*words[2*i:2*i+1])))
|
|
# self.play(FadeIn(words))
|
|
self.dither(2)
|
|
|
|
class TraditionalOrdering(RandolphScene):
|
|
def construct(self):
|
|
title = TextMobject("Traditional ordering:")
|
|
title.highlight(YELLOW)
|
|
title.scale(1.2)
|
|
title.to_corner(UP+LEFT)
|
|
topics = VMobject(*map(TextMobject, [
|
|
"Topic 1: Vectors",
|
|
"Topic 2: Dot products",
|
|
"\\vdots",
|
|
"(everything else)",
|
|
"\\vdots",
|
|
]))
|
|
topics.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = SMALL_BUFF)
|
|
# topics.next_to(title, DOWN+RIGHT)
|
|
|
|
self.play(
|
|
Write(title, run_time = 1),
|
|
FadeIn(
|
|
topics,
|
|
run_time = 3,
|
|
submobject_mode = "lagged_start"
|
|
),
|
|
)
|
|
self.play(topics[1].highlight, PINK)
|
|
self.dither()
|
|
|
|
class ThisSeriesOrdering(RandolphScene):
|
|
def construct(self):
|
|
title = TextMobject("Essence of linear algebra")
|
|
title.scale(1.2).highlight(BLUE)
|
|
title.to_corner(UP+LEFT)
|
|
line = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT, color = WHITE)
|
|
line.next_to(title, DOWN, buff = SMALL_BUFF)
|
|
line.to_edge(LEFT, buff = 0)
|
|
|
|
chapters = VMobject(*[
|
|
TextMobject("\\small " + text)
|
|
for text in [
|
|
"Chapter 1: Vectors, what even are they?",
|
|
"Chapter 2: Linear combinations, span and bases",
|
|
"Chapter 3: Matrices as linear transformations",
|
|
"Chapter 4: Matrix multiplication as composition",
|
|
"Chapter 5: The determinant",
|
|
"Chapter 6: Inverse matrices, column space and null space",
|
|
"Chapter 7: Dot products and duality",
|
|
"Chapter 8: Cross products via transformations",
|
|
"Chapter 9: Change of basis",
|
|
"Chapter 10: Eigenvectors and eigenvalues",
|
|
"Chapter 11: Abstract vector spaces",
|
|
]
|
|
])
|
|
chapters.arrange_submobjects(
|
|
DOWN, buff = SMALL_BUFF, aligned_edge = LEFT
|
|
)
|
|
chapters.scale_to_fit_height(1.5*SPACE_HEIGHT)
|
|
chapters.next_to(line, DOWN, buff = SMALL_BUFF)
|
|
chapters.to_edge(RIGHT)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(line))
|
|
self.play(
|
|
FadeIn(
|
|
chapters,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 3
|
|
),
|
|
self.randy.change_mode, "sassy"
|
|
)
|
|
self.play(chapters[6].highlight, PINK)
|
|
self.dither(2)
|
|
self.dither(2)
|
|
|
|
class OneMustViewThroughTransformations(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("""
|
|
Only with transformations
|
|
can we truly understand
|
|
""")
|
|
self.change_student_modes(
|
|
"pondering",
|
|
"plain",
|
|
"raise_right_hand"
|
|
)
|
|
self.random_blink(2)
|
|
self.teacher_says("""
|
|
First, the
|
|
standard view...
|
|
""")
|
|
self.random_blink(2)
|
|
|
|
class ShowNumericalDotProduct(Scene):
|
|
CONFIG = {
|
|
"v1" : [2, 7, 1],
|
|
"v2" : [8, 2, 8],
|
|
}
|
|
def construct(self):
|
|
v1 = Matrix(self.v1)
|
|
v2 = Matrix(self.v2)
|
|
inter_array_dot = TexMobject("\\cdot").scale(1.5)
|
|
dot_product = VMobject(v1, inter_array_dot, v2)
|
|
dot_product.arrange_submobjects(RIGHT)
|
|
dot_product.to_edge(LEFT)
|
|
pairs = zip(v1.get_entries(), v2.get_entries())
|
|
|
|
for pair, color in zip(pairs, [X_COLOR, Y_COLOR, Z_COLOR, PINK]):
|
|
VMobject(*pair).highlight(color)
|
|
|
|
dot = TexMobject("\\cdot")
|
|
products = VMobject(*[
|
|
VMobject(
|
|
p1.copy(), dot.copy(), p2.copy()
|
|
).arrange_submobjects(RIGHT, buff = SMALL_BUFF)
|
|
for p1, p2 in pairs
|
|
])
|
|
products.arrange_submobjects(DOWN, buff = LARGE_BUFF)
|
|
products.next_to(dot_product, RIGHT, buff = LARGE_BUFF)
|
|
|
|
|
|
products.target = products.copy()
|
|
plusses = ["+"]*(len(self.v1)-1)
|
|
symbols = VMobject(*map(TexMobject, ["="] + plusses))
|
|
final_sum = VMobject(*it.chain(*zip(
|
|
symbols, products.target
|
|
)))
|
|
final_sum.arrange_submobjects(RIGHT, buff = SMALL_BUFF)
|
|
final_sum.next_to(dot_product, RIGHT)
|
|
|
|
|
|
self.play(
|
|
Write(v1),
|
|
Write(v2),
|
|
FadeIn(inter_array_dot)
|
|
)
|
|
self.dither()
|
|
|
|
self.dither()
|
|
self.play(Transform(
|
|
VMobject(*it.starmap(VMobject, pairs)).copy(),
|
|
products,
|
|
path_arc = -np.pi/2,
|
|
run_time = 2
|
|
))
|
|
self.remove(*self.get_mobjects_from_last_animation())
|
|
self.add(products)
|
|
self.dither()
|
|
|
|
self.play(
|
|
Write(symbols),
|
|
Transform(products, products.target, path_arc = np.pi/2)
|
|
)
|
|
self.dither()
|
|
|
|
class TwoDDotProductExample(ShowNumericalDotProduct):
|
|
CONFIG = {
|
|
"v1" : [1, 2],
|
|
"v2" : [3, 4],
|
|
}
|
|
|
|
class FourDDotProductExample(ShowNumericalDotProduct):
|
|
CONFIG = {
|
|
"v1" : [6, 2, 8, 3],
|
|
"v2" : [1, 8, 5, 3],
|
|
}
|
|
|
|
class GeometricInterpretation(VectorScene):
|
|
CONFIG = {
|
|
"v_coords" : [4, 1],
|
|
"w_coords" : [2, -1],
|
|
"v_color" : V_COLOR,
|
|
"w_color" : W_COLOR,
|
|
"project_onto_v" : True,
|
|
}
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
self.add_symbols()
|
|
self.add_vectors()
|
|
self.line()
|
|
self.project()
|
|
self.show_lengths()
|
|
self.handle_possible_negative()
|
|
|
|
|
|
def add_symbols(self):
|
|
v = matrix_to_mobject(self.v_coords).highlight(self.v_color)
|
|
w = matrix_to_mobject(self.w_coords).highlight(self.w_color)
|
|
v.add_background_rectangle()
|
|
w.add_background_rectangle()
|
|
dot = TexMobject("\\cdot")
|
|
eq = VMobject(v, dot, w)
|
|
eq.arrange_submobjects(RIGHT, buff = SMALL_BUFF)
|
|
eq.to_corner(UP+LEFT)
|
|
self.play(Write(eq), run_time = 1)
|
|
for array, char in zip([v, w], ["v", "w"]):
|
|
brace = Brace(array, DOWN)
|
|
label = brace.get_text("$\\vec{\\textbf{%s}}$"%char)
|
|
label.highlight(array.get_color())
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(label),
|
|
run_time = 1
|
|
)
|
|
self.dot_product = eq
|
|
|
|
|
|
def add_vectors(self):
|
|
self.v = Vector(self.v_coords, color = self.v_color)
|
|
self.w = Vector(self.w_coords, color = self.w_color)
|
|
self.play(ShowCreation(self.v))
|
|
self.play(ShowCreation(self.w))
|
|
for vect, char, direction in zip(
|
|
[self.v, self.w], ["v", "w"], [DOWN+RIGHT, DOWN]
|
|
):
|
|
label = TexMobject("\\vec{\\textbf{%s}}"%char)
|
|
label.next_to(vect.get_end(), direction)
|
|
label.highlight(vect.get_color())
|
|
self.play(Write(label, run_time = 1))
|
|
self.stable_vect = self.v if self.project_onto_v else self.w
|
|
self.proj_vect = self.w if self.project_onto_v else self.v
|
|
|
|
def line(self):
|
|
line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
|
|
line.rotate(self.stable_vect.get_angle())
|
|
self.play(ShowCreation(line), Animation(self.stable_vect))
|
|
self.dither()
|
|
|
|
def project(self):
|
|
dot_product = np.dot(self.v.get_end(), self.w.get_end())
|
|
v_norm, w_norm = [
|
|
np.linalg.norm(vect.get_end())
|
|
for vect in self.v, self.w
|
|
]
|
|
projected = Vector(
|
|
self.stable_vect.get_end()*dot_product/(
|
|
self.stable_vect.get_length()**2
|
|
),
|
|
color = self.proj_vect.get_color()
|
|
)
|
|
projection_line = Line(
|
|
self.proj_vect.get_end(), projected.get_end(),
|
|
color = GREY
|
|
)
|
|
|
|
self.play(ShowCreation(projection_line))
|
|
self.add(self.proj_vect.copy().fade())
|
|
self.play(Transform(self.proj_vect, projected))
|
|
self.dither()
|
|
|
|
def show_lengths(self):
|
|
stable_char = "v" if self.project_onto_v else "w"
|
|
proj_char = "w" if self.project_onto_v else "v"
|
|
product = TextMobject(
|
|
"=",
|
|
"(",
|
|
"Length of projected $\\vec{\\textbf{%s}}$"%proj_char,
|
|
")",
|
|
"(",
|
|
"Length of $\\vec{\\textbf{%s}}$"%stable_char,
|
|
")",
|
|
separate_list_arg_with_spaces = False
|
|
)
|
|
product.scale(0.9)
|
|
product.next_to(self.dot_product, RIGHT)
|
|
proj_words = product[2]
|
|
proj_words.highlight(self.proj_vect.get_color())
|
|
stable_words = product[5]
|
|
stable_words.highlight(self.stable_vect.get_color())
|
|
product.remove(proj_words, stable_words)
|
|
for words in stable_words, proj_words:
|
|
words.add_to_back(BackgroundRectangle(words))
|
|
words.start = words.copy()
|
|
|
|
proj_brace, stable_brace = braces = [
|
|
Brace(Line(ORIGIN, vect.get_length()*RIGHT*sgn), UP)
|
|
for vect in self.proj_vect, self.stable_vect
|
|
for sgn in [np.sign(np.dot(vect.get_end(), self.stable_vect.get_end()))]
|
|
]
|
|
proj_brace.put_at_tip(proj_words.start)
|
|
proj_brace.words = proj_words.start
|
|
stable_brace.put_at_tip(stable_words.start)
|
|
stable_brace.words = stable_words.start
|
|
for brace in braces:
|
|
brace.rotate(self.stable_vect.get_angle())
|
|
brace.words.rotate(self.stable_vect.get_angle())
|
|
|
|
self.play(
|
|
GrowFromCenter(proj_brace),
|
|
Write(proj_words.start, run_time = 2)
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
Transform(proj_words.start, proj_words),
|
|
FadeOut(proj_brace)
|
|
)
|
|
self.play(
|
|
GrowFromCenter(stable_brace),
|
|
Write(stable_words.start, run_time = 2),
|
|
Animation(self.stable_vect)
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
Transform(stable_words.start, stable_words),
|
|
Write(product)
|
|
)
|
|
self.dither()
|
|
|
|
product.add(stable_words.start, proj_words.start)
|
|
self.product = product
|
|
|
|
def handle_possible_negative(self):
|
|
if np.dot(self.w.get_end(), self.v.get_end()) > 0:
|
|
return
|
|
neg = TexMobject("-").highlight(RED)
|
|
neg.next_to(self.product[0], RIGHT)
|
|
words = TextMobject("Should be negative")
|
|
words.highlight(RED)
|
|
words.next_to(
|
|
VMobject(*self.product[2:]),
|
|
DOWN,
|
|
buff = LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
words.add_background_rectangle()
|
|
arrow = Arrow(words.get_left(), neg, color = RED)
|
|
|
|
self.play(
|
|
Write(neg),
|
|
ShowCreation(arrow),
|
|
VMobject(*self.product[1:]).next_to, neg,
|
|
Write(words)
|
|
)
|
|
self.dither()
|
|
|
|
class GeometricInterpretationNegative(GeometricInterpretation):
|
|
CONFIG = {
|
|
"v_coords" : [3, 1],
|
|
"w_coords" : [-1, -2],
|
|
"v_color" : YELLOW,
|
|
"w_color" : MAROON_B,
|
|
}
|
|
|
|
class ShowQualitativeDotProductValues(VectorScene):
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
v_sym, dot, w_sym, comp, zero = ineq = TexMobject(
|
|
"\\vec{\\textbf{v}}",
|
|
"\\cdot",
|
|
"\\vec{\\textbf{w}}",
|
|
">",
|
|
"0",
|
|
)
|
|
ineq.to_edge(UP)
|
|
ineq.add_background_rectangle()
|
|
comp.highlight(GREEN)
|
|
equals = TexMobject("=").highlight(PINK).move_to(comp)
|
|
less_than = TexMobject("<").highlight(RED).move_to(comp)
|
|
v_sym.highlight(V_COLOR)
|
|
w_sym.highlight(W_COLOR)
|
|
words = map(TextMobject, [
|
|
"Similar directions",
|
|
"Perpendicular",
|
|
"Opposing directions"
|
|
])
|
|
for word, sym in zip(words, [comp, equals, less_than]):
|
|
word.add_background_rectangle()
|
|
word.next_to(sym, DOWN, aligned_edge = LEFT, buff = MED_BUFF)
|
|
word.highlight(sym.get_color())
|
|
|
|
v = Vector([1.5, 1.5], color = V_COLOR)
|
|
w = Vector([2, 2], color = W_COLOR)
|
|
w.rotate(-np.pi/6)
|
|
shadow = Vector(
|
|
v.get_end()*np.dot(v.get_end(), w.get_end())/(v.get_length()**2),
|
|
color = MAROON_E,
|
|
preserve_tip_size_when_scaling = False
|
|
)
|
|
shadow_opposite = shadow.copy().scale(-1)
|
|
line = Line(LEFT, RIGHT, color = WHITE)
|
|
line.scale(SPACE_WIDTH)
|
|
line.rotate(v.get_angle())
|
|
proj_line = Line(w.get_end(), shadow.get_end(), color = GREY)
|
|
|
|
|
|
word = words[0]
|
|
|
|
self.add(ineq)
|
|
for mob in v, w, line, proj_line:
|
|
self.play(
|
|
ShowCreation(mob),
|
|
Animation(v)
|
|
)
|
|
self.play(Transform(w.copy(), shadow))
|
|
self.remove(*self.get_mobjects_from_last_animation())
|
|
self.add(shadow)
|
|
self.play(FadeOut(proj_line))
|
|
|
|
self.play(Write(word, run_time = 1))
|
|
self.dither()
|
|
self.play(
|
|
Rotate(w, -np.pi/3),
|
|
shadow.scale, 0
|
|
)
|
|
self.play(
|
|
Transform(comp, equals),
|
|
Transform(word, words[1])
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
Rotate(w, -np.pi/3),
|
|
Transform(shadow, shadow_opposite)
|
|
)
|
|
self.play(
|
|
Transform(comp, less_than),
|
|
Transform(word, words[2])
|
|
)
|
|
self.dither()
|
|
|
|
class AskAboutSymmetry(TeacherStudentsScene):
|
|
def construct(self):
|
|
v, w = "\\vec{\\textbf{v}}", "\\vec{\\textbf{w}}",
|
|
question = TexMobject(
|
|
"\\text{Why does }",
|
|
v, "\\cdot", w, "=", w, "\\cdot", v,
|
|
"\\text{?}"
|
|
)
|
|
VMobject(question[1], question[7]).highlight(V_COLOR)
|
|
VMobject(question[3], question[5]).highlight(W_COLOR)
|
|
self.student_says(
|
|
question,
|
|
pi_creature_target_mode = "raise_left_hand"
|
|
)
|
|
self.change_student_modes("confused")
|
|
self.play(self.get_teacher().change_mode, "pondering")
|
|
self.play(self.get_teacher().look, RIGHT)
|
|
self.play(self.get_teacher().look, LEFT)
|
|
self.random_blink()
|
|
|
|
class GeometricInterpretationSwapVectors(GeometricInterpretation):
|
|
CONFIG = {
|
|
"project_onto_v" : False,
|
|
}
|
|
|
|
class SymmetricVAndW(VectorScene):
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
v = Vector([3, 1], color = V_COLOR)
|
|
w = Vector([1, 3], color = W_COLOR)
|
|
for vect, char in zip([v, w], ["v", "w"]):
|
|
vect.label = TexMobject("\\vec{\\textbf{%s}}"%char)
|
|
vect.label.highlight(vect.get_color())
|
|
vect.label.next_to(vect.get_end(), DOWN+RIGHT)
|
|
for v1, v2 in (v, w), (w, v):
|
|
v1.proj = get_projection(v2, v1)
|
|
v1.proj_line = Line(
|
|
v1.get_end(), v1.proj.get_end(), color = GREY
|
|
)
|
|
line_of_symmetry = DashedLine(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
|
|
line_of_symmetry.rotate(np.mean([v.get_angle(), w.get_angle()]))
|
|
line_of_symmetry_words = TextMobject("Line of symmetry")
|
|
line_of_symmetry_words.add_background_rectangle()
|
|
line_of_symmetry_words.next_to(ORIGIN, UP+RIGHT)
|
|
line_of_symmetry_words.rotate(line_of_symmetry.get_angle())
|
|
|
|
for vect in v, w:
|
|
self.play(ShowCreation(vect))
|
|
self.play(Write(vect.label, run_time = 1))
|
|
self.dither()
|
|
angle = (v.get_angle()-w.get_angle())/2
|
|
self.play(
|
|
Rotate(w, angle),
|
|
Rotate(v, -angle),
|
|
rate_func = there_and_back,
|
|
run_time = 2
|
|
)
|
|
self.dither()
|
|
self.play(ShowCreation(line_of_symmetry))
|
|
self.play(Write(line_of_symmetry_words))
|
|
self.dither()
|
|
self.play(Transform(line_of_symmetry_words, line_of_symmetry))
|
|
for vect in v, w:
|
|
self.play(ShowCreation(vect.proj_line))
|
|
vect_copy = vect.copy()
|
|
self.play(Transform(vect_copy, vect.proj))
|
|
self.remove(vect_copy)
|
|
self.add(vect.proj)
|
|
self.dither()
|
|
|
|
self.show_doubling(v, w)
|
|
|
|
def show_doubling(self, v, w):
|
|
scalar = 2
|
|
new_v = v.copy().scale(scalar)
|
|
new_v.label = VMobject(TexMobject(str(scalar)), v.label.copy())
|
|
new_v.label.arrange_submobjects()
|
|
new_v.label.highlight(new_v.get_color())
|
|
new_v.label.next_to(new_v.get_end(), DOWN+RIGHT)
|
|
new_v.proj = v.proj.copy().scale(scalar)
|
|
new_v.proj.fade()
|
|
new_v.proj_line = Line(
|
|
new_v.get_end(), new_v.proj.get_end(),
|
|
color = GREY
|
|
)
|
|
|
|
v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in "v", "w"]
|
|
equation = TexMobject(
|
|
"(", "2", v_tex, ")", "\\cdot", w_tex,
|
|
"=",
|
|
"2(", v_tex, "\\cdot", w_tex, ")"
|
|
)
|
|
equation.highlight_by_tex(v_tex, V_COLOR)
|
|
equation.highlight_by_tex(w_tex, W_COLOR)
|
|
equation.next_to(ORIGIN, DOWN).to_edge(RIGHT)
|
|
|
|
words = TextMobject("Symmetry is broken")
|
|
words.next_to(ORIGIN, LEFT)
|
|
words.to_edge(UP)
|
|
|
|
v.save_state()
|
|
v.proj.save_state()
|
|
self.play(Transform(*[
|
|
VMobject(mob, mob.proj, mob.proj_line, mob.label)
|
|
for mob in v, new_v
|
|
]), run_time = 2)
|
|
self.play(Write(words))
|
|
self.dither()
|
|
|
|
two_v_parts = equation[1:3]
|
|
equation.remove(*two_v_parts)
|
|
self.play(
|
|
Write(equation),
|
|
Transform(new_v.label.copy(), VMobject(*two_v_parts))
|
|
)
|
|
self.dither()
|
|
|
|
for vect in v, v.proj:
|
|
self.play(
|
|
vect.restore,
|
|
rate_func = there_and_back,
|
|
run_time = 2
|
|
)
|
|
self.dither()
|
|
|
|
class LurkingQuestion(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("That's the standard intro")
|
|
self.dither()
|
|
self.student_says(
|
|
"""
|
|
Wait, why are the
|
|
two views connected?
|
|
""",
|
|
pi_creature_target_mode = "confused"
|
|
)
|
|
self.change_student_modes(
|
|
"raise_right_hand", "confused", "raise_left_hand"
|
|
)
|
|
self.random_blink(5)
|
|
answer = TextMobject("""
|
|
The most satisfactory
|
|
answer comes from""",
|
|
"duality"
|
|
)
|
|
answer.highlight_by_tex("duality", PINK)
|
|
self.teacher_says(answer)
|
|
self.random_blink(2)
|
|
self.teacher_thinks("")
|
|
everything = VMobject(*self.get_mobjects())
|
|
self.play(ApplyPointwiseFunction(
|
|
lambda p : 10*(p+2*DOWN)/np.linalg.norm(p+2*DOWN),
|
|
everything
|
|
))
|
|
|
|
class TwoDToOneDScene(LinearTransformationScene):
|
|
CONFIG = {
|
|
"include_background_plane" : False,
|
|
"foreground_plane_kwargs" : {
|
|
"x_radius" : SPACE_WIDTH,
|
|
"y_radius" : SPACE_HEIGHT,
|
|
"secondary_line_ratio" : 1
|
|
},
|
|
"t_matrix" : [[2, 0], [1, 0]]
|
|
}
|
|
def setup(self):
|
|
self.number_line = NumberLine()
|
|
self.add(self.number_line)
|
|
LinearTransformationScene.setup(self)
|
|
|
|
class Introduce2Dto1DLinearTransformations(TwoDToOneDScene):
|
|
def construct(self):
|
|
number_line_words = TextMobject("Number line")
|
|
number_line_words.next_to(self.number_line, UP, buff = MED_BUFF)
|
|
numbers = VMobject(*self.number_line.get_number_mobjects())
|
|
|
|
self.remove(self.number_line)
|
|
self.apply_transposed_matrix(self.t_matrix)
|
|
self.play(
|
|
ShowCreation(number_line),
|
|
*[Animation(v) for v in self.i_hat, self.j_hat]
|
|
)
|
|
self.play(*map(Write, [numbers, number_line_words]))
|
|
self.dither()
|
|
|
|
class Symbolic2To1DTransform(Scene):
|
|
def construct(self):
|
|
func = TexMobject("L(", "\\vec{\\textbf{v}}", ")")
|
|
input_array = Matrix([2, 7])
|
|
input_array.highlight(YELLOW)
|
|
in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color())
|
|
func[1].highlight(input_array.get_color())
|
|
output_array = Matrix([1.8])
|
|
output_array.highlight(PINK)
|
|
out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color())
|
|
VMobject(
|
|
input_array, in_arrow, func, out_arrow, output_array
|
|
).arrange_submobjects(RIGHT, buff = SMALL_BUFF)
|
|
|
|
input_brace = Brace(input_array, DOWN)
|
|
input_words = input_brace.get_text("2d input")
|
|
output_brace = Brace(output_array, UP)
|
|
output_words = output_brace.get_text("1d output")
|
|
input_words.highlight(input_array.get_color())
|
|
output_words.highlight(output_array.get_color())
|
|
|
|
special_words = TextMobject("Linear", "functions are quite special")
|
|
special_words.highlight_by_tex("Linear", BLUE)
|
|
special_words.to_edge(UP)
|
|
|
|
|
|
self.add(func, input_array)
|
|
self.play(
|
|
GrowFromCenter(input_brace),
|
|
Write(input_words)
|
|
)
|
|
mover = input_array.copy()
|
|
self.play(
|
|
Transform(mover, Dot().move_to(func)),
|
|
ShowCreation(in_arrow),
|
|
rate_func = rush_into,
|
|
run_time = 0.5
|
|
)
|
|
self.play(
|
|
Transform(mover, output_array),
|
|
ShowCreation(out_arrow),
|
|
rate_func = rush_from,
|
|
run_time = 0.5
|
|
)
|
|
self.play(
|
|
GrowFromCenter(output_brace),
|
|
Write(output_words)
|
|
)
|
|
self.dither()
|
|
self.play(Write(special_words))
|
|
self.dither()
|
|
|
|
class FormalVsVisual(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Linearity")
|
|
title.highlight(BLUE)
|
|
title.to_edge(UP)
|
|
line = Line(LEFT, RIGHT).scale(SPACE_WIDTH)
|
|
line.next_to(title, DOWN)
|
|
v_line = Line(line.get_center(), SPACE_HEIGHT*DOWN)
|
|
|
|
formal = TextMobject("Formal definition")
|
|
visual = TextMobject("Visual intuition")
|
|
formal.next_to(line, DOWN).shift(SPACE_WIDTH*LEFT/2)
|
|
visual.next_to(line, DOWN).shift(SPACE_WIDTH*RIGHT/2)
|
|
|
|
v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in "v", "w"]
|
|
additivity = TexMobject(
|
|
"L(", v_tex, "+", w_tex, ") = ",
|
|
"L(", v_tex, ")+", "L(", w_tex, ")"
|
|
)
|
|
additivity.highlight_by_tex(v_tex, V_COLOR)
|
|
additivity.highlight_by_tex(w_tex, W_COLOR)
|
|
scaling = TexMobject(
|
|
"L(", "c", v_tex, ")=", "c", "L(", v_tex, ")"
|
|
)
|
|
scaling.highlight_by_tex(v_tex, V_COLOR)
|
|
scaling.highlight_by_tex("c", GREEN)
|
|
|
|
visual_statement = TextMobject("""
|
|
Line of dots evenly spaced
|
|
dots remains evenly spaced
|
|
""")
|
|
visual_statement.submobject_gradient_highlight(YELLOW, MAROON_B)
|
|
|
|
properties = VMobject(additivity, scaling)
|
|
properties.arrange_submobjects(DOWN, buff = MED_BUFF)
|
|
|
|
for text, mob in (formal, properties), (visual, visual_statement):
|
|
mob.scale(0.75)
|
|
mob.next_to(text, DOWN, buff = MED_BUFF)
|
|
|
|
self.add(title)
|
|
self.play(*map(ShowCreation, [line, v_line]))
|
|
for mob in formal, visual, additivity, scaling, visual_statement:
|
|
self.play(Write(mob, run_time = 2))
|
|
self.dither()
|
|
|
|
class AdditivityProperty(TwoDToOneDScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"sum_before" : True
|
|
}
|
|
def construct(self):
|
|
v = Vector([2, 1], color = V_COLOR)
|
|
w = Vector([-1, 1], color = W_COLOR)
|
|
|
|
self.play(ShowCreation(v))
|
|
self.play(ShowCreation(w))
|
|
if self.sum_before:
|
|
sum_vect = self.play_sum(v, w)
|
|
else:
|
|
self.add_vector(v, animate = False)
|
|
self.add_vector(w, animate = False)
|
|
self.apply_transposed_matrix(self.t_matrix)
|
|
if not self.sum_before:
|
|
sum_vect = self.play_sum(v, w)
|
|
self.dither()
|
|
self.write_symbols(sum_vect)
|
|
|
|
def play_sum(self, v, w):
|
|
sum_vect = Vector(v.get_end()+w.get_end(), color = SUM_COLOR)
|
|
self.play(w.shift, v.get_end(), path_arc = np.pi/4)
|
|
self.play(ShowCreation(sum_vect))
|
|
for vect in v, w:
|
|
vect.target = vect.copy()
|
|
vect.target.set_fill(opacity = 0)
|
|
vect.target.set_stroke(width = 0)
|
|
sum_vect.target = sum_vect
|
|
self.play(*[
|
|
Transform(mob, mob.target)
|
|
for mob in v, w, sum_vect
|
|
])
|
|
self.add_vector(sum_vect, animate = False)
|
|
return sum_vect
|
|
|
|
def write_symbols(self, sum_vect):
|
|
v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in "v", "w"]
|
|
if self.sum_before:
|
|
tex_mob = TexMobject(
|
|
"L(", v_tex, "+", w_tex, ")"
|
|
)
|
|
tex_mob.next_to(sum_vect, UP)
|
|
else:
|
|
tex_mob = TexMobject(
|
|
"L(", v_tex, ")+L(", w_tex, ")"
|
|
)
|
|
tex_mob.next_to(sum_vect, DOWN)
|
|
tex_mob.highlight_by_tex(v_tex, V_COLOR)
|
|
tex_mob.highlight_by_tex(w_tex, W_COLOR)
|
|
|
|
|
|
self.play(Write(tex_mob))
|
|
self.dither()
|
|
|
|
class AdditivityPropertyPart2(AdditivityProperty):
|
|
CONFIG = {
|
|
"sum_before" : False
|
|
}
|
|
|
|
class ScalingProperty(TwoDToOneDScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"scale_before" : True,
|
|
"scalar" : 2,
|
|
}
|
|
def construct(self):
|
|
v = Vector([-1, 1], color = V_COLOR)
|
|
|
|
self.play(ShowCreation(v))
|
|
if self.scale_before:
|
|
scaled_vect = self.show_scaling(v)
|
|
self.add_vector(v, animate = False)
|
|
self.apply_transposed_matrix(self.t_matrix)
|
|
if not self.scale_before:
|
|
scaled_vect = self.show_scaling(v)
|
|
self.dither()
|
|
self.write_symbols(scaled_vect)
|
|
|
|
def show_scaling(self, v):
|
|
self.add_vector(v.copy().fade(), animate = False)
|
|
self.play(v.scale, self.scalar)
|
|
return v
|
|
|
|
def write_symbols(self, scaled_vect):
|
|
v_tex = "\\vec{\\textbf{v}}"
|
|
if self.scale_before:
|
|
tex_mob = TexMobject(
|
|
"L(", "c", v_tex, ")"
|
|
)
|
|
tex_mob.next_to(scaled_vect, UP)
|
|
else:
|
|
tex_mob = TexMobject(
|
|
"c", "L(", v_tex, ")",
|
|
)
|
|
tex_mob.next_to(scaled_vect, DOWN)
|
|
tex_mob.highlight_by_tex(v_tex, V_COLOR)
|
|
tex_mob.highlight_by_tex("c", GREEN)
|
|
|
|
self.play(Write(tex_mob))
|
|
self.dither()
|
|
|
|
class ScalingPropertyPart2(ScalingProperty):
|
|
CONFIG = {
|
|
"scale_before" : False
|
|
}
|
|
|
|
class ThisTwoDTo1DTransformWithDots(TwoDTo1DTransformWithDots):
|
|
pass
|
|
|
|
class AlwaysfollowIHatJHat(TeacherStudentsScene):
|
|
def construct(self):
|
|
i_tex, j_tex = ["$\\hat{\\%smath}$"%c for c in "i", "j"]
|
|
words = TextMobject(
|
|
"Always follow", i_tex, "and", j_tex
|
|
)
|
|
words.highlight_by_tex(i_tex, X_COLOR)
|
|
words.highlight_by_tex(j_tex, Y_COLOR)
|
|
self.teacher_says(words)
|
|
students = VMobject(*self.get_students())
|
|
ponderers = VMobject(*[
|
|
pi.copy().change_mode("pondering")
|
|
for pi in students
|
|
])
|
|
self.play(Transform(
|
|
students, ponderers,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 2
|
|
))
|
|
self.random_blink(2)
|
|
|
|
class ShowMatrix(TwoDToOneDScene):
|
|
def construct(self):
|
|
self.apply_transposed_matrix(self.t_matrix)
|
|
self.play(Write(self.number_line.get_numbers()))
|
|
self.show_matrix()
|
|
|
|
def show_matrix(self):
|
|
for vect, char in zip([self.i_hat, self.j_hat], ["i", "j"]):
|
|
vect.words = TextMobject(
|
|
"$\\hat\\%smath$ lands on"%char,
|
|
str(int(vect.get_end()[0]))
|
|
)
|
|
direction = UP if vect is self.i_hat else DOWN
|
|
vect.words.next_to(vect.get_end(), direction, buff = LARGE_BUFF)
|
|
vect.words.highlight(vect.get_color())
|
|
matrix = Matrix([[1, 2]])
|
|
matrix_words = TextMobject("Transformation matrix: ")
|
|
matrix_group = VMobject(matrix_words, matrix)
|
|
matrix_group.arrange_submobjects()
|
|
matrix_group.to_edge(UP)
|
|
entries = matrix.get_entries()
|
|
|
|
self.play(
|
|
Write(matrix_words),
|
|
Write(matrix.get_brackets()),
|
|
run_time = 1
|
|
)
|
|
for i, vect in enumerate([self.i_hat, self.j_hat]):
|
|
self.play(
|
|
Write(vect.words, run_time = 1),
|
|
ApplyMethod(vect.shift, 0.5*UP, rate_func = there_and_back)
|
|
)
|
|
self.dither()
|
|
self.play(vect.words[1].copy().move_to, entries[i])
|
|
self.dither()
|
|
|
|
class FollowVectorViaCoordinates(TwoDToOneDScene):
|
|
CONFIG = {
|
|
"t_matrix" : [[1, 0], [-2, 0]],
|
|
"v_coords" : [3, 3],
|
|
"written_v_coords" : ["x", "y"],
|
|
"concrete" : False,
|
|
}
|
|
def construct(self):
|
|
v = Vector(self.v_coords)
|
|
array = Matrix(self.v_coords if self.concrete else self.written_v_coords)
|
|
array.get_entries().gradient_highlight(X_COLOR, Y_COLOR)
|
|
array.add_to_back(BackgroundRectangle(array))
|
|
v_label = TexMobject("\\vec{\\textbf{v}}", "=")
|
|
v_label[0].highlight(YELLOW)
|
|
v_label.next_to(v.get_end(), RIGHT)
|
|
v_label.add_background_rectangle()
|
|
array.next_to(v_label, RIGHT)
|
|
|
|
bases = self.i_hat, self.j_hat
|
|
basis_labels = self.get_basis_vector_labels(direction = "right")
|
|
scaling_anim_tuples = self.get_scaling_anim_tuples(
|
|
basis_labels, array, [DOWN, RIGHT]
|
|
)
|
|
|
|
self.play(*map(Write, basis_labels))
|
|
self.play(
|
|
ShowCreation(v),
|
|
Write(array),
|
|
Write(v_label)
|
|
)
|
|
self.add_foreground_mobject(v_label, array)
|
|
self.add_vector(v, animate = False)
|
|
self.dither()
|
|
to_fade = basis_labels
|
|
for i, anim_tuple in enumerate(scaling_anim_tuples):
|
|
self.play(*anim_tuple)
|
|
movers = self.get_mobjects_from_last_animation()
|
|
to_fade += movers[:-1]
|
|
if i == 1:
|
|
self.play(*[
|
|
ApplyMethod(m.shift, self.v_coords[0]*RIGHT)
|
|
for m in movers
|
|
])
|
|
self.dither()
|
|
self.play(
|
|
*map(FadeOut, to_fade) + [
|
|
vect.restore
|
|
for vect in self.i_hat, self.j_hat
|
|
]
|
|
)
|
|
|
|
self.apply_transposed_matrix(self.t_matrix)
|
|
self.play(Write(self.number_line.get_numbers(), run_time = 1))
|
|
self.play(
|
|
self.i_hat.shift, 0.5*UP,
|
|
self.j_hat.shift, DOWN,
|
|
)
|
|
if self.concrete:
|
|
new_labels = [
|
|
TexMobject("(%d)"%num)
|
|
for num in self.t_matrix[:,0]
|
|
]
|
|
else:
|
|
new_labels = [
|
|
TexMobject("L(\\hat{\\%smath})"%char)
|
|
for char in "i", "j"
|
|
]
|
|
|
|
new_labels[0].highlight(X_COLOR)
|
|
new_labels[1].highlight(Y_COLOR)
|
|
|
|
new_labels.append(
|
|
TexMobject("L(\\vec{\\textbf{v}})").highlight(YELLOW)
|
|
)
|
|
for label, vect, direction in zip(new_labels, list(bases) + [v], [UP, DOWN, UP]):
|
|
label.next_to(vect, direction)
|
|
|
|
self.play(*map(Write, new_labels))
|
|
self.dither()
|
|
scaling_anim_tuples = self.get_scaling_anim_tuples(
|
|
new_labels, array, [UP, DOWN]
|
|
)
|
|
for i, anim_tuple in enumerate(scaling_anim_tuples):
|
|
self.play(*anim_tuple)
|
|
movers = VMobject(*self.get_mobjects_from_last_animation())
|
|
self.dither()
|
|
self.play(movers.shift, self.i_hat.get_end()[0]*RIGHT)
|
|
self.dither()
|
|
if self.concrete:
|
|
final_label = TexMobject(str(int(v.get_end()[0])))
|
|
final_label.move_to(new_labels[-1])
|
|
final_label.highlight(new_labels[-1].get_color())
|
|
self.play(Transform(new_labels[-1], final_label))
|
|
self.dither()
|
|
|
|
|
|
def get_scaling_anim_tuples(self, labels, array, directions):
|
|
scaling_anim_tuples = []
|
|
bases = self.i_hat, self.j_hat
|
|
quints = zip(
|
|
bases, self.v_coords, labels,
|
|
array.get_entries(), directions
|
|
)
|
|
for basis, scalar, label, entry, direction in quints:
|
|
basis.save_state()
|
|
basis.scaled = basis.copy().scale(scalar)
|
|
basis.scaled.shift(basis.get_start() - basis.scaled.get_start())
|
|
scaled_label = VMobject(entry.copy(), label.copy())
|
|
entry.target, label.target = scaled_label.split()
|
|
entry.target.next_to(label.target, LEFT)
|
|
scaled_label.next_to(
|
|
basis.scaled,
|
|
direction
|
|
)
|
|
|
|
scaling_anim_tuples.append((
|
|
ApplyMethod(label.move_to, label.target),
|
|
ApplyMethod(entry.copy().move_to, entry.target),
|
|
Transform(basis, basis.scaled),
|
|
))
|
|
return scaling_anim_tuples
|
|
|
|
class FollowVectorViaCoordinatesConcrete(FollowVectorViaCoordinates):
|
|
CONFIG = {
|
|
"v_coords" : [4, 3],
|
|
"concrete" : True
|
|
}
|
|
|
|
class TwoDOneDMatrixMultiplication(Scene):
|
|
def construct(self):
|
|
matrix = Matrix([[1, -2]])
|
|
matrix.label = "Transform"
|
|
vector = Matrix([4, 3])
|
|
vector.label = "Vector"
|
|
matrix.next_to(vector, LEFT, buff = 0.2)
|
|
for m, vect in zip([matrix, vector], [UP, DOWN]):
|
|
x, y = m.get_entries()
|
|
x.highlight(X_COLOR)
|
|
y.highlight(Y_COLOR)
|
|
m.brace = Brace(m, vect)
|
|
m.label = m.brace.get_text(m.label)
|
|
matrix.label.highlight(BLUE)
|
|
vector.label.highlight(YELLOW)
|
|
|
|
starter_pairs = zip(vector.get_entries(), matrix.get_entries())
|
|
pairs = [
|
|
VMobject(
|
|
e1.copy(), TexMobject("\\cdot"), e2.copy()
|
|
).arrange_submobjects()
|
|
for e1, e2 in starter_pairs
|
|
]
|
|
symbols = map(TexMobject, ["=", "+"])
|
|
equation = VMobject(*it.chain(*zip(symbols, pairs)))
|
|
equation.arrange_submobjects()
|
|
equation.next_to(vector, RIGHT)
|
|
|
|
for m in vector, matrix:
|
|
self.play(Write(m))
|
|
self.play(
|
|
GrowFromCenter(m.brace),
|
|
Write(m.label),
|
|
run_time = 1
|
|
)
|
|
self.dither()
|
|
self.play(Write(VMobject(*symbols)))
|
|
for starter_pair, pair in zip(starter_pairs, pairs):
|
|
self.play(Transform(
|
|
VMobject(*starter_pair).copy(),
|
|
pair,
|
|
path_arc = -np.pi/2
|
|
))
|
|
self.dither()
|
|
|
|
class AssociationBetweenMatricesAndVectors(Scene):
|
|
def construct(self):
|
|
matrices_words = TextMobject("$1\\times 2$ matrices")
|
|
matrices_words.highlight(BLUE)
|
|
vectors_words = TextMobject("2d vectors")
|
|
vectors_words.highlight(YELLOW)
|
|
arrow = DoubleArrow(LEFT, RIGHT, color = WHITE)
|
|
VMobject(
|
|
matrices_words, arrow, vectors_words
|
|
).arrange_submobjects(buff = MED_BUFF)
|
|
|
|
matrices = VMobject(Matrix([[2, 7]]), Matrix([[1, -2]]))
|
|
vectors = VMobject(Matrix([2, 7]), Matrix([1, -2]))
|
|
for m in list(matrices) + list(vectors):
|
|
x, y = m.get_entries()
|
|
x.highlight(X_COLOR)
|
|
y.highlight(Y_COLOR)
|
|
matrices[0].next_to(matrices_words, UP, buff = MED_BUFF)
|
|
matrices[1].next_to(matrices_words, DOWN, buff = MED_BUFF)
|
|
vectors[0].next_to(vectors_words, UP, buff = MED_BUFF)
|
|
vectors[1].next_to(vectors_words, DOWN, buff = MED_BUFF)
|
|
|
|
self.play(*map(Write, [matrices_words, vectors_words]))
|
|
self.play(ShowCreation(arrow))
|
|
self.dither()
|
|
self.play(FadeIn(vectors))
|
|
vectors.save_state()
|
|
self.dither()
|
|
self.play(Transform(
|
|
vectors, matrices,
|
|
path_arc = np.pi/2,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 2,
|
|
))
|
|
self.dither()
|
|
self.play(
|
|
vectors.restore,
|
|
path_arc = -np.pi/2,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 2
|
|
)
|
|
self.dither()
|
|
|
|
class WhatAboutTheGeometricView(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says("""
|
|
What does this association
|
|
mean geometrically?
|
|
""",
|
|
pi_creature_target_mode = "raise_right_hand"
|
|
)
|
|
self.change_student_modes("pondering", "raise_right_hand", "pondering")
|
|
self.random_blink(2)
|
|
|
|
class AnExampleWillClarify(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("An example will clarify...")
|
|
self.change_student_modes(*["happy"]*3)
|
|
self.random_blink(3)
|
|
|
|
class ImagineYouDontKnowThis(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Imagine you don't know this")
|
|
words.highlight(RED)
|
|
words.scale(1.5)
|
|
self.play(Write(words))
|
|
self.dither()
|
|
|
|
class ProjectOntoUnitVectorNumberline(VectorScene):
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
u_hat = Vector([1, 0], color = PINK)
|
|
u_hat.rotate(np.pil/6)
|
|
number_line = NumberLine()
|
|
numbers = number_line.get_numbers()
|
|
VMobject(number_line, numbers).rotate(u_hat.get_angle())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|