from big_ol_pile_of_manim_imports import *
from ka_playgrounds.circuits import Resistor, Source, LongResistor

class OpeningQuote(Scene):
    def construct(self):
        words = TextMobject(
            "To ask the",
            "right question\\\\",
            "is harder than to answer it." 
        )
        words.to_edge(UP)
        words[1].set_color(BLUE)
        author = TextMobject("-Georg Cantor")
        author.set_color(YELLOW)
        author.next_to(words, DOWN, buff = 0.5)

        self.play(FadeIn(words))
        self.wait(2)
        self.play(Write(author, run_time = 3))
        self.wait()

class ListTerms(Scene):
    def construct(self):
        title = TextMobject("Under the light of linear transformations")
        title.set_color(YELLOW)
        title.to_edge(UP)
        randy = Randolph().to_corner()
        words = VMobject(*list(map(TextMobject, [
            "Inverse matrices", 
            "Column space", 
            "Rank",
            "Null space",
        ])))
        words.arrange(DOWN, aligned_edge = LEFT)
        words.next_to(title, DOWN, aligned_edge = LEFT)
        words.shift(RIGHT)

        self.add(title, randy)
        for i, word in enumerate(words.split()):
            self.play(Write(word), run_time = 1)
            if i%2 == 0:
                self.play(Blink(randy))
            else:
                self.wait()
        self.wait()

