from manimlib.imports 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.set_width(FRAME_WIDTH - 2) words.to_edge(UP) words.split()[1].set_color(GREEN) words.split()[3].set_color(BLUE) author = TextMobject("-Morpheus") author.set_color(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.wait(3) self.play(Write(author, run_time = 3)) self.wait() self.play(Write(comment)) self.wait() class Introduction(TeacherStudentsScene): def construct(self): title = TextMobject(["Matrices as", "Linear transformations"]) title.to_edge(UP) title.set_color(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 (FRAME_X_RADIUS+FRAME_Y_RADIUS)*p/get_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.set_color(YELLOW) self.play(Write(title)) self.wait() 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(*list(map(TexMobject, list(map(str, nums))))) num_outputs = VMobject(*[ TexMobject(str(num**2)) for num in nums ]) for mob in num_inputs, num_outputs: mob.arrange(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.set_color(MAROON_C) vector_input_words.next_to(input_vect, DOWN) vector_output_words = TextMobject("Vector output") vector_output_words.set_color(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.wait() for mob in f_point, num_outputs: self.play(Transform( num_inputs, mob, lag_ratio = 0.5 )) self.wait() self.play( FadeOut(num_inputs), Transform(f_of_x, L_of_v) ) self.play( Write(input_vect), Write(vector_input_words) ) self.wait() for mob in f_point, output_vect: self.play(Transform( input_vect, mob, lag_ratio = 0.5 )) self.play(Write(vector_output_words)) self.wait() 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.wait() statement = TextMobject([ "The word", "``transformation''", "suggests \\\\ that you think using", "movement", ]) statement.split()[1].set_color(BLUE) statement.split()[-1].set_color(YELLOW) self.teacher_says(statement, width = 10) self.play(*[ ApplyMethod(student.change_mode, "happy") for student in other_students ]) self.random_blink() self.wait() 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.wait() def collapse_func(p): return np.dot(p, [RIGHT, RIGHT, OUT]) + (FRAME_Y_RADIUS+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].set_color(YELLOW) self.play(Write(words)) self.wait() 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.set_color(v.get_color()) self.play(ShowCreation(v)) self.play(Write(v.label)) self.wait() self.remove(v2) self.play( Transform( v1.copy(), v2, path_arc = -np.pi/2, run_time = 3 ), ApplyMethod(v1.fade) ) self.wait() 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(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) ]) vectors.set_submobject_colors_by_gradient(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, lag_ratio = 0.5)) self.wait() if self.use_dots: self.play(Transform( vectors, self.vectors_to_dots(vectors), run_time = 3, lag_ratio = 0.5 )) transformed_vectors = self.vectors_to_dots(transformed_vectors) self.wait() self.play(Transform( vectors, transformed_vectors, run_time = 3, path_arc = -np.pi/2 )) self.wait() if self.use_dots: self.play(Transform( vectors, self.dots_to_vectors(vectors), run_time = 2, lag_ratio = 0.5 )) self.wait() 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" : FRAME_WIDTH, "y_radius" : FRAME_HEIGHT, }, "show_basis_vectors" : False } def construct(self): self.setup() self.play(ShowCreation( self.plane, run_time = 3, lag_ratio = 0.5 )) self.wait() self.apply_transposed_matrix([[2, 1], [1, 2]]) self.wait() class TransformInfiniteGridWithBackground(TransformInfiniteGrid): CONFIG = { "include_background_plane" : True, "foreground_plane_kwargs" : { "x_radius" : FRAME_WIDTH, "y_radius" : FRAME_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" : FRAME_X_RADIUS, "y_radius" : FRAME_Y_RADIUS, "secondary_line_ratio" : 0 }, } def construct(self): self.setup() self.plane.prepare_for_nonlinear_transform(100) self.wait() self.play(ApplyMethod( self.plane.apply_complex_function, self.function, run_time = 5, path_arc = np.pi/2 )) self.wait() 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.set_color(YELLOW) function.next_to(transformation_brace, DOWN) new_sub_word = TextMobject("What does this mean?") new_sub_word.set_color(BLUE) new_sub_word.next_to(linear_brace, DOWN) self.add( faded_linear, transformation, transformation_brace, function ) self.wait() self.play( Transform(faded_linear, linear), Transform(transformation, faded_transformation), Transform(transformation_brace, linear_brace), Transform(function, new_sub_word), lag_ratio = 0.5 ) self.wait() class IntroduceLinearTransformations(LinearTransformationScene): CONFIG = { "show_basis_vectors" : False, } def construct(self): self.setup() self.wait() self.apply_transposed_matrix([[2, 1], [1, 2]]) self.wait() 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.wait() self.play( Write(origin_rule, run_time = 2), ShowCreation(arrow), GrowFromCenter(dot) ) self.wait() class ToThePedants(Scene): def construct(self): words = TextMobject([ "To the pedants:\\\\", """ Yeah yeah, I know that's not the formal definition for linear transformations, as seen in textbooks, but I'm building visual intuition here, and what I've said is equivalent to the formal definition (which I'll get to later in the series). """]) words.split()[0].set_color(RED) words.to_edge(UP) self.add(words) self.wait() class SimpleLinearTransformationScene(LinearTransformationScene): CONFIG = { "show_basis_vectors" : False, "transposed_matrix" : [[2, 1], [1, 2]] } def construct(self): self.setup() self.wait() self.apply_transposed_matrix(self.transposed_matrix) self.wait() class SimpleNonlinearTransformationScene(LinearTransformationScene): CONFIG = { "show_basis_vectors" : False, "words" : "Not linear: some lines get curved" } def construct(self): self.setup() self.wait() self.apply_nonlinear_transformation(self.func) words = TextMobject(self.words) words.to_corner(UP+RIGHT) words.set_color(RED) words.add_background_rectangle() self.play(Write(words)) self.wait() 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)*FRAME_X_RADIUS*smooth(abs(x) / FRAME_X_RADIUS) new_y = np.sign(y)*FRAME_Y_RADIUS*smooth(abs(y) / FRAME_Y_RADIUS) 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( FRAME_Y_RADIUS*LEFT+FRAME_Y_RADIUS*DOWN, FRAME_Y_RADIUS*RIGHT + FRAME_Y_RADIUS*UP ) diag.insert_n_curves(20) diag.make_smooth() diag.set_color(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.set_color(YELLOW) es.set_color(GREEN) text.add_background_rectangle() text.shift(-text.get_bottom()) self.play(Write(text)) self.wait() 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.set_color(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.wait() self.play( ApplyMethod(self.plane.fade, 0.7), ApplyMethod(self.background_plane.fade, 0.7), Write(formula, run_time = 2), Animation(words) ) self.wait() 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_factor = 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_factor = 1 ) self.wait() self.play(*list(map(FadeOut, [i_label, j_label]))) self.apply_transposed_matrix([[-1, 1], [-2, -1]]) self.wait() 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.wait() self.apply_transposed_matrix(self.transposed_matrix) self.wait() 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_FACTOR) 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, lag_ratio = 0 )) self.remove(pre_def) self.add_foreground_mobject(v_def) self.wait() 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.wait(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.set_color(YELLOW) i_hat.set_color(X_COLOR) j_hat.set_color(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.set_color(YELLOW) i_hat.set_color(X_COLOR) j_hat.set_color(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.wait() 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.set_color(X_COLOR) j_coords.set_color(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.set_color(X_COLOR) j_hat.set_color(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.wait() self.play(Write(j_coords, run_time = 1)) self.wait() self.play(Write(calculation)) self.wait() self.play(Write(result)) self.wait() class WatchManyVectorsMove(TransformManyVectors): def construct(self): self.setup() vectors = VMobject(*[ Vector([x, y]) for x in np.arange(-int(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) ]) vectors.set_submobject_colors_by_gradient(PINK, YELLOW) dots = self.vectors_to_dots(vectors) self.play(ShowCreation(dots, lag_ratio = 0.5)) self.play(Transform( dots, vectors, lag_ratio = 0.5, 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.wait() self.play( ApplyMethod(self.plane.fade), FadeOut(vectors), Animation(self.i_hat), Animation(self.j_hat), ) self.wait() 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.wait(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.set_color(X_COLOR) j_group.set_color(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).set_color(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] ).set_color(X_COLOR) VMobject( row1.split()[1], row1.split()[4], row2.split()[2], row2.split()[5] ).set_color(YELLOW) VMobject( row1.split()[3], row2.split()[4] ).set_color(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(RIGHT, buff = 0.1) self.add(i_group, j_group) for mob in vect_group.split(): self.play(Write(mob)) self.wait() 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.wait() self.apply_transposed_matrix([[3, -2], [2, 1]]) self.wait() 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.set_color(X_COLOR) j_coords.set_color(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_to_entries = 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.wait() 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, lag_ratio = 0 ) 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.set_color(X_COLOR) j_message.set_color(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.wait() self.play(ShowCreation(i_circle)) self.play( Write(i_message, run_time = 1.5), ShowCreation(i_arrow), ) self.wait() self.play(ShowCreation(j_circle)) self.play( Write(j_message, run_time = 1.5), ShowCreation(j_arrow) ) self.wait() self.play(*list(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.set_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.wait() 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(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, lag_ratio = 0 ) ) self.wait() 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(*list(map(TexMobject, row1))) row2 = VMobject(*list(map(TexMobject, row2))) for row in row1, row2: row.arrange(RIGHT, buff = 0.1) final_sum = Matrix([row1, row2]) row1, row2 = final_sum.get_mob_matrix().flatten() row1.split()[0].set_color(X_COLOR) row2.split()[0].set_color(X_COLOR) row1.split()[3].set_color(Y_COLOR) row2.split()[3].set_color(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.wait() def reposition_matrix_and_vector(self, matrix, vector, formula): start_state = VMobject(matrix, vector) end_state = start_state.copy() end_state.arrange(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.set_color(YELLOW) self.play( Transform( start_state, end_state, lag_ratio = 0 ), Write(equals, run_time = 1) ) self.wait() self.play( FadeIn(brace), FadeIn(brace_words), lag_ratio = 0.5 ) self.wait() class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication): CONFIG = { "abstract" : True, } class ColumnsToBasisVectors(LinearTransformationScene): CONFIG = { "t_matrix" : [[3, 1], [1, 2]] } def construct(self): self.setup() vector_coords = [-1, 2] vector = self.move_matrix_columns(self.t_matrix, vector_coords) self.scale_and_add(vector, vector_coords) self.wait(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) matrix_mob.add_background_to_entries() col1 = VMobject(*matrix_mob.get_mob_matrix()[:,0]) col1.set_color(X_COLOR) col2 = VMobject(*matrix_mob.get_mob_matrix()[:,1]) col2.set_color(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()).set_color(YELLOW) vector.set_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).set_color(X_COLOR) j_label = vector_coordinate_label(new_j).set_color(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.wait() 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.wait() self.matrix = VGroup(matrix_background, matrix_mob) self.i_coords = i_coords_start self.j_coords = j_coords_start 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.wait() 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.set_color(X_COLOR) col2.set_color(Y_COLOR) self.add_foreground_mobject(matrix_background, matrix.get_brackets()) self.wait() self.apply_transposed_matrix(self.transposed_matrix) self.wait() 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.set_color(color) background = BackgroundRectangle(label) coords = VMobject(*label.get_mob_matrix().flatten()) brackets = label.get_brackets() self.play(ShowCreation(background), Write(label)) self.wait() 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.wait() self.show_vector(matrix) def show_vector(self, matrix): vector = Matrix(["x", "y"]) VMobject(*vector.get_mob_matrix().flatten()).set_color(YELLOW) vector.set_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.wait() self.apply_transposed_matrix(self.transposed_matrix) self.play(ShowCreation(v_background), Write(vector)) self.wait() 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.wait(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.set_color(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.set_height(6) rect.next_to(title, DOWN) self.add(title) self.play(ShowCreation(rect)) self.wait() 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.set_height(FRAME_HEIGHT - 2) text.to_edge(UP) self.add(text) self.wait() ### 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.wait() self.play(FadeOut(i_label)) self.apply_transposed_matrix([[0, 1], [-1, 0]]) self.wait() self.play(Write(j_label, run_time = 1)) self.wait() class TransformationsAreFunctions(Scene): def construct(self): title = TextMobject([ """Linear transformations are a special kind of""", "function" ]) title_start, function = title.split() function.set_color(YELLOW) title.to_edge(UP) equation = TexMobject([ "L", "(", "\\vec{\\textbf{v}}", ") = ", "\\vec{\\textbf{w}}", ]) L, lp, _input, equals, _output = equation.split() L.set_color(YELLOW) _input.set_color(MAROON_C) _output.set_color(BLUE) equation.scale(2) equation.next_to(title, DOWN, buff = 1) starting_vector = TextMobject("Starting vector") starting_vector.shift(DOWN+3*LEFT) starting_vector.set_color(MAROON_C) ending_vector = TextMobject("The vector where it lands") ending_vector.shift(DOWN).to_edge(RIGHT) ending_vector.set_color(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.wait() 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.wait() def collapse_func(p): return np.dot(p, [RIGHT, RIGHT, OUT]) + (FRAME_Y_RADIUS+1)*DOWN self.play( ApplyPointwiseFunction( collapse_func, axes, lag_ratio = 0, ), 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.wait() def show_inputs_and_output(self): numbers = list(range(-3, 4)) inputs = VMobject(*list(map(TexMobject, list(map(str, numbers))))) inputs.arrange(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.wait() self.play( Transform(inputs.copy(), outputs), ShowCreation(arrows) ) self.wait() 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].set_color(X_COLOR) formula.split()[4].set_color(Y_COLOR) VMobject(*formula.split()[9:9+4]).set_color(MAROON_C) VMobject(*formula.split()[13:13+4]).set_color(BLUE) thought = TextMobject(""" Do I imagine plotting $(x, y, 2x+y, x+2y)$??? """) thought.split()[-17].set_color(X_COLOR) thought.split()[-15].set_color(Y_COLOR) VMobject(*thought.split()[-13:-13+4]).set_color(MAROON_C) VMobject(*thought.split()[-8:-8+4]).set_color(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.wait() self.remove(thought) bubble.make_green_screen() self.wait() self.play(Blink(randy)) self.play(ApplyMethod(randy.change_mode, "confused")) self.wait() self.play(Blink(randy)) self.wait() class ForgetAboutGraphs(Scene): def construct(self): self.play(Write("You must unlearn graphs")) self.wait() 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.wait() self.apply_transposed_matrix([[1, 1], [-3, 1]]) self.wait() class PrepareForFormalDefinition(TeacherStudentsScene): def construct(self): self.setup() self.teacher_says("Get ready for a formal definition!") self.wait(3) bubble = self.student_thinks("") bubble.make_green_screen() self.wait(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.set_color(YELLOW) title.add_background_rectangle() self.play(Write(title)) added_anims.append(Animation(title)) self.wait() 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.wait() def show_final_sum(self, v, w): new_w = w.copy() self.play(ApplyMethod(new_w.shift, v.get_end())) self.wait() if self.proclaim_sum: text = TextMobject("It's still their sum!") text.add_background_rectangle() text.move_to(new_w.get_end(), aligned_edge = -new_w.get_end()) text.shift_onto_screen() self.play(Write(text)) self.wait() 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.wait() 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(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) ]) vectors.set_submobject_colors_by_gradient(PINK, YELLOW) dots = self.get_dots(vectors) self.wait() self.play(ShowCreation(dots)) self.wait() self.play(Transform(dots, vectors)) self.wait() 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.wait() self.add(vector.copy().set_color(DARK_GREY)) else: for vector in vectors.split(): self.add_vector(vector, animate = False) self.apply_transposed_matrix([[3, 0], [1, 2]]) self.wait() dots = self.get_dots(vectors) self.play(Transform(vectors, dots)) self.wait() 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.wait() self.play(Transform(vectors, dots)) self.wait() self.play(Transform(vectors, vectors_copy)) self.wait() 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.wait() self.play(Transform( vectors, vectors_copy, lag_ratio = 0 )) self.wait() 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)