Skip to content

Instantly share code, notes, and snippets.

@jajaperson
Created May 24, 2020 09:18
Show Gist options
  • Save jajaperson/275f6a5ab52797f038d031f27f2f0034 to your computer and use it in GitHub Desktop.
Save jajaperson/275f6a5ab52797f038d031f27f2f0034 to your computer and use it in GitHub Desktop.
My first experience with manim. Steals some ideas from 3b1b's episode of EOLA on dot products.
#!/usr/bin/env python
from manimlib.imports import *
V_COLOR = YELLOW
W_COLOR = MAROON_B
SUM_COLOR = PINK
class ShowProjection(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.add_plane(animate=True)
self.add_symbols()
self.add_vectors()
self.stable_span()
self.project()
self.scalar_and_vector()
def add_symbols(self):
v = matrix_to_mobject(self.v_coords).set_color(self.v_color)
w = matrix_to_mobject(self.w_coords).set_color(self.w_color)
v.add_background_rectangle()
w.add_background_rectangle()
projection_text = TextMobject("projected onto")
pr = VGroup(w if self.project_onto_v else v, projection_text, v if self.project_onto_v else w)
pr.arrange(RIGHT, buff = SMALL_BUFF)
pr.space_out_submobjects(factor=1.1)
pr.to_corner(UP+LEFT)
self.play(Write(pr), run_time = 1)
for array, char in zip([w, v], ["w", "v"]):
brace = Brace(array, DOWN)
label = brace.get_text("$\\vec{\\textbf{%s}}$"%char)
label.set_color(array.get_color())
self.play(
GrowFromCenter(brace),
Write(label),
run_time = 1
)
self.projection_desc = pr
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.set_color(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 stable_span(self):
self.span = Line(LEFT, RIGHT)
self.span.scale(FRAME_X_RADIUS * 1.5)
self.span.rotate(self.stable_vect.get_angle())
self.play(ShowCreation(self.span))
self.wait()
def project(self):
vdotw = np.dot(self.v.get_end(), self.w.get_end())
projected = Vector(
self.stable_vect.get_end() * (
vdotw / self.stable_vect.get_length()**2
),
color = self.proj_vect.get_color()
)
self.projection_line = Line(
self.proj_vect.get_end(),
projected.get_end(),
color = GREY
)
self.play(ShowCreation(self.projection_line))
self.add(self.proj_vect.copy().fade())
self.play(Transform(self.proj_vect, projected), Transform(self.span, self.span.copy().fade()))
self.wait()
def scalar_and_vector(self):
proj_brace = Brace(Line(ORIGIN, self.proj_vect.get_length()*RIGHT*np.sign(np.dot(self.proj_vect.get_end(), self.stable_vect.get_end()))), UP)
proj_brace.rotate(self.stable_vect.get_angle())
desc_vect = TextMobject(
"This new vector is called the vector projection of ",
"$\\vec{\\textbf{%s}}$"%self.get_proj_char(),
" onto ",
"$\\vec{\\textbf{%s}}$"%self.get_stable_char(),
".",
arg_seperator = ""
)
desc_vect.scale(0.9)
desc_vect.to_edge(DOWN)
desc_vect_proj = desc_vect[1]
desc_vect_proj.set_color(self.proj_vect.get_color())
desc_vect_stable = desc_vect[3]
desc_vect_stable.set_color(self.stable_vect.get_color())
desc_scalar = TextMobject(
"The length of this vector is called the scalar projection of ",
"$\\vec{\\textbf{%s}}$"%self.get_proj_char(),
" onto ",
"$\\vec{\\textbf{%s}}$"%self.get_stable_char(),
".",
arg_seperator = ""
)
desc_scalar.scale(0.9)
desc_scalar.to_edge(DOWN)
desc_scalar_proj = desc_scalar[1]
desc_scalar_proj.set_color(self.proj_vect.get_color())
desc_scalar_stable = desc_scalar[3]
desc_scalar_stable.set_color(self.stable_vect.get_color())
self.play(Write(desc_vect), GrowFromCenter(proj_brace))
self.wait()
self.play(Transform(desc_vect, desc_scalar))
self.wait()
def get_stable_char(self):
return "v" if self.project_onto_v else "w"
def get_proj_char(self):
return "w" if self.project_onto_v else "v"
class ShowProjectionFlipped(ShowProjection):
CONFIG = {
"project_onto_v": False
}
class DotProduct(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.add_plane()
self.add_symbols()
self.redraw_vectors()
self.show_lengths()
def add_symbols(self):
title = TextMobject("Dot product")
title.scale(1.75)
self.play(Write(title))
self.wait(duration=1.5)
v = matrix_to_mobject(self.v_coords).set_color(self.v_color)
w = matrix_to_mobject(self.w_coords).set_color(self.w_color)
v.add_background_rectangle()
w.add_background_rectangle()
dot = TexMobject("\\cdot")
dot.set_color(RED)
eq = VGroup(v, dot, w)
eq.arrange(RIGHT, buff=SMALL_BUFF)
eq.scale(1.5)
self.play(FadeOut(title))
self.play(Write(eq))
self.remove(eq)
self.play(eq.scale, 1/1.5, eq.to_corner, UP + LEFT)
for array, char in zip([eq[0], eq[2]], ["v", "w"]):
brace = Brace(array, DOWN)
label = brace.get_text("$\\vec{\\textbf{%s}}$"%char)
label.set_color(array.get_color())
self.play(
GrowFromCenter(brace),
Write(label),
run_time = 0.2
)
self.dot_product = eq
def redraw_vectors(self):
self.v = Vector(self.v_coords, color = self.v_color)
self.w = Vector(self.w_coords, color = self.w_color)
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
self.span = Line(LEFT, RIGHT)
self.span.scale(FRAME_X_RADIUS * 1.5)
self.span.rotate(self.stable_vect.get_angle())
set1 = [ShowCreation(self.span), ShowCreation(self.v), 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.set_color(vect.get_color())
set1.append(Write(label))
self.play(*set1, run_time = 0.2)
self.vdotw = np.dot(self.v.get_end(), self.w.get_end())
projected = Vector(
self.stable_vect.get_end() * (
self.vdotw / 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
)
set2 = [ShowCreation(projected), ShowCreation(projection_line)]
self.play(*set2, run_time = 0.4)
self.projected_vect = projected
def show_lengths(self):
product = TexMobject(
"=",
"(",
"\\text{Length of projected $\\vec{\\textbf{%s}}$}"%self.get_proj_char(),
")",
"\\times"
"(",
"\\text{Length of $\\vec{\\textbf{%s}}$}"%self.get_stable_char(),
")",
arg_seperator = ""
)
product.scale(0.9)
product.next_to(self.dot_product)
proj_l = product[2]
proj_l.set_color(self.proj_vect.get_color())
stable_l = product[5]
stable_l.set_color(self.stable_vect.get_color())
product.remove(proj_l, stable_l),
for desc in stable_l, proj_l:
desc.add_to_back(BackgroundRectangle(desc))
desc.start = desc.copy()
proj_brace, stable_brace = braces = [
Brace(Line(ORIGIN, vect.get_length()*RIGHT*sign), UP)
for vect in [self.projected_vect, self.stable_vect]
for sign in [np.sign(np.dot(vect.get_end(), self.stable_vect.get_end()))]
]
proj_brace.put_at_tip(proj_l.start)
proj_brace.desc = proj_l.start
stable_brace.put_at_tip(stable_l.start)
stable_brace.desc = stable_l.start
for brace in braces:
brace.rotate(self.stable_vect.get_angle())
brace.desc.rotate(self.stable_vect.get_angle())
self.play(
GrowFromCenter(proj_brace),
Write(proj_l.start, run_time = 2)
)
self.wait()
self.play(
Transform(proj_l.start, proj_l),
FadeOut(proj_brace)
)
self.play(
GrowFromCenter(stable_brace),
Write(stable_l.start, runtime = 2),
Animation(self.stable_vect)
)
self.wait()
self.play(
Transform(stable_l.start, stable_l),
FadeOut(stable_brace),
Write(product)
)
self.wait()
product.add(stable_l.start, proj_l.start)
self.product = product
def get_stable_char(self):
return "v" if self.project_onto_v else "w"
def get_proj_char(self):
return "w" if self.project_onto_v else "v"
class DotProductFlipped(DotProduct):
CONFIG = {
"project_onto_v": False
}
class ComputingDotProduct(Scene):
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.add_symbols_from_prev()
self.add_title()
self.show_algebra()
def add_symbols_from_prev(self):
old_v = matrix_to_mobject(self.v_coords).set_color(self.v_color)
old_w = matrix_to_mobject(self.w_coords).set_color(self.w_color)
old_v.add_background_rectangle()
old_w.add_background_rectangle()
old_dot = TexMobject("\\cdot")
old_dot.set_color(RED)
eq = VGroup(old_v, old_dot, old_w)
eq.arrange(RIGHT, buff=SMALL_BUFF)
eq.to_corner(UP + LEFT)
self.add(eq)
v = Matrix(self.v_coords)
w = Matrix(self.w_coords)
inter_array_dot = TexMobject("\\cdot").scale(1.5)
dot_product = VGroup(v, inter_array_dot, w)
dot_product.arrange(RIGHT, buff = MED_SMALL_BUFF/2)
dot_product.to_edge(LEFT)
pairs = list(zip(v.get_entries(), w.get_entries()))
for pair, color in zip(pairs, [X_COLOR, Y_COLOR, Z_COLOR]):
VGroup(*pair).set_color(color)
self.play(
Transform(eq, dot_product)
)
self.v = v
self.w = w
self.dot_product = dot_product
self.pairs = pairs
def add_title(self):
title = TextMobject("Computing the dot product")
title.scale(1.5)
title.to_edge(UP)
self.play(Write(title))
def show_algebra(self):
dot = TexMobject("\\cdot")
products = VGroup(*[
VGroup(
c1.copy(), dot.copy(), c2.copy()
).arrange(RIGHT, buff = SMALL_BUFF)
for c1, c2 in self.pairs
])
products.arrange(DOWN, buff = LARGE_BUFF)
products.next_to(self.dot_product, RIGHT, buff = LARGE_BUFF)
self.play(Transform(
VGroup(*it.starmap(VGroup, self.pairs)).copy(),
products,
path_arc = -np.pi/2,
run_time = 2
))
self.remove(*self.get_mobjects_from_last_animation())
self.add(products)
self.wait()
products.target = products.copy()
symbols = VGroup(*list(map(TexMobject, ["=", "+"])))
final_sum = VGroup(*it.chain(*list(zip(
symbols, products.target
))))
final_sum.arrange(RIGHT, buff = LARGE_BUFF)
final_sum.next_to(self.dot_product, RIGHT, buff = LARGE_BUFF)
self.play(
Write(symbols),
Transform(products, products.target, path_arc = np.pi/2)
)
self.wait()
result = VGroup(
TexMobject("="),
TexMobject(np.dot(self.v_coords, self.w_coords)).set_color(SUM_COLOR)
)
result.arrange(RIGHT, buff = LARGE_BUFF)
result.next_to(final_sum, RIGHT, buff = LARGE_BUFF)
self.play(
Write(result)
)
class ComputingDotProductAlt(ComputingDotProduct):
CONFIG = {
"v_coords": [8, 3],
"w_coords": [-12, -2],
}
class CommutativeProperty(Scene):
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.introduce_property()
self.show_algebra()
def introduce_property(self):
l1 = TextMobject("The dot product is ", "commutative,", arg_seperator = "")
l2 = TextMobject("i.e. order doesn't matter.")
introduction = VGroup(l1, l2)
introduction.arrange(DOWN)
introduction.scale(1.5)
self.play(Write(l1))
self.play(l1[1].set_color, YELLOW, ShowPassingFlashAround(l1[1]))
self.play(Write(l2))
self.introduction = introduction
def show_algebra(self):
v = Matrix(self.v_coords)
w = Matrix(self.w_coords)
inter_array_dot = TexMobject("\\cdot").scale(1.5)
dot_product = VGroup(v, inter_array_dot, w)
dot_product.arrange(RIGHT, buff = MED_SMALL_BUFF/2)
pairs = list(zip(v.get_entries(), w.get_entries()))
for pair, color in zip(pairs, [X_COLOR, Y_COLOR, Z_COLOR]):
VGroup(*pair).set_color(color)
dot = TexMobject("\\cdot")
symbols = VGroup(*list(map(TexMobject, ["=", "+"])))
products = VGroup(*[
VGroup(
c1.copy(), dot.copy(), c2.copy()
).arrange(RIGHT, buff = SMALL_BUFF)
for c1, c2 in pairs
])
dp_sum = VGroup(*it.chain(*list(zip(
symbols,
products
))), TexMobject("="))
dp_sum.arrange(RIGHT, buff = MED_LARGE_BUFF)
reconstructed_pairs = VGroup(*[
VGroup(
c1.copy(), c2.copy()
)
for c1, _, c2 in products
])
# reconstructed_vects = zip(reconstructed_pairs.copy())
flipped_dot_product = dot_product.copy()
flipped_dot_product.arrange(LEFT, buff = MED_SMALL_BUFF/2)
algebra = VGroup(
dot_product,
dp_sum,
flipped_dot_product
)
algebra.arrange(RIGHT, buff = MED_LARGE_BUFF)
self.play(Transform(self.introduction, dot_product))
self.play(Transform(
VGroup(*it.starmap(VGroup, pairs)).copy(),
dp_sum,
path_arc = np.pi/2
))
self.play(Transform(
VGroup(*it.starmap(VGroup, reconstructed_pairs)).copy(),
flipped_dot_product,
path_arc = np.pi/2
))
class ComputingScalarProjection(Scene):
CONFIG = {
"v_coords": [4, 1],
"w_coords": [2, -1],
"v_color": V_COLOR,
"w_color": W_COLOR,
"project_onto_v": True,
}
def construct(self):
l1 = TexMobject(
"\\text{Since the dot product }",
"\\vec{\\textbf{%s}}"%self.get_stable_char(),
"\\cdot", "\\vec{\\textbf{%s}}"%self.get_proj_char(),
"\\text{ is }"
)
l1[1].set_color(self.v_color if self.project_onto_v else self.w_color)
l1[3].set_color(self.w_color if self.project_onto_v else self.v_color)
l2 = TexMobject(
"(",
"\\text{Length of projected $\\vec{\\textbf{%s}}$}"%self.get_proj_char(),
")\\times(",
"\\text{Length of $\\vec{\\textbf{%s}}$}"%self.get_stable_char(),
")"
)
l2[1].set_color(self.w_color if self.project_onto_v else self.v_color)
l2[3].set_color(self.v_color if self.project_onto_v else self.w_color)
explanation1 = VGroup(l1, l2)
explanation1.arrange(DOWN)
proj_val = TexMobject("\\text{proj}_{\\vec{\\textbf{%s}}}"%self.get_stable_char(), "\\vec{\\textbf{%s}}"%self.get_proj_char(), arg_separator = "")
frac1 = TexMobject(
f'\\frac{{(\\text{{Length of projected $\\vec{{\\textbf{{{self.get_stable_char()}}}}}$}})\\times(\\text{{Length of $\\vec{{\\textbf{{{self.get_proj_char()}}}}}$}})}}{{(\\text{{Length of $\\vec{{\\textbf{{{self.get_stable_char()}}}}}$}})}}'
)
eq1 = VGroup(proj_val, TexMobject("="), frac1)
eq1.arrange(RIGHT, buff = MED_LARGE_BUFF)
frac2 = TexMobject(
f'\\frac{{\\vec{{\\textbf{{{self.get_proj_char()}}}}} \\cdot \\vec{{\\textbf{{{self.get_stable_char()}}}}}}}{{\\norm{{\\vec{{\\textbf{{{self.get_stable_char()}}}}}}}}}'
)
eq2 = VGroup(proj_val.copy(), TexMobject("="), frac2)
eq2.arrange(RIGHT, buff = MED_LARGE_BUFF)
self.play(Write(explanation1))
self.wait(2)
self.play(FadeOut(l1))
self.play(Transform(l2, eq1))
self.wait(3)
self.play(Transform(l2, eq2))
self.wait(3)
def get_stable_char(self):
return "v" if self.project_onto_v else "w"
def get_proj_char(self):
return "w" if self.project_onto_v else "v"
class ComputingVectorProjection(Scene):
CONFIG = {
"v_coords": [4, 1],
"w_coords": [2, -1],
"v_color": V_COLOR,
"w_color": W_COLOR,
"project_onto_v": True,
}
def construct(self):
l1 = TextMobject(
"The vector projection of ",
"$\\vec{\\textbf{%s}}$"%self.get_proj_char(),
" onto ",
"$\\vec{\\textbf{%s}}$"%self.get_stable_char(),
" is",
arg_separator = ""
)
l1[1].set_color(self.w_color if self.project_onto_v else self.v_color)
l1[3].set_color(self.v_color if self.project_onto_v else self.w_color)
l2 = TextMobject(
"the same as the scalar projection in"
)
l3 = TextMobject(
"the direction of, ",
"$\\vec{\\textbf{%s}}$"%self.get_stable_char(),
arg_separator = ""
)
l3[1].set_color(self.v_color if self.project_onto_v else self.w_color)
explanation = VGroup(l1, l2, l3)
explanation.arrange(DOWN)
proj_val = TexMobject("\\vec{\\textbf{\\text{proj}}}_{\\vec{\\textbf{%s}}}"%self.get_stable_char(), "\\vec{\\textbf{%s}}"%self.get_proj_char(), arg_separator = "")
scaling = TexMobject(
"\\left(",
"\\text{proj}_{\\vec{\\textbf{%s}}}"%self.get_stable_char(),
"\\vec{\\textbf{%s}}"%self.get_proj_char(),
"\\right)",
"\\hat{\\textbf{%s}}"%self.get_stable_char(),
arg_separator = ""
)
eq1 = VGroup(proj_val, TexMobject("="), scaling)
eq1.arrange(RIGHT, buff = MED_LARGE_BUFF)
frac1 = TexMobject(
f'\\frac{{\\vec{{\\textbf{{{self.get_proj_char()}}}}} \\cdot \\vec{{\\textbf{{{self.get_stable_char()}}}}}}}{{\\norm{{\\vec{{\\textbf{{{self.get_stable_char()}}}}}}}}}',
"\\times",
f'\\frac{{\\vec{{\\textbf{{{self.get_stable_char()}}}}}}}{{\\norm{{\\vec{{\\textbf{{{self.get_stable_char()}}}}}}}}}',
arg_separator = ""
)
eq2 = VGroup(proj_val.copy(), TexMobject("="), frac1)
eq2.arrange(RIGHT, buff = MED_LARGE_BUFF)
frac2 = TexMobject(
f'\\frac{{\\vec{{\\textbf{{{self.get_proj_char()}}}}} \\cdot \\vec{{\\textbf{{{self.get_stable_char()}}}}}}}{{\\norm{{\\vec{{\\textbf{{{self.get_stable_char()}}}}}}}^2}}',
"\\times \\vec{\\textbf{%s}}"%self.get_stable_char(),
arg_separator = ""
)
eq3 = VGroup(proj_val.copy(), TexMobject("="), frac2)
eq3.arrange(RIGHT, buff = MED_LARGE_BUFF)
frac3 = TexMobject(
f'\\frac{{\\vec{{\\textbf{{{self.get_proj_char()}}}}} \\cdot \\vec{{\\textbf{{{self.get_stable_char()}}}}}}}{{\\vec{{\\textbf{{{self.get_stable_char()}}}}} \\cdot \\vec{{\\textbf{{{self.get_stable_char()}}}}}}}',
"\\times \\vec{\\textbf{%s}}"%self.get_stable_char(),
arg_separator = ""
)
eq4 = VGroup(proj_val.copy(), TexMobject("="), frac3)
eq4.arrange(RIGHT, buff = MED_LARGE_BUFF)
self.play(Write(explanation))
self.wait(2)
self.play(Transform(explanation, eq1))
self.wait(3)
self.play(Transform(explanation, eq2))
self.play(Transform(explanation, eq3))
self.play(Transform(explanation, eq4))
self.wait(3)
def get_stable_char(self):
return "v" if self.project_onto_v else "w"
def get_proj_char(self):
return "w" if self.project_onto_v else "v"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment