mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
1749 lines
56 KiB
Python
1749 lines
56 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 *
|
|
|
|
def curvy_squish(point):
|
|
x, y, z = point
|
|
return (x+np.cos(y))*RIGHT + (y+np.sin(x))*UP
|
|
|
|
class OpeningQuote(Scene):
|
|
def construct(self):
|
|
words = TextMobject([
|
|
"Unfortunately, no one can be told what the",
|
|
"Matrix",
|
|
"is. You have to",
|
|
"see it for yourself.",
|
|
])
|
|
words.scale_to_fit_width(2*SPACE_WIDTH - 2)
|
|
words.to_edge(UP)
|
|
words.split()[1].highlight(GREEN)
|
|
words.split()[3].highlight(BLUE)
|
|
author = TextMobject("-Morpheus")
|
|
author.highlight(YELLOW)
|
|
author.next_to(words, DOWN, buff = 0.5)
|
|
comment = TextMobject("""
|
|
(Surprisingly apt words on the importance
|
|
of understanding matrix operations visually.)
|
|
""")
|
|
comment.next_to(author, DOWN, buff = 1)
|
|
|
|
self.play(FadeIn(words))
|
|
self.dither(3)
|
|
self.play(Write(author, run_time = 3))
|
|
self.dither()
|
|
self.play(Write(comment))
|
|
self.dither()
|
|
|
|
class Introduction(TeacherStudentsScene):
|
|
def construct(self):
|
|
title = TextMobject(["Matrices as", "Linear transformations"])
|
|
title.to_edge(UP)
|
|
title.highlight(YELLOW)
|
|
linear_transformations = title.split()[1]
|
|
self.add(*title.split())
|
|
self.setup()
|
|
self.teacher_says("""
|
|
Listen up folks, this one is
|
|
particularly important
|
|
""", height = 3)
|
|
self.random_blink(2)
|
|
self.teacher_thinks("", height = 3)
|
|
self.remove(linear_transformations)
|
|
everything = VMobject(*self.get_mobjects())
|
|
def spread_out(p):
|
|
p = p + 2*DOWN
|
|
return (SPACE_WIDTH+SPACE_HEIGHT)*p/np.linalg.norm(p)
|
|
self.play(
|
|
ApplyPointwiseFunction(spread_out, everything),
|
|
ApplyFunction(
|
|
lambda m : m.center().to_edge(UP),
|
|
linear_transformations
|
|
)
|
|
)
|
|
|
|
class MatrixVectorMechanicalMultiplication(NumericalMatrixMultiplication):
|
|
CONFIG = {
|
|
"left_matrix" : [[1, -3], [2, 4]],
|
|
"right_matrix" : [[5], [7]]
|
|
}
|
|
|
|
class PostponeHigherDimensions(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.setup()
|
|
self.student_says("What about 3 dimensions?")
|
|
self.random_blink()
|
|
self.teacher_says("All in due time,\\\\ young padawan")
|
|
self.random_blink()
|
|
|
|
class DescribeTransformation(Scene):
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_function()
|
|
|
|
def add_title(self):
|
|
title = TextMobject(["Linear", "transformation"])
|
|
title.to_edge(UP)
|
|
linear, transformation = title.split()
|
|
brace = Brace(transformation, DOWN)
|
|
function = TextMobject("function").next_to(brace, DOWN)
|
|
function.highlight(YELLOW)
|
|
|
|
self.play(Write(title))
|
|
self.dither()
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(function),
|
|
ApplyMethod(linear.fade)
|
|
)
|
|
|
|
def show_function(self):
|
|
f_of_x = TexMobject("f(x)")
|
|
L_of_v = TexMobject("L(\\vec{\\textbf{v}})")
|
|
nums = [5, 2, -3]
|
|
num_inputs = VMobject(*map(TexMobject, map(str, nums)))
|
|
num_outputs = VMobject(*[
|
|
TexMobject(str(num**2))
|
|
for num in nums
|
|
])
|
|
for mob in num_inputs, num_outputs:
|
|
mob.arrange_submobjects(DOWN, buff = 1)
|
|
num_inputs.next_to(f_of_x, LEFT, buff = 1)
|
|
num_outputs.next_to(f_of_x, RIGHT, buff = 1)
|
|
f_point = VectorizedPoint(f_of_x.get_center())
|
|
|
|
input_vect = Matrix([5, 7])
|
|
input_vect.next_to(L_of_v, LEFT, buff = 1)
|
|
output_vect = Matrix([2, -3])
|
|
output_vect.next_to(L_of_v, RIGHT, buff = 1)
|
|
|
|
vector_input_words = TextMobject("Vector input")
|
|
vector_input_words.highlight(MAROON_C)
|
|
vector_input_words.next_to(input_vect, DOWN)
|
|
vector_output_words = TextMobject("Vector output")
|
|
vector_output_words.highlight(BLUE)
|
|
vector_output_words.next_to(output_vect, DOWN)
|
|
|
|
self.play(Write(f_of_x, run_time = 1))
|
|
self.play(Write(num_inputs, run_time = 2))
|
|
self.dither()
|
|
for mob in f_point, num_outputs:
|
|
self.play(Transform(
|
|
num_inputs, mob,
|
|
submobject_mode = "lagged_start"
|
|
))
|
|
self.dither()
|
|
|
|
self.play(
|
|
FadeOut(num_inputs),
|
|
Transform(f_of_x, L_of_v)
|
|
)
|
|
self.play(
|
|
Write(input_vect),
|
|
Write(vector_input_words)
|
|
)
|
|
self.dither()
|
|
for mob in f_point, output_vect:
|
|
self.play(Transform(
|
|
input_vect, mob,
|
|
submobject_mode = "lagged_start"
|
|
))
|
|
self.play(Write(vector_output_words))
|
|
self.dither()
|
|
|
|
class WhyConfuseWithTerminology(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.setup()
|
|
self.student_says("Why confuse us with \\\\ redundant terminology?")
|
|
other_students = [self.get_students()[i] for i in 0, 2]
|
|
self.play(*[
|
|
ApplyMethod(student.change_mode, "confused")
|
|
for student in other_students
|
|
])
|
|
self.random_blink()
|
|
self.dither()
|
|
statement = TextMobject([
|
|
"The word",
|
|
"``transformation''",
|
|
"suggests \\\\ that you think using",
|
|
"movement",
|
|
])
|
|
statement.split()[1].highlight(BLUE)
|
|
statement.split()[-1].highlight(YELLOW)
|
|
self.teacher_says(statement, width = 10)
|
|
self.play(*[
|
|
ApplyMethod(student.change_mode, "happy")
|
|
for student in other_students
|
|
])
|
|
self.random_blink()
|
|
self.dither()
|
|
|
|
class ThinkinfOfFunctionsAsGraphs(VectorScene):
|
|
def construct(self):
|
|
axes = self.add_axes()
|
|
graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2)
|
|
name = TexMobject("f(x) = x^2")
|
|
name.next_to(graph, RIGHT).to_edge(UP)
|
|
point = Dot(graph.point_from_proportion(0.8))
|
|
point_label = TexMobject("(2, f(2))")
|
|
point_label.next_to(point.get_center(), DOWN+RIGHT, buff = 0.1)
|
|
|
|
self.play(ShowCreation(graph))
|
|
self.play(Write(name, run_time = 1))
|
|
self.play(
|
|
ShowCreation(point),
|
|
Write(point_label),
|
|
run_time = 1
|
|
)
|
|
self.dither()
|
|
|
|
def collapse_func(p):
|
|
return np.dot(p, [RIGHT, RIGHT, OUT]) + (SPACE_HEIGHT+1)*DOWN
|
|
self.play(
|
|
ApplyPointwiseFunction(collapse_func, axes),
|
|
ApplyPointwiseFunction(collapse_func, graph),
|
|
ApplyMethod(point.shift, 10*DOWN),
|
|
ApplyMethod(point_label.shift, 10*DOWN),
|
|
ApplyPointwiseFunction(collapse_func, name),
|
|
run_time = 2
|
|
)
|
|
self.clear()
|
|
words = TextMobject(["Instead think about", "\\emph{movement}"])
|
|
words.split()[-1].highlight(YELLOW)
|
|
self.play(Write(words))
|
|
self.dither()
|
|
|
|
class TransformJustOneVector(VectorScene):
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
v1_coords = [-3, 1]
|
|
t_matrix = [[0, -1], [2, -1]]
|
|
v1 = Vector(v1_coords)
|
|
v2 = Vector(
|
|
np.dot(np.array(t_matrix).transpose(), v1_coords),
|
|
color = PINK
|
|
)
|
|
for v, word in (v1, "Input"), (v2, "Output"):
|
|
v.label = TextMobject("%s vector"%word)
|
|
v.label.next_to(v.get_end(), UP)
|
|
v.label.highlight(v.get_color())
|
|
self.play(ShowCreation(v))
|
|
self.play(Write(v.label))
|
|
self.dither()
|
|
self.remove(v2)
|
|
self.play(
|
|
Transform(
|
|
v1.copy(), v2,
|
|
path_arc = -np.pi/2, run_time = 3
|
|
),
|
|
ApplyMethod(v1.fade)
|
|
)
|
|
self.dither()
|
|
|
|
class TransformManyVectors(LinearTransformationScene):
|
|
CONFIG = {
|
|
"transposed_matrix" : [[2, 1], [1, 2]],
|
|
"use_dots" : False,
|
|
}
|
|
def construct(self):
|
|
self.lock_in_faded_grid()
|
|
vectors = VMobject(*[
|
|
Vector([x, y])
|
|
for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5)
|
|
for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5)
|
|
])
|
|
vectors.submobject_gradient_highlight(PINK, YELLOW)
|
|
t_matrix = self.transposed_matrix
|
|
transformed_vectors = VMobject(*[
|
|
Vector(
|
|
np.dot(np.array(t_matrix).transpose(), v.get_end()[:2]),
|
|
color = v.get_color()
|
|
)
|
|
for v in vectors.split()
|
|
])
|
|
|
|
self.play(ShowCreation(vectors, submobject_mode = "lagged_start"))
|
|
self.dither()
|
|
if self.use_dots:
|
|
self.play(Transform(
|
|
vectors, self.vectors_to_dots(vectors),
|
|
run_time = 3,
|
|
submobject_mode = "lagged_start"
|
|
))
|
|
transformed_vectors = self.vectors_to_dots(transformed_vectors)
|
|
self.dither()
|
|
self.play(Transform(
|
|
vectors, transformed_vectors,
|
|
run_time = 3,
|
|
path_arc = -np.pi/2
|
|
))
|
|
self.dither()
|
|
if self.use_dots:
|
|
self.play(Transform(
|
|
vectors, self.dots_to_vectors(vectors),
|
|
run_time = 2,
|
|
submobject_mode = "lagged_start"
|
|
))
|
|
self.dither()
|
|
|
|
def vectors_to_dots(self, vectors):
|
|
return VMobject(*[
|
|
Dot(v.get_end(), color = v.get_color())
|
|
for v in vectors.split()
|
|
])
|
|
|
|
def dots_to_vectors(self, dots):
|
|
return VMobject(*[
|
|
Vector(dot.get_center(), color = dot.get_color())
|
|
for dot in dots.split()
|
|
])
|
|
|
|
class TransformManyVectorsAsPoints(TransformManyVectors):
|
|
CONFIG = {
|
|
"use_dots" : True
|
|
}
|
|
|
|
class TransformInfiniteGrid(LinearTransformationScene):
|
|
CONFIG = {
|
|
"include_background_plane" : False,
|
|
"foreground_plane_kwargs" : {
|
|
"x_radius" : 2*SPACE_WIDTH,
|
|
"y_radius" : 2*SPACE_HEIGHT,
|
|
},
|
|
"show_basis_vectors" : False
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.play(ShowCreation(
|
|
self.plane, run_time = 3, submobject_mode = "lagged_start"
|
|
))
|
|
self.dither()
|
|
self.apply_transposed_matrix([[2, 1], [1, 2]])
|
|
self.dither()
|
|
|
|
class TransformInfiniteGridWithBackground(TransformInfiniteGrid):
|
|
CONFIG = {
|
|
"include_background_plane" : True,
|
|
"foreground_plane_kwargs" : {
|
|
"x_radius" : 2*SPACE_WIDTH,
|
|
"y_radius" : 2*SPACE_HEIGHT,
|
|
"secondary_line_ratio" : 0
|
|
},
|
|
|
|
}
|
|
|
|
class ApplyComplexFunction(LinearTransformationScene):
|
|
CONFIG = {
|
|
"function" : lambda z : 0.5*z**2,
|
|
"show_basis_vectors" : False,
|
|
"foreground_plane_kwargs" : {
|
|
"x_radius" : SPACE_WIDTH,
|
|
"y_radius" : SPACE_HEIGHT,
|
|
"secondary_line_ratio" : 0
|
|
},
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.plane.prepare_for_nonlinear_transform(100)
|
|
self.dither()
|
|
self.play(ApplyMethod(
|
|
self.plane.apply_complex_function, self.function,
|
|
run_time = 5,
|
|
path_arc = np.pi/2
|
|
))
|
|
self.dither()
|
|
|
|
class ExponentialTransformation(ApplyComplexFunction):
|
|
CONFIG = {
|
|
"function" : np.exp,
|
|
}
|
|
|
|
class CrazyTransformation(ApplyComplexFunction):
|
|
CONFIG = {
|
|
"function" : lambda z : np.sin(z)**2 + np.sinh(z)
|
|
}
|
|
|
|
class LookToWordLinear(Scene):
|
|
def construct(self):
|
|
title = TextMobject(["Linear ", "transformations"])
|
|
title.to_edge(UP)
|
|
faded_title = title.copy().fade()
|
|
linear, transformation = title.split()
|
|
faded_linear, faded_transformation = faded_title.split()
|
|
linear_brace = Brace(linear, DOWN)
|
|
transformation_brace = Brace(transformation, DOWN)
|
|
function = TextMobject("function")
|
|
function.highlight(YELLOW)
|
|
function.next_to(transformation_brace, DOWN)
|
|
new_sub_word = TextMobject("What does this mean?")
|
|
new_sub_word.highlight(BLUE)
|
|
new_sub_word.next_to(linear_brace, DOWN)
|
|
|
|
self.add(
|
|
faded_linear, transformation,
|
|
transformation_brace, function
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
Transform(faded_linear, linear),
|
|
Transform(transformation, faded_transformation),
|
|
Transform(transformation_brace, linear_brace),
|
|
Transform(function, new_sub_word),
|
|
submobject_mode = "lagged_start"
|
|
)
|
|
self.dither()
|
|
|
|
class IntroduceLinearTransformations(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.dither()
|
|
self.apply_transposed_matrix([[2, 1], [1, 2]])
|
|
self.dither()
|
|
|
|
lines_rule = TextMobject("Lines remain lines")
|
|
lines_rule.shift(2*UP).to_edge(LEFT)
|
|
origin_rule = TextMobject("Origin remains fixed")
|
|
origin_rule.shift(2*UP).to_edge(RIGHT)
|
|
arrow = Arrow(origin_rule, ORIGIN)
|
|
dot = Dot(ORIGIN, radius = 0.1, color = RED)
|
|
|
|
for rule in lines_rule, origin_rule:
|
|
rule.add_background_rectangle()
|
|
|
|
self.play(
|
|
Write(lines_rule, run_time = 2),
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
Write(origin_rule, run_time = 2),
|
|
ShowCreation(arrow),
|
|
GrowFromCenter(dot)
|
|
)
|
|
self.dither()
|
|
|
|
class SimpleLinearTransformationScene(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"transposed_matrix" : [[2, 1], [1, 2]]
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.dither()
|
|
self.apply_transposed_matrix(self.transposed_matrix)
|
|
self.dither()
|
|
|
|
class SimpleNonlinearTransformationScene(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"words" : "Not linear: some lines get curved"
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.dither()
|
|
self.apply_nonlinear_transformation(self.func)
|
|
words = TextMobject(self.words)
|
|
words.to_corner(UP+RIGHT)
|
|
words.highlight(RED)
|
|
words.add_background_rectangle()
|
|
self.play(Write(words))
|
|
self.dither()
|
|
|
|
def func(self, point):
|
|
return curvy_squish(point)
|
|
|
|
class MovingOrigin(SimpleNonlinearTransformationScene):
|
|
CONFIG = {
|
|
"words" : "Not linear: Origin moves"
|
|
}
|
|
def setup(self):
|
|
LinearTransformationScene.setup(self)
|
|
dot = Dot(ORIGIN, color = RED)
|
|
self.add_transformable_mobject(dot)
|
|
|
|
def func(self, point):
|
|
matrix_transform = self.get_matrix_transformation([[2, 0], [1, 1]])
|
|
return matrix_transform(point) + 2*UP+3*LEFT
|
|
|
|
class SneakyNonlinearTransformation(SimpleNonlinearTransformationScene):
|
|
CONFIG = {
|
|
"words" : "\\dots"
|
|
}
|
|
def func(self, point):
|
|
x, y, z = point
|
|
new_x = np.sign(x)*SPACE_WIDTH*smooth(abs(x) / SPACE_WIDTH)
|
|
new_y = np.sign(y)*SPACE_HEIGHT*smooth(abs(y) / SPACE_HEIGHT)
|
|
return [new_x, new_y, 0]
|
|
|
|
class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation):
|
|
CONFIG = {
|
|
"words" : "Not linear: diagonal lines get curved"
|
|
}
|
|
def setup(self):
|
|
LinearTransformationScene.setup(self)
|
|
diag = Line(
|
|
SPACE_HEIGHT*LEFT+SPACE_HEIGHT*DOWN,
|
|
SPACE_HEIGHT*RIGHT + SPACE_HEIGHT*UP
|
|
)
|
|
diag.insert_n_anchor_points(20)
|
|
diag.change_anchor_mode("smooth")
|
|
diag.highlight(YELLOW)
|
|
self.play(ShowCreation(diag))
|
|
self.add_transformable_mobject(diag)
|
|
|
|
class GridLinesRemainParallel(SimpleLinearTransformationScene):
|
|
CONFIG = {
|
|
"transposed_matrix" : [
|
|
[3, 0],
|
|
[1, 2]
|
|
]
|
|
}
|
|
def construct(self):
|
|
SimpleLinearTransformationScene.construct(self)
|
|
text = TextMobject([
|
|
"Grid lines remain",
|
|
"parallel",
|
|
"and",
|
|
"evenly spaced",
|
|
])
|
|
glr, p, a, es = text.split()
|
|
p.highlight(YELLOW)
|
|
es.highlight(GREEN)
|
|
text.add_background_rectangle()
|
|
text.shift(-text.get_bottom())
|
|
self.play(Write(text))
|
|
self.dither()
|
|
|
|
class Rotation(SimpleLinearTransformationScene):
|
|
CONFIG = {
|
|
"angle" : np.pi/3,
|
|
}
|
|
def construct(self):
|
|
self.transposed_matrix = [
|
|
[np.cos(self.angle), np.sin(self.angle)],
|
|
[-np.sin(self.angle), np.cos(self.angle)]
|
|
]
|
|
SimpleLinearTransformationScene.construct(self)
|
|
|
|
class YetAnotherLinearTransformation(SimpleLinearTransformationScene):
|
|
CONFIG = {
|
|
"transposed_matrix" : [
|
|
[-1, 1],
|
|
[3, 2],
|
|
]
|
|
}
|
|
def construct(self):
|
|
SimpleLinearTransformationScene.construct(self)
|
|
words = TextMobject("""
|
|
How would you describe
|
|
one of these numerically?
|
|
"""
|
|
)
|
|
words.add_background_rectangle()
|
|
words.to_edge(UP)
|
|
words.highlight(GREEN)
|
|
formula = TexMobject([
|
|
matrix_to_tex_string(["x_\\text{in}", "y_\\text{in}"]),
|
|
"\\rightarrow ???? \\rightarrow",
|
|
matrix_to_tex_string(["x_\\text{out}", "y_{\\text{out}}"])
|
|
])
|
|
formula.add_background_rectangle()
|
|
|
|
self.play(Write(words))
|
|
self.dither()
|
|
self.play(
|
|
ApplyMethod(self.plane.fade, 0.7),
|
|
ApplyMethod(self.background_plane.fade, 0.7),
|
|
Write(formula, run_time = 2),
|
|
Animation(words)
|
|
)
|
|
self.dither()
|
|
|
|
class FollowIHatJHat(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
i_hat = self.add_vector([1, 0], color = X_COLOR)
|
|
i_label = self.label_vector(
|
|
i_hat, "\\hat{\\imath}",
|
|
color = X_COLOR,
|
|
label_scale_val = 1
|
|
)
|
|
j_hat = self.add_vector([0, 1], color = Y_COLOR)
|
|
j_label = self.label_vector(
|
|
j_hat, "\\hat{\\jmath}",
|
|
color = Y_COLOR,
|
|
label_scale_val = 1
|
|
)
|
|
|
|
self.dither()
|
|
self.play(*map(FadeOut, [i_label, j_label]))
|
|
self.apply_transposed_matrix([[-1, 1], [-2, -1]])
|
|
self.dither()
|
|
|
|
class TrackBasisVectorsExample(LinearTransformationScene):
|
|
CONFIG = {
|
|
"transposed_matrix" : [[1, -2], [3, 0]],
|
|
"v_coords" : [-1, 2],
|
|
"v_coord_strings" : ["-1", "2"],
|
|
"result_coords_string" : """
|
|
=
|
|
\\left[ \\begin{array}{c}
|
|
-1(1) + 2(3) \\\\
|
|
-1(-2) + 2(0)
|
|
\\end{arary}\\right]
|
|
=
|
|
\\left[ \\begin{array}{c}
|
|
5 \\\\
|
|
2
|
|
\\end{arary}\\right]
|
|
"""
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.label_bases()
|
|
self.introduce_vector()
|
|
self.dither()
|
|
self.apply_transposed_matrix(self.transposed_matrix)
|
|
self.dither()
|
|
self.show_linear_combination(clean_up = False)
|
|
self.write_linear_map_rule()
|
|
self.show_basis_vector_coords()
|
|
|
|
def label_bases(self):
|
|
triplets = [
|
|
(self.i_hat, "\\hat{\\imath}", X_COLOR),
|
|
(self.j_hat, "\\hat{\\jmath}", Y_COLOR),
|
|
]
|
|
label_mobs = []
|
|
for vect, label, color in triplets:
|
|
label_mobs.append(self.add_transformable_label(
|
|
vect, label, "\\text{Transformed } " + label,
|
|
color = color,
|
|
direction = "right",
|
|
))
|
|
self.i_label, self.j_label = label_mobs
|
|
|
|
def introduce_vector(self):
|
|
v = self.add_vector(self.v_coords)
|
|
coords = Matrix(self.v_coords)
|
|
coords.scale(VECTOR_LABEL_SCALE_VAL)
|
|
coords.next_to(v.get_end(), np.sign(self.v_coords[0])*RIGHT)
|
|
|
|
self.play(Write(coords, run_time = 1))
|
|
v_def = self.get_v_definition()
|
|
pre_def = VMobject(
|
|
VectorizedPoint(coords.get_center()),
|
|
VMobject(*[
|
|
mob.copy()
|
|
for mob in coords.get_mob_matrix().flatten()
|
|
])
|
|
)
|
|
self.play(Transform(
|
|
pre_def, v_def,
|
|
run_time = 2,
|
|
submobject_mode = "all_at_once"
|
|
))
|
|
self.remove(pre_def)
|
|
self.add_foreground_mobject(v_def)
|
|
self.dither()
|
|
self.show_linear_combination()
|
|
self.remove(coords)
|
|
|
|
def show_linear_combination(self, clean_up = True):
|
|
i_hat_copy, j_hat_copy = [m.copy() for m in self.i_hat, self.j_hat]
|
|
self.play(ApplyFunction(
|
|
lambda m : m.scale(self.v_coords[0]).fade(0.3),
|
|
i_hat_copy
|
|
))
|
|
self.play(ApplyFunction(
|
|
lambda m : m.scale(self.v_coords[1]).fade(0.3),
|
|
j_hat_copy
|
|
))
|
|
self.play(ApplyMethod(j_hat_copy.shift, i_hat_copy.get_end()))
|
|
self.dither(2)
|
|
if clean_up:
|
|
self.play(FadeOut(i_hat_copy), FadeOut(j_hat_copy))
|
|
|
|
|
|
def get_v_definition(self):
|
|
v_def = TexMobject([
|
|
"\\vec{\\textbf{v}}",
|
|
" = %s"%self.v_coord_strings[0],
|
|
"\\hat{\\imath}",
|
|
"+%s"%self.v_coord_strings[1],
|
|
"\\hat{\\jmath}",
|
|
])
|
|
v, equals_neg_1, i_hat, plus_2, j_hat = v_def.split()
|
|
v.highlight(YELLOW)
|
|
i_hat.highlight(X_COLOR)
|
|
j_hat.highlight(Y_COLOR)
|
|
v_def.add_background_rectangle()
|
|
v_def.to_corner(UP + LEFT)
|
|
self.v_def = v_def
|
|
return v_def
|
|
|
|
def write_linear_map_rule(self):
|
|
rule = TexMobject([
|
|
"\\text{Transformed } \\vec{\\textbf{v}}",
|
|
" = %s"%self.v_coord_strings[0],
|
|
"(\\text{Transformed }\\hat{\\imath})",
|
|
"+%s"%self.v_coord_strings[1],
|
|
"(\\text{Transformed } \\hat{\\jmath})",
|
|
])
|
|
v, equals_neg_1, i_hat, plus_2, j_hat = rule.split()
|
|
v.highlight(YELLOW)
|
|
i_hat.highlight(X_COLOR)
|
|
j_hat.highlight(Y_COLOR)
|
|
rule.scale(0.85)
|
|
rule.next_to(self.v_def, DOWN, buff = 0.2)
|
|
rule.to_edge(LEFT)
|
|
rule.add_background_rectangle()
|
|
|
|
self.play(Write(rule, run_time = 2))
|
|
self.dither()
|
|
self.linear_map_rule = rule
|
|
|
|
|
|
def show_basis_vector_coords(self):
|
|
i_coords = matrix_to_mobject(self.transposed_matrix[0])
|
|
j_coords = matrix_to_mobject(self.transposed_matrix[1])
|
|
i_coords.highlight(X_COLOR)
|
|
j_coords.highlight(Y_COLOR)
|
|
for coords in i_coords, j_coords:
|
|
coords.add_background_rectangle()
|
|
coords.scale(0.7)
|
|
i_coords.next_to(self.i_hat.get_end(), RIGHT)
|
|
j_coords.next_to(self.j_hat.get_end(), RIGHT)
|
|
|
|
calculation = TexMobject([
|
|
" = %s"%self.v_coord_strings[0],
|
|
matrix_to_tex_string(self.transposed_matrix[0]),
|
|
"+%s"%self.v_coord_strings[1],
|
|
matrix_to_tex_string(self.transposed_matrix[1]),
|
|
])
|
|
equals_neg_1, i_hat, plus_2, j_hat = calculation.split()
|
|
i_hat.highlight(X_COLOR)
|
|
j_hat.highlight(Y_COLOR)
|
|
calculation.scale(0.8)
|
|
calculation.next_to(self.linear_map_rule, DOWN)
|
|
calculation.to_edge(LEFT)
|
|
calculation.add_background_rectangle()
|
|
|
|
result = TexMobject(self.result_coords_string)
|
|
result.scale(0.8)
|
|
result.add_background_rectangle()
|
|
result.next_to(calculation, DOWN)
|
|
result.to_edge(LEFT)
|
|
|
|
self.play(Write(i_coords, run_time = 1))
|
|
self.dither()
|
|
self.play(Write(j_coords, run_time = 1))
|
|
self.dither()
|
|
self.play(Write(calculation))
|
|
self.dither()
|
|
self.play(Write(result))
|
|
self.dither()
|
|
|
|
class WatchManyVectorsMove(TransformManyVectors):
|
|
def construct(self):
|
|
self.setup()
|
|
vectors = VMobject(*[
|
|
Vector([x, y])
|
|
for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5)
|
|
for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5)
|
|
])
|
|
vectors.submobject_gradient_highlight(PINK, YELLOW)
|
|
dots = self.vectors_to_dots(vectors)
|
|
self.play(ShowCreation(dots, submobject_mode = "lagged_start"))
|
|
self.play(Transform(
|
|
dots, vectors,
|
|
submobject_mode = "lagged_start",
|
|
run_time = 2
|
|
))
|
|
self.remove(dots)
|
|
for v in vectors.split():
|
|
self.add_vector(v, animate = False)
|
|
self.apply_transposed_matrix([[1, -2], [3, 0]])
|
|
self.dither()
|
|
self.play(
|
|
ApplyMethod(self.plane.fade),
|
|
FadeOut(vectors),
|
|
Animation(self.i_hat),
|
|
Animation(self.j_hat),
|
|
)
|
|
self.dither()
|
|
|
|
class NowWithoutWatching(Scene):
|
|
def construct(self):
|
|
text = TextMobject("Now without watching...")
|
|
text.to_edge(UP)
|
|
randy = Randolph(mode = "pondering")
|
|
self.add(randy)
|
|
self.play(Write(text, run_time = 1))
|
|
self.play(ApplyMethod(randy.blink))
|
|
self.dither(2)
|
|
|
|
class DeduceResultWithGeneralCoordinates(Scene):
|
|
def construct(self):
|
|
i_hat_to = TexMobject("\\hat{\\imath} \\rightarrow")
|
|
j_hat_to = TexMobject("\\hat{\\jmath} \\rightarrow")
|
|
i_coords = Matrix([1, -2])
|
|
j_coords = Matrix([3, 0])
|
|
i_coords.next_to(i_hat_to, RIGHT, buff = 0.1)
|
|
j_coords.next_to(j_hat_to, RIGHT, buff = 0.1)
|
|
i_group = VMobject(i_hat_to, i_coords)
|
|
j_group = VMobject(j_hat_to, j_coords)
|
|
i_group.highlight(X_COLOR)
|
|
j_group.highlight(Y_COLOR)
|
|
i_group.next_to(ORIGIN, LEFT, buff = 1).to_edge(UP)
|
|
j_group.next_to(ORIGIN, RIGHT, buff = 1).to_edge(UP)
|
|
|
|
vect = Matrix(["x", "y"])
|
|
x, y = vect.get_mob_matrix().flatten()
|
|
VMobject(x, y).highlight(YELLOW)
|
|
rto = TexMobject("\\rightarrow")
|
|
equals = TexMobject("=")
|
|
plus = TexMobject("+")
|
|
row1 = TexMobject("1x + 3y")
|
|
row2 = TexMobject("-2x + 0y")
|
|
VMobject(
|
|
row1.split()[0], row2.split()[0], row2.split()[1]
|
|
).highlight(X_COLOR)
|
|
VMobject(
|
|
row1.split()[1], row1.split()[4], row2.split()[2], row2.split()[5]
|
|
).highlight(YELLOW)
|
|
VMobject(
|
|
row1.split()[3], row2.split()[4]
|
|
).highlight(Y_COLOR)
|
|
result = Matrix([row1, row2])
|
|
result.show()
|
|
vect_group = VMobject(
|
|
vect, rto,
|
|
x.copy(), i_coords.copy(), plus,
|
|
y.copy(), j_coords.copy(), equals,
|
|
result
|
|
)
|
|
vect_group.arrange_submobjects(RIGHT, buff = 0.1)
|
|
|
|
self.add(i_group, j_group)
|
|
for mob in vect_group.split():
|
|
self.play(Write(mob))
|
|
self.dither()
|
|
|
|
class MatrixVectorMultiplication(LinearTransformationScene):
|
|
CONFIG = {
|
|
"abstract" : False
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
matrix = self.build_to_matrix()
|
|
self.label_matrix(matrix)
|
|
vector, formula = self.multiply_by_vector(matrix)
|
|
self.reposition_matrix_and_vector(matrix, vector, formula)
|
|
|
|
def build_to_matrix(self):
|
|
self.dither()
|
|
self.apply_transposed_matrix([[3, -2], [2, 1]])
|
|
self.dither()
|
|
i_coords = vector_coordinate_label(self.i_hat)
|
|
j_coords = vector_coordinate_label(self.j_hat)
|
|
if self.abstract:
|
|
new_i_coords = Matrix(["a", "c"])
|
|
new_j_coords = Matrix(["b", "d"])
|
|
new_i_coords.move_to(i_coords)
|
|
new_j_coords.move_to(j_coords)
|
|
i_coords = new_i_coords
|
|
j_coords = new_j_coords
|
|
i_coords.highlight(X_COLOR)
|
|
j_coords.highlight(Y_COLOR)
|
|
i_brackets = i_coords.get_brackets()
|
|
j_brackets = j_coords.get_brackets()
|
|
for coords in i_coords, j_coords:
|
|
rect = BackgroundRectangle(coords)
|
|
coords.rect = rect
|
|
|
|
abstract_matrix = np.append(
|
|
i_coords.get_mob_matrix(),
|
|
j_coords.get_mob_matrix(),
|
|
axis = 1
|
|
)
|
|
concrete_matrix = Matrix(
|
|
copy.deepcopy(abstract_matrix),
|
|
add_background_rectangles = True
|
|
)
|
|
concrete_matrix.to_edge(UP)
|
|
if self.abstract:
|
|
m = concrete_matrix.get_mob_matrix()[1, 0]
|
|
m.shift(m.get_height()*DOWN/2)
|
|
matrix_brackets = concrete_matrix.get_brackets()
|
|
|
|
self.play(ShowCreation(i_coords.rect), Write(i_coords))
|
|
self.play(ShowCreation(j_coords.rect), Write(j_coords))
|
|
self.dither()
|
|
self.remove(i_coords.rect, j_coords.rect)
|
|
self.play(
|
|
Transform(
|
|
VMobject(*abstract_matrix.flatten()),
|
|
VMobject(*concrete_matrix.get_mob_matrix().flatten()),
|
|
),
|
|
Transform(i_brackets, matrix_brackets),
|
|
Transform(j_brackets, matrix_brackets),
|
|
run_time = 2,
|
|
submobject_mode = "all_at_once"
|
|
)
|
|
everything = VMobject(*self.get_mobjects())
|
|
self.play(
|
|
FadeOut(everything),
|
|
Animation(concrete_matrix)
|
|
)
|
|
return concrete_matrix
|
|
|
|
def label_matrix(self, matrix):
|
|
title = TextMobject("``2x2 Matrix''")
|
|
title.to_edge(UP+LEFT)
|
|
col_circles = []
|
|
for i, color in enumerate([X_COLOR, Y_COLOR]):
|
|
col = VMobject(*matrix.get_mob_matrix()[:,i])
|
|
col_circle = Circle(color = color)
|
|
col_circle.stretch_to_fit_width(matrix.get_width()/3)
|
|
col_circle.stretch_to_fit_height(matrix.get_height())
|
|
col_circle.move_to(col)
|
|
col_circles.append(col_circle)
|
|
i_circle, j_circle = col_circles
|
|
i_message = TextMobject("Where $\\hat{\\imath}$ lands")
|
|
j_message = TextMobject("Where $\\hat{\\jmath}$ lands")
|
|
i_message.highlight(X_COLOR)
|
|
j_message.highlight(Y_COLOR)
|
|
i_message.next_to(i_circle, DOWN, buff = 2, aligned_edge = RIGHT)
|
|
j_message.next_to(j_circle, DOWN, buff = 2, aligned_edge = LEFT)
|
|
i_arrow = Arrow(i_message, i_circle)
|
|
j_arrow = Arrow(j_message, j_circle)
|
|
|
|
self.play(Write(title))
|
|
self.dither()
|
|
self.play(ShowCreation(i_circle))
|
|
self.play(
|
|
Write(i_message, run_time = 1.5),
|
|
ShowCreation(i_arrow),
|
|
)
|
|
self.dither()
|
|
self.play(ShowCreation(j_circle))
|
|
self.play(
|
|
Write(j_message, run_time = 1.5),
|
|
ShowCreation(j_arrow)
|
|
)
|
|
self.dither()
|
|
self.play(*map(FadeOut, [
|
|
i_message, i_circle, i_arrow, j_message, j_circle, j_arrow
|
|
]))
|
|
|
|
|
|
def multiply_by_vector(self, matrix):
|
|
vector = Matrix(["x", "y"]) if self.abstract else Matrix([5, 7])
|
|
vector.scale_to_fit_height(matrix.get_height())
|
|
vector.next_to(matrix, buff = 2)
|
|
brace = Brace(vector, DOWN)
|
|
words = TextMobject("Any ol' vector")
|
|
words.next_to(brace, DOWN)
|
|
|
|
self.play(
|
|
Write(vector),
|
|
GrowFromCenter(brace),
|
|
Write(words),
|
|
run_time = 1
|
|
)
|
|
self.dither()
|
|
|
|
v1, v2 = vector.get_mob_matrix().flatten()
|
|
mob_matrix = matrix.copy().get_mob_matrix()
|
|
col1 = Matrix(mob_matrix[:,0])
|
|
col2 = Matrix(mob_matrix[:,1])
|
|
formula = VMobject(
|
|
v1.copy(), col1, TexMobject("+"), v2.copy(), col2
|
|
)
|
|
formula.arrange_submobjects(RIGHT, buff = 0.1)
|
|
formula.center()
|
|
formula_start = VMobject(
|
|
v1.copy(),
|
|
VMobject(*matrix.copy().get_mob_matrix()[:,0]),
|
|
VectorizedPoint(),
|
|
v2.copy(),
|
|
VMobject(*matrix.copy().get_mob_matrix()[:,1]),
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(brace),
|
|
FadeOut(words),
|
|
Transform(
|
|
formula_start, formula,
|
|
run_time = 2,
|
|
submobject_mode = "all_at_once"
|
|
)
|
|
)
|
|
self.dither()
|
|
self.show_result(formula)
|
|
return vector, formula
|
|
|
|
def show_result(self, formula):
|
|
if self.abstract:
|
|
row1 = ["a", "x", "+", "b", "y"]
|
|
row2 = ["c", "x", "+", "d", "y"]
|
|
else:
|
|
row1 = ["3", "(5)", "+", "2", "(7)"]
|
|
row2 = ["-2", "(5)", "+", "1", "(7)"]
|
|
row1 = VMobject(*map(TexMobject, row1))
|
|
row2 = VMobject(*map(TexMobject, row2))
|
|
for row in row1, row2:
|
|
row.arrange_submobjects(RIGHT, buff = 0.1)
|
|
final_sum = Matrix([row1, row2])
|
|
row1, row2 = final_sum.get_mob_matrix().flatten()
|
|
row1.split()[0].highlight(X_COLOR)
|
|
row2.split()[0].highlight(X_COLOR)
|
|
row1.split()[3].highlight(Y_COLOR)
|
|
row2.split()[3].highlight(Y_COLOR)
|
|
equals = TexMobject("=")
|
|
equals.next_to(formula, RIGHT)
|
|
final_sum.next_to(equals, RIGHT)
|
|
|
|
self.play(
|
|
Write(equals, run_time = 1),
|
|
Write(final_sum)
|
|
)
|
|
self.dither()
|
|
|
|
|
|
def reposition_matrix_and_vector(self, matrix, vector, formula):
|
|
start_state = VMobject(matrix, vector)
|
|
end_state = start_state.copy()
|
|
end_state.arrange_submobjects(RIGHT, buff = 0.1)
|
|
equals = TexMobject("=")
|
|
equals.next_to(formula, LEFT)
|
|
end_state.next_to(equals, LEFT)
|
|
brace = Brace(formula, DOWN)
|
|
brace_words = TextMobject("Where all the intuition is")
|
|
brace_words.next_to(brace, DOWN)
|
|
brace_words.highlight(YELLOW)
|
|
|
|
self.play(
|
|
Transform(
|
|
start_state, end_state,
|
|
submobject_mode = "all_at_once"
|
|
),
|
|
Write(equals, run_time = 1)
|
|
)
|
|
self.dither()
|
|
self.play(
|
|
FadeIn(brace),
|
|
FadeIn(brace_words),
|
|
submobject_mode = "lagged_start"
|
|
)
|
|
self.dither()
|
|
|
|
class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication):
|
|
CONFIG = {
|
|
"abstract" : True,
|
|
}
|
|
|
|
class ColumnsToBasisVectors(LinearTransformationScene):
|
|
def construct(self):
|
|
self.setup()
|
|
transposed_matrix = [[3, 1], [1, 2]]
|
|
vector_coords = [-1, 2]
|
|
|
|
vector = self.move_matrix_columns(transposed_matrix, vector_coords)
|
|
self.scale_and_add(vector, vector_coords)
|
|
self.dither(3)
|
|
|
|
def move_matrix_columns(self, transposed_matrix, vector_coords = None):
|
|
matrix = np.array(transposed_matrix).transpose()
|
|
matrix_mob = Matrix(matrix)
|
|
matrix_mob.to_corner(UP+LEFT)
|
|
col1 = VMobject(*matrix_mob.get_mob_matrix()[:,0])
|
|
col1.highlight(X_COLOR)
|
|
col2 = VMobject(*matrix_mob.get_mob_matrix()[:,1])
|
|
col2.highlight(Y_COLOR)
|
|
matrix_brackets = matrix_mob.get_brackets()
|
|
matrix_background = BackgroundRectangle(matrix_mob)
|
|
self.add_foreground_mobject(matrix_background, matrix_mob)
|
|
|
|
if vector_coords is not None:
|
|
vector = Matrix(vector_coords)
|
|
VMobject(*vector.get_mob_matrix().flatten()).highlight(YELLOW)
|
|
vector.scale_to_fit_height(matrix_mob.get_height())
|
|
vector.next_to(matrix_mob, RIGHT)
|
|
vector_background = BackgroundRectangle(vector)
|
|
self.add_foreground_mobject(vector_background, vector)
|
|
|
|
new_i = Vector(matrix[:,0])
|
|
new_j = Vector(matrix[:,1])
|
|
i_label = vector_coordinate_label(new_i).highlight(X_COLOR)
|
|
j_label = vector_coordinate_label(new_j).highlight(Y_COLOR)
|
|
i_coords = VMobject(*i_label.get_mob_matrix().flatten())
|
|
j_coords = VMobject(*j_label.get_mob_matrix().flatten())
|
|
i_brackets = i_label.get_brackets()
|
|
j_brackets = j_label.get_brackets()
|
|
i_label_background = BackgroundRectangle(i_label)
|
|
j_label_background = BackgroundRectangle(j_label)
|
|
i_coords_start = VMobject(
|
|
matrix_background.copy(),
|
|
col1.copy(),
|
|
matrix_brackets.copy()
|
|
)
|
|
i_coords_end = VMobject(
|
|
i_label_background,
|
|
i_coords,
|
|
i_brackets,
|
|
)
|
|
j_coords_start = VMobject(
|
|
matrix_background.copy(),
|
|
col2.copy(),
|
|
matrix_brackets.copy()
|
|
)
|
|
j_coords_end = VMobject(
|
|
j_label_background,
|
|
j_coords,
|
|
j_brackets,
|
|
)
|
|
|
|
transform_matrix1 = np.array(matrix)
|
|
transform_matrix1[:,1] = [0, 1]
|
|
transform_matrix2 = np.dot(
|
|
matrix,
|
|
np.linalg.inv(transform_matrix1)
|
|
)
|
|
|
|
self.dither()
|
|
self.apply_transposed_matrix(
|
|
transform_matrix1.transpose(),
|
|
added_anims = [Transform(i_coords_start, i_coords_end)],
|
|
path_arc = np.pi/2
|
|
)
|
|
self.add_foreground_mobject(i_coords_start)
|
|
self.apply_transposed_matrix(
|
|
transform_matrix2.transpose(),
|
|
added_anims = [Transform(j_coords_start, j_coords_end) ],
|
|
path_arc = np.pi/2
|
|
)
|
|
self.add_foreground_mobject(j_coords_start)
|
|
self.dither()
|
|
return vector if vector_coords is not None else None
|
|
|
|
|
|
def scale_and_add(self, vector, vector_coords):
|
|
i_copy = self.i_hat.copy()
|
|
j_copy = self.j_hat.copy()
|
|
i_target = i_copy.copy().scale(vector_coords[0]).fade(0.3)
|
|
j_target = j_copy.copy().scale(vector_coords[1]).fade(0.3)
|
|
|
|
coord1, coord2 = vector.copy().get_mob_matrix().flatten()
|
|
coord1.add_background_rectangle()
|
|
coord2.add_background_rectangle()
|
|
|
|
self.play(
|
|
Transform(i_copy, i_target),
|
|
ApplyMethod(coord1.next_to, i_target.get_center(), DOWN)
|
|
)
|
|
self.play(
|
|
Transform(j_copy, j_target),
|
|
ApplyMethod(coord2.next_to, j_target.get_center(), LEFT)
|
|
)
|
|
j_copy.add(coord2)
|
|
self.play(ApplyMethod(j_copy.shift, i_copy.get_end()))
|
|
self.add_vector(j_copy.get_end())
|
|
self.dither()
|
|
|
|
class Describe90DegreeRotation(LinearTransformationScene):
|
|
CONFIG = {
|
|
"transposed_matrix" : [[0, 1], [-1, 0]],
|
|
"title" : "$90^\\circ$ rotation counterclockwise",
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
title = TextMobject(self.title)
|
|
title.shift(DOWN)
|
|
title.add_background_rectangle()
|
|
matrix = Matrix(np.array(self.transposed_matrix).transpose())
|
|
matrix.to_corner(UP+LEFT)
|
|
matrix_background = BackgroundRectangle(matrix)
|
|
col1 = VMobject(*matrix.get_mob_matrix()[:,0])
|
|
col2 = VMobject(*matrix.get_mob_matrix()[:,1])
|
|
col1.highlight(X_COLOR)
|
|
col2.highlight(Y_COLOR)
|
|
self.add_foreground_mobject(matrix_background, matrix.get_brackets())
|
|
|
|
self.dither()
|
|
self.apply_transposed_matrix(self.transposed_matrix)
|
|
self.dither()
|
|
self.play(Write(title))
|
|
self.add_foreground_mobject(title)
|
|
|
|
for vect, color, col in [(self.i_hat, X_COLOR, col1), (self.j_hat, Y_COLOR, col2)]:
|
|
label = vector_coordinate_label(vect)
|
|
label.highlight(color)
|
|
background = BackgroundRectangle(label)
|
|
coords = VMobject(*label.get_mob_matrix().flatten())
|
|
brackets = label.get_brackets()
|
|
|
|
self.play(ShowCreation(background), Write(label))
|
|
self.dither()
|
|
self.play(
|
|
ShowCreation(background, rate_func = lambda t : smooth(1-t)),
|
|
ApplyMethod(coords.replace, col),
|
|
FadeOut(brackets),
|
|
)
|
|
self.remove(label)
|
|
self.add_foreground_mobject(coords)
|
|
self.dither()
|
|
self.show_vector(matrix)
|
|
|
|
def show_vector(self, matrix):
|
|
vector = Matrix(["x", "y"])
|
|
VMobject(*vector.get_mob_matrix().flatten()).highlight(YELLOW)
|
|
vector.scale_to_fit_height(matrix.get_height())
|
|
vector.next_to(matrix, RIGHT)
|
|
v_background = BackgroundRectangle(vector)
|
|
|
|
matrix = np.array(self.transposed_matrix).transpose()
|
|
inv = np.linalg.inv(matrix)
|
|
self.apply_transposed_matrix(inv.transpose(), run_time = 0.5)
|
|
self.add_vector([1, 2])
|
|
self.dither()
|
|
self.apply_transposed_matrix(self.transposed_matrix)
|
|
self.play(ShowCreation(v_background), Write(vector))
|
|
self.dither()
|
|
|
|
class DescribeShear(Describe90DegreeRotation):
|
|
CONFIG = {
|
|
"transposed_matrix" : [[1, 0], [1, 1]],
|
|
"title" : "``Shear''",
|
|
}
|
|
|
|
class OtherWayAround(Scene):
|
|
def construct(self):
|
|
self.play(Write("What about the other way around?"))
|
|
self.dither(2)
|
|
|
|
class DeduceTransformationFromMatrix(ColumnsToBasisVectors):
|
|
def construct(self):
|
|
self.setup()
|
|
self.move_matrix_columns([[1, 2], [3, 1]])
|
|
|
|
class LinearlyDependentColumns(ColumnsToBasisVectors):
|
|
def construct(self):
|
|
self.setup()
|
|
title = TextMobject("Linearly dependent")
|
|
subtitle = TextMobject("columns")
|
|
title.add_background_rectangle()
|
|
subtitle.add_background_rectangle()
|
|
subtitle.next_to(title, DOWN)
|
|
title.add(subtitle)
|
|
title.shift(UP).to_edge(LEFT)
|
|
title.highlight(YELLOW)
|
|
self.add_foreground_mobject(title)
|
|
self.move_matrix_columns([[2, 1], [-2, -1]])
|
|
|
|
class NextVideo(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Next video: Matrix multiplication as composition")
|
|
title.to_edge(UP)
|
|
rect = Rectangle(width = 16, height = 9, color = BLUE)
|
|
rect.scale_to_fit_height(6)
|
|
rect.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.dither()
|
|
|
|
class FinalSlide(Scene):
|
|
def construct(self):
|
|
text = TextMobject("""
|
|
\\footnotesize
|
|
Technically, the definition of ``linear'' is as follows:
|
|
A transformation L is linear if it satisfies these
|
|
two properties:
|
|
|
|
\\begin{align*}
|
|
L(\\vec{\\textbf{v}} + \\vec{\\textbf{w}})
|
|
&= L(\\vec{\\textbf{v}}) + L(\\vec{\\textbf{w}})
|
|
& & \\text{``Additivity''} \\\\
|
|
L(c\\vec{\\textbf{v}}) &= c L(\\vec{\\textbf{v}})
|
|
& & \\text{``Scaling''}
|
|
\\end{align*}
|
|
|
|
I'll talk about these properties later on, but I'm a big
|
|
believer in first understanding things visually.
|
|
Once you do, it becomes much more intuitive why these
|
|
two properties make sense. So for now, you can
|
|
feel fine thinking of linear transformations as those
|
|
which keep grid lines parallel and evenly spaced
|
|
(and which fix the origin in place), since this visual
|
|
definition is actually equivalent to the two properties
|
|
above.
|
|
""", enforce_new_line_structure = False)
|
|
text.scale_to_fit_height(2*SPACE_HEIGHT - 2)
|
|
text.to_edge(UP)
|
|
self.add(text)
|
|
self.dither()
|
|
|
|
### Old scenes
|
|
|
|
class RotateIHat(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
i_hat, j_hat = self.get_basis_vectors()
|
|
i_label, j_label = self.get_basis_vector_labels()
|
|
self.add_vector(i_hat)
|
|
self.play(Write(i_label, run_time = 1))
|
|
self.dither()
|
|
self.play(FadeOut(i_label))
|
|
self.apply_transposed_matrix([[0, 1], [-1, 0]])
|
|
self.dither()
|
|
self.play(Write(j_label, run_time = 1))
|
|
self.dither()
|
|
|
|
class TransformationsAreFunctions(Scene):
|
|
def construct(self):
|
|
title = TextMobject([
|
|
"""Linear transformations are a
|
|
special kind of""",
|
|
"function"
|
|
])
|
|
title_start, function = title.split()
|
|
function.highlight(YELLOW)
|
|
title.to_edge(UP)
|
|
|
|
equation = TexMobject([
|
|
"L",
|
|
"(",
|
|
"\\vec{\\textbf{v}}",
|
|
") = ",
|
|
"\\vec{\\textbf{w}}",
|
|
])
|
|
L, lp, _input, equals, _output = equation.split()
|
|
L.highlight(YELLOW)
|
|
_input.highlight(MAROON_C)
|
|
_output.highlight(BLUE)
|
|
equation.scale(2)
|
|
equation.next_to(title, DOWN, buff = 1)
|
|
|
|
starting_vector = TextMobject("Starting vector")
|
|
starting_vector.shift(DOWN+3*LEFT)
|
|
starting_vector.highlight(MAROON_C)
|
|
ending_vector = TextMobject("The vector where it lands")
|
|
ending_vector.shift(DOWN).to_edge(RIGHT)
|
|
ending_vector.highlight(BLUE)
|
|
|
|
func_arrow = Arrow(function.get_bottom(), L.get_top(), color = YELLOW)
|
|
start_arrow = Arrow(starting_vector.get_top(), _input.get_bottom(), color = MAROON_C)
|
|
ending_arrow = Arrow(ending_vector, _output, color = BLUE)
|
|
|
|
|
|
self.add(title)
|
|
self.play(
|
|
Write(equation),
|
|
ShowCreation(func_arrow)
|
|
)
|
|
for v, a in [(starting_vector, start_arrow), (ending_vector, ending_arrow)]:
|
|
self.play(Write(v), ShowCreation(a), run_time = 1)
|
|
self.dither()
|
|
|
|
class UsedToThinkinfOfFunctionsAsGraphs(VectorScene):
|
|
def construct(self):
|
|
self.show_graph()
|
|
self.show_inputs_and_output()
|
|
|
|
def show_graph(self):
|
|
axes = self.add_axes()
|
|
graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2)
|
|
name = TexMobject("f(x) = x^2")
|
|
name.next_to(graph, RIGHT).to_edge(UP)
|
|
point = Dot(graph.point_from_proportion(0.8))
|
|
point_label = TexMobject("(x, x^2)")
|
|
point_label.next_to(point, DOWN+RIGHT, buff = 0.1)
|
|
|
|
self.play(ShowCreation(graph))
|
|
self.play(Write(name, run_time = 1))
|
|
self.play(
|
|
ShowCreation(point),
|
|
Write(point_label),
|
|
run_time = 1
|
|
)
|
|
self.dither()
|
|
|
|
def collapse_func(p):
|
|
return np.dot(p, [RIGHT, RIGHT, OUT]) + (SPACE_HEIGHT+1)*DOWN
|
|
self.play(
|
|
ApplyPointwiseFunction(
|
|
collapse_func, axes,
|
|
submobject_mode = "all_at_once",
|
|
),
|
|
ApplyPointwiseFunction(collapse_func, graph),
|
|
ApplyMethod(point.shift, 10*DOWN),
|
|
ApplyMethod(point_label.shift, 10*DOWN),
|
|
ApplyFunction(lambda m : m.center().to_edge(UP), name),
|
|
run_time = 1
|
|
)
|
|
self.clear()
|
|
self.add(name)
|
|
self.dither()
|
|
|
|
def show_inputs_and_output(self):
|
|
numbers = range(-3, 4)
|
|
inputs = VMobject(*map(TexMobject, map(str, numbers)))
|
|
inputs.arrange_submobjects(DOWN, buff = 0.5, aligned_edge = RIGHT)
|
|
arrows = VMobject(*[
|
|
Arrow(LEFT, RIGHT).next_to(mob)
|
|
for mob in inputs.split()
|
|
])
|
|
outputs = VMobject(*[
|
|
TexMobject(str(num**2)).next_to(arrow)
|
|
for num, arrow in zip(numbers, arrows.split())
|
|
])
|
|
everyone = VMobject(inputs, arrows, outputs)
|
|
everyone.center().to_edge(UP, buff = 1.5)
|
|
|
|
self.play(Write(inputs, run_time = 1))
|
|
self.dither()
|
|
self.play(
|
|
Transform(inputs.copy(), outputs),
|
|
ShowCreation(arrows)
|
|
)
|
|
self.dither()
|
|
|
|
class TryingToVisualizeFourDimensions(Scene):
|
|
def construct(self):
|
|
randy = Randolph().to_corner()
|
|
bubble = randy.get_bubble()
|
|
formula = TexMobject("""
|
|
L\\left(\\left[
|
|
\\begin{array}{c}
|
|
x \\\\
|
|
y
|
|
\\end{array}
|
|
\\right]\\right) =
|
|
\\left[
|
|
\\begin{array}{c}
|
|
2x + y \\\\
|
|
x + 2y
|
|
\\end{array}
|
|
\\right]
|
|
""")
|
|
formula.next_to(randy, RIGHT)
|
|
formula.split()[3].highlight(X_COLOR)
|
|
formula.split()[4].highlight(Y_COLOR)
|
|
VMobject(*formula.split()[9:9+4]).highlight(MAROON_C)
|
|
VMobject(*formula.split()[13:13+4]).highlight(BLUE)
|
|
thought = TextMobject("""
|
|
Do I imagine plotting
|
|
$(x, y, 2x+y, x+2y)$???
|
|
""")
|
|
thought.split()[-17].highlight(X_COLOR)
|
|
thought.split()[-15].highlight(Y_COLOR)
|
|
VMobject(*thought.split()[-13:-13+4]).highlight(MAROON_C)
|
|
VMobject(*thought.split()[-8:-8+4]).highlight(BLUE)
|
|
|
|
bubble.position_mobject_inside(thought)
|
|
thought.shift(0.2*UP)
|
|
|
|
self.add(randy)
|
|
|
|
self.play(
|
|
ApplyMethod(randy.look, DOWN+RIGHT),
|
|
Write(formula)
|
|
)
|
|
self.play(
|
|
ApplyMethod(randy.change_mode, "pondering"),
|
|
ShowCreation(bubble),
|
|
Write(thought)
|
|
)
|
|
self.play(Blink(randy))
|
|
self.dither()
|
|
self.remove(thought)
|
|
bubble.make_green_screen()
|
|
self.dither()
|
|
self.play(Blink(randy))
|
|
self.play(ApplyMethod(randy.change_mode, "confused"))
|
|
self.dither()
|
|
self.play(Blink(randy))
|
|
self.dither()
|
|
|
|
class ForgetAboutGraphs(Scene):
|
|
def construct(self):
|
|
self.play(Write("You must unlearn graphs"))
|
|
self.dither()
|
|
|
|
class ThinkAboutFunctionAsMovingVector(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"leave_ghost_vectors" : True,
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
vector = self.add_vector([2, 1])
|
|
label = self.add_transformable_label(vector, "v")
|
|
self.dither()
|
|
self.apply_transposed_matrix([[1, 1], [-3, 1]])
|
|
self.dither()
|
|
|
|
class PrepareForFormalDefinition(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.setup()
|
|
self.teacher_says("Get ready for a formal definition!")
|
|
self.dither(3)
|
|
bubble = self.student_thinks("")
|
|
bubble.make_green_screen()
|
|
self.dither(3)
|
|
|
|
class AdditivityProperty(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"give_title" : True,
|
|
"transposed_matrix" : [[2, 0], [1, 1]],
|
|
"nonlinear_transformation" : None,
|
|
"vector_v" : [2, 1],
|
|
"vector_w" : [1, -2],
|
|
"proclaim_sum" : True,
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
added_anims = []
|
|
if self.give_title:
|
|
title = TextMobject("""
|
|
First fundamental property of
|
|
linear transformations
|
|
""")
|
|
title.to_edge(UP)
|
|
title.highlight(YELLOW)
|
|
title.add_background_rectangle()
|
|
self.play(Write(title))
|
|
added_anims.append(Animation(title))
|
|
self.dither()
|
|
self.play(ApplyMethod(self.plane.fade), *added_anims)
|
|
|
|
v, w = self.draw_all_vectors()
|
|
self.apply_transformation(added_anims)
|
|
self.show_final_sum(v, w)
|
|
|
|
def draw_all_vectors(self):
|
|
v = self.add_vector(self.vector_v, color = MAROON_C)
|
|
v_label = self.add_transformable_label(v, "v")
|
|
w = self.add_vector(self.vector_w, color = GREEN)
|
|
w_label = self.add_transformable_label(w, "w")
|
|
new_w = w.copy().fade(0.4)
|
|
self.play(ApplyMethod(new_w.shift, v.get_end()))
|
|
sum_vect = self.add_vector(new_w.get_end(), color = PINK)
|
|
sum_label = self.add_transformable_label(
|
|
sum_vect,
|
|
"%s + %s"%(v_label.expression, w_label.expression),
|
|
rotate = True
|
|
)
|
|
self.play(FadeOut(new_w))
|
|
return v, w
|
|
|
|
def apply_transformation(self, added_anims):
|
|
if self.nonlinear_transformation:
|
|
self.apply_nonlinear_transformation(self.nonlinear_transformation)
|
|
else:
|
|
self.apply_transposed_matrix(
|
|
self.transposed_matrix,
|
|
added_anims = added_anims
|
|
)
|
|
self.dither()
|
|
|
|
def show_final_sum(self, v, w):
|
|
new_w = w.copy()
|
|
self.play(ApplyMethod(new_w.shift, v.get_end()))
|
|
self.dither()
|
|
if self.proclaim_sum:
|
|
text = TextMobject("It's still their sum!")
|
|
text.add_background_rectangle()
|
|
text.move_to(new_w.get_end(), side_to_align = -new_w.get_end())
|
|
text.shift_onto_screen()
|
|
self.play(Write(text))
|
|
self.dither()
|
|
|
|
class NonlinearLacksAdditivity(AdditivityProperty):
|
|
CONFIG = {
|
|
"give_title" : False,
|
|
"nonlinear_transformation" : curvy_squish,
|
|
"vector_v" : [3, 2],
|
|
"vector_w" : [2, -3],
|
|
"proclaim_sum" : False,
|
|
}
|
|
|
|
class SecondAdditivityExample(AdditivityProperty):
|
|
CONFIG = {
|
|
"give_title" : False,
|
|
"transposed_matrix" : [[1, -1], [2, 1]],
|
|
"vector_v" : [-2, 2],
|
|
"vector_w" : [3, 0],
|
|
"proclaim_sum" : False,
|
|
}
|
|
|
|
class ShowGridCreation(Scene):
|
|
def construct(self):
|
|
plane = NumberPlane()
|
|
coords = VMobject(*plane.get_coordinate_labels())
|
|
self.play(ShowCreation(plane, run_time = 3))
|
|
self.play(Write(coords, run_time = 3))
|
|
self.dither()
|
|
|
|
class MoveAroundAllVectors(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
"focus_on_one_vector" : False,
|
|
"include_background_plane" : False,
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
vectors = VMobject(*[
|
|
Vector([x, y])
|
|
for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5)
|
|
for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5)
|
|
])
|
|
vectors.submobject_gradient_highlight(PINK, YELLOW)
|
|
dots = self.get_dots(vectors)
|
|
|
|
self.dither()
|
|
self.play(ShowCreation(dots))
|
|
self.dither()
|
|
self.play(Transform(dots, vectors))
|
|
self.dither()
|
|
self.remove(dots)
|
|
if self.focus_on_one_vector:
|
|
vector = vectors.split()[43]#yeah, great coding Grant
|
|
self.remove(vectors)
|
|
self.add_vector(vector)
|
|
self.play(*[
|
|
FadeOut(v)
|
|
for v in vectors.split()
|
|
if v is not vector
|
|
])
|
|
self.dither()
|
|
self.add(vector.copy().highlight(DARK_GREY))
|
|
else:
|
|
for vector in vectors.split():
|
|
self.add_vector(vector, animate = False)
|
|
self.apply_transposed_matrix([[3, 0], [1, 2]])
|
|
self.dither()
|
|
dots = self.get_dots(vectors)
|
|
self.play(Transform(vectors, dots))
|
|
self.dither()
|
|
|
|
def get_dots(self, vectors):
|
|
return VMobject(*[
|
|
Dot(v.get_end(), color = v.get_color())
|
|
for v in vectors.split()
|
|
])
|
|
|
|
class ReasonForThinkingAboutArrows(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
self.plane.fade()
|
|
v_color = MAROON_C
|
|
w_color = BLUE
|
|
|
|
v = self.add_vector([3, 1], color = v_color)
|
|
w = self.add_vector([1, -2], color = w_color)
|
|
vectors = VMobject(v, w)
|
|
|
|
self.to_and_from_dots(vectors)
|
|
self.scale_and_add(vectors)
|
|
self.apply_transposed_matrix([[1, 1], [-1, 0]])
|
|
self.scale_and_add(vectors)
|
|
|
|
def to_and_from_dots(self, vectors):
|
|
vectors_copy = vectors.copy()
|
|
dots = VMobject(*[
|
|
Dot(v.get_end(), color = v.get_color())
|
|
for v in vectors.split()
|
|
])
|
|
|
|
self.dither()
|
|
self.play(Transform(vectors, dots))
|
|
self.dither()
|
|
self.play(Transform(vectors, vectors_copy))
|
|
self.dither()
|
|
|
|
def scale_and_add(self, vectors):
|
|
vectors_copy = vectors.copy()
|
|
v, w, = vectors.split()
|
|
scaled_v = Vector(0.5*v.get_end(), color = v.get_color())
|
|
scaled_w = Vector(1.5*w.get_end(), color = w.get_color())
|
|
shifted_w = scaled_w.copy().shift(scaled_v.get_end())
|
|
sum_vect = Vector(shifted_w.get_end(), color = PINK)
|
|
|
|
self.play(
|
|
ApplyMethod(v.scale, 0.5),
|
|
ApplyMethod(w.scale, 1.5),
|
|
)
|
|
self.play(ApplyMethod(w.shift, v.get_end()))
|
|
self.add_vector(sum_vect)
|
|
self.dither()
|
|
self.play(Transform(
|
|
vectors, vectors_copy,
|
|
submobject_mode = "all_at_once"
|
|
))
|
|
self.dither()
|
|
|
|
class LinearTransformationWithOneVector(LinearTransformationScene):
|
|
CONFIG = {
|
|
"show_basis_vectors" : False,
|
|
}
|
|
def construct(self):
|
|
self.setup()
|
|
v = self.add_vector([3, 1])
|
|
self.vector_to_coords(v)
|
|
self.apply_transposed_matrix([[-1, 1], [-2, -1]])
|
|
self.vector_to_coords(v)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|