class NoComputations(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.student_says(
            "Will you cover \\\\ computations?",
            target_mode = "raise_left_hand"
        )
        self.random_blink()
        self.teacher_says(
            "Well...uh...no",
            target_mode = "guilty",
        )
        self.play(*[
            ApplyMethod(student.change_mode, mode)
            for student, mode in zip(
                self.get_students(),
                ["dejected", "confused", "angry"]
            )
        ])
        self.random_blink()
        self.wait()
        new_words = self.teacher.bubble.position_mobject_inside(
            TextMobject([
                "Search",
                "``Gaussian elimination'' \\\\",
                "and",
                "``Row echelon form''",
            ])
        )
        new_words.split()[1].set_color(YELLOW)
        new_words.split()[3].set_color(GREEN)
        self.play(
            Transform(self.teacher.bubble.content, new_words),
            self.teacher.change_mode, "speaking"
        )
        self.play(*[
            ApplyMethod(student.change_mode, "pondering")
            for student in self.get_students()
        ])
        self.random_blink()

class PuntToSoftware(Scene):
    def construct(self):
        self.play(Write("Let the computers do the computing"))

class UsefulnessOfMatrices(Scene):
    def construct(self):
        title = TextMobject("Usefulness of matrices")
        title.set_color(YELLOW)
        title.to_edge(UP)
        self.add(title)
        self.wait(3) #Play some 3d linear transform over this

        equations = TexMobject("""
            6x - 3y + 2z &= 7 \\\\
            x + 2y + 5z &= 0 \\\\
            2x - 8y - z &= -2 \\\\
        """)
        equations.to_edge(RIGHT, buff = 2)
        syms = VMobject(*np.array(equations.split())[[1, 4, 7]])
        new_syms = VMobject(*[
            m.copy().set_color(c) 
            for m, c in zip(syms.split(), [X_COLOR, Y_COLOR, Z_COLOR])
        ])
        new_syms.arrange(RIGHT, buff = 0.5)
        new_syms.next_to(equations, LEFT, buff = 3)
        sym_brace = Brace(new_syms, DOWN)
        unknowns = sym_brace.get_text("Unknown variables")
        eq_brace = Brace(equations, DOWN)
        eq_words = eq_brace.get_text("Equations")

        self.play(Write(equations))
        self.wait()
        self.play(Transform(syms.copy(), new_syms, path_arc = np.pi/2))
        for brace, words in (sym_brace, unknowns), (eq_brace, eq_words):
            self.play(
                GrowFromCenter(brace),
                Write(words)
            )
            self.wait()

class CircuitDiagram(Scene):
    def construct(self):
        self.add(TextMobject("Voltages").to_edge(UP))

        source = Source()
        p1, p2 = source.get_top(), source.get_bottom()
        r1 = Resistor(p1, p1+2*RIGHT)
        r2 = LongResistor(p1+2*RIGHT, p2+2*RIGHT)
        r3 = Resistor(p1+2*RIGHT, p1+2*2*RIGHT)
        l1 = Line(p1+2*2*RIGHT, p2+2*2*RIGHT)
        l2 = Line(p2+2*2*RIGHT, p2)
        circuit = VMobject(source, r1, r2, r3, l1, l2)
        circuit.center()
        v1 = TexMobject("v_1").next_to(r1, UP)
        v2 = TexMobject("v_2").next_to(r2, RIGHT)
        v3 = TexMobject("v_3").next_to(r3, UP)
        unknowns = VMobject(v1, v2, v3)
        unknowns.set_color(BLUE)

        self.play(ShowCreation(circuit))
        self.wait()
        self.play(Write(unknowns))
        self.wait()

class StockLine(VMobject):
    CONFIG = {
        "num_points" : 15,
        "step_range" : 2
    }
    def generate_points(self):
        points = [ORIGIN]
        for x in range(self.num_points):
            step_size = self.step_range*(random.random() - 0.5)
            points.append(points[-1] + 0.5*RIGHT + step_size*UP)
        self.set_points_as_corners(points)

class StockPrices(Scene):
    def construct(self):
        self.add(TextMobject("Stock prices").to_edge(UP))

        x_axis = Line(ORIGIN, FRAME_X_RADIUS*RIGHT)
        y_axis = Line(ORIGIN, FRAME_Y_RADIUS*UP)
        everyone = VMobject(x_axis, y_axis)
        stock_lines = []
        for color in TEAL, PINK, YELLOW, RED, BLUE:
            sl = StockLine(color = color)
            sl.move_to(y_axis.get_center(), aligned_edge = LEFT)
            everyone.add(sl)
            stock_lines.append(sl)
        everyone.center()

        self.add(x_axis, y_axis)
        self.play(ShowCreation(
            VMobject(*stock_lines),
            run_time = 3,
            lag_ratio = 0.5
        ))
        self.wait()

class MachineLearningNetwork(Scene):
    def construct(self):
        self.add(TextMobject("Machine learning parameters").to_edge(UP))

        layers = []
        for i, num_nodes in enumerate([3, 4, 4, 1]):
            layer = VMobject(*[
                Circle(radius = 0.5, color = YELLOW)
                for x in range(num_nodes)
            ])
            for j, mob in enumerate(layer.split()):
                sym = TexMobject("x_{%d, %d}"%(i, j))
                sym.move_to(mob)
                mob.add(sym)
            layer.arrange(DOWN, buff = 0.5)
            layer.center()
            layers.append(layer)
        VMobject(*layers).arrange(RIGHT, buff = 1.5)
        lines = VMobject()
        for l_layer, r_layer in zip(layers, layers[1:]):
            for l_node, r_node in it.product(l_layer.split(), r_layer.split()):
                lines.add(Line(l_node, r_node))
        lines.set_submobject_colors_by_gradient(BLUE_E, BLUE_A)
        for mob in VMobject(*layers), lines:
            self.play(Write(mob), run_time = 2)
        self.wait()

class ComplicatedSystem(Scene):
    def construct(self):
        system = TexMobject("""
        \\begin{align*}
            \\dfrac{1}{1-e^{2x-3y+4z}} &= 1 \\\\ \\\\
            \\sin(xy) + z^2 &= \\sqrt{y} \\\\ \\\\
            x^2 + y^2 &= e^{-z}
        \\end{align*}
        """)
        system.to_edge(UP)
        randy = Randolph().to_corner(DOWN+LEFT)

        self.add(randy)
        self.play(Write(system, run_time = 1))
        self.play(randy.change_mode, "sassy")
        self.play(Blink(randy))
        self.wait()

class SystemOfEquations(Scene):
    def construct(self):
        equations = self.get_equations()
        self.show_linearity_rules(equations)
        self.describe_organization(equations)
        self.factor_into_matrix(equations)

    def get_equations(self):
        matrix = Matrix([
            [2, 5, 3],
            [4, 0, 8],
            [1, 3, 0]
        ])
        mob_matrix = matrix.get_mob_matrix()
        rhs = list(map(TexMobject, list(map(str, [-3, 0, 2]))))
        variables = list(map(TexMobject, list("xyz")))
        for v, color in zip(variables, [X_COLOR, Y_COLOR, Z_COLOR]):
            v.set_color(color)
        equations = VMobject()
        for row in mob_matrix:
            equation = VMobject(*it.chain(*list(zip(
                row, 
                [v.copy() for v in variables],
                list(map(TexMobject, list("++=")))
            ))))
            equation.arrange(
                RIGHT, buff = 0.1, 
                aligned_edge = DOWN
            )
            equation.split()[4].shift(0.1*DOWN)
            equation.split()[-1].next_to(equation.split()[-2], RIGHT)
            equations.add(equation)
        equations.arrange(DOWN, aligned_edge = RIGHT)
        for eq, rhs_elem in zip(equations.split(), rhs):
            rhs_elem.next_to(eq, RIGHT)
            eq.add(rhs_elem)
        equations.center()
        self.play(Write(equations))
        self.add(equations)
        return equations

    def show_linearity_rules(self, equations):
        top_equation = equations.split()[0]
        other_equations = VMobject(*equations.split()[1:])
        other_equations.save_state()
        scaled_vars = VMobject(*[
            VMobject(*top_equation.split()[3*i:3*i+2])
            for i in range(3)
        ])
        scaled_vars.save_state()
        isolated_scaled_vars = scaled_vars.copy()
        isolated_scaled_vars.scale(1.5)
        isolated_scaled_vars.next_to(top_equation, UP)
        scalars = VMobject(*[m.split()[0] for m in scaled_vars.split()])
        plusses = np.array(top_equation.split())[[2, 5]]

        self.play(other_equations.fade, 0.7)
        self.play(Transform(scaled_vars, isolated_scaled_vars))
        self.play(scalars.set_color, YELLOW, lag_ratio = 0.5)
        self.play(*[
            ApplyMethod(m.scale_in_place, 1.2, rate_func = there_and_back)
            for m in scalars.split()
        ])
        self.wait()
        self.remove(scalars)
        self.play(scaled_vars.restore)
        self.play(*[
            ApplyMethod(p.scale_in_place, 1.5, rate_func = there_and_back)
            for p in plusses
        ])
        self.wait()
        self.show_nonlinearity_examples()
        self.play(other_equations.restore)

    def show_nonlinearity_examples(self):
        squared = TexMobject("x^2")
        squared.split()[0].set_color(X_COLOR)
        sine = TexMobject("\\sin(x)")
        sine.split()[-2].set_color(X_COLOR)
        product = TexMobject("xy")
        product.split()[0].set_color(X_COLOR)
        product.split()[1].set_color(Y_COLOR)


        words = TextMobject("Not allowed!")
        words.set_color(RED)
        words.to_corner(UP+LEFT, buff = 1)
        arrow = Vector(RIGHT, color = RED)
        arrow.next_to(words, RIGHT)
        for mob in squared, sine, product:
            mob.scale(1.7)
            mob.next_to(arrow.get_end(), RIGHT, buff = 0.5)
        circle_slash = Circle(color = RED)
        line = Line(LEFT, RIGHT, color = RED)
        line.rotate(np.pi/4)
        circle_slash.add(line)
        circle_slash.next_to(arrow, RIGHT)
        def draw_circle_slash(mob):
            circle_slash.replace(mob)
            circle_slash.scale_in_place(1.4)
            self.play(ShowCreation(circle_slash), run_time = 0.5)
            self.wait(0.5)
            self.play(FadeOut(circle_slash), run_time = 0.5)

        self.play(
            Write(squared),
            Write(words, run_time = 1),
            ShowCreation(arrow),
        )
        draw_circle_slash(squared)
        for mob in sine, product:
            self.play(Transform(squared, mob))
            draw_circle_slash(mob)
        self.play(*list(map(FadeOut, [words, arrow, squared])))
        self.wait()


    def describe_organization(self, equations):
        variables = VMobject(*it.chain(*[
            eq.split()[:-2]
            for eq in equations.split()
        ]))
        variables.words = "Throw variables on the left"
        constants = VMobject(*[
            eq.split()[-1]
            for eq in equations.split()
        ])
        constants.words = "Lingering constants on the right"
        xs, ys, zs = [
            VMobject(*[
                eq.split()[i]
                for eq in equations.split()
            ])
            for i in (1, 4, 7)
        ]
        ys.words = "Vertically align variables"
        colors = [PINK, YELLOW, BLUE_B, BLUE_C, BLUE_D]
        for mob, color in zip([variables, constants, xs, ys, zs], colors):
            mob.square = Square(color = color)
            mob.square.replace(mob, stretch = True)
            mob.square.scale_in_place(1.1)
            if hasattr(mob, "words"):
                mob.words = TextMobject(mob.words)
                mob.words.set_color(color)
                mob.words.next_to(mob.square, UP)
        ys.square.add(xs.square, zs.square)
        zero_circles = VMobject(*[
            Circle().replace(mob).scale_in_place(1.3)
            for mob in [
                VMobject(*equations.split()[i].split()[j:j+2])
                for i, j in [(1, 3), (2, 6)]
            ]
        ])
        zero_circles.set_color(PINK)
        zero_circles.words = TextMobject("Add zeros as needed")
        zero_circles.words.set_color(zero_circles.get_color())
        zero_circles.words.next_to(equations, UP)

        for mob in variables, constants, ys:
            self.play(
                FadeIn(mob.square),
                FadeIn(mob.words)
            )
            self.wait()
            self.play(*list(map(FadeOut, [mob.square, mob.words])))
        self.play(
            ShowCreation(zero_circles),
            Write(zero_circles.words, run_time = 1)
        )
        self.wait()
        self.play(*list(map(FadeOut, [zero_circles, zero_circles.words])))
        self.wait()
        title = TextMobject("``Linear system of equations''")
        title.scale(1.5)
        title.to_edge(UP)
        self.play(Write(title))
        self.wait()
        self.play(FadeOut(title))


    def factor_into_matrix(self, equations):
        coefficients = np.array([
            np.array(eq.split())[[0, 3, 6]]
            for eq in equations.split()
        ])
        variable_arrays = np.array([
            np.array(eq.split())[[1, 4, 7]]
            for eq in equations.split()
        ])
        rhs_entries = np.array([
            eq.split()[-1]
            for eq in equations.split()
        ])

        matrix = Matrix(copy.deepcopy(coefficients))
        x_array = Matrix(copy.deepcopy(variable_arrays[0]))
        v_array = Matrix(copy.deepcopy(rhs_entries))
        equals = TexMobject("=")
        ax_equals_v = VMobject(matrix, x_array, equals, v_array)
        ax_equals_v.arrange(RIGHT)
        ax_equals_v.to_edge(RIGHT)
        all_brackets = [
            mob.get_brackets()
            for mob in (matrix, x_array, v_array)
        ]

        self.play(equations.to_edge, LEFT)
        arrow = Vector(RIGHT, color = YELLOW)
        arrow.next_to(ax_equals_v, LEFT)
        self.play(ShowCreation(arrow))
        self.play(*it.chain(*[
            [
                Transform(
                    m1.copy(), m2, 
                    run_time = 2,
                    path_arc = -np.pi/2
                )
                for m1, m2 in zip(
                    start_array.flatten(),
                    matrix_mobject.get_entries().split()
                )
            ]
            for start_array, matrix_mobject in [
                (coefficients, matrix),
                (variable_arrays[0], x_array),
                (variable_arrays[1], x_array),
                (variable_arrays[2], x_array),
                (rhs_entries, v_array)
            ]
        ]))
        self.play(*[
            Write(mob)
            for mob in all_brackets + [equals]
        ])
        self.wait()
        self.label_matrix_product(matrix, x_array, v_array)
        
    def label_matrix_product(self, matrix, x_array, v_array):
        matrix.words = "Coefficients"
        matrix.symbol = "A"
        x_array.words = "Variables"
        x_array.symbol = "\\vec{\\textbf{x}}"
        v_array.words = "Constants"
        v_array.symbol = "\\vec{\\textbf{v}}"
        parts = matrix, x_array, v_array
        for mob in parts:
            mob.brace = Brace(mob, UP)
            mob.words = mob.brace.get_text(mob.words)
            mob.words.shift_onto_screen()
            mob.symbol = TexMobject(mob.symbol)
            mob.brace.put_at_tip(mob.symbol)
        x_array.words.set_submobject_colors_by_gradient(
            X_COLOR, Y_COLOR, Z_COLOR
        )
        x_array.symbol.set_color(PINK)
        v_array.symbol.set_color(YELLOW)
        for mob in parts:
            self.play(
                GrowFromCenter(mob.brace),
                FadeIn(mob.words)
            )
            self.wait()
            self.play(*list(map(FadeOut, [mob.brace, mob.words])))
        self.wait()
        for mob in parts:
            self.play(
                FadeIn(mob.brace),
                Write(mob.symbol)
            )
        compact_equation = VMobject(*[
            mob.symbol for mob in parts
        ])
        compact_equation.submobjects.insert(
            2, TexMobject("=").next_to(x_array, RIGHT)
        )
        compact_equation.target = compact_equation.copy()
        compact_equation.target.arrange(buff = 0.1)
        compact_equation.target.to_edge(UP)

        self.play(Transform(
            compact_equation.copy(), 
            compact_equation.target
        ))
        self.wait()

class LinearSystemTransformationScene(LinearTransformationScene):
    def setup(self):
        LinearTransformationScene.setup(self)
        equation = TexMobject([
            "A",
            "\\vec{\\textbf{x}}",
            "=",
            "\\vec{\\textbf{v}}",
        ])
        equation.scale(1.5)
        equation.next_to(ORIGIN, LEFT).to_edge(UP)
        equation.add_background_rectangle()
        self.add_foreground_mobject(equation)
        self.equation = equation
        self.A, self.x, eq, self.v = equation.split()[1].split()
        self.x.set_color(PINK)
        self.v.set_color(YELLOW)

class MentionThatItsATransformation(LinearSystemTransformationScene):
    CONFIG = {
        "t_matrix" : np.array([[2, 1], [2, 3]])
    }
    def construct(self):
        self.setup()
        brace = Brace(self.A)
        words = brace.get_text("Transformation")
        words.add_background_rectangle()
        self.play(GrowFromCenter(brace), Write(words, run_time = 1))
        self.add_foreground_mobject(words, brace)
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()

class LookForX(MentionThatItsATransformation):
    CONFIG = {
        "show_basis_vectors" : False
    }
    def construct(self):
        self.setup()
        v = [-4, - 1]
        x = np.linalg.solve(self.t_matrix.T, v)
        v = Vector(v, color = YELLOW)
        x = Vector(x, color = PINK)
        v_label = self.get_vector_label(v, "v", color = YELLOW)
        x_label = self.get_vector_label(x, "x", color = PINK)
        for label in x_label, v_label:
            label.add_background_rectangle()
        self.play(
            ShowCreation(v),
            Write(v_label)
        )
        self.add_foreground_mobject(v_label)
        x = self.add_vector(x, animate = False)
        self.play(
            ShowCreation(x),
            Write(x_label)
        )
        self.wait()
        self.add(VMobject(x, x_label).copy().fade())
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()

class ThinkAboutWhatsHappening(Scene):
    def construct(self):
        randy = Randolph()
        randy.to_corner()
        bubble = randy.get_bubble(height = 5)
        bubble.add_content(TexMobject("""
            3x + 1y + 4z &= 1 \\\\
            5x + 9y + 2z &= 6 \\\\
            5x + 3y + 5z &= 8
        """))

        self.play(randy.change_mode, "pondering")
        self.play(ShowCreation(bubble))
        self.play(Write(bubble.content, run_time = 2))
        self.play(Blink(randy))
        self.wait()
        everything = VMobject(*self.get_mobjects())
        self.play(
            ApplyFunction(
                lambda m : m.shift(2*DOWN).scale(5),
                everything
            ),
            bubble.content.set_color, BLACK,
            run_time = 2
        )

class SystemOfTwoEquationsTwoUnknowns(Scene):
    def construct(self):
        system = TexMobject("""
            2x + 2y &= -4 \\\\
            1x + 3y &= -1
        """)
        system.to_edge(UP)
        for indices, color in ((1, 9), X_COLOR), ((4, 12), Y_COLOR):
            for i in indices:
                system.split()[i].set_color(color)
        matrix = Matrix([[2, 2], [1, 3]])
        v = Matrix([-4, -1])
        x = Matrix(["x", "y"])
        x.get_entries().set_submobject_colors_by_gradient(X_COLOR, Y_COLOR)
        matrix_system = VMobject(
            matrix, x, TexMobject("="), v
        )
        matrix_system.arrange(RIGHT)
        matrix_system.next_to(system, DOWN, buff = 1)
        matrix.label = "A"
        matrix.label_color = WHITE
        x.label = "\\vec{\\textbf{x}}"
        x.label_color = PINK
        v.label = "\\vec{\\textbf{v}}"
        v.label_color = YELLOW
        for mob in matrix, x, v:
            brace = Brace(mob)
            label = brace.get_text("$%s$"%mob.label)
            label.set_color(mob.label_color)
            brace.add(label)
            mob.brace = brace

        self.add(system)
        self.play(Write(matrix_system))
        self.wait()
        for mob in matrix, v, x:
            self.play(Write(mob.brace))
            self.wait()

class ShowBijectivity(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors" : False,
        "t_matrix" : np.array([[0, -1], [2, 1]])
    }
    def construct(self):
        self.setup()
        vectors = VMobject(*[
            Vector([x, y])
            for x, y in it.product(*[
                np.arange(-int(val)+0.5, int(val)+0.5)
                for val in (FRAME_X_RADIUS, FRAME_Y_RADIUS)
            ])
        ])
        vectors.set_submobject_colors_by_gradient(BLUE_E, PINK)
        dots = VMobject(*[
            Dot(v.get_end(), color = v.get_color())
            for v in vectors.split()
        ])
        titles = [
            TextMobject([
                "Each vector lands on\\\\",
                "exactly one vector"
            ]),
            TextMobject([
                "Every vector has \\\\",
                "been landed on"
            ])
        ]
        for title in titles:
            title.to_edge(UP)
        background = BackgroundRectangle(VMobject(*titles))
        self.add_foreground_mobject(background, titles[0])

        kwargs = {
            "lag_ratio" : 0.5,
            "run_time" : 2
        }
        anims = list(map(Animation, self.foreground_mobjects))
        self.play(ShowCreation(vectors, **kwargs), *anims)
        self.play(Transform(vectors, dots, **kwargs), *anims)
        self.wait()
        self.add_transformable_mobject(vectors)
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()
        self.play(Transform(*titles))
        self.wait()
        self.apply_transposed_matrix(
            np.linalg.inv(self.t_matrix.T).T
        )
        self.wait()

class LabeledExample(LinearSystemTransformationScene):
    CONFIG = {
        "title" : "",
        "t_matrix" : [[0, 0], [0, 0]],
        "show_square" : False,
    }
    def setup(self):
        LinearSystemTransformationScene.setup(self)
        title = TextMobject(self.title)
        title.next_to(self.equation, DOWN, buff = 1)
        title.add_background_rectangle()
        title.shift_onto_screen()
        self.add_foreground_mobject(title)        
        self.title = title
        if self.show_square:
            self.add_unit_square()

    def construct(self):
        self.wait()
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()

class SquishExmapleWithWords(LabeledExample):
    CONFIG = {
        "title" : "$A$ squishes things to a lower dimension",
        "t_matrix" : [[-2, -1], [2, 1]]
    }

class FullRankExmapleWithWords(LabeledExample):
    CONFIG = {
        "title" : "$A$ keeps things 2D",
        "t_matrix" : [[3, 0], [2, 1]]
    }

class SquishExmapleDet(SquishExmapleWithWords):
    CONFIG = {
        "title" : "$\\det(A) = 0$",
        "show_square" : True,
    }

class FullRankExmapleDet(FullRankExmapleWithWords):
    CONFIG = {
        "title" : "$\\det(A) \\ne 0$",
        "show_square" : True,
    }

class StartWithNonzeroDetCase(TeacherStudentsScene):
    def construct(self):
        words = TextMobject(
            "Let's start with \\\\",
            "the", "$\\det(A) \\ne 0$", "case"
        )
        words[2].set_color(TEAL)
        self.teacher_says(words)
        self.random_blink()
        self.play(
            random.choice(self.get_students()).change_mode,
            "happy"
        )
        self.wait()

class DeclareNewTransformation(TeacherStudentsScene):
    def construct(self):
        words = TextMobject(
            "Playing a transformation in\\\\",
            "reverse gives a", "new transformation"
        )
        words[-1].set_color(GREEN)
        self.teacher_says(words)
        self.change_student_modes("pondering", "sassy")
        self.random_blink()

class PlayInReverse(FullRankExmapleDet):
    CONFIG = {
        "show_basis_vectors" : False
    }
    def construct(self):
        FullRankExmapleDet.construct(self)
        v = self.add_vector([-4, -1], color = YELLOW)
        v_label = self.label_vector(v, "v", color = YELLOW)
        self.add(v.copy())
        self.apply_inverse_transpose(self.t_matrix)
        self.play(v.set_color, PINK)
        self.label_vector(v, "x", color = PINK)
        self.wait()

class DescribeInverse(LinearTransformationScene):
    CONFIG = {
        "show_actual_inverse" : False,
        "matrix_label" : "$A$",
        "inv_label" : "$A^{-1}$",
    }
    def construct(self):
        title = TextMobject("Transformation:")
        new_title = TextMobject("Inverse transformation:")
        matrix = Matrix(self.t_matrix.T)
        if not self.show_actual_inverse:
            inv_matrix = matrix.copy()
            neg_1 = TexMobject("-1")
            neg_1.move_to(
                inv_matrix.get_corner(UP+RIGHT), 
                aligned_edge = LEFT
            )
            neg_1.shift(0.1*RIGHT)
            inv_matrix.add(neg_1)
            matrix.add(VectorizedPoint(matrix.get_corner(UP+RIGHT)))
        else:
            inv_matrix = Matrix(np.linalg.inv(self.t_matrix.T).astype('int'))
        matrix.label = self.matrix_label
        inv_matrix.label = self.inv_label
        for m, text in (matrix, title), (inv_matrix, new_title):
            m.add_to_back(BackgroundRectangle(m))
            text.add_background_rectangle()
            m.next_to(text, RIGHT)
            brace = Brace(m)
            label_mob = brace.get_text(m.label)
            label_mob.add_background_rectangle()
            m.add(brace, label_mob)
            text.add(m)
            if text.get_width() > FRAME_WIDTH-1:
                text.set_width(FRAME_WIDTH-1)
            text.center().to_corner(UP+RIGHT)
        matrix.set_color(PINK)
        inv_matrix.set_color(YELLOW)

        self.add_foreground_mobject(title)
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()
        self.play(Transform(title, new_title))
        self.apply_inverse_transpose(self.t_matrix)
        self.wait()

class ClockwiseCounterclockwise(DescribeInverse):
    CONFIG = {
        "t_matrix" : [[0, 1], [-1, 0]],
        "show_actual_inverse" : True,
        "matrix_label" : "$90^\\circ$ Couterclockwise",
        "inv_label" : "$90^\\circ$ Clockwise",
    }

class ShearInverseShear(DescribeInverse):
    CONFIG = {
        "t_matrix" : [[1, 0], [1, 1]],
        "show_actual_inverse" : True,
        "matrix_label" : "Rightward shear",
        "inv_label" : "Leftward shear",
    }

class MultiplyToIdentity(LinearTransformationScene):
    def construct(self):
        self.setup()
        lhs = TexMobject("A^{-1}", "A", "=")
        lhs.scale(1.5)
        A_inv, A, eq = lhs.split()
        identity = Matrix([[1, 0], [0, 1]])
        identity.set_column_colors(X_COLOR, Y_COLOR)
        identity.next_to(eq, RIGHT)
        VMobject(lhs, identity).center().to_corner(UP+RIGHT)
        for mob in A, A_inv, eq:
            mob.add_to_back(BackgroundRectangle(mob))
        identity.background = BackgroundRectangle(identity)

        col1 = VMobject(*identity.get_mob_matrix()[:,0])
        col2 = VMobject(*identity.get_mob_matrix()[:,1])

        A.text = "Transformation"
        A_inv.text = "Inverse transformation"
        product = VMobject(A, A_inv)
        product.text = "Matrix multiplication"
        identity.text = "The transformation \\\\ that does nothing"
        for mob in A, A_inv, product, identity:
            mob.brace = Brace(mob)
            mob.text = mob.brace.get_text(mob.text)
            mob.text.shift_onto_screen()
            mob.text.add_background_rectangle()

        self.add_foreground_mobject(A, A_inv)
        brace, text = A.brace, A.text
        self.play(GrowFromCenter(brace), Write(text), run_time = 1)
        self.add_foreground_mobject(brace, text)
        self.apply_transposed_matrix(self.t_matrix)
        self.play(
            Transform(brace, A_inv.brace),
            Transform(text, A_inv.text),
        )
        self.apply_inverse_transpose(self.t_matrix)
        self.wait()
        self.play(
            Transform(brace, product.brace),
            Transform(text, product.text)
        )
        self.wait()
        self.play(
            Write(identity.background),
            Write(identity.get_brackets()),
            Write(eq),
            Transform(brace, identity.brace),
            Transform(text, identity.text)
        )
        self.wait()
        self.play(Write(col1))
        self.wait()
        self.play(Write(col2))
        self.wait()

class ThereAreComputationMethods(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            There are methods
            to compute $A^{-1}$
        """)
        self.random_blink()
        self.wait()

class TwoDInverseFormula(Scene):
    def construct(self):
        title = TextMobject("If you're curious...")
        title.set_color(YELLOW)
        title.to_edge(UP)
        morty = Mortimer().to_corner(DOWN+RIGHT)
        self.add(title, morty)
        matrix = [["a", "b"], ["c", "d"]]
        scaled_inv = [["d", "-b"], ["-c", "a"]]
        formula = TexMobject("""
            %s^{-1} = \\dfrac{1}{ad-bc} %s
        """%(
            matrix_to_tex_string(matrix),
            matrix_to_tex_string(scaled_inv)
        ))
        self.play(Write(formula))
        self.play(morty.change_mode, "confused")
        self.play(Blink(morty))

class SymbolicInversion(Scene):
    def construct(self):
        vec = lambda s : "\\vec{\\textbf{%s}}"%s
        
        words = TextMobject("Once you have this:")
        words.to_edge(UP, buff = 2)
        inv = TexMobject("A^{-1}")
        inv.set_color(GREEN)
        inv.next_to(words.split()[-1], RIGHT, aligned_edge = DOWN)
        inv2 = inv.copy()

        start = TexMobject("A", vec("x"), "=", vec("v"))
        interim = TexMobject("A^{-1}", "A", vec("x"), "=", "A^{-1}", vec("v"))
        end = TexMobject(vec("x"), "=", "A^{-1}", vec("v"))

        A, x, eq, v = start.split()
        x.set_color(PINK)
        v.set_color(YELLOW)
        interim_mobs = [inv, A, x, eq, inv2, v]
        for i, mob in enumerate(interim_mobs):
            mob.interim = mob.copy().move_to(interim.split()[i])

        self.add(start)
        self.play(Write(words), FadeIn(inv), run_time = 1)
        self.wait()
        self.play(
            FadeOut(words),
            *[Transform(m, m.interim) for m in interim_mobs]
        )
        self.wait()

        product = VMobject(A, inv)
        product.brace = Brace(product)
        product.words = product.brace.get_text(
            "The ``do nothing'' matrix"
        )
        product.words.set_color(BLUE)
        self.play(
            GrowFromCenter(product.brace),
            Write(product.words, run_time = 1),
            product.set_color, BLUE
        )
        self.wait()
        self.play(*[
            ApplyMethod(m.set_color, BLACK)
            for m in (product, product.brace, product.words)
        ])
        self.wait()
        self.play(ApplyFunction(
            lambda m : m.center().to_edge(UP),
            VMobject(x, eq, inv2, v)
        ))
        self.wait()

class PlayInReverseWithSolution(PlayInReverse):
    CONFIG = {
        "t_matrix" : [[2, 1], [2, 3]]
    }
    def setup(self):
        LinearTransformationScene.setup(self)
        equation = TexMobject([
            "\\vec{\\textbf{x}}",
            "=",
            "A^{-1}",
            "\\vec{\\textbf{v}}",
        ])
        equation.to_edge(UP)
        equation.add_background_rectangle()
        self.add_foreground_mobject(equation)
        self.equation = equation
        self.x, eq, self.inv, self.v = equation.split()[1].split()
        self.x.set_color(PINK)
        self.v.set_color(YELLOW)
        self.inv.set_color(GREEN)

class OneUniqueSolution(Scene):
    def construct(self):
        system = TexMobject("""
            \\begin{align*}
                ax + cy &= e \\\\
                bx + dy &= f
            \\end{align*}
        """)
        VMobject(*np.array(system.split())[[1, 8]]).set_color(X_COLOR)
        VMobject(*np.array(system.split())[[4, 11]]).set_color(Y_COLOR)
        brace = Brace(system, UP)
        brace.set_color(YELLOW)
        words = brace.get_text("One unique solution \\dots", "probably")
        words.set_color(YELLOW)
        words.split()[1].set_color(GREEN)

        self.add(system)
        self.wait()
        self.play(
            GrowFromCenter(brace),
            Write(words.split()[0])
        )
        self.wait()
        self.play(Write(words.split()[1], run_time = 1))
        self.wait()

class ThreeDTransformXToV(Scene):
    pass

class ThreeDTransformAndReverse(Scene):
    pass

class DetNEZeroRule(Scene):
    def construct(self):
        text = TexMobject("\\det(A) \\ne 0")
        text.shift(2*UP)
        A_inv = TextMobject("$A^{-1}$ exists")
        A_inv.shift(DOWN)
        arrow = Arrow(text, A_inv)
        self.play(Write(text))
        self.wait()
        self.play(ShowCreation(arrow))
        self.play(Write(A_inv, run_time = 1))
        self.wait()


class ThreeDInverseRule(Scene):
    def construct(self):
        form = TexMobject("A^{-1} A = ")
        form.scale(2)
        matrix = Matrix(np.identity(3, 'int'))
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        matrix.next_to(form, RIGHT)
        self.add(form)
        self.play(Write(matrix))
        self.wait()

class ThreeDApplyReverseToV(Scene):
    pass

class InversesDontAlwaysExist(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("$A^{-1}$ doesn't always exist")
        self.random_blink()
        self.wait()
        self.random_blink()

class InvertNonInvertable(LinearTransformationScene):
    CONFIG = {
        "t_matrix" : [[2, 1], [-2, -1]]
    }
    def setup(self):
        LinearTransformationScene.setup(self)
        det_text = TexMobject("\\det(A) = 0")
        det_text.scale(1.5)
        det_text.to_corner(UP+LEFT)
        det_text.add_background_rectangle()
        self.add_foreground_mobject(det_text)

    def construct(self):
        no_func = TextMobject("No function does this")
        no_func.shift(2*UP)
        no_func.set_color(RED)
        no_func.add_background_rectangle()
        grid = VMobject(self.plane, self.i_hat, self.j_hat)
        grid.save_state()
        self.apply_transposed_matrix(self.t_matrix, path_arc = 0)
        self.wait()
        self.play(Write(no_func, run_time = 1))
        self.add_foreground_mobject(no_func)
        self.play(
            grid.restore,
            *list(map(Animation, self.foreground_mobjects)),
            run_time = 3
        )
        self.wait()

class OneInputMultipleOutputs(InvertNonInvertable):
    def construct(self):
        input_vectors = VMobject(*[
            Vector([x+2, x]) for x in np.arange(-4, 4.5, 0.5)
        ])
        input_vectors.set_submobject_colors_by_gradient(PINK, YELLOW)
        output_vector = Vector([4, 2], color = YELLOW)

        grid = VMobject(self.plane, self.i_hat, self.j_hat)
        grid.save_state()

        self.apply_transposed_matrix(self.t_matrix, path_arc = 0)
        self.play(ShowCreation(output_vector))
        single_input = TextMobject("Single vector")
        single_input.add_background_rectangle()
        single_input.next_to(output_vector.get_end(), UP)
        single_input.set_color(YELLOW) 
        self.play(Write(single_input))
        self.wait()
        self.remove(single_input, output_vector)
        self.play(
            grid.restore,
            *[
                Transform(output_vector.copy(), input_vector)
                for input_vector in input_vectors.split()
            ] + list(map(Animation, self.foreground_mobjects)),
            run_time = 3
        )
        multiple_outputs = TextMobject(
            "Must map to \\\\",
            "multiple vectors"
        )
        multiple_outputs.split()[1].set_submobject_colors_by_gradient(YELLOW, PINK)
        multiple_outputs.next_to(ORIGIN, DOWN).to_edge(RIGHT)
        multiple_outputs.add_background_rectangle()
        self.play(Write(multiple_outputs, run_time = 2))
        self.wait()

class SolutionsCanStillExist(TeacherStudentsScene):
    def construct(self):
        words = TextMobject("""
            Solutions can still 
            exist when""",  "$\\det(A) = 0$"
        )
        words[1].set_color(TEAL)
        self.teacher_says(words)
        self.random_blink(2)

class ShowVInAndOutOfColumnSpace(LinearSystemTransformationScene):
    CONFIG = {
        "t_matrix" : [[2, 1], [-2, -1]]
    }
    def construct(self):
        v_out = Vector([1, -1])
        v_in = Vector([-4, -2])
        v_out.words = "No solution exists"
        v_in.words = "Solutions exist"
        v_in.words_color = YELLOW
        v_out.words_color = RED


        self.apply_transposed_matrix(self.t_matrix, path_arc = 0)
        self.wait()
        for v in v_in, v_out:
            self.add_vector(v, animate = True)
            words = TextMobject(v.words)
            words.set_color(v.words_color)
            words.next_to(v.get_end(), DOWN+RIGHT)
            words.add_background_rectangle()
            self.play(Write(words), run_time = 2)
        self.wait()

class NotAllSquishesAreCreatedEqual(TeacherStudentsScene):
    def construct(self):
        self.student_says("""
            Some squishes feel
            ...squishier
        """)
        self.random_blink(2)

class PrepareForRank(Scene):
    def construct(self):
        new_term, rank = words = TextMobject(
            "New terminology: ",
            "rank"
        )
        rank.set_color(TEAL)
        self.play(Write(words))
        self.wait()

class DefineRank(Scene):
    def construct(self):
        rank = TextMobject("``Rank''")
        rank.set_color(TEAL)
        arrow = DoubleArrow(LEFT, RIGHT)
        dims = TextMobject(
            "Number of\\\\", "dimensions \\\\", 
            "in the output"
        )
        dims[1].set_color(rank.get_color())

        rank.next_to(arrow, LEFT)
        dims.next_to(arrow, RIGHT)

        self.play(Write(rank))
        self.play(
            ShowCreation(arrow),
            *list(map(Write, dims))
        )
        self.wait()

class DefineColumnSpace(Scene):
    def construct(self):
        left_words = TextMobject(
            "Set of all possible\\\\",
            "outputs",
            "$A\\vec{\\textbf{v}}$",
        )
        left_words[1].set_color(TEAL)
        VMobject(*left_words[-1][1:]).set_color(YELLOW)
        arrow = DoubleArrow(LEFT, RIGHT).to_edge(UP)
        right_words = TextMobject("``Column space''", "of $A$")
        right_words[0].set_color(left_words[1].get_color())

        everyone = VMobject(left_words, arrow, right_words)
        everyone.arrange(RIGHT)
        everyone.to_edge(UP)

        self.play(Write(left_words))
        self.wait()
        self.play(
            ShowCreation(arrow),
            Write(right_words)
        )
        self.wait()

class ColumnsRepresentBasisVectors(Scene):
    def construct(self):
        matrix = Matrix([[3, 1], [4, 1]])
        matrix.shift(UP)
        i_hat_words, j_hat_words = [
            TextMobject("Where $\\hat{\\%smath}$ lands"%char)
            for char in ("i", "j")
        ]
        i_hat_words.set_color(X_COLOR)
        i_hat_words.next_to(ORIGIN, LEFT).to_edge(UP)
        j_hat_words.set_color(Y_COLOR)
        j_hat_words.next_to(ORIGIN, RIGHT).to_edge(UP)

        self.add(matrix)
        self.wait()
        for i, words in enumerate([i_hat_words, j_hat_words]):

            arrow = Arrow(
                words.get_bottom(),
                matrix.get_mob_matrix()[0,i].get_top(),
                color = words.get_color()
            )
            self.play(
                Write(words, run_time = 1),
                ShowCreation(arrow),
                *[
                    ApplyMethod(m.set_color, words.get_color())
                    for m in matrix.get_mob_matrix()[:,i]
                ]
            )
        self.wait()

class ThreeDOntoPlane(Scene):
    pass

class ThreeDOntoLine(Scene):
    pass

class ThreeDOntoPoint(Scene):
    pass

class TowDColumnsDontSpan(LinearTransformationScene):
    CONFIG = {
        "t_matrix" : [[2, 1], [-2, -1]]
    }
    def construct(self):
        matrix = Matrix(self.t_matrix.T)
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        matrix.add_to_back(BackgroundRectangle(matrix))
        self.add_foreground_mobject(matrix)
        brace = Brace(matrix)
        words = VMobject(
            TextMobject("Span", "of columns"),
            TexMobject("\\Updownarrow"),
            TextMobject("``Column space''")
        )
        words.arrange(DOWN, buff = 0.1)
        words.next_to(brace, DOWN)
        words[0][0].set_color(PINK)
        words[2].set_color(TEAL)
        words[0].add_background_rectangle()
        words[2].add_background_rectangle()
        VMobject(matrix, brace, words).to_corner(UP+LEFT)

        self.apply_transposed_matrix(self.t_matrix, path_arc = 0)
        self.play(
            GrowFromCenter(brace),
            Write(words, run_time = 2)
        )
        self.wait()
        self.play(ApplyFunction(
            lambda m : m.scale(-1).shift(self.i_hat.get_end()),
            self.j_hat
        ))
        bases = [self.i_hat, self.j_hat]
        for mob in bases:
            mob.original = mob.copy()
        for x in range(8):
            for mob in bases:
                mob.target = mob.original.copy()
                mob.target.set_stroke(width = 6)
                target_len = random.uniform(0.5, 1.5)
                target_len *= random.choice([-1, 1])
                mob.target.scale(target_len)
            self.j_hat.target.shift(
                -self.j_hat.target.get_start()+ \
                self.i_hat.target.get_end()
            )
            self.play(Transform(
                VMobject(*bases),
                VMobject(*[m.target for m in bases]),
                run_time = 2
            ))

class FullRankWords(Scene):
    def construct(self):
        self.play(Write(TextMobject("Full rank").scale(2)))

class ThreeDColumnsDontSpan(Scene):
    def construct(self):
        matrix = Matrix(np.array([
            [1, 1, 0],
            [0, 1, 1],
            [-1, -2, -1],
        ]).T)
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        brace = Brace(matrix)
        words = brace.get_text(
            "Columns don't",
            "span \\\\",
            "full output space"
        )
        words[1].set_color(PINK)

        self.add(matrix)
        self.play(
            GrowFromCenter(brace),
            Write(words, run_time = 2)
        )
        self.wait()

class NameColumnSpace(Scene):
    def construct(self):
        matrix = Matrix(np.array([
            [1, 1, 0],
            [0, 1, 1],
            [-1, -2, -1],
        ]).T)
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        matrix.to_corner(UP+LEFT)
        cols = list(matrix.copy().get_mob_matrix().T)
        col_arrays = list(map(Matrix, cols))

        span_text = TexMobject(
            "\\text{Span}", 
            "\\Big(",
            matrix_to_tex_string([1, 2, 3]),
            ",",
            matrix_to_tex_string([1, 2, 3]),
            ",",
            matrix_to_tex_string([1, 2, 3]),
            "\\big)"
        )
        for i in 1, -1:
            span_text[i].stretch(1.5, 1)
            span_text[i].do_in_place(
                span_text[i].set_height, 
                span_text.get_height()
            )
        for col_array, index in zip(col_arrays, [2, 4, 6]):
            col_array.replace(span_text[index], dim_to_match = 1)
            span_text.submobjects[index] = col_array
        span_text.arrange(RIGHT, buff = 0.2)

        arrow = DoubleArrow(LEFT, RIGHT)
        column_space = TextMobject("``Column space''")
        for mob in column_space, arrow:
            mob.set_color(TEAL)
        text = VMobject(span_text, arrow, column_space)
        text.arrange(RIGHT)
        text.next_to(matrix, DOWN, buff = 1, aligned_edge = LEFT)

        self.add(matrix)
        self.wait()
        self.play(*[
            Transform(
                VMobject(*matrix.copy().get_mob_matrix()[:,i]),
                col_arrays[i].get_entries()
            )
            for i in range(3)
        ])
        self.play(
            Write(span_text),
            *list(map(Animation, self.get_mobjects_from_last_animation()))
        )
        self.play(
            ShowCreation(arrow),
            Write(column_space)
        )
        self.wait()
        self.play(FadeOut(matrix))
        self.clear()
        self.add(text)

        words = TextMobject(
            "To solve", 
            "$A\\vec{\\textbf{x}} = \\vec{\\textbf{v}}$,\\\\",
            "$\\vec{\\textbf{v}}$", 
            "must be in \\\\ the",
            "column space."
        )
        VMobject(*words[1][1:3]).set_color(PINK)
        VMobject(*words[1][4:6]).set_color(YELLOW)
        words[2].set_color(YELLOW)
        words[4].set_color(TEAL)
        words.to_corner(UP+LEFT)

        self.play(Write(words))
        self.wait(2)
        self.play(FadeOut(words))

        brace = Brace(column_space, UP)
        rank_words = brace.get_text(
            "Number of dimensions \\\\ is called",
            "``rank''"
        )
        rank_words[1].set_color(MAROON)
        self.play(
            GrowFromCenter(brace),
            Write(rank_words)
        )
        self.wait()
        self.cycle_through_span_possibilities(span_text)

    def cycle_through_span_possibilities(self, span_text):
        span_text.save_state()
        two_d_span = span_text.copy()
        for index, arr, c in (2, [1, 1], X_COLOR), (4, [0, 1], Y_COLOR):
            col = Matrix(arr)
            col.replace(two_d_span[index])
            two_d_span.submobjects[index] = col
            col.get_entries().set_color(c)
        for index in 5, 6:
            two_d_span[index].scale(0)
        two_d_span.arrange(RIGHT, buff = 0.2)
        two_d_span[-1].next_to(two_d_span[4], RIGHT, buff = 0.2)
        two_d_span.move_to(span_text, aligned_edge = RIGHT)
        mob_matrix = np.array([
            two_d_span[i].get_entries().split()
            for i in (2, 4)
        ])

        self.play(Transform(span_text, two_d_span))
        #horrible hack
        span_text.shift(10*DOWN)
        span_text = span_text.copy().restore()
        ###
        self.add(two_d_span)
        self.wait()
        self.replace_number_matrix(mob_matrix, [[1, 1], [1, 1]])
        self.wait()
        self.replace_number_matrix(mob_matrix, [[0, 0], [0, 0]])
        self.wait()
        self.play(Transform(two_d_span, span_text))
        self.wait()
        self.remove(two_d_span)
        self.add(span_text)
        mob_matrix = np.array([
            span_text[i].get_entries().split()
            for i in (2, 4, 6)
        ])
        self.replace_number_matrix(mob_matrix, [[1, 1, 0], [0, 1, 1], [1, 0, 1]])
        self.wait()
        self.replace_number_matrix(mob_matrix, [[1, 1, 0], [0, 1, 1], [-1, -2, -1]])
        self.wait()
        self.replace_number_matrix(mob_matrix, [[1, 1, 0], [2, 2, 0], [3, 3, 0]])
        self.wait()
        self.replace_number_matrix(mob_matrix, np.zeros((3, 3)).astype('int'))
        self.wait()


    def replace_number_matrix(self, matrix, new_numbers):
        starters = matrix.flatten()
        targets = list(map(TexMobject, list(map(str, np.array(new_numbers).flatten()))))
        for start, target in zip(starters, targets):
            target.move_to(start)
            target.set_color(start.get_color())
        self.play(*[
            Transform(*pair, path_arc = np.pi)
            for pair in zip(starters, targets)
        ])

class IHatShear(LinearTransformationScene):
    CONFIG = {
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_WIDTH,
            "secondary_line_ratio" : 0
        },
    }
    def construct(self):
        self.apply_transposed_matrix([[1, 1], [0, 1]])
        self.wait()

class DiagonalDegenerate(LinearTransformationScene):
    def construct(self):
        self.apply_transposed_matrix([[1, 1], [1, 1]])

class ZeroMatirx(LinearTransformationScene):
    def construct(self):
        origin = Dot(ORIGIN)
        self.play(Transform(
            VMobject(self.plane, self.i_hat, self.j_hat),
            origin,
            run_time = 3
        ))
        self.wait()

class RankNumber(Scene):
    CONFIG = {
        "number" : 3,
        "color" : BLUE
    }
    def construct(self):
        words = TextMobject("Rank", "%d"%self.number)
        words[1].set_color(self.color)
        self.add(words)

class RankNumber2(RankNumber):
    CONFIG = {
        "number" : 2,
        "color" : RED,
    }

class RankNumber1(RankNumber):
    CONFIG = {
        "number" : 1,
        "color" : GREEN
    }

class RankNumber0(RankNumber):
    CONFIG = {
        "number" : 0,
        "color" : GREY
    }  

class NameFullRank(Scene):
    def construct(self):
        matrix = Matrix([[2, 5, 1], [3, 1, 4], [-4, 0, 0]])
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        matrix.to_edge(UP)
        brace = Brace(matrix)
        top_words = brace.get_text(
            "When", "rank", "$=$", "number of columns",
        )
        top_words[1].set_color(MAROON)
        low_words = TextMobject(
            "matrix is", "``full rank''"
        )
        low_words[1].set_color(MAROON)
        low_words.next_to(top_words, DOWN)
        VMobject(matrix, brace, top_words, low_words).to_corner(UP+LEFT)
        self.add(matrix)
        self.play(
            GrowFromCenter(brace),
            Write(top_words)
        )
        self.wait()
        self.play(Write(low_words))
        self.wait()

class OriginIsAlwaysInColumnSpace(LinearTransformationScene):
    def construct(self):
        vector = Matrix([0, 0]).set_color(YELLOW)
        words = TextMobject("is always in the", "column space")
        words[1].set_color(TEAL)
        words.next_to(vector, RIGHT)
        vector.add_to_back(BackgroundRectangle(vector))
        words.add_background_rectangle()
        VMobject(vector, words).center().to_edge(UP)
        arrow = Arrow(vector.get_bottom(), ORIGIN)
        dot = Dot(ORIGIN, color = YELLOW)

        self.play(Write(vector), Write(words))
        self.play(ShowCreation(arrow))
        self.play(ShowCreation(dot, run_time = 0.5))
        self.add_foreground_mobject(vector, words, arrow, dot)
        self.wait()
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()
        self.apply_transposed_matrix([[1./3, -1./2], [-1./3, 1./2]])
        self.wait()

class FullRankCase(LinearTransformationScene):
    CONFIG = {
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_WIDTH,
            "secondary_line_ratio" : 0
        },
    }
    def construct(self):
        t_matrices = [
            [[2, 1], [-3, 2]],
            [[1./2, 1], [1./3, -1./2]]
        ]
        vector = Matrix([0, 0]).set_color(YELLOW)
        title = VMobject(
            TextMobject("Only"), vector,
            TextMobject("lands on"), vector.copy()
        )
        title.arrange(buff = 0.2)
        title.to_edge(UP)
        for mob in title:
            mob.add_to_back(BackgroundRectangle(mob))
        arrow = Arrow(vector.get_bottom(), ORIGIN)
        dot = Dot(ORIGIN, color = YELLOW) 

        words_on = False
        for t_matrix in t_matrices:
            self.apply_transposed_matrix(t_matrix)
            if not words_on:
                self.play(Write(title))
                self.play(ShowCreation(arrow))
                self.play(ShowCreation(dot, run_time = 0.5))
                self.add_foreground_mobject(title, arrow, dot)
                words_on = True
            self.apply_inverse_transpose(t_matrix)
            self.wait()

class NameNullSpace(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors" : False,
        "t_matrix" : [[1, -1], [-1, 1]]
    }
    def construct(self):
        vectors = self.get_vectors()
        dot = Dot(ORIGIN, color = YELLOW)
        line = Line(vectors[0].get_end(), vectors[-1].get_end())
        line.set_color(YELLOW)
        null_space_label = TextMobject("``Null space''")
        kernel_label = TextMobject("``Kernel''")
        null_space_label.move_to(vectors[13].get_end(), aligned_edge = UP+LEFT)
        kernel_label.next_to(null_space_label, DOWN)
        for mob in null_space_label, kernel_label:
            mob.set_color(YELLOW)
            mob.add_background_rectangle()

        self.play(ShowCreation(vectors, run_time = 3))
        self.wait()
        vectors.save_state()
        self.plane.save_state()
        self.apply_transposed_matrix(
            self.t_matrix,
            added_anims = [Transform(vectors, dot)],
            path_arc = 0
        )
        self.wait()
        self.play(
            vectors.restore, 
            self.plane.restore, 
            *list(map(Animation, self.foreground_mobjects)),
            run_time = 2
        )
        self.play(Transform(
            vectors, line, 
            run_time = 2,
            lag_ratio = 0.5
        ))
        self.wait()
        for label in null_space_label, kernel_label:
            self.play(Write(label))
            self.wait()
        self.apply_transposed_matrix(
            self.t_matrix,
            added_anims = [
                Transform(vectors, dot),
                ApplyMethod(null_space_label.scale, 0),
                ApplyMethod(kernel_label.scale, 0),
            ],
            path_arc = 0
        )
        self.wait()

    def get_vectors(self, offset = 0):
        vect = np.array(UP+RIGHT)
        vectors = VMobject(*[
            Vector(a*vect + offset)
            for a in np.linspace(-5, 5, 18)
        ])
        vectors.set_submobject_colors_by_gradient(PINK, YELLOW)
        return vectors

class ThreeDNullSpaceIsLine(Scene):
    pass

class ThreeDNullSpaceIsPlane(Scene):
    pass

class NullSpaceSolveForVEqualsZero(NameNullSpace):
    def construct(self):
        vec = lambda s : "\\vec{\\textbf{%s}}"%s
        equation = TexMobject("A", vec("x"), "=", vec("v"))
        A, x, eq, v = equation
        x.set_color(PINK)
        v.set_color(YELLOW)
        zero_vector = Matrix([0, 0])
        zero_vector.set_color(YELLOW)
        zero_vector.scale(0.7)
        zero_vector.move_to(v, aligned_edge = LEFT)
        VMobject(equation, zero_vector).next_to(ORIGIN, LEFT).to_edge(UP)
        zero_vector_rect = BackgroundRectangle(zero_vector)        
        equation.add_background_rectangle()

        self.play(Write(equation))
        self.wait()
        self.play(
            ShowCreation(zero_vector_rect),
            Transform(v, zero_vector)
        )
        self.wait()
        self.add_foreground_mobject(zero_vector_rect, equation)
        NameNullSpace.construct(self)

class OffsetNullSpace(NameNullSpace):
    def construct(self):
        x = Vector([-2, 1], color = RED)
        vectors = self.get_vectors()
        offset_vectors = self.get_vectors(offset = x.get_end())
        dots = VMobject(*[
            Dot(v.get_end(), color = v.get_color())
            for v in offset_vectors
        ])
        dot = Dot(
            self.get_matrix_transformation(self.t_matrix)(x.get_end()),
            color = RED
        )
        circle = Circle(color = YELLOW).replace(dot)
        circle.scale_in_place(5)
        words = TextMobject("""
            All vectors still land 
            on the same spot
        """)
        words.set_color(YELLOW)
        words.add_background_rectangle()
        words.next_to(circle)
        x_copies = VMobject(*[
            x.copy().shift(v.get_end())
            for v in vectors
        ])

        self.play(FadeIn(vectors))
        self.wait()
        self.add_vector(x, animate = True)
        self.wait()
        x_copy = VMobject(x.copy())
        self.play(Transform(x_copy, x_copies))
        self.play(
            Transform(vectors, offset_vectors),
            *[
                Transform(v, VectorizedPoint(v.get_end()))
                for v in x_copy
            ]
        )
        self.remove(x_copy)
        self.wait()
        self.play(Transform(vectors, dots))
        self.wait()
        self.apply_transposed_matrix(
            self.t_matrix,
            added_anims = [Transform(vectors, dot)]
        )
        self.wait()        
        self.play(
            ShowCreation(circle),
            Write(words)
        )
        self.wait()

class ShowAdditivityProperty(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors" : False,
        "t_matrix" : [[1, 0.5], [-1, 1]],
        "include_background_plane" : False,
    }
    def construct(self):
        v = Vector([2, -1])
        w = Vector([1, 2])
        v.set_color(YELLOW)
        w.set_color(MAROON_B)
        sum_vect = Vector(v.get_end()+w.get_end(), color = PINK)
        form = TexMobject(
            "A(",
            "\\vec{\\textbf{v}}",
            "+",
            "\\vec{\\textbf{w}}",
            ")",
            "=A",
            "\\vec{\\textbf{v}}",
            "+A",
            "\\vec{\\textbf{w}}",
        )
        form.to_corner(UP+RIGHT)
        VMobject(form[1], form[6]).set_color(YELLOW)
        VMobject(form[3], form[8]).set_color(MAROON_B)
        initial_sum = VMobject(*form[1:4])
        transformer = VMobject(form[0], form[4])
        final_sum = VMobject(*form[5:])
        form_rect = BackgroundRectangle(form)

        self.add(form_rect)
        self.add_vector(v, animate = True)
        self.add_vector(w, animate = True)
        w_copy = w.copy()
        self.play(w_copy.shift, v.get_end())
        self.add_vector(sum_vect, animate = True)
        self.play(
            Write(initial_sum),
            FadeOut(w_copy)
        )
        self.add_foreground_mobject(form_rect, initial_sum)
        self.apply_transposed_matrix(
            self.t_matrix,
            added_anims = [Write(transformer)]
        )
        self.wait()
        self.play(w.copy().shift, v.get_end())
        self.play(Write(final_sum))
        self.wait()

class AddJustOneNullSpaceVector(NameNullSpace):
    def construct(self):
        vectors = self.get_vectors()
        self.add(vectors)
        null_vector = vectors[int(0.7*len(vectors.split()))]
        vectors.remove(null_vector)
        null_vector.label = "$\\vec{\\textbf{n}}$"
        x = Vector([-1, 1], color = RED)
        x.label = "$\\vec{\\textbf{x}}$"
        sum_vect = Vector(
            x.get_end() + null_vector.get_end(),
            color = PINK
        )
        for v in x, null_vector:
            v.label = TextMobject(v.label)
            v.label.set_color(v.get_color())
            v.label.next_to(v.get_end(), UP)
            v.label.add_background_rectangle()
        dot = Dot(ORIGIN, color = null_vector.get_color())

        form = TexMobject(
            "A(",
            "\\vec{\\textbf{x}}",
            "+",
            "\\vec{\\textbf{n}}",
            ")",
            "=A",
            "\\vec{\\textbf{x}}",
            "+A",
            "\\vec{\\textbf{n}}",
        )
        form.to_corner(UP+RIGHT)
        VMobject(form[1], form[6]).set_color(x.get_color())
        VMobject(form[3], form[8]).set_color(null_vector.get_color())
        initial_sum = VMobject(*form[1:4])
        transformer = VMobject(form[0], form[4])
        final_sum = VMobject(*form[5:])
        brace = Brace(VMobject(*form[-2:]))
        brace.add(brace.get_text("+0").add_background_rectangle())
        form_rect = BackgroundRectangle(form)
        sum_vect.label = initial_sum.copy()
        sum_vect.label.next_to(sum_vect.get_end(), UP)

        self.add_vector(x, animate = True)
        self.play(Write(x.label))
        self.wait()
        self.play(
            FadeOut(vectors),
            Animation(null_vector)
        )
        self.play(Write(null_vector.label))
        self.wait()
        x_copy = x.copy()
        self.play(x_copy.shift, null_vector.get_end())
        self.add_vector(sum_vect, animate = True)
        self.play(
            FadeOut(x_copy),
            Write(sum_vect.label)
        )
        self.wait()
        self.play(
            ShowCreation(form_rect),
            sum_vect.label.replace, initial_sum
        )
        self.add_foreground_mobject(form_rect, sum_vect.label)
        self.remove(x.label, null_vector.label)
        self.apply_transposed_matrix(
            self.t_matrix,
            added_anims = [
                Transform(null_vector, dot),
                Write(transformer)
            ]
        )
        self.play(Write(final_sum))
        self.wait()
        self.play(Write(brace))
        self.wait()
        words = TextMobject(
            "$\\vec{\\textbf{x}}$",
            "and the",
            "$\\vec{\\textbf{x}} + \\vec{\\textbf{n}}$\\\\",
            "land on the same spot"
        )
        words[0].set_color(x.get_color())
        VMobject(*words[2][:2]).set_color(x.get_color())
        VMobject(*words[2][3:]).set_color(null_vector.get_color())
        words.next_to(brace, DOWN)
        words.to_edge(RIGHT)
        self.play(Write(words))
        self.wait()

class NullSpaceOffsetRule(Scene):
    def construct(self):
        vec = lambda s : "\\vec{\\textbf{%s}}"%s
        equation = TexMobject("A", vec("x"), "=", vec("v"))
        A, x, equals, v = equation
        x.set_color(PINK)
        v.set_color(YELLOW)
        A_text = TextMobject(
            "When $A$ is not", "full rank"
        )
        A_text[1].set_color(MAROON_C)
        A_text.next_to(A, UP+LEFT, buff = 1)
        A_text.shift_onto_screen()
        A_arrow = Arrow(A_text.get_bottom(), A, color = WHITE)
        v_text = TextMobject(
            "If", "$%s$"%vec("v"), "is in the", 
            "column space", "of $A$"
        )
        v_text[1].set_color(YELLOW)
        v_text[3].set_color(TEAL)
        v_text.next_to(v, DOWN+RIGHT, buff = 1)
        v_text.shift_onto_screen()
        v_arrow = Arrow(v_text.get_top(), v, color = YELLOW)


        self.add(equation)
        self.play(Write(A_text, run_time = 2))
        self.play(ShowCreation(A_arrow))
        self.wait()
        self.play(Write(v_text, run_time = 2))
        self.play(ShowCreation(v_arrow))
        self.wait()

class MuchLeftToLearn(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "That's the high \\\\",
            "level overview"
        )
        self.random_blink()
        self.wait()
        self.teacher_says(
            "There is still \\\\",
            "much to learn"
        )
        for pi in self.get_students():
            target_mode = random.choice([
                "raise_right_hand", "raise_left_hand"
            ])
            self.play(pi.change_mode, target_mode)
        self.random_blink()
        self.wait()

class NotToLearnItAllNow(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            The goal is not to
            learn it all now
        """)
        self.random_blink()
        self.wait()
        self.random_blink()

class NextVideo(Scene):
    def construct(self):
        title = TextMobject("""
            Next video: Nonsquare matrices
        """)
        title.set_width(FRAME_WIDTH - 2)
        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 WhatAboutNonsquareMatrices(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "What about \\\\ nonsquare matrices?",
            target_mode = "raise_right_hand"
        )
        self.play(self.get_students()[0].change_mode, "confused")
        self.random_blink(6)

















