diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 59e95e51..139b5d42 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -68,7 +68,7 @@ class Write(ShowCreation): if "run_time" not in kwargs: self.establish_run_time(mobject) if "lag_factor" not in kwargs: - self.lag_factor = self.run_time - 1 + self.lag_factor = max(self.run_time - 1, 1) ShowCreation.__init__(self, mobject, **kwargs) def establish_run_time(self, mobject): diff --git a/eola/chapter10.py b/eola/chapter10.py index e1f47d63..661d5d5b 100644 --- a/eola/chapter10.py +++ b/eola/chapter10.py @@ -20,6 +20,10 @@ from mobject.vectorized_mobject import * from eola.matrix import * from eola.two_d_space import * from eola.chapter1 import plane_wave_homotopy +from eola.chapter3 import ColumnsToBasisVectors +from eola.chapter5 import get_det_text +from eola.chapter9 import get_small_bubble + class OpeningQuote(Scene): def construct(self): @@ -32,7 +36,8 @@ class OpeningQuote(Scene): "music", """means to you, would you have answered: `The manipulation of notes?' '' """, - enforce_new_line_structure = False + enforce_new_line_structure = False, + alignment = "", ) words.highlight_by_tex("mathematics", BLUE) words.highlight_by_tex("music", BLUE) @@ -199,11 +204,15 @@ class IntroduceExampleTransformation(ExampleTranformationScene): ) ] ]) + to_remove = self.get_mobjects_from_last_animation() self.play(Transform( j_coords.copy().get_entries(), VGroup(*self.matrix.get_mob_matrix()[:,1]) )) + to_remove += self.get_mobjects_from_last_animation() self.dither() + self.remove(*to_remove) + self.add(self.matrix) class VectorKnockedOffSpan(ExampleTranformationScene): def construct(self): @@ -483,10 +492,1686 @@ class NameEigenvectorsAndEigenvalues(ExampleTranformationScene): self.play(Write(words.eigen_val_words), run_time = 2) self.dither() - - - - +class CanEigenvaluesBeNegative(TeacherStudentsScene): + def construct(self): + self.student_says("Can eigenvalues be negative?") + self.random_blink() + self.teacher_says("But of course!", pi_creature_target_mode = "hooray") + self.random_blink() + +class EigenvalueNegativeOneHalf(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[0.5, -1], [-1, 0.5]], + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_WIDTH, + "secondary_line_ratio" : 0 + }, + "include_background_plane" : False + } + def construct(self): + matrix = Matrix(self.t_matrix.T) + matrix.add_to_back(BackgroundRectangle(matrix)) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrix.next_to(ORIGIN, LEFT) + matrix.to_edge(UP) + self.add_foreground_mobject(matrix) + + vector = self.add_vector([1, 1]) + words = TextMobject("Eigenvector with \\\\ eigenvalue $-\\frac{1}{2}$") + words.add_background_rectangle() + words.next_to(vector.get_end(), RIGHT) + span = Line( + -SPACE_HEIGHT*vector.get_end(), + SPACE_HEIGHT*vector.get_end(), + color = MAROON_B + ) + + self.play(Write(words)) + self.play( + ShowCreation(span), + Animation(vector), + Animation(words), + ) + self.dither() + self.apply_transposed_matrix( + self.t_matrix, + added_anims = [FadeOut(words)] + ) + self.dither() + self.play( + Rotate(span, np.pi/12, rate_func = wiggle), + Animation(vector), + ) + self.dither() + +class ThreeDRotationTitle(Scene): + def construct(self): + title = TextMobject("3D Rotation") + title.scale(2) + self.play(Write(title)) + self.dither() + +class ThreeDShowRotation(Scene): + pass + +class ThreeDShowRotationWithEigenvector(Scene): + pass + +class EigenvectorToAxisOfRotation(Scene): + def construct(self): + words = [ + TextMobject("Eigenvector"), + TextMobject("Axis of rotation"), + ] + for word in words: + word.scale(2) + self.play(Write(words[0])) + self.dither() + self.play(Transform(*words)) + self.dither() + +class EigenvalueOne(Scene): + def construct(self): + text = TextMobject("Eigenvalue = $1$") + text.highlight(MAROON_B) + self.play(Write(text)) + self.dither() + +class ContrastMatrixUnderstandingWithEigenvalue(TeacherStudentsScene): + def construct(self): + axis_and_rotation = TextMobject( + "Rotate", "$30^\\circ$", "around", + "$%s$"%matrix_to_tex_string([2, 3, 1]) + ) + axis_and_rotation[1].highlight(BLUE) + axis_and_rotation[-1].highlight(MAROON_B) + + matrix = Matrix([ + [ + "\\cos(\\theta)\\cos(\\phi)", + "-\\sin(\\phi)", + "\\cos(\\theta)\\sin(\\phi)", + ], + [ + "\\sin(\\theta)\\cos(\\phi)", + "\\cos(\\theta)", + "\\sin(\\theta)\\sin(\\phi)", + ], + [ + "-\\sin(\\phi)", + "0", + "\\cos(\\phi)" + ] + ]) + matrix.scale(0.7) + for mob in axis_and_rotation, matrix: + mob.to_corner(UP+RIGHT) + + everyone = self.get_everyone() + self.play( + Write(axis_and_rotation), + *it.chain(*zip( + [pi.change_mode for pi in everyone], + ["hooray"]*4, + [pi.look_at for pi in everyone], + [axis_and_rotation]*4, + )), + run_time = 2 + ) + self.random_blink(2) + self.play( + Transform(axis_and_rotation, matrix), + *it.chain(*zip( + [pi.change_mode for pi in everyone], + ["confused"]*4, + [pi.look_at for pi in everyone], + [matrix]*4, + )) + + ) + self.random_blink(3) + +class CommonPattern(TeacherStudentsScene): + def construct(self): + self.teacher_says(""" + This is a common pattern + in linear algebra. + """) + self.random_blink(2) + +class DeduceTransformationFromMatrix(ColumnsToBasisVectors): + def construct(self): + self.setup() + self.move_matrix_columns([[3, 0], [1, 2]]) + +class WordsOnComputation(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "I won't cover the full\\\\", + "details of computation...", + pi_creature_target_mode = "guilty" + ) + self.change_student_modes("angry", "sassy", "angry") + self.random_blink() + self.teacher_says( + "...but I'll hit the \\\\", + "important parts" + ) + self.change_student_modes(*["happy"]*3) + self.random_blink(3) + +class SymbolicEigenvectors(Scene): + def construct(self): + self.introduce_terms() + self.contrast_multiplication_types() + self.rewrite_righthand_side() + self.reveal_as_linear_system() + self.bring_in_determinant() + + def introduce_terms(self): + self.expression = TexMobject( + "A", "\\vec{\\textbf{v}}", "=", + "\\lambda", "\\vec{\\textbf{v}}" + ) + self.expression.scale(1.5) + self.expression.shift(UP+2*LEFT) + A, v1, equals, lamb, v2 = self.expression + vs = VGroup(v1, v2) + vs.highlight(YELLOW) + lamb.highlight(MAROON_B) + + A_brace = Brace(A, UP, buff = 0) + A_text = TextMobject("Transformation \\\\ matrix") + A_text.next_to(A_brace, UP) + + lamb_brace = Brace(lamb, UP, buff = 0) + lamb_text = TextMobject("Eigenvalue") + lamb_text.highlight(lamb.get_color()) + lamb_text.next_to(lamb_brace, UP, aligned_edge = LEFT) + + v_text = TextMobject("Eigenvector") + v_text.highlight(vs.get_color()) + v_text.next_to(vs, DOWN, buff = 1.5*LARGE_BUFF) + v_arrows = VGroup(*[ + Arrow(v_text.get_top(), v.get_bottom()) + for v in vs + ]) + + self.play(Write(self.expression)) + self.dither() + self.play( + GrowFromCenter(A_brace), + Write(A_text) + ) + self.dither() + self.play( + Write(v_text), + ShowCreation(v_arrows) + ) + self.dither() + self.play( + GrowFromCenter(lamb_brace), + Write(lamb_text) + ) + self.dither(2) + self.play(*map(FadeOut, [ + A_brace, A_text, + lamb_brace, lamb_text, + v_text, v_arrows + ])) + + def contrast_multiplication_types(self): + A, v1, equals, lamb, v2 = self.expression + + left_group = VGroup(A, v1) + left_group.brace = Brace(left_group, UP) + left_group.text = left_group.brace.get_text("Matrix-vector multiplication") + + right_group = VGroup(lamb, v2) + right_group.brace = Brace(right_group, DOWN) + right_group.text = right_group.brace.get_text("Scalar multiplication") + right_group.text.highlight(lamb.get_color()) + + for group in left_group, right_group: + self.play( + GrowFromCenter(group.brace), + Write(group.text, run_time = 2) + ) + self.play(group.scale_in_place, 1.2, rate_func = there_and_back) + self.dither() + + morty = Mortimer().to_edge(DOWN) + morty.change_mode("speaking") + bubble = morty.get_bubble("speech", width = 5, direction = LEFT) + VGroup(morty, bubble).to_edge(RIGHT) + solve_text = TextMobject( + "Solve for \\\\", + "$\\lambda$", "and", "$\\vec{\\textbf{v}}$" + ) + solve_text.highlight_by_tex("$\\lambda$", lamb.get_color()) + solve_text.highlight_by_tex("$\\vec{\\textbf{v}}$", v1.get_color()) + bubble.add_content(solve_text) + + self.play( + FadeIn(morty), + FadeIn(bubble), + Write(solve_text) + ) + self.play(Blink(morty)) + self.dither(2) + bubble.write("Fix different", "\\\\ multiplication", "types") + self.play( + Transform(solve_text, bubble.content), + morty.change_mode, "sassy" + ) + self.play(Blink(morty)) + self.dither() + self.play(*map(FadeOut, [ + left_group.brace, left_group.text, + right_group.brace, right_group.text, + morty, bubble, solve_text + ])) + + def rewrite_righthand_side(self): + A, v1, equals, lamb, v2 = self.expression + + lamb_copy = lamb.copy() + scaling_by = VGroup( + TextMobject("Scaling by "), lamb_copy + ) + scaling_by.arrange_submobjects() + arrow = TexMobject("\\Updownarrow") + matrix_multiplication = TextMobject( + "Matrix multiplication by" + ) + matrix = Matrix(np.identity(3, dtype = int)) + + corner_group = VGroup( + scaling_by, arrow, matrix_multiplication, matrix + ) + corner_group.arrange_submobjects(DOWN) + corner_group.to_corner(UP+RIGHT) + + q_marks = VGroup(*[ + TexMobject("?").replace(entry) + for entry in matrix.get_entries() + ]) + q_marks.gradient_highlight(X_COLOR, Y_COLOR, Z_COLOR) + diag_entries = VGroup(*[ + matrix.get_mob_matrix()[i,i] + for i in range(3) + ]) + diag_entries.save_state() + for entry in diag_entries: + new_lamb = TexMobject("\\lambda") + new_lamb.move_to(entry) + new_lamb.highlight(lamb.get_color()) + Transform(entry, new_lamb).update(1) + new_lamb = lamb.copy() + new_lamb.next_to(matrix, LEFT) + id_brace = Brace(matrix) + id_text = TexMobject("I").scale(1.5) + id_text.next_to(id_brace, DOWN) + + self.play( + Transform(lamb.copy(), lamb_copy), + Write(scaling_by) + ) + self.remove(*self.get_mobjects_from_last_animation()) + self.add(scaling_by) + self.play(Write(VGroup( + matrix_multiplication, + arrow, + matrix.get_brackets(), + q_marks, + ), run_time = 2 + )) + self.dither() + self.play(Transform( + q_marks, matrix.get_entries(), + submobject_mode = "lagged_start", + run_time = 2 + )) + self.remove(q_marks) + self.add(*matrix.get_entries()) + self.dither() + self.play( + Transform(diag_entries.copy(), new_lamb), + diag_entries.restore + ) + self.remove(*self.get_mobjects_from_last_animation()) + self.add(new_lamb, diag_entries) + self.play( + GrowFromCenter(id_brace), + Write(id_text) + ) + self.dither() + id_text_copy = id_text.copy() + self.add(id_text_copy) + + l_paren, r_paren = parens = TexMobject("()").scale(1.5) + for mob in lamb, id_text, v2: + mob.target = mob.copy() + VGroup( + l_paren, lamb.target, id_text.target, + r_paren, v2.target + ).arrange_submobjects().next_to(equals).shift(SMALL_BUFF*UP) + self.play( + Write(parens), + *map(MoveToTarget, [lamb, id_text, v2]) + ) + self.dither() + self.play(*map(FadeOut, [ + corner_group, id_brace, id_text_copy, new_lamb + ])) + self.expression = VGroup( + A, v1, equals, + VGroup(l_paren, lamb, id_text, r_paren), + v2 + ) + + def reveal_as_linear_system(self): + A, v1, equals, lamb_group, v2 = self.expression + l_paren, lamb, I, r_paren = lamb_group + zero = TexMobject("\\vec{\\textbf{0}}") + zero.scale(1.3) + zero.next_to(equals, RIGHT) + zero.shift(SMALL_BUFF*UP/2) + minus = TexMobject("-").scale(1.5) + movers = A, v1, lamb_group, v2 + for mob in movers: + mob.target = mob.copy() + VGroup( + A.target, v1.target, minus, + lamb_group.target, v2.target + ).arrange_submobjects().next_to(equals, LEFT) + self.play( + Write(zero), + Write(minus), + *map(MoveToTarget, movers), + path_arc = np.pi/3 + ) + self.dither() + A.target.next_to(minus, LEFT) + l_paren.target = l_paren.copy() + l_paren.target.next_to(A.target, LEFT) + self.play( + MoveToTarget(A), + MoveToTarget(l_paren), + Transform(v1, v2, path_arc = np.pi/3) + ) + self.remove(v1) + self.dither() + + brace = Brace(VGroup(l_paren, r_paren)) + brace.text = TextMobject("This matrix looks \\\\ something like") + brace.text.next_to(brace, DOWN) + brace.text.to_edge(LEFT) + matrix = Matrix([ + ["3-\\lambda", "1", "4"], + ["1", "5-\\lambda", "9"], + ["2", "6", "5-\\lambda"], + ]) + matrix.scale(0.7) + VGroup( + matrix.get_brackets()[1], + *matrix.get_mob_matrix()[:,2] + ).shift(0.5*RIGHT) + matrix.next_to(brace.text, DOWN) + for entry in matrix.get_entries(): + if len(entry.get_tex_string()) > 1: + entry[-1].highlight(lamb.get_color()) + self.play( + GrowFromCenter(brace), + Write(brace.text), + Write(matrix) + ) + self.dither() + + vect_words = TextMobject( + "We want a nonzero solution for" + ) + v_copy = v2.copy().next_to(vect_words) + vect_words.add(v_copy) + vect_words.to_corner(UP+LEFT) + arrow = Arrow(vect_words.get_bottom(), v2.get_top()) + self.play( + Write(vect_words), + ShowCreation(arrow) + ) + self.dither() + + def bring_in_determinant(self): + randy = Randolph(mode = "speaking").to_edge(DOWN) + randy.flip() + randy.look_at(self.expression) + bubble = randy.get_bubble("speech", direction = LEFT, width = 5) + words = TextMobject("We need") + equation = TexMobject( + "\\det(A-", "\\lambda", "I)", "=0" + ) + equation.highlight_by_tex("\\lambda", MAROON_B) + equation.next_to(words, DOWN) + words.add(equation) + bubble.add_content(words) + + self.play( + FadeIn(randy), + ShowCreation(bubble), + Write(words) + ) + self.play(Blink(randy)) + self.dither() + everything = self.get_mobjects() + equation_copy = equation.copy() + self.play( + FadeOut(VGroup(*everything)), + Animation(equation_copy) + ) + self.play( + equation_copy.center, + equation_copy.scale, 1.5 + ) + self.dither() + +class NonZeroSolutionsVisually(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[1, 1], [2, 2]], + "v_coords" : [2, -1] + } + def construct(self): + equation = TexMobject( + "(A-", "\\lambda", "I)", "\\vec{\\textbf{v}}", + "= \\vec{\\textbf{0}}" + ) + equation_matrix = VGroup(*equation[:3]) + equation.highlight_by_tex("\\lambda", MAROON_B) + equation.highlight_by_tex("\\vec{\\textbf{v}}", YELLOW) + equation.add_background_rectangle() + equation.next_to(ORIGIN, LEFT, buff = MED_BUFF) + equation.to_edge(UP) + + det_equation = TexMobject( + "\\text{Squishification} \\Rightarrow", + "\\det", "(A-", "\\lambda", "I", ")", "=0" + ) + det_equation_matrix = VGroup(*det_equation[2:2+4]) + det_equation.highlight_by_tex("\\lambda", MAROON_B) + det_equation.next_to(equation, DOWN, buff = MED_BUFF) + det_equation.to_edge(LEFT) + det_equation.add_background_rectangle() + + + self.add_foreground_mobject(equation) + v = self.add_vector(self.v_coords) + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + transform = Transform( + equation_matrix.copy(), det_equation_matrix + ) + self.play(Write(det_equation), transform) + self.dither() + +class TweakLambda(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[2, 1], [2, 3]], + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_WIDTH, + "secondary_line_ratio" : 1 + }, + } + def construct(self): + matrix = Matrix([ + ["2-0.00", "2"], + ["1", "3-0.00"], + ]) + matrix.add_to_back(BackgroundRectangle(matrix)) + matrix.next_to(ORIGIN, LEFT, buff = LARGE_BUFF) + matrix.to_edge(UP) + + self.lambda_vals = [] + for i in range(2): + entry = matrix.get_mob_matrix()[i,i] + place_holders = VGroup(*entry[2:]) + entry.remove(*place_holders) + place_holders.highlight(MAROON_B) + self.lambda_vals.append(place_holders) + + + brace = Brace(matrix) + brace_text = TexMobject("(A-", "\\lambda", "I)") + brace_text.highlight_by_tex("\\lambda", MAROON_B) + brace_text.next_to(brace, DOWN) + brace_text.add_background_rectangle() + + det_text = get_det_text(matrix) + equals = TexMobject("=").next_to(det_text) + det = DecimalNumber(np.linalg.det(self.t_matrix)) + det.highlight(YELLOW) + det.next_to(equals) + det.rect = BackgroundRectangle(det) + + self.det = det + + self.matrix = VGroup(matrix, brace, brace_text) + self.add_foreground_mobject( + self.matrix, *self.lambda_vals + ) + self.add_unit_square() + self.plane_mobjects = [ + self.plane, self.square, + ] + for mob in self.plane_mobjects: + mob.save_state() + self.apply_transposed_matrix(self.t_matrix) + self.play( + Write(det_text), + Write(equals), + ShowCreation(det.rect), + Write(det) + ) + self.matrix.add(det_text, equals, det.rect) + self.dither() + + self.range_lambda(0, 3, run_time = 5) + self.dither() + self.range_lambda(3, 0.5, run_time = 5) + self.dither() + self.range_lambda(0.5, 1.5, run_time = 3) + self.dither() + self.range_lambda(1.5, 1, run_time = 2) + self.dither() + + def get_t_matrix(self, lambda_val): + return self.t_matrix - lambda_val*np.identity(2) + + def range_lambda(self, start_val, end_val, run_time = 3): + alphas = np.linspace(0, 1, run_time/self.frame_duration) + matrix_transform = self.get_matrix_transformation( + self.get_t_matrix(end_val) + ) + transformations = [] + for mob in self.plane_mobjects: + mob.target = mob.copy().restore().apply_function(matrix_transform) + transformations.append(MoveToTarget(mob)) + transformations += [ + Transform( + self.i_hat, + Vector(matrix_transform(RIGHT), color = X_COLOR) + ), + Transform( + self.j_hat, + Vector(matrix_transform(UP), color = Y_COLOR) + ), + ] + for alpha in alphas: + self.clear() + val = interpolate(start_val, end_val, alpha) + new_t_matrix = self.get_t_matrix(val) + for transformation in transformations: + transformation.update(alpha) + self.add(transformation.mobject) + self.add(self.matrix) + + new_lambda_vals = [] + for lambda_val in self.lambda_vals: + new_lambda = DecimalNumber(val) + new_lambda.move_to(lambda_val, aligned_edge = LEFT) + new_lambda.highlight(lambda_val.get_color()) + new_lambda_vals.append(new_lambda) + self.lambda_vals = new_lambda_vals + self.add(*self.lambda_vals) + + new_det = DecimalNumber( + np.linalg.det(self.t_matrix - val*np.identity(2)) + ) + new_det.move_to(self.det, aligned_edge = LEFT) + new_det.highlight(self.det.get_color()) + self.det = new_det + self.add(self.det) + + self.dither(self.frame_duration) + +class ShowEigenVectorAfterComputing(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[2, 1], [2, 3]], + "v_coords" : [2, -1], + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_WIDTH, + "secondary_line_ratio" : 1 + }, + } + def construct(self): + matrix = Matrix(self.t_matrix.T) + matrix.add_to_back(BackgroundRectangle(matrix)) + matrix.next_to(ORIGIN, RIGHT) + matrix.shift(self.v_coords[0]*RIGHT) + self.add_foreground_mobject(matrix) + + v_label = TexMobject( + "\\vec{\\textbf{v}}", + "=", + "1", + "\\vec{\\textbf{v}}", + ) + v_label.next_to(matrix, RIGHT) + v_label.highlight_by_tex("\\vec{\\textbf{v}}", YELLOW) + v_label.highlight_by_tex("1", MAROON_B) + v_label.add_background_rectangle() + + v = self.add_vector(self.v_coords) + eigenvector = TextMobject("Eigenvector") + eigenvector.add_background_rectangle() + eigenvector.next_to(ORIGIN, DOWN+RIGHT) + eigenvector.rotate(v.get_angle()) + self.play(Write(eigenvector)) + self.add_foreground_mobject(eigenvector) + + line = Line(v.get_end()*(-4), v.get_end()*4, color = MAROON_B) + self.play(Write(v_label)) + self.add_foreground_mobject(v_label) + self.play(ShowCreation(line), Animation(v)) + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class LineOfReasoning(Scene): + def construct(self): + v_tex = "\\vec{\\textbf{v}}" + expressions = VGroup(*it.starmap(TexMobject, [ + ("A", v_tex, "=", "\\lambda", v_tex), + ("A", v_tex, "-", "\\lambda", "I", v_tex, "=", "0"), + ("(", "A", "-", "\\lambda", "I)", v_tex, "=", "0"), + ("\\det(A-", "\\lambda", "I)", "=", "0") + ])) + expressions.arrange_submobjects(DOWN, buff = LARGE_BUFF/2.) + for expression in expressions: + for i, expression_part in enumerate(expression.expression_parts): + if expression_part == "=": + equals = expression[i] + expression.shift(equals.get_center()[0]*LEFT) + break + expression.highlight_by_tex(v_tex, YELLOW) + expression.highlight_by_tex("\\lambda", MAROON_B) + self.play(FadeIn(expression)) + self.dither() + +class IfYouDidntKnowDeterminants(TeacherStudentsScene): + def construct(self): + expression = TexMobject("\\det(A-", "\\lambda", ")=0") + expression.highlight_by_tex("\\lambda", MAROON_B) + expression.scale(1.3) + self.teacher_says(expression) + self.random_blink() + student = self.get_students()[0] + bubble = get_small_bubble(student) + bubble.write("Wait...why?") + self.play( + ShowCreation(bubble), + Write(bubble.content), + student.change_mode, "confused" + ) + self.random_blink(4) + +class RevisitExampleTransformation(ExampleTranformationScene): + def construct(self): + self.introduce_matrix() + + seeking_eigenvalue = TextMobject("Seeking eigenvalue") + seeking_eigenvalue.add_background_rectangle() + lamb = TexMobject("\\lambda") + lamb.highlight(MAROON_B) + words = VGroup(seeking_eigenvalue, lamb) + words.arrange_submobjects() + words.next_to(self.matrix, DOWN, buff = LARGE_BUFF) + self.play(Write(words)) + self.dither() + self.play(*self.get_lambda_to_diag_movements(lamb.copy())) + self.add_foreground_mobject(*self.get_mobjects_from_last_animation()) + self.dither() + self.show_determinant(to_fade = words) + self.show_diagonally_altered_transform() + self.show_unaltered_transform() + + def introduce_matrix(self): + self.matrix.shift(2*LEFT) + for mob in self.plane, self.i_hat, self.j_hat: + mob.save_state() + self.remove_matrix() + i_coords = Matrix(self.t_matrix[0]) + j_coords = Matrix(self.t_matrix[1]) + + self.apply_transposed_matrix(self.t_matrix) + for coords, vect in (i_coords, self.i_hat), (j_coords, self.j_hat): + coords.highlight(vect.get_color()) + coords.scale(0.8) + coords.rect = BackgroundRectangle(coords) + coords.add_to_back(coords.rect) + coords.next_to( + vect.get_end(), + RIGHT+DOWN if coords is i_coords else RIGHT + ) + self.play( + Write(i_coords), + Write(j_coords), + ) + self.dither() + self.play(*[ + Transform(*pair) + for pair in [ + (i_coords.rect, self.matrix.rect), + (i_coords.get_brackets(), self.matrix.get_brackets()), + ( + i_coords.get_entries(), + VGroup(*self.matrix.get_mob_matrix()[:,0]) + ) + ] + ]) + to_remove = self.get_mobjects_from_last_animation() + self.play( + FadeOut(j_coords.get_brackets()), + FadeOut(j_coords.rect), + Transform( + j_coords.get_entries(), + VGroup(*self.matrix.get_mob_matrix()[:,1]) + ), + ) + to_remove += self.get_mobjects_from_last_animation() + self.dither() + self.remove(*to_remove) + self.add_foreground_mobject(self.matrix) + + def get_lambda_to_diag_movements(self, lamb): + three, two = [self.matrix.get_mob_matrix()[i, i] for i in range(2)] + l_bracket, r_bracket = self.matrix.get_brackets() + rect = self.matrix.rect + lamb_copy = lamb.copy() + movers = [rect, three, two, l_bracket, r_bracket, lamb, lamb_copy] + for mover in movers: + mover.target = mover.copy() + minus1, minus2 = [TexMobject("-") for x in range(2)] + new_three = VGroup(three.target, minus1, lamb.target) + new_three.arrange_submobjects() + new_three.move_to(three) + new_two = VGroup(two.target, minus2, lamb_copy.target) + new_two.arrange_submobjects() + new_two.move_to(two) + l_bracket.target.next_to(VGroup(new_three, new_two), LEFT) + r_bracket.target.next_to(VGroup(new_three, new_two), RIGHT) + rect.target = BackgroundRectangle( + VGroup(l_bracket.target, r_bracket.target) + ) + result = map(MoveToTarget, movers) + result += map(Write, [minus1, minus2]) + result += map(Animation, [ + self.matrix.get_mob_matrix()[i, 1-i] + for i in range(2) + ]) + self.diag_entries = [ + VGroup(three, minus1, lamb), + VGroup(two, minus2, lamb_copy), + ] + return result + + def show_determinant(self, to_fade = None): + det_text = get_det_text(self.matrix) + equals = TexMobject("=").next_to(det_text) + three_minus_lamb, two_minus_lamb = diag_entries = [ + entry.copy() for entry in self.diag_entries + ] + one = self.matrix.get_mob_matrix()[0, 1].copy() + zero = self.matrix.get_mob_matrix()[1, 0].copy() + for entry in diag_entries + [one, zero]: + entry.target = entry.copy() + lp1, rp1, lp2, rp2 = parens = TexMobject("()()") + minus = TexMobject("-") + cdot = TexMobject("\\cdot") + VGroup( + lp1, three_minus_lamb.target, rp1, + lp2, two_minus_lamb.target, rp2, + minus, one.target, cdot, zero.target + ).arrange_submobjects().next_to(equals) + + parens.add_background_rectangle() + new_rect = BackgroundRectangle(VGroup(minus, zero.target)) + + brace = Brace(new_rect, buff = 0) + brace_text = brace.get_text("Equals 0, so ", "ignore") + brace_text.add_background_rectangle() + + brace.target = Brace(parens) + brace_text.target = brace.target.get_text( + "Quadratic polynomial in ", "$\\lambda$" + ) + brace_text.target.highlight_by_tex("$\\lambda$", MAROON_B) + brace_text.target.add_background_rectangle() + + equals_0 = TexMobject("=0") + equals_0.next_to(parens, RIGHT) + equals_0.add_background_rectangle() + + final_brace = Brace(VGroup(parens, equals_0)) + final_text = TexMobject( + "\\lambda", "=2", "\\text{ or }", + "\\lambda", "=3" + ) + final_text.highlight_by_tex("\\lambda", MAROON_B) + final_text.next_to(final_brace, DOWN) + lambda_equals_two = VGroup(*final_text[:2]).copy() + lambda_equals_two.add_to_back(BackgroundRectangle(lambda_equals_two)) + final_text.add_background_rectangle() + + self.play( + Write(det_text), + Write(equals) + ) + self.dither() + self.play( + Write(parens), + MoveToTarget(three_minus_lamb), + MoveToTarget(two_minus_lamb), + run_time = 2 + ) + self.dither() + self.play( + FadeIn(new_rect), + MoveToTarget(one), + MoveToTarget(zero), + Write(minus), + Write(cdot), + run_time = 2 + ) + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.dither() + self.play(*it.chain( + map(MoveToTarget, [brace, brace_text]), + map(FadeOut, [one, zero, minus, cdot, new_rect]) + )) + self.dither() + self.play(Write(equals_0)) + self.dither() + self.play( + Transform(brace, final_brace), + Transform(brace_text, final_text) + ) + self.dither() + faders = [ + det_text, equals, parens, + three_minus_lamb, two_minus_lamb, + brace, brace_text, equals_0, + ] + if to_fade is not None: + faders.append(to_fade) + self.play(*it.chain( + map(FadeOut, faders), + [ + lambda_equals_two.scale_in_place, 1.3, + lambda_equals_two.next_to, self.matrix, DOWN + ] + )) + self.add_foreground_mobject(lambda_equals_two) + self.lambda_equals_two = lambda_equals_two + self.dither() + + def show_diagonally_altered_transform(self): + for entry in self.diag_entries: + lamb = entry[-1] + two = TexMobject("2") + two.highlight(lamb.get_color()) + two.move_to(lamb) + self.play(Transform(lamb, two)) + self.play(*it.chain( + [mob.restore for mob in self.plane, self.i_hat, self.j_hat], + map(Animation, self.foreground_mobjects), + )) + + xy_array = Matrix(["x", "y"]) + xy_array.highlight(YELLOW) + zero_array = Matrix([0, 0]) + for array in xy_array, zero_array: + array.scale_to_fit_height(self.matrix.get_height()) + array.add_to_back(BackgroundRectangle(array)) + xy_array.next_to(self.matrix) + equals = TexMobject("=").next_to(xy_array) + equals.add_background_rectangle() + zero_array.next_to(equals) + self.play(*map(Write, [xy_array, equals, zero_array])) + self.dither() + + vectors = VGroup(*[ + self.add_vector(u*x*(LEFT+UP), animate = False) + for x in range(4, 0, -1) + for u in -1, 1 + ]) + vectors.gradient_highlight(MAROON_B, YELLOW) + vectors.save_state() + self.play( + ShowCreation( + vectors, + submobject_mode = "lagged_start", + run_time = 2 + ), + *map(Animation, self.foreground_mobjects) + ) + self.dither() + self.apply_transposed_matrix( + self.t_matrix - 2*np.identity(2) + ) + self.dither() + self.play(*it.chain( + [mob.restore for mob in self.plane, self.i_hat, self.j_hat, vectors], + map(FadeOut, [xy_array, equals, zero_array]), + map(Animation, self.foreground_mobjects) + )) + + def show_unaltered_transform(self): + movers = [] + faders = [] + for entry in self.diag_entries: + mover = entry[0] + faders += list(entry[1:]) + mover.target = mover.copy() + mover.target.move_to(entry) + movers.append(mover) + brace = Brace(self.matrix) + brace_text = brace.get_text("Unaltered matrix") + brace_text.add_background_rectangle() + self.lambda_equals_two.target = brace_text + movers.append(self.lambda_equals_two) + self.play(*it.chain( + map(MoveToTarget, movers), + map(FadeOut, faders), + [GrowFromCenter(brace)] + )) + VGroup(*faders).set_fill(opacity = 0) + self.add_foreground_mobject(brace) + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class ThereMightNotBeEigenvectors(TeacherStudentsScene): + def construct(self): + self.teacher_says(""" + There could be + \\emph{no} eigenvectors + """) + self.random_blink(3) + +class Rotate90Degrees(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[0, 1], [-1, 0]], + "example_vector_coords" : None, + } + def setup(self): + LinearTransformationScene.setup(self) + matrix = Matrix(self.t_matrix.T) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrix.next_to(ORIGIN, LEFT) + matrix.to_edge(UP) + matrix.rect = BackgroundRectangle(matrix) + matrix.add_to_back(matrix.rect) + self.add_foreground_mobject(matrix) + self.matrix = matrix + if self.example_vector_coords is not None: + v = self.add_vector(self.example_vector_coords, animate = False) + line = Line(v.get_end()*(-4), v.get_end()*4, color = MAROON_B) + self.play(ShowCreation(line), Animation(v)) + self.add_foreground_mobject(line) + + def construct(self): + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class Rotate90DegreesWithVector(Rotate90Degrees): + CONFIG = { + "example_vector_coords" : [1, 2] + } + +class SolveRotationEigenvalues(Rotate90Degrees): + def construct(self): + self.apply_transposed_matrix(self.t_matrix, run_time = 0) + self.dither() + diag_entries = [ + self.matrix.get_mob_matrix()[i, i] + for i in range(2) + ] + off_diag_entries = [ + self.matrix.get_mob_matrix()[i, 1-i] + for i in range(2) + ] + for entry in diag_entries: + minus_lambda = TexMobject("-\\lambda") + minus_lambda.highlight(MAROON_B) + minus_lambda.move_to(entry) + self.play(Transform(entry, minus_lambda)) + self.dither() + + det_text = get_det_text(self.matrix) + equals = TexMobject("=").next_to(det_text) + self.play(*map(Write, [det_text, equals])) + self.dither() + minus = TexMobject("-") + for entries, sym in (diag_entries, equals), (off_diag_entries, minus): + lp1, rp1, lp2, rp2 = parens = TexMobject("()()") + for entry in entries: + entry.target = entry.copy() + group = VGroup( + lp1, entries[0].target, rp1, + lp2, entries[1].target, rp2, + ) + group.arrange_submobjects() + group.next_to(sym) + parens.add_background_rectangle() + self.play( + Write(parens), + *[MoveToTarget(entry.copy()) for entry in entries], + run_time = 2 + ) + self.dither() + if entries == diag_entries: + minus.next_to(parens) + self.play(Write(minus)) + polynomial = TexMobject( + "=", "\\lambda^2", "+1=0" + ) + polynomial.highlight_by_tex("\\lambda^2", MAROON_B) + polynomial.add_background_rectangle() + polynomial.next_to(equals, DOWN, buff = 2*MED_BUFF, aligned_edge = LEFT) + self.play(Write(polynomial)) + self.dither() + + result = TexMobject( + "\\lambda", "= i", "\\text{ or }", + "\\lambda", "= -i" + ) + result.highlight_by_tex("\\lambda", MAROON_B) + result.add_background_rectangle() + result.next_to(polynomial, DOWN, buff = 2*MED_BUFF, aligned_edge = LEFT) + self.play(Write(result)) + self.dither() + + interesting_tidbit = TextMobject(""" + Interestingly, though, the fact that multiplication by i + in the complex plane looks like a 90 degree rotation is + related to the fact that i is an eigenvalue of this + transformation of 2d real vectors. The specifics of this + are a little beyond what I want to talk about today, but + note that that eigenvalues which are complex numbers + generally correspond to some kind of rotation in the + transformation. + """, alignment = "") + interesting_tidbit.add_background_rectangle() + interesting_tidbit.scale_to_fit_height(SPACE_HEIGHT-0.5) + interesting_tidbit.to_corner(DOWN+RIGHT) + self.play(FadeIn(interesting_tidbit)) + self.dither() + +class ShearExample(RevisitExampleTransformation): + CONFIG = { + "t_matrix" : [[1, 0], [1, 1]], + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + "secondary_line_ratio" : 1 + }, + } + def construct(self): + self.plane.fade() + self.introduce_matrix() + self.point_out_eigenvectors() + lamb = TexMobject("\\lambda") + lamb.highlight(MAROON_B) + lamb.next_to(self.matrix, DOWN) + self.play(FadeIn(lamb)) + self.play(*self.get_lambda_to_diag_movements(lamb)) + self.add_foreground_mobject(*self.get_mobjects_from_last_animation()) + self.dither() + self.show_determinant() + + def point_out_eigenvectors(self): + vectors = VGroup(*[ + self.add_vector(u*x*RIGHT, animate = False) + for x in range(int(SPACE_WIDTH)+1, 0, -1) + for u in -1, 1 + ]) + vectors.gradient_highlight(YELLOW, X_COLOR) + words = VGroup( + TextMobject("Eigenvectors"), + TextMobject("with eigenvalue", "1") + ) + for word in words: + word.highlight_by_tex("1", MAROON_B) + word.add_to_back(BackgroundRectangle(word)) + words.arrange_submobjects(DOWN, buff = MED_BUFF) + words.next_to(ORIGIN, DOWN+RIGHT, buff = MED_BUFF) + self.play(ShowCreation(vectors), run_time = 2) + self.play(Write(words)) + self.dither() + + def show_determinant(self): + det_text = get_det_text(self.matrix) + equals = TexMobject("=").next_to(det_text) + three_minus_lamb, two_minus_lamb = diag_entries = [ + entry.copy() for entry in self.diag_entries + ] + one = self.matrix.get_mob_matrix()[0, 1].copy() + zero = self.matrix.get_mob_matrix()[1, 0].copy() + for entry in diag_entries + [one, zero]: + entry.target = entry.copy() + lp1, rp1, lp2, rp2 = parens = TexMobject("()()") + minus = TexMobject("-") + cdot = TexMobject("\\cdot") + VGroup( + lp1, three_minus_lamb.target, rp1, + lp2, two_minus_lamb.target, rp2, + minus, one.target, cdot, zero.target + ).arrange_submobjects().next_to(equals) + + parens.add_background_rectangle() + new_rect = BackgroundRectangle(VGroup(minus, zero.target)) + + brace = Brace(new_rect, buff = 0) + brace_text = brace.get_text("Equals 0, so ", "ignore") + brace_text.add_background_rectangle() + + brace.target = Brace(parens) + brace_text.target = brace.target.get_text( + "Quadratic polynomial in ", "$\\lambda$" + ) + brace_text.target.highlight_by_tex("$\\lambda$", MAROON_B) + brace_text.target.add_background_rectangle() + + equals_0 = TexMobject("=0") + equals_0.next_to(parens, RIGHT) + equals_0.add_background_rectangle() + + final_brace = Brace(VGroup(parens, equals_0)) + final_text = TexMobject("\\lambda", "=1") + final_text.highlight_by_tex("\\lambda", MAROON_B) + final_text.next_to(final_brace, DOWN) + lambda_equals_two = VGroup(*final_text[:2]).copy() + lambda_equals_two.add_to_back(BackgroundRectangle(lambda_equals_two)) + final_text.add_background_rectangle() + + self.play( + Write(det_text), + Write(equals) + ) + self.dither() + self.play( + Write(parens), + MoveToTarget(three_minus_lamb), + MoveToTarget(two_minus_lamb), + run_time = 2 + ) + self.dither() + self.play( + FadeIn(new_rect), + MoveToTarget(one), + MoveToTarget(zero), + Write(minus), + Write(cdot), + run_time = 2 + ) + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.dither() + self.play(* map(FadeOut, [ + one, zero, minus, cdot, new_rect, brace, brace_text + ])) + self.dither() + self.play(Write(equals_0)) + self.dither() + self.play( + FadeIn(final_brace), + FadeIn(final_text) + ) + self.dither() + # faders = [ + # det_text, equals, parens, + # three_minus_lamb, two_minus_lamb, + # brace, brace_text, equals_0, + # ] + # if to_fade is not None: + # faders.append(to_fade) + # self.play(*it.chain( + # map(FadeOut, faders), + # [ + # lambda_equals_two.scale_in_place, 1.3, + # lambda_equals_two.next_to, self.matrix, DOWN + # ] + # )) + # self.add_foreground_mobject(lambda_equals_two) + # self.lambda_equals_two = lambda_equals_two + # self.dither() + +class ScalingExample(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[2, 0], [0, 2]] + } + def construct(self): + matrix = Matrix(self.t_matrix.T) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrix.add_to_back(BackgroundRectangle(matrix)) + matrix.next_to(ORIGIN, LEFT) + matrix.to_edge(UP) + words = TextMobject("Scale everything by 2") + words.add_background_rectangle() + words.next_to(matrix, RIGHT) + self.add_foreground_mobject(matrix, words) + for coords in [2, 1], [-2.5, -1], [1, -1]: + self.add_vector(coords, color = random_color()) + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class IntroduceEigenbasis(TeacherStudentsScene): + def construct(self): + words1, words2 = map(TextMobject, [ + "Finish with ``eigenbasis.''", + """Make sure you've + watched the last video""" + ]) + words1.highlight(YELLOW) + self.teacher_says(words1) + self.change_student_modes( + "pondering", "raise_right_hand", "erm" + ) + self.random_blink() + new_words = VGroup(words1.copy(), words2) + new_words.arrange_submobjects(DOWN, buff = MED_BUFF) + new_words.scale(0.8) + self.teacher.bubble.add_content(new_words) + self.play( + self.get_teacher().change_mode, "sassy", + Write(words2), + Transform(words1, new_words[0]) + ) + student = self.get_students()[0] + self.play( + student.change_mode, "guilty", + student.look, LEFT + ) + self.random_blink(2) + +class BasisVectorsAreEigenvectors(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[-1, 0], [0, 2]] + } + def construct(self): + matrix = Matrix(self.t_matrix.T) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrix.next_to(ORIGIN, LEFT) + matrix.to_edge(UP) + + words = TextMobject( + "What if both basis vectors \\\\", + "are eigenvectors?" + ) + for word in words: + word.add_to_back(BackgroundRectangle(word)) + words.to_corner(UP+RIGHT) + + self.play(Write(words)) + self.add_foreground_mobject(words) + self.dither() + self.apply_transposed_matrix([self.t_matrix[0], [0, 1]]) + self.dither() + self.apply_transposed_matrix([[1, 0], self.t_matrix[1]]) + self.dither() + + i_coords = Matrix(self.t_matrix[0]) + i_coords.next_to(self.i_hat.get_end(), DOWN+LEFT) + i_coords.highlight(X_COLOR) + j_coords = Matrix(self.t_matrix[1]) + j_coords.next_to(self.j_hat.get_end(), RIGHT) + j_coords.highlight(Y_COLOR) + + for array in matrix, i_coords, j_coords: + array.rect = BackgroundRectangle(array) + array.add_to_back(array.rect) + self.play(*map(Write, [i_coords, j_coords])) + self.dither() + self.play( + Transform(i_coords.rect, matrix.rect), + Transform(i_coords.get_brackets(), matrix.get_brackets()), + i_coords.get_entries().move_to, VGroup( + *matrix.get_mob_matrix()[:,0] + ) + ) + self.play( + FadeOut(j_coords.rect), + FadeOut(j_coords.get_brackets()), + j_coords.get_entries().move_to, VGroup( + *matrix.get_mob_matrix()[:,1] + ) + ) + self.remove(i_coords, j_coords) + self.add(matrix) + self.dither() + + + diag_entries = VGroup(*[ + matrix.get_mob_matrix()[i, i] + for i in range(2) + ]) + off_diag_entries = VGroup(*[ + matrix.get_mob_matrix()[1-i, i] + for i in range(2) + ]) + for entries in diag_entries, off_diag_entries: + self.play( + entries.scale_in_place, 1.3, + entries.highlight, YELLOW, + run_time = 2, + rate_func = there_and_back + ) + self.dither() + +class DefineDiagonalMatrix(Scene): + def construct(self): + n_dims = 4 + numerical_matrix = np.identity(n_dims, dtype = 'int') + for x in range(n_dims): + numerical_matrix[x, x] = random.randint(-9, 9) + matrix = Matrix(numerical_matrix) + diag_entries = VGroup(*[ + matrix.get_mob_matrix()[i,i] + for i in range(n_dims) + ]) + off_diag_entries = VGroup(*[ + matrix.get_mob_matrix()[i, j] + for i in range(n_dims) + for j in range(n_dims) + if i != j + ]) + + title = TextMobject("``Diagonal matrix''") + title.to_edge(UP) + + self.add(matrix) + self.dither() + for entries in off_diag_entries, diag_entries: + self.play( + entries.scale_in_place, 1.1, + entries.highlight, YELLOW, + rate_func = there_and_back, + ) + self.dither() + self.play(Write(title)) + self.dither() + self.play( + matrix.highlight_columns, + X_COLOR, Y_COLOR, Z_COLOR, YELLOW + ) + self.dither() + self.play(diag_entries.highlight, MAROON_B) + self.play( + diag_entries.scale_in_place, 1.1, + rate_func = there_and_back, + ) + self.dither() + +class RepeatedMultilpicationOfMatrices(Scene): + CONFIG = { + "matrix" : [[3, 0], [0, 2]], + "diagonal" : True, + } + def construct(self): + vector = Matrix(["x", "y"]) + vector.highlight(YELLOW) + matrix = Matrix(self.matrix) + matrix.highlight_columns(X_COLOR, Y_COLOR) + matrices = VGroup(*[ + matrix.copy(), + TexMobject("\\dots\\dots"), + matrix.copy(), + matrix.copy(), + matrix.copy(), + ]) + last_matrix = matrices[-1] + group = VGroup(*list(matrices) + [vector]) + group.arrange_submobjects() + + brace = Brace(matrices) + brace_text = brace.get_text("100", "times") + hundred = brace_text[0] + hundred_copy = hundred.copy() + + self.add(vector) + for matrix in reversed(list(matrices)): + self.play(FadeIn(matrix)) + self.dither() + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.dither() + + if self.diagonal: + last_matrix.target = last_matrix.copy() + for i, hund in enumerate([hundred, hundred_copy]): + entry = last_matrix.target.get_mob_matrix()[i, i] + hund.target = hund.copy() + hund.target.scale(0.5) + hund.target.next_to(entry, UP+RIGHT, buff = 0) + hund.target.highlight(entry.get_color()) + VGroup(hund.target, entry).move_to(entry, aligned_edge = DOWN) + lb, rb = last_matrix.target.get_brackets() + lb.shift(SMALL_BUFF*LEFT) + rb.shift(SMALL_BUFF*RIGHT) + VGroup( + last_matrix.target, hundred.target, hundred_copy.target + ).next_to(vector, LEFT) + + self.play(*it.chain( + map(FadeOut, [brace, brace_text[1]] + list(matrices[:-1])), + map(MoveToTarget, [hundred, hundred_copy, last_matrix]) + ), run_time = 2) + self.dither() + else: + randy = Randolph().to_corner() + self.play(FadeIn(randy)) + self.play(randy.change_mode, "angry") + self.play(Blink(randy)) + self.dither() + self.play(Blink(randy)) + self.dither() + +class RepeatedMultilpicationOfNonDiagonalMatrices(RepeatedMultilpicationOfMatrices): + CONFIG = { + "matrix" : [[3, 4], [1, 1]], + "diagonal" : False, + } + +class WhatAreTheOddsOfThat(TeacherStudentsScene): + def construct(self): + self.student_says(""" + Sure, but what are the + odds of that happening? + """) + self.random_blink() + self.change_student_modes("pondering") + self.random_blink(3) + +class ChangeToEigenBasis(ExampleTranformationScene): + CONFIG = { + "show_basis_vectors" : False + } + def construct(self): + self.plane.fade() + self.introduce_eigenvectors() + self.write_change_of_basis_matrix() + self.ask_about_power() + + def introduce_eigenvectors(self): + x_vectors, v_vectors = [ + VGroup(*[ + self.add_vector(u*x*vect, animate = False) + for x in range(num, 0, -1) + for u in -1, 1 + ]) + for vect, num in (RIGHT, 7), (UP+LEFT, 4) + ] + x_vectors.gradient_highlight(YELLOW, X_COLOR) + v_vectors.gradient_highlight(MAROON_B, YELLOW) + self.remove(x_vectors, v_vectors) + self.play(ShowCreation(x_vectors, run_time = 2)) + self.play(ShowCreation(v_vectors, run_time = 2)) + self.dither() + self.plane.save_state() + self.apply_transposed_matrix( + self.t_matrix, + rate_func = there_and_back, + path_arc = 0 + ) + + x_vector = x_vectors[-1] + v_vector = v_vectors[-1] + x_vectors.remove(x_vector) + v_vectors.remove(v_vector) + words = TextMobject("Eigenvectors span space") + new_words = TextMobject("Use eigenvectors as basis") + for text in words, new_words: + text.add_background_rectangle() + text.next_to(ORIGIN, DOWN+LEFT, buff = MED_BUFF) + # text.to_edge(RIGHT) + + self.play(Write(words)) + self.play( + FadeOut(x_vectors), + FadeOut(v_vectors), + Animation(x_vector), + Animation(v_vector), + ) + self.dither() + self.play(Transform(words, new_words)) + self.dither() + self.b1, self.b2 = x_vector, v_vector + self.moving_vectors = [self.b1, self.b2] + self.to_fade = [words] + + def write_change_of_basis_matrix(self): + b1, b2 = self.b1, self.b2 + for vect in b1, b2: + vect.coords = vector_coordinate_label(vect) + vect.coords.highlight(vect.get_color()) + vect.entries = vect.coords.get_entries() + vect.entries.target = vect.entries.copy() + b1.coords.next_to(b1.get_end(), DOWN+RIGHT) + b2.coords.next_to(b2.get_end(), LEFT) + for vect in b1, b2: + self.play(Write(vect.coords)) + self.dither() + + cob_matrix = Matrix(np.array([ + list(vect.entries.target) + for vect in b1, b2 + ]).T) + cob_matrix.rect = BackgroundRectangle(cob_matrix) + cob_matrix.add_to_back(cob_matrix.rect) + cob_matrix.scale_to_fit_height(self.matrix.get_height()) + cob_matrix.next_to(self.matrix) + brace = Brace(cob_matrix) + brace_text = brace.get_text("Change of basis matrix") + brace_text.next_to(brace, DOWN, aligned_edge = LEFT) + brace_text.add_background_rectangle() + + copies = [vect.coords.copy() for vect in b1, b2] + self.to_fade += copies + self.add(*copies) + self.play( + Transform(b1.coords.rect, cob_matrix.rect), + Transform(b1.coords.get_brackets(), cob_matrix.get_brackets()), + MoveToTarget(b1.entries) + ) + to_remove = self.get_mobjects_from_last_animation() + self.play(MoveToTarget(b2.entries)) + to_remove += self.get_mobjects_from_last_animation() + self.remove(*to_remove) + self.add(cob_matrix) + self.to_fade += [b2.coords] + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.to_fade += [brace, brace_text] + self.dither() + + inv_cob = cob_matrix.copy() + inv_cob.target = inv_cob.copy() + neg_1 = TexMobject("-1") + neg_1.add_background_rectangle() + inv_cob.target.next_to( + self.matrix, LEFT, buff = neg_1.get_width()+SMALL_BUFF + ) + neg_1.next_to( + inv_cob.target.get_corner(UP+RIGHT), + RIGHT, + ) + self.play( + MoveToTarget(inv_cob, path_arc = -np.pi/2), + Write(neg_1) + ) + self.dither() + self.add_foreground_mobject(cob_matrix, inv_cob, neg_1) + self.play(*map(FadeOut, self.to_fade)) + self.dither() + self.apply_transposed_matrix(self.t_matrix) + + equals = TexMobject("=").next_to(cob_matrix) + final_matrix = Matrix([[3, 0], [0, 2]]) + final_matrix.add_to_back(BackgroundRectangle(final_matrix)) + for i in range(2): + final_matrix.get_mob_matrix()[i, i].highlight(MAROON_B) + final_matrix.next_to(equals, RIGHT) + self.play( + Write(equals), + Write(final_matrix) + ) + self.dither() + + eigenbasis = TextMobject("``Eigenbasis''") + eigenbasis.add_background_rectangle() + eigenbasis.next_to(ORIGIN, DOWN+LEFT) + self.play(Write(eigenbasis)) + self.dither() + + def ask_about_power(self): + morty = Mortimer() + morty.to_corner(DOWN+RIGHT) + bubble = morty.get_bubble("speech", height = 3, width = 5) + bubble.set_fill(BLACK, opacity = 1) + matrix_copy = self.matrix.copy().scale(0.7) + hundred = TexMobject("100").scale(0.7) + hundred.next_to(matrix_copy.get_corner(UP+RIGHT), RIGHT) + compute = TextMobject("Compute") + compute.next_to(matrix_copy, LEFT, buff = MED_BUFF) + words = VGroup(compute, matrix_copy, hundred) + bubble.add_content(words) + + self.play(FadeIn(morty)) + self.play( + morty.change_mode, "speaking", + ShowCreation(bubble), + Write(words) + ) + for x in range(2): + self.play(Blink(morty)) + self.dither(2) diff --git a/eola/chapter3.py b/eola/chapter3.py index 4722ceec..c2d9b644 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -1083,12 +1083,14 @@ class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication): } class ColumnsToBasisVectors(LinearTransformationScene): + CONFIG = { + "t_matrix" : [[3, 1], [1, 2]] + } def construct(self): self.setup() - transposed_matrix = [[3, 1], [1, 2]] vector_coords = [-1, 2] - vector = self.move_matrix_columns(transposed_matrix, vector_coords) + vector = self.move_matrix_columns(self.t_matrix, vector_coords) self.scale_and_add(vector, vector_coords) self.dither(3) @@ -1096,6 +1098,7 @@ class ColumnsToBasisVectors(LinearTransformationScene): 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.highlight(X_COLOR) col2 = VMobject(*matrix_mob.get_mob_matrix()[:,1]) diff --git a/eola/matrix.py b/eola/matrix.py index cb546a21..fc4039b0 100644 --- a/eola/matrix.py +++ b/eola/matrix.py @@ -48,8 +48,8 @@ def vector_coordinate_label(vector_mob, integer_labels = True, shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT label.shift(shift_dir) label.highlight(color) - background = BackgroundRectangle(label) - label.submobjects = [background] + label.submobjects + label.rect = BackgroundRectangle(label) + label.add_to_back(label.rect) return label class Matrix(VMobject): diff --git a/eola/two_d_space.py b/eola/two_d_space.py index ca45b4c7..3e2c9724 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -394,6 +394,9 @@ class LinearTransformationScene(VectorScene): def get_vector_movement(self, func): for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color = v.get_color()) + norm = np.linalg.norm(v.target.get_end()) + if norm < 0.1: + v.target.get_tip().scale_in_place(norm) return self.get_piece_movement(self.moving_vectors) def get_transformable_label_movement(self): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 9089c38f..f63616ad 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -200,6 +200,7 @@ class VMobject(Mobject): it comes time to display. """ subpath_mobject = self.copy()#TODO, better way? + # subpath_mobject = VMobject() subpath_mobject.is_subpath = True subpath_mobject.set_points(points) self.add(subpath_mobject) diff --git a/topics/characters.py b/topics/characters.py index ae5cb9ca..396b0002 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -248,8 +248,8 @@ class Bubble(SVGMobject): self.content = mobject return self.content - def write(self, text): - self.add_content(TextMobject(text)) + def write(self, *text): + self.add_content(TextMobject(*text)) return self def clear(self): @@ -414,15 +414,19 @@ class TeacherStudentsScene(Scene): self.play(Blink(pi_creature)) self.dither() - def change_student_modes(self, *modes): + def change_student_modes(self, *modes, **kwargs): + added_anims = kwargs.get("added_anims", []) pairs = zip(self.get_students(), modes) start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) - self.play(Transform( - start, target, - submobject_mode = "lagged_start", - run_time = 2 - )) + self.play( + Transform( + start, target, + submobject_mode = "lagged_start", + run_time = 2 + ), + *added_anims + ) def zoom_in_on_thought_bubble(self, radius = SPACE_HEIGHT+SPACE_WIDTH): diff --git a/topics/geometry.py b/topics/geometry.py index 0ac671b0..d625916f 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -203,6 +203,9 @@ class Arrow(Line): self.add(self.tip) self.init_colors() + def get_tip(self): + return self.tip + def scale(self, scale_factor): Line.scale(self, scale_factor) if self.preserve_tip_size_when_scaling: diff --git a/topics/numerals.py b/topics/numerals.py index d435c79e..3d8c8950 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -3,10 +3,9 @@ from mobject.vectorized_mobject import VMobject from mobject.tex_mobject import TexMobject from animation import Animation +from scene import Scene from helpers import * - - class DecimalNumber(VMobject): CONFIG = { "num_decimal_points" : 2, @@ -30,57 +29,66 @@ class DecimalNumber(VMobject): buff = self.digit_to_digit_buff ) +#Todo, this class is now broken - -class RangingValues(Animation): +class RangingValue(Animation): CONFIG = { "num_decimal_points" : 2, "rate_func" : None, - "tracking_function" : None, - "value_function" : None, "tracked_mobject" : None, "tracked_mobject_next_to_kwargs" : {}, - "scale_factor" : None + "scale_factor" : None, + "color" : WHITE, } - def __init__(self, start_val = 0, end_val = 1, **kwargs): + def __init__(self, value_function, **kwargs): + """ + Value function should return a real value + depending on the state of the surrounding scene + """ digest_config(self, kwargs, locals()) - Animation.__init__(self, self.get_mobject_at_alpha(0), **kwargs) + self.update_mobject() + Animation.__init__(self, self.mobject, **kwargs) - def update_mobject(self, alpha): - target = self.get_mobject_at_alpha(alpha) - self.mobject.submobjects = target.submobjects - - def get_number(self, alpha): - if self.value_function: - return self.value_function(alpha) - return interpolate(self.start_val, self.end_val, alpha) - - def get_mobject_at_alpha(self, alpha): - mob = DecimalNumber( - self.get_number(alpha), - num_decimal_points=self.num_decimal_points + def update_mobject(self, alpha = 0): + mobject = DecimalNumber( + self.value_function(), + num_decimal_points = self.num_decimal_points, + color = self.color, ) + if not hasattr(self, "mobject"): + self.mobject = mobject + else: + self.mobject.points = mobject.points + self.mobject.submobjects = mobject.submobjects if self.scale_factor: - mob.scale(self.scale_factor) - if self.tracking_function: - self.tracking_function(alpha, mob) + self.mobject.scale(self.scale_factor) elif self.tracked_mobject: - mob.next_to( + self.mobject.next_to( self.tracked_mobject, **self.tracked_mobject_next_to_kwargs ) - return mob + return self + + +class RangingValueScene(Scene): + CONFIG = { + "ranging_values" : [] + } + + def add_ranging_value(self, value_function, **kwargs): + self.ranging_values.append( + RangingValue(value_function, **kwargs) + ) + + def update_frame(self, *args, **kwargs): + for val in self.ranging_values: + self.remove(val.mobject) + val.update_mobject() + self.add(val.mobject) + return Scene.update_frame(self, *args, **kwargs) + + + + - def set_tracking_function(self, func): - """ - func shoudl be of the form func(alpha, mobject), and - should dictate where to place running number during an - animation - """ - self.tracking_function = func - def set_value_function(self, func): - """ - func must be of the form alpha->number - """ - self.value_function = func