from big_ol_pile_of_manim_imports import *
from old_projects.eola.chapter1 import plane_wave_homotopy
from old_projects.eola.chapter3 import ColumnsToBasisVectors
from old_projects.eola.chapter5 import NameDeterminant, Blob
from old_projects.eola.chapter9 import get_small_bubble
from old_projects.eola.chapter10 import ExampleTranformationScene

class Student(PiCreature):
    CONFIG = {
        "name" : "Student"
    }
    def get_name(self):
        text = TextMobject(self.name)
        text.add_background_rectangle()
        text.next_to(self, DOWN)
        return text

class PhysicsStudent(Student):
    CONFIG = {
        "color" : PINK,
        "name" : "Physics student"
    }

class CSStudent(Student):
    CONFIG = {
        "color" : PURPLE_E,
        "flip_at_start" : True,
        "name" : "CS Student"
    } 

class OpeningQuote(Scene):
    def construct(self):
        words = TextMobject(
            "``Such",
            "axioms,", 
            "together with other unmotivated definitions,", 
            "serve mathematicians mainly by making it",
            "difficult for the uninitiated",
            "to master their subject, thereby elevating its authority.''",
            enforce_new_line_structure = False,
            alignment = "",
        )
        words.set_color_by_tex("axioms,", BLUE)
        words.set_color_by_tex("difficult for the uninitiated", RED)
        words.set_width(FRAME_WIDTH - 2)
        words.to_edge(UP)
        author = TextMobject("-Vladmir Arnold")
        author.set_color(YELLOW)
        author.next_to(words, DOWN, buff = MED_LARGE_BUFF)

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

class RevisitOriginalQuestion(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("Let's revisit ", "\\\\ an old question")
        self.random_blink()
        question = TextMobject("What are ", "vectors", "?", arg_separator = "")
        question.set_color_by_tex("vectors", YELLOW)
        self.teacher_says(
            question,
            added_anims = [
                ApplyMethod(self.get_students()[i].change_mode, mode)
                for i, mode in enumerate([
                    "pondering", "raise_right_hand", "erm"
                ])
            ]
        )
        self.random_blink(2)

class WhatIsA2DVector(LinearTransformationScene):
    CONFIG = {
        "v_coords" : [1, 2],
        "show_basis_vectors" : False,
        "include_background_plane" : False,
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_HEIGHT,
            "secondary_line_ratio" : 1
        },
    }
    def construct(self):
        self.plane.fade()
        self.introduce_vector_and_space()
        self.bring_in_students()

    def introduce_vector_and_space(self):
        v = Vector(self.v_coords)
        coords = Matrix(self.v_coords)
        coords.add_to_back(BackgroundRectangle(coords))
        coords.next_to(v.get_end(), RIGHT)

        two_d_vector = TextMobject(
            "``Two-dimensional ", "vector", "''", 
            arg_separator = ""
        )
        two_d_vector.set_color_by_tex("vector", YELLOW)
        two_d_vector.add_background_rectangle()
        two_d_vector.to_edge(UP)

        self.play(
            Write(two_d_vector),
            ShowCreation(v),
            Write(coords),
            run_time = 2
        )
        self.wait()
        self.v, self.coords = v, coords

    def bring_in_students(self):
        everything = self.get_mobjects()
        v, coords = self.v, self.coords
        physics_student = PhysicsStudent()
        cs_student = CSStudent()
        students = [physics_student, cs_student]
        for student, vect in zip(students, [LEFT, RIGHT]):
            student.change_mode("confused")
            student.to_corner(DOWN+vect, buff = MED_LARGE_BUFF)
            student.look_at(v)
            student.bubble = get_small_bubble(
                student, height = 4, width = 4,
            )
        self.play(*list(map(FadeIn, students)))
        self.play(Blink(physics_student))
        self.wait()
        for student, vect in zip(students, [RIGHT, LEFT]):
            for mob in v, coords:
                mob.target = mob.copy()
                mob.target.scale(0.7)
            arrow = TexMobject("\\Rightarrow")
            group = VGroup(v.target, arrow, coords.target)
            group.arrange(vect)
            student.bubble.add_content(group)
            student.v, student.coords = v.copy(), coords.copy()
            student.arrow = arrow

            self.play(
                student.change_mode, "pondering",
                ShowCreation(student.bubble),
                Write(arrow),
                Transform(student.v, v.target),
                Transform(student.coords, coords.target),
            )
            self.play(Blink(student))
            self.wait()
        anims = []
        for student in students:
            v, coords = student.v, student.coords
            v.target = v.copy()
            coords.target = coords.copy()
            group = VGroup(v.target, coords.target)
            group.arrange(DOWN)
            group.set_height(coords.get_height())
            group.next_to(student.arrow, RIGHT)
            student.q_marks = TexMobject("???")
            student.q_marks.set_color_by_gradient(BLUE, YELLOW)
            student.q_marks.next_to(student.arrow, LEFT)
            anims += [
                Write(student.q_marks),
                MoveToTarget(v),
                MoveToTarget(coords),
                student.change_mode, "erm",
                student.look_at, student.bubble
            ]
        cs_student.v.save_state()
        cs_student.coords.save_state()
        self.play(*anims)
        for student in students:
            self.play(Blink(student))
        self.wait()
        self.play(*it.chain(
            list(map(FadeOut, everything + [
                physics_student.bubble,
                physics_student.v,
                physics_student.coords,
                physics_student.arrow,
                physics_student.q_marks,
                cs_student.q_marks,
            ])),
            [ApplyMethod(s.change_mode, "plain") for s in students],
            list(map(Animation, [cs_student.bubble, cs_student.arrow])),
            [mob.restore for mob in (cs_student.v, cs_student.coords)],
        ))
        bubble = cs_student.get_bubble(SpeechBubble, width = 4, height = 3)
        bubble.set_fill(BLACK, opacity = 1)
        bubble.next_to(cs_student, UP+LEFT)
        bubble.write("Consider higher \\\\ dimensions")
        self.play(
            cs_student.change_mode, "speaking",
            ShowCreation(bubble),
            Write(bubble.content)
        )
        self.play(Blink(physics_student))
        self.wait()

class HigherDimensionalVectorsNumerically(Scene):
    def construct(self):
        words = VGroup(*list(map(TextMobject, [
            "4D vector", 
            "5D vector", 
            "100D vector", 
        ])))
        words.arrange(RIGHT, buff = LARGE_BUFF*2)
        words.to_edge(UP)
        vectors = VGroup(*list(map(Matrix, [
            [3, 1, 4, 1],
            [5, 9, 2, 6, 5],
            [3, 5, 8, "\\vdots", 0, 8, 6]
        ])))
        colors = [YELLOW, MAROON_B, GREEN]
        for word, vector, color in zip(words, vectors, colors):
            vector.shift(word.get_center()[0]*RIGHT)
            word.set_color(color)
            vector.set_color(color)

        for word in words:
            self.play(FadeIn(word))
        self.play(Write(vectors))
        self.wait()
        for index, dim, direction in (0, 4, RIGHT), (2, 100, LEFT):
            v = vectors[index]
            v.target = v.copy()
            brace = Brace(v, direction)
            brace.move_to(v)
            v.target.next_to(brace, -direction)
            text = brace.get_text("%d numbers"%dim)            
            self.play(
                MoveToTarget(v),
                GrowFromCenter(brace),
                Write(text)
            )
            entries = v.get_entries()
            num_entries = len(list(entries))
            self.play(*[
                Transform(
                    entries[i],
                    entries[i].copy().scale_in_place(1.2).set_color(WHITE),
                    rate_func = squish_rate_func(
                        there_and_back, 
                        i/(2.*num_entries), 
                        i/(2.*num_entries)+0.5
                    ),
                    run_time = 2                    
                )
                for i in range(num_entries)
            ])
            self.wait()

class HyperCube(VMobject):
    CONFIG = {
        "color" : BLUE_C,
        "color2" : BLUE_D, 
        "dims" : 4,
    }
    def generate_points(self):
        corners = np.array(list(map(np.array, it.product(*[(-1, 1)]*self.dims))))
        def project(four_d_array):
            result = four_d_array[:3]
            w = four_d_array[self.dims-1]
            scalar = interpolate(0.8, 1.2 ,(w+1)/2.)
            return scalar*result
        for a1, a2 in it.combinations(corners, 2):
            if sum(a1==a2) != self.dims-1:
                continue
            self.add(Line(project(a1), project(a2)))
        self.pose_at_angle()
        self.set_color_by_gradient(self.color, self.color2)

class AskAbout4DPhysicsStudent(Scene):
    def construct(self):
        physy = PhysicsStudent().to_edge(DOWN).shift(2*LEFT)
        compy = CSStudent().to_edge(DOWN).shift(2*RIGHT)
        for pi1, pi2 in (physy, compy), (compy, physy):
            pi1.look_at(pi2.eyes)
        physy.bubble = physy.get_bubble(SpeechBubble, width = 5, height = 4.5)

        line = Line(LEFT, RIGHT, color = BLUE_B)
        square = Square(color = BLUE_C)
        square.scale_in_place(0.5)
        cube = HyperCube(color = BLUE_D, dims = 3)
        hyper_cube = HyperCube()
        thought_mobs = []
        for i, mob in enumerate([line, square, cube, hyper_cube]):
            mob.set_height(2)            
            tex = TexMobject("%dD"%(i+1))
            tex.next_to(mob, UP)
            group = VGroup(mob, tex)
            thought_mobs.append(group)
            group.shift(
                physy.bubble.get_top() -\
                tex.get_top() + MED_SMALL_BUFF*DOWN
            )
        line.shift(DOWN)
        curr_mob = thought_mobs[0]

        self.add(compy, physy)
        self.play(
            compy.change_mode, "confused",
            physy.change_mode, "hooray",
            ShowCreation(physy.bubble),
            Write(curr_mob, run_time = 1),
        )
        self.play(Blink(compy))
        for i, mob in enumerate(thought_mobs[1:]):
            self.play(Transform(curr_mob, mob))
            self.remove(curr_mob)
            curr_mob = mob
            self.add(curr_mob)
            if i%2 == 1:
                self.play(Blink(physy))
            else:
                self.wait()
        self.play(Blink(compy))
        self.wait()

class ManyCoordinateSystems(LinearTransformationScene):
    CONFIG = {
        "v_coords" : [2, 1],
        "include_background_plane" : False,
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_WIDTH,
            "secondary_line_ratio" : 1
        },
    }
    def construct(self):
        self.title = TextMobject("Many possible coordinate systems")
        self.title.add_background_rectangle()
        self.title.to_edge(UP)
        self.add_foreground_mobject(self.title)
        self.v = Vector(self.v_coords)
        self.play(ShowCreation(self.v))
        self.add_foreground_mobject(self.v)

        t_matrices = [
            [[0.5, 0.5], [-0.5, 0.5]],
            [[1, -1], [-3, -1]],
            [[-1, 2], [-0.5, -1]],
        ]
        movers = [self.plane, self.i_hat, self.j_hat]
        for mover in movers:
            mover.save_state()
        for t_matrix in t_matrices:
            self.animate_coordinates()
            self.play(*it.chain(
                list(map(FadeOut, movers)),
                list(map(Animation, self.foreground_mobjects))
            ))
            for mover in movers:
                mover.restore()
            self.apply_transposed_matrix(t_matrix, run_time = 0)
            self.play(*it.chain(
                list(map(FadeIn, movers)),
                list(map(Animation, self.foreground_mobjects))
            ))
        self.animate_coordinates()


    def animate_coordinates(self):
        self.i_hat.save_state()
        self.j_hat.save_state()
        cob_matrix = np.array([
            self.i_hat.get_end()[:2],
            self.j_hat.get_end()[:2]
        ]).T
        inv_cob = np.linalg.inv(cob_matrix)
        coords = np.dot(inv_cob, self.v_coords)
        array = Matrix(list(map(DecimalNumber, coords)))
        array.get_entries()[0].set_color(X_COLOR)
        array.get_entries()[1].set_color(Y_COLOR)
        array.add_to_back(BackgroundRectangle(array))
        for entry in array.get_entries():
            entry.add_to_back(BackgroundRectangle(entry))
        array.next_to(self.title, DOWN)

        self.i_hat.target = self.i_hat.copy().scale(coords[0])
        self.j_hat.target = self.j_hat.copy().scale(coords[1])
        coord1, coord2 = array.get_entries().copy()
        for coord, vect in (coord1, self.i_hat), (coord2, self.j_hat):
            coord.target = coord.copy().next_to(
                vect.target.get_end()/2, 
                rotate_vector(vect.get_end(), -np.pi/2)
            )

        self.play(Write(array, run_time = 1))
        self.wait()
        self.play(*list(map(MoveToTarget, [self.i_hat, coord1])))
        self.play(*list(map(MoveToTarget, [self.j_hat, coord2])))
        self.play(VGroup(self.j_hat, coord2).shift, self.i_hat.get_end())
        self.wait(2)
        self.play(
            self.i_hat.restore,
            self.j_hat.restore,
            *list(map(FadeOut, [array, coord1, coord2]))
        )

class DeterminantAndEigenvectorDontCare(LinearTransformationScene):
    CONFIG = {
        "t_matrix" : [[3, 1], [1, 2]],
        "include_background_plane" : False,
        "show_basis_vectors" : False,
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_HEIGHT,
            "secondary_line_ratio" : 1
        },
    }
    def construct(self):
        words = TextMobject(
            "Determinant", 
            "and", 
            "eigenvectors", 
            "don't \\\\ care about the coordinate system"
        )
        words.set_color_by_tex("Determinant", YELLOW)
        words.set_color_by_tex("eigenvectors", MAROON_B)
        words.add_background_rectangle()
        words.to_edge(UP)
        dark_yellow = Color(rgb = interpolate(
            color_to_rgb(YELLOW),
            color_to_rgb(BLACK),
            0.5
        ))

        blob = Blob(
            stroke_color = YELLOW,
            fill_color = dark_yellow,
            fill_opacity = 1,
        )
        blob.shift(2*LEFT+UP)
        det_label = TexMobject("A")
        det_label = VGroup(
            VectorizedPoint(det_label.get_left()).set_color(WHITE),
            det_label
        )
        det_label_target = TexMobject("\\det(M)\\cdot", "A")
        det_label.move_to(blob)

        eigenvectors = VGroup(*self.get_eigenvectors())

        self.add_foreground_mobject(words)
        self.wait()
        self.play(
            FadeIn(blob),
            Write(det_label)
        )
        self.play(
            ShowCreation(
                eigenvectors,
                run_time = 2,
            ),
            Animation(words)
        )
        self.wait()
        
        self.add_transformable_mobject(blob)
        self.add_moving_mobject(det_label, det_label_target)
        for vector in eigenvectors:
            self.add_vector(vector, animate = False)
        self.remove(self.plane)
        non_plane_mobs = self.get_mobjects()
        self.add(self.plane, *non_plane_mobs)
        
        cob_matrices = [
            None,
            [[1, -1], [-3, -1]],
            [[-1, 2], [-0.5, -1]],
        ] 
        def special_rate_func(t):
            if t < 0.3:
                return smooth(t/0.3)
            if t > 0.7:
                return smooth((1-t)/0.3)
            return 1
        for cob_matrix in cob_matrices:
            if cob_matrix is not None:
                self.play(
                    FadeOut(self.plane),
                    *list(map(Animation, non_plane_mobs))
                )
                transform = self.get_matrix_transformation(cob_matrix)
                self.plane.apply_function(transform)
                self.play(
                    FadeIn(self.plane),
                    *list(map(Animation, non_plane_mobs))
                )
                self.wait()
            self.apply_transposed_matrix(
                self.t_matrix,
                rate_func = special_rate_func,
                run_time = 8
            )


        

    def get_eigenvectors(self):
        vals, (eig_matrix) = np.linalg.eig(self.t_matrix.T)
        v1, v2 = eig_matrix.T
        result = []
        for v in v1, v2:
            vectors = VGroup(*[
                Vector(u*x*v)
                for x in range(7, 0, -1)
                for u in [-1, 1]
            ])
            vectors.set_color_by_gradient(MAROON_A, MAROON_C)
            result += list(vectors)
        return result

class WhatIsSpace(Scene):
    def construct(self):
        physy = PhysicsStudent()
        compy = CSStudent()
        physy.to_edge(DOWN).shift(4*LEFT)
        compy.to_edge(DOWN).shift(4*RIGHT)
        physy.make_eye_contact(compy)

        physy.bubble = get_small_bubble(physy)
        vector = Vector([1, 2])
        physy.bubble.add_content(vector)
        compy.bubble = compy.get_bubble(SpeechBubble, width = 6, height = 4)
        compy.bubble.set_fill(BLACK, opacity = 1)
        compy.bubble.write("What exactly do\\\\ you mean by ``space''?")

        self.add(compy, physy)
        self.play(
            physy.change_mode, "pondering",
            ShowCreation(physy.bubble),
            ShowCreation(vector)
        )
        self.play(
            compy.change_mode, "sassy",
            ShowCreation(compy.bubble),
            Write(compy.bubble.content)
        )
        self.play(Blink(physy))
        self.wait()
        self.play(Blink(compy))
        self.wait()

class OtherVectorishThings(TeacherStudentsScene):
    def construct(self):
        words = TextMobject(
            "There are other\\\\",
            "vectorish",
            "things..."
        )
        words.set_color_by_tex("vectorish", YELLOW)
        self.teacher_says(words)
        self.change_student_modes(
            "pondering", "raise_right_hand", "erm"
        )
        self.random_blink(2)
        words = TextMobject("...like", "functions")
        words.set_color_by_tex("functions", PINK)
        self.teacher_says(words)
        self.change_student_modes(*["pondering"]*3)
        self.random_blink(2)
        self.teacher_thinks("")
        self.zoom_in_on_thought_bubble(self.get_teacher().bubble)

class FunctionGraphScene(Scene):
    CONFIG = {
        "graph_colors" : [RED, YELLOW, PINK],
        "default_functions" : [
            lambda x : (x**3 - 9*x)/20.,
            lambda x : -(x**2)/8.+1
        ],
        "default_names" : ["f", "g", "h"],
        "x_min" : -4,
        "x_max" : 4,
        "line_to_line_buff" : 0.03
    }
    def setup(self):
        self.axes = Axes(
            x_min = self.x_min,
            x_max = self.x_max,
        )
        self.add(self.axes)
        self.graphs = []

    def get_function_graph(self, func = None, animate = True, 
                           add = True, **kwargs):
        index = len(self.graphs)
        if func is None:
            func = self.default_functions[
                index%len(self.default_functions)
            ]
        default_color = self.graph_colors[index%len(self.graph_colors)]
        kwargs["color"] = kwargs.get("color", default_color)
        kwargs["x_min"] = kwargs.get("x_min", self.x_min)
        kwargs["x_max"] = kwargs.get("x_max", self.x_max)
        graph = FunctionGraph(func, **kwargs)
        if animate:
            self.play(ShowCreation(graph))
        if add:
            self.add(graph)
        self.graphs.append(graph)
        return graph

    def get_index(self, function_graph):
        if function_graph not in self.graphs:
            self.graphs.append(function_graph)
        return self.graphs.index(function_graph)

    def get_output_lines(self, function_graph, num_steps = None, nudge = True):
        index = self.get_index(function_graph)
        num_steps = num_steps or function_graph.num_steps
        lines = VGroup()
        nudge_size = index*self.line_to_line_buff
        x_min, x_max = function_graph.x_min, function_graph.x_max
        for x in np.linspace(x_min, x_max, num_steps):
            if nudge:
                x += nudge_size
            y = function_graph.function(x)
            lines.add(Line(x*RIGHT, x*RIGHT+y*UP))
        lines.set_color(function_graph.get_color())
        return lines

    def add_lines(self, output_lines):
        self.play(ShowCreation(
            output_lines,
            lag_ratio = 0.5,
            run_time = 2
        ))


    def label_graph(self, function_graph, name = None, animate = True):
        index = self.get_index(function_graph)
        name = name or self.default_names[index%len(self.default_names)]
        label = TexMobject("%s(x)"%name)
        label.next_to(function_graph.point_from_proportion(1), RIGHT)
        label.shift_onto_screen()
        label.set_color(function_graph.get_color())
        if animate:
            self.play(Write(label))
        else:
            self.add(label)
        return label

class AddTwoFunctions(FunctionGraphScene):
    def construct(self):
        f_graph = self.get_function_graph()
        g_graph = self.get_function_graph()
        def sum_func(x):
            return f_graph.get_function()(x)+g_graph.get_function()(x)
        sum_graph = self.get_function_graph(sum_func, animate = False)
        self.remove(sum_graph)
        f_label = self.label_graph(f_graph)
        g_label = self.label_graph(g_graph)

        f_lines = self.get_output_lines(f_graph)
        g_lines = self.get_output_lines(g_graph)
        sum_lines = self.get_output_lines(sum_graph, nudge = False)

        curr_x_point = f_lines[0].get_start()
        sum_def = self.get_sum_definition(DecimalNumber(curr_x_point[0]))
        # sum_def.set_width(FRAME_X_RADIUS-1)
        sum_def.to_corner(UP+LEFT)
        arrow = Arrow(sum_def[2].get_bottom(), curr_x_point, color = WHITE)        
        prefix = sum_def[0]
        suffix = VGroup(*sum_def[1:])
        rect = BackgroundRectangle(sum_def)
        brace = Brace(prefix)
        brace.add(brace.get_text("New function").shift_onto_screen())

        self.play(
            Write(prefix, run_time = 2),
            FadeIn(brace)
        )
        self.wait()        
        for lines in f_lines, g_lines:
            self.add_lines(lines)
        self.play(*list(map(FadeOut, [f_graph, g_graph])))
        self.wait()
        self.play(FadeOut(brace))
        fg_group = VGroup(*list(f_label)+list(g_label))
        self.play(
            FadeIn(rect),
            Animation(prefix),
            Transform(fg_group, suffix),
        )
        self.remove(prefix, fg_group)
        self.add(sum_def)
        self.play(ShowCreation(arrow))

        self.show_line_addition(f_lines[0], g_lines[0], sum_lines[0])
        self.wait()

        curr_x_point = f_lines[1].get_start()
        new_sum_def = self.get_sum_definition(DecimalNumber(curr_x_point[0]))
        new_sum_def.to_corner(UP+LEFT)
        new_arrow = Arrow(sum_def[2].get_bottom(), curr_x_point, color = WHITE)
        self.play(
            Transform(sum_def, new_sum_def),
            Transform(arrow, new_arrow),
        )
        self.show_line_addition(f_lines[1], g_lines[1], sum_lines[1])
        self.wait()

        final_sum_def = self.get_sum_definition(TexMobject("x"))
        final_sum_def.to_corner(UP+LEFT)
        self.play(
            FadeOut(rect),
            Transform(sum_def, final_sum_def),
            FadeOut(arrow)
        )
        self.show_line_addition(*it.starmap(VGroup, [
            f_lines[2:], g_lines[2:], sum_lines[2:]
        ]))
        self.play(ShowCreation(sum_graph))

    def get_sum_definition(self, input_mob):
        result = VGroup(*it.chain(
            TexMobject("(f+g)", "("), 
            [input_mob.copy()],
            TexMobject(")", "=", "f("),
            [input_mob.copy()],
            TexMobject(")", "+", "g("),
            [input_mob.copy()],
            TexMobject(")")
        ))
        result.arrange()
        result[0].set_color(self.graph_colors[2])
        VGroup(result[5], result[7]).set_color(self.graph_colors[0])
        VGroup(result[9], result[11]).set_color(self.graph_colors[1])
        return result


    def show_line_addition(self, f_lines, g_lines, sum_lines):
        g_lines.target = g_lines.copy()
        dots = VGroup()
        dots.target = VGroup()
        for f_line, g_line in zip(f_lines, g_lines.target):
            align_perfectly = f_line.get_end()[1]*g_line.get_end()[1] > 0
            dot = Dot(g_line.get_end(), radius = 0.07)
            g_line.shift(f_line.get_end()-g_line.get_start())
            dot.target = Dot(g_line.get_end())            
            if not align_perfectly:
                g_line.shift(self.line_to_line_buff*RIGHT)
            dots.add(dot)
            dots.target.add(dot.target)
        for group in dots, dots.target:
            group.set_color(sum_lines[0].get_color())
        self.play(ShowCreation(dots))
        if len(list(g_lines)) == 1:
            kwargs = {}
        else:
            kwargs = {
                "lag_ratio" : 0.5,
                "run_time" : 3
            }
        self.play(*[
            MoveToTarget(mob, **kwargs)
            for mob in (g_lines, dots)
        ])
        # self.play(
        #     *[mob.fade for mob in g_lines, f_lines]+[
        #     Animation(dots)
        # ])
        self.wait()

class AddVectorsCoordinateByCoordinate(Scene):
    def construct(self):
        v1 = Matrix(["x_1", "y_1", "z_1"])
        v2 = Matrix(["x_2", "y_2", "z_2"])
        v_sum =  Matrix(["x_1 + x_2", "y_1 + y_2", "z_1 + z_2"])
        for v in v1, v2, v_sum:
            v.get_entries()[0].set_color(X_COLOR)
            v.get_entries()[1].set_color(Y_COLOR)
            v.get_entries()[2].set_color(Z_COLOR)
        plus, equals = TexMobject("+=")
        VGroup(v1, plus, v2, equals, v_sum).arrange()

        self.add(v1, plus, v2)
        self.wait()
        self.play(
            Write(equals),
            Write(v_sum.get_brackets())
        )
        self.play(
            Transform(v1.get_entries().copy(), v_sum.get_entries()),
            Transform(v2.get_entries().copy(), v_sum.get_entries()),
        )
        self.wait()

class ScaleFunction(FunctionGraphScene):
    def construct(self):
        graph = self.get_function_graph(
            lambda x : self.default_functions[0](x),
            animate = False
        )
        scaled_graph = self.get_function_graph(
            lambda x : graph.get_function()(x)*2,
            animate = False, add = False
        )
        graph_lines = self.get_output_lines(graph)
        scaled_lines = self.get_output_lines(scaled_graph, nudge = False)

        f_label = self.label_graph(graph, "f", animate = False)
        two_f_label = self.label_graph(scaled_graph, "(2f)", animate = False)
        self.remove(two_f_label)

        title = TexMobject("(2f)", "(x) = 2", "f", "(x)")
        title.set_color_by_tex("(2f)", scaled_graph.get_color())
        title.set_color_by_tex("f", graph.get_color())
        title.next_to(ORIGIN, LEFT, buff = MED_SMALL_BUFF)
        title.to_edge(UP)
        self.add(title)

        self.add_lines(graph_lines)
        self.wait()
        self.play(Transform(graph_lines, scaled_lines))
        self.play(ShowCreation(scaled_graph))
        self.play(Write(two_f_label))
        self.play(FadeOut(graph_lines))
        self.wait()

class ScaleVectorByCoordinates(Scene):
    def construct(self):
        two, dot, equals = TexMobject("2 \\cdot =")
        v1 = Matrix(list("xyz"))
        v1.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR, Z_COLOR)
        v2 = v1.copy()
        two_targets = VGroup(*[
            two.copy().next_to(entry, LEFT)
            for entry in v2.get_entries()
        ])
        v2.get_brackets()[0].next_to(two_targets, LEFT)
        v2.add(two_targets)
        VGroup(two, dot, v1, equals, v2).arrange()

        self.add(two, dot, v1)
        self.play(
            Write(equals),
            Write(v2.get_brackets())
        )
        self.play(
            Transform(two.copy(), two_targets),
            Transform(v1.get_entries().copy(), v2.get_entries())
        )
        self.wait()

class ShowSlopes(Animation):
    CONFIG = {
        "line_color" : YELLOW,
        "dx" : 0.01,
        "rate_func" : None,
        "run_time" : 5
    }
    def __init__(self, graph, **kwargs):
        digest_config(self, kwargs, locals())
        line = Line(LEFT, RIGHT, color = self.line_color)
        line.save_state()
        Animation.__init__(self, line, **kwargs)

    def interpolate_mobject(self, alpha):
        f = self.graph.point_from_proportion        
        low, high = list(map(f, np.clip([alpha-self.dx, alpha+self.dx], 0, 1)))
        slope = (high[1]-low[1])/(high[0]-low[0])
        self.mobject.restore()
        self.mobject.rotate(np.arctan(slope))
        self.mobject.move_to(f(alpha))

class FromVectorsToFunctions(VectorScene):
    def construct(self):
        self.show_vector_addition_and_scaling()
        self.bring_in_functions()
        self.show_derivative()

    def show_vector_addition_and_scaling(self):
        self.plane = self.add_plane()
        self.plane.fade()
        words1 = TextMobject("Vector", "addition")
        words2 = TextMobject("Vector", "scaling")
        for words in words1, words2:
            words.add_background_rectangle()
            words.next_to(ORIGIN, RIGHT).to_edge(UP)
        self.add(words1)

        v = self.add_vector([2, -1], color = MAROON_B)
        w = self.add_vector([3, 2], color = YELLOW)
        w.save_state()
        self.play(w.shift, v.get_end())
        vw_sum = self.add_vector(w.get_end(), color = PINK)
        self.wait()
        self.play(
            Transform(words1, words2),
            FadeOut(vw_sum),
            w.restore
        )
        self.add(
            v.copy().fade(),
            w.copy().fade()
        )
        self.play(v.scale, 2)
        self.play(w.scale, -0.5)
        self.wait()

    def bring_in_functions(self):
        everything = VGroup(*self.get_mobjects())
        axes = Axes()
        axes.shift(FRAME_WIDTH*LEFT)

        fg_scene_config = FunctionGraphScene.CONFIG
        graph = FunctionGraph(fg_scene_config["default_functions"][0])
        graph.set_color(MAROON_B)
        func_tex = TexMobject("\\frac{1}{9}x^3 - x")
        func_tex.set_color(graph.get_color())
        func_tex.shift(5.5*RIGHT+2*UP)

        words = VGroup(*[
            TextMobject(words).add_background_rectangle()
            for words in [
                "Linear transformations",
                "Null space",
                "Dot products",
                "Eigen-everything",
            ]
        ])
        words.set_color_by_gradient(BLUE_B, BLUE_D)
        words.arrange(DOWN, aligned_edge = LEFT)
        words.to_corner(UP+LEFT)
        self.play(FadeIn(
            words,
            lag_ratio = 0.5,
            run_time = 3
        ))
        self.wait()
        self.play(*[
            ApplyMethod(mob.shift, FRAME_WIDTH*RIGHT)
            for mob in (axes, everything)
        ] + [Animation(words)]
        )
        self.play(ShowCreation(graph), Animation(words))
        self.play(Write(func_tex, run_time = 2))
        self.wait(2)

        top_word = words[0]
        words.remove(top_word)
        self.play(
            FadeOut(words),
            top_word.shift, top_word.get_center()[0]*LEFT
        )
        self.wait()
        self.func_tex = func_tex
        self.graph = graph

    def show_derivative(self):
        func_tex, graph = self.func_tex, self.graph
        new_graph = FunctionGraph(lambda x : (x**2)/3.-1)
        new_graph.set_color(YELLOW)

        func_tex.generate_target()
        lp, rp = parens = TexMobject("()")
        parens.set_height(func_tex.get_height())
        L, equals = TexMobject("L=")
        deriv = TexMobject("\\frac{d}{dx}")
        new_func = TexMobject("\\frac{1}{3}x^2 - 1")
        new_func.set_color(YELLOW)
        group = VGroup(
            L, lp, func_tex.target, rp,
            equals, new_func
        )
        group.arrange()
        group.shift(2*UP).to_edge(LEFT, buff = MED_LARGE_BUFF)
        rect = BackgroundRectangle(group)
        group.add_to_back(rect)
        deriv.move_to(L, aligned_edge = RIGHT)

        self.play(
            MoveToTarget(func_tex),
            *list(map(Write, [L, lp, rp, equals, new_func]))
        )
        self.remove(func_tex)
        self.add(func_tex.target)
        self.wait()
        faded_graph = graph.copy().fade()
        self.add(faded_graph)
        self.play(
            Transform(graph, new_graph, run_time = 2),
            Animation(group)
        )
        self.wait()
        self.play(Transform(L, deriv))
        self.play(ShowSlopes(faded_graph))
        self.wait()

class TransformationsAndOperators(TeacherStudentsScene):
    def construct(self):
        self.student_says("""
            Are these the same
            as ``linear operators''?
        """, student_index = 0)
        self.random_blink()
        teacher = self.get_teacher()
        bubble = teacher.get_bubble(SpeechBubble, height = 2, width = 2)
        bubble.set_fill(BLACK, opacity = 1)
        bubble.write("Yup!")
        self.play(
            teacher.change_mode, "hooray",
            ShowCreation(bubble),
            Write(bubble.content, run_time = 1)
        )
        self.random_blink(2)

class ManyFunctions(FunctionGraphScene):
    def construct(self):
        randy = Randolph().to_corner(DOWN+LEFT)
        self.add(randy)
        for i in range(100):
            if i < 3:
                run_time = 1
                self.wait()
            elif i < 10:
                run_time = 0.4
            else:
                run_time = 0.2
            added_anims = []
            if i == 3:
                added_anims = [randy.change_mode, "confused"]
            if i == 10:
                added_anims = [randy.change_mode, "pleading"]
            self.add_random_function(
                run_time = run_time,
                added_anims = added_anims
            )

    def add_random_function(self, run_time = 1, added_anims = []):
        coefs = np.random.randint(-3, 3, np.random.randint(3, 8))
        def func(x):
            return sum([c*x**(i) for i, c, in enumerate(coefs)])
        graph = self.get_function_graph(func, animate = False)
        if graph.get_height() > FRAME_HEIGHT:
            graph.stretch_to_fit_height(FRAME_HEIGHT)
            graph.shift(graph.point_from_proportion(0.5)[1]*DOWN)
            graph.shift(interpolate(-3, 3, random.random())*UP)
        graph.set_color(random_bright_color())
        self.play(
            ShowCreation(graph, run_time = run_time),
            *added_anims
        )

class WhatDoesLinearMean(TeacherStudentsScene):
    def construct(self):
        words = TextMobject("""
            What does it mean for
            a transformation of functions
            to be """, "linear", "?",
            arg_separator = ""
        )
        words.set_color_by_tex("linear", BLUE)
        self.student_says(words)
        self.change_student_modes("pondering")
        self.random_blink(4)

class FormalDefinitionOfLinear(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors" : False,
        "include_background_plane" : False,
        "t_matrix" : [[1, 1], [-0.5, 1]],
        "w_coords" : [1, 1],
        "v_coords" : [1, -2],        
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_HEIGHT,
            "secondary_line_ratio" : 1
        },
    }
    def construct(self):
        self.plane.fade()
        self.write_properties()
        self.show_additive_property()
        self.show_scaling_property()
        self.add_words()

    def write_properties(self):
        title = TextMobject(
            "Formal definition of linearity"
        )
        title.add_background_rectangle()
        title.to_edge(UP)
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        h_line.next_to(title, DOWN)

        v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "vw"]
        tex_sets = [
            [
                ("\\text{Additivity: }",),
                ("L(", v_tex, "+", w_tex, ")"),
                ("=", "L(", v_tex, ")", "+", "L(", w_tex, ")"),
            ],
            [
                ("\\text{Scaling: }",),
                ("L(", "c", v_tex, ")"),
                ("=", "c", "L(", v_tex, ")"),
            ],
        ]
        properties = VGroup()
        for tex_set in tex_sets:
            words = VGroup(*it.starmap(TexMobject, tex_set))
            for word in words:
                word.set_color_by_tex(v_tex, YELLOW)
                word.set_color_by_tex(w_tex, MAROON_B)
                word.set_color_by_tex("c", GREEN)
            words.arrange()
            words.lhs = words[1]
            words.rhs = words[2]
            words.add_to_back(BackgroundRectangle(words))
            # words.scale(0.8)
            properties.add(words)
        properties.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF)
        properties.next_to(h_line, DOWN, buff = MED_LARGE_BUFF).to_edge(LEFT)

        self.play(Write(title), ShowCreation(h_line))
        self.wait()
        for words in properties:
            self.play(Write(words))
        self.wait()
        self.add_foreground_mobject(title, h_line, *properties)
        self.additivity, self.scaling = properties

    def show_additive_property(self):
        self.plane.save_state()

        v = self.add_vector(self.v_coords)
        v_label = self.add_transformable_label(v, "v", direction = "right")
        w = self.add_vector(self.w_coords, color = MAROON_B)
        w_label = self.add_transformable_label(w, "w", direction = "left")
        w_group = VGroup(w, w_label)
        w_group.save_state()
        self.play(w_group.shift, v.get_end())
        vw_sum = self.add_vector(w.get_end(), color = PINK)
        v_label_copy, w_label_copy = v_label.copy(), w_label.copy()
        v_label_copy.generate_target()
        w_label_copy.generate_target()
        plus = TexMobject("+")
        vw_label = VGroup(v_label_copy.target, plus, w_label_copy.target)
        vw_label.arrange()
        vw_label.next_to(vw_sum.get_end(), RIGHT)
        self.play(
            MoveToTarget(v_label_copy),
            MoveToTarget(w_label_copy),
            Write(plus)
        )
        vw_label_copy = vw_label.copy()
        vw_label = VGroup(
            VectorizedPoint(vw_label.get_left()),
            vw_label,
            VectorizedPoint(vw_label.get_right()),
        )
        self.remove(v_label_copy, w_label_copy, plus)
        self.add(vw_label)
        self.play(
            w_group.restore,
        )
        vw_label.target = VGroup(
            TexMobject("L(").scale(0.8),
            vw_label_copy,
            TexMobject(")").scale(0.8),
        )
        vw_label.target.arrange()
        for mob in vw_label, vw_label.target:
            mob.add_to_back(BackgroundRectangle(mob))

        transform = self.get_matrix_transformation(self.t_matrix)
        point = transform(vw_sum.get_end())
        vw_label.target.next_to(point, UP)
        self.apply_transposed_matrix(
            self.t_matrix, 
            added_anims = [MoveToTarget(vw_label)]
        )
        self.wait()
        self.play(w_group.shift, v.get_end())
        v_label_copy, w_label_copy = v_label.copy(), w_label.copy()
        v_label_copy.generate_target()
        w_label_copy.generate_target()
        equals, plus = TexMobject("=+")
        rhs = VGroup(
            equals, v_label_copy.target,
            plus, w_label_copy.target
        )
        rhs.arrange()
        rhs.next_to(vw_label, RIGHT)
        rect = BackgroundRectangle(rhs)
        self.play(*it.chain(
            list(map(Write, [rect, equals, plus])),
            list(map(MoveToTarget, [v_label_copy, w_label_copy])),
        ))
        to_fade = [self.plane, v, v_label, w_group, vw_label, vw_sum]
        to_fade += self.get_mobjects_from_last_animation()

        self.wait()
        self.play(*it.chain(
            list(map(FadeOut, to_fade)),
            list(map(Animation, self.foreground_mobjects))
        ))
        self.plane.restore()
        self.play(FadeIn(self.plane), *list(map(Animation, self.foreground_mobjects)))
        self.transformable_mobjects = []
        self.moving_vectors = []        
        self.transformable_labels = []
        self.moving_mobjects = []
        self.add_transformable_mobject(self.plane)
        self.add(*self.foreground_mobjects)

    def show_scaling_property(self):
        v = self.add_vector([1, -1])
        v_label = self.add_transformable_label(v, "v")
        scaled_v = v.copy().scale(2)
        scaled_v_label = TexMobject("c\\vec{\\textbf{v}}")
        scaled_v_label.set_color(YELLOW)
        scaled_v_label[0].set_color(GREEN)
        scaled_v_label.next_to(scaled_v.get_end(), RIGHT)
        scaled_v_label.add_background_rectangle()
        v_copy, v_label_copy = v.copy(), v_label.copy()
        self.play(
            Transform(v_copy, scaled_v),
            Transform(v_label_copy, scaled_v_label),
        )
        self.remove(v_copy, v_label_copy)
        self.add(scaled_v_label)
        self.add_vector(scaled_v, animate = False)
        self.wait()

        transform = self.get_matrix_transformation(self.t_matrix)
        point = transform(scaled_v.get_end())
        scaled_v_label.target = TexMobject("L(", "c", "\\vec{\\textbf{v}}", ")")
        scaled_v_label.target.set_color_by_tex("c", GREEN)
        scaled_v_label.target.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW)
        scaled_v_label.target.scale(0.8)
        scaled_v_label.target.next_to(point, RIGHT)
        scaled_v_label.target.add_background_rectangle()

        self.apply_transposed_matrix(
            self.t_matrix, 
            added_anims = [MoveToTarget(scaled_v_label)]
        )
        self.wait()
        scaled_v = v.copy().scale(2)
        rhs = TexMobject("=", "c", "L(", "\\vec{\\textbf{v}}", ")")
        rhs.set_color_by_tex("c", GREEN)
        rhs.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW)
        rhs.add_background_rectangle()
        rhs.scale(0.8)
        rhs.next_to(scaled_v_label, RIGHT)
        v_copy = v.copy()
        self.add(v_copy)
        self.play(Transform(v, scaled_v))
        self.play(Write(rhs))
        self.wait()
        faders = [
            scaled_v_label, scaled_v, v_copy, 
            v, rhs
        ] + self.transformable_labels + self.moving_vectors
        self.play(*list(map(FadeOut, faders)))

    def add_words(self):
        randy = Randolph().shift(LEFT).to_edge(DOWN)
        bubble = randy.get_bubble(SpeechBubble, width = 6, height = 4)
        bubble.set_fill(BLACK, opacity = 0.8)
        bubble.shift(0.5*DOWN)
        VGroup(randy, bubble).to_edge(RIGHT, buff = 0)
        words = TextMobject(
            "Linear transformations\\\\",
            "preserve",
            "addition and \\\\ scalar multiplication",
        )
        words.scale(0.9)
        words.set_color_by_tex("preserve", YELLOW)
        bubble.add_content(words)

        self.play(FadeIn(randy))
        self.play(
            ShowCreation(bubble),            
            Write(words),
            randy.change_mode, "speaking",
        )
        self.play(Blink(randy))
        self.wait()

class CalcStudentsKnowThatDerivIsLinear(TeacherStudentsScene):
    def construct(self):
        words = TextMobject(
            """Calc students subconsciously
            know that""",
            "$\\dfrac{d}{dx}$",
            "is linear"
        )
        words.set_color_by_tex("$\\dfrac{d}{dx}$", BLUE)
        self.teacher_says(words)
        self.change_student_modes(
            "pondering", "confused", "erm"
        )
        self.random_blink(3)

class DerivativeIsLinear(Scene):
    def construct(self):
        self.add_title()
        self.prepare_text()
        self.show_additivity()
        self.show_scaling()

    def add_title(self):
        title = TextMobject("Derivative is linear")
        title.to_edge(UP)
        self.add(title)

    def prepare_text(self):
        v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "vw"]
        additivity = TexMobject(
            "L(", v_tex, "+", w_tex, ")", "=",
            "L(", v_tex, ")+L(", w_tex, ")"
        )
        scaling = TexMobject(
            "L(", "c", v_tex, ")=", "c", "L(", v_tex, ")"
        )
        for text in additivity, scaling:
            text.set_color_by_tex(v_tex, YELLOW)
            text.set_color_by_tex(w_tex, MAROON_B)
            text.set_color_by_tex("c", GREEN)

        deriv_tex = "\\dfrac{d}{dx}"
        deriv_additivity = TexMobject(
            deriv_tex, "(", "x^3", "+", "x^2", ")", "=",
            deriv_tex, "(", "x^3", ")", "+", 
            deriv_tex, "(", "x^2", ")"
        )
        deriv_scaling = TexMobject(
            deriv_tex, "(", "4", "x^3", ")", "=",
            "4", deriv_tex, "(", "x^3", ")"
        )
        for text in deriv_additivity, deriv_scaling:
            text.set_color_by_tex("x^3", YELLOW)
            text.set_color_by_tex("x^2", MAROON_B)
            text.set_color_by_tex("4", GREEN)

        self.additivity = additivity
        self.scaling = scaling
        self.deriv_additivity = deriv_additivity
        self.deriv_scaling = deriv_scaling

    def show_additivity(self):
        general, deriv = self.additivity, self.deriv_additivity
        group = VGroup(general, deriv )
        group.arrange(DOWN, buff = 1.5)

        inner_sum = VGroup(*deriv[2:2+3])
        outer_sum_deriv = VGroup(deriv[0], deriv[1], deriv[5])
        inner_func1 = deriv[9]
        outer_deriv1 = VGroup(deriv[7], deriv[8], deriv[10])
        plus = deriv[11]
        inner_func2 = deriv[14]
        outer_deriv2 = VGroup(deriv[12], deriv[13], deriv[15])

        self.play(FadeIn(group))
        self.wait()
        self.point_out(inner_sum)
        self.point_out(outer_sum_deriv)
        self.wait()
        self.point_out(outer_deriv1, outer_deriv2)        
        self.point_out(inner_func1, inner_func2)
        self.point_out(plus)
        self.wait()
        self.play(FadeOut(group))

    def show_scaling(self):
        general, deriv = self.scaling, self.deriv_scaling
        group = VGroup(general, deriv)
        group.arrange(DOWN, buff = 1.5)

        inner_scaling = VGroup(*deriv[2:4])
        lhs_deriv = VGroup(deriv[0], deriv[1], deriv[4])
        rhs_deriv = VGroup(*deriv[7:])
        outer_scaling = deriv[6]

        self.play(FadeIn(group))
        self.wait()
        self.point_out(inner_scaling)
        self.point_out(lhs_deriv)
        self.wait()
        self.point_out(rhs_deriv)
        self.point_out(outer_scaling)
        self.wait()

    def point_out(self, *terms):
        anims = []
        for term in terms:
            anims += [
                term.scale_in_place, 1.2,
                term.set_color, RED,
            ]
        self.play(
            *anims,
            run_time = 1,
            rate_func = there_and_back
        )

class ProposeDerivativeAsMatrix(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            """
            Let's describe the
            derivative with 
            a matrix
            """,
            target_mode = "hooray"
        )
        self.random_blink()
        self.change_student_modes("pondering", "confused", "erm")
        self.random_blink(3)

class PolynomialsHaveArbitrarilyLargeDegree(Scene):
    def construct(self):
        polys = VGroup(*list(map(TexMobject, [
            "x^{300} + 9x^2",
            "4x^{4{,}000{,}000{,}000} + 1",
            "3x^{\\left(10^{100}\\right)}",
            "\\vdots"
        ])))
        polys.set_color_by_gradient(BLUE_B, BLUE_D)
        polys.arrange(DOWN, buff = MED_LARGE_BUFF)
        polys.scale(1.3)

        arrow = TexMobject("\\Rightarrow").scale(1.5)

        brace = Brace(
            Line(UP, DOWN).scale(FRAME_Y_RADIUS).shift(FRAME_X_RADIUS*RIGHT),
            LEFT
        )
        words = TextMobject("Infinitely many")
        words.scale(1.5)
        words.next_to(brace, LEFT)
        arrow.next_to(words, LEFT)
        polys.next_to(arrow, LEFT)

        self.play(Write(polys))
        self.wait()
        self.play(
            FadeIn(arrow),
            Write(words),
            GrowFromCenter(brace)
        )
        self.wait()

class GeneneralPolynomialCoordinates(Scene):
    def construct(self):
        poly = TexMobject(
            "a_n", "x^n", "+",
            "a_{n-1}", "x^{n-1}", "+",
            "\\cdots",
            "a_1", "x", "+",             
            "a_0", 
        )
        poly.set_color_by_tex("a_n", YELLOW)
        poly.set_color_by_tex("a_{n-1}", MAROON_B)
        poly.set_color_by_tex("a_1", RED)
        poly.set_color_by_tex("a_0", GREEN)
        poly.scale(1.3)

        array = Matrix(
            ["a_0", "a_1", "\\vdots", "a_{n-1}", "a_n", "0", "\\vdots"]
        )
        array.get_entries()[0].set_color(GREEN)
        array.get_entries()[1].set_color(RED)
        array.get_entries()[3].set_color(MAROON_B)
        array.get_entries()[4].set_color(YELLOW)
        array.scale(1.2)

        equals = TexMobject("=").scale(1.3)
        group = VGroup(poly, equals, array)
        group.arrange()
        group.to_edge(RIGHT)

        pre_entries = VGroup(
            poly[-1], poly[-4], poly[-5], 
            poly[3], poly[0], 
            VectorizedPoint(poly.get_left()),
            VectorizedPoint(poly.get_left()),
        )

        self.add(poly, equals, array.get_brackets())
        self.wait()
        self.play(
            Transform(pre_entries.copy(), array.get_entries())
        )
        self.wait()

class SimplePolynomialCoordinates(Scene):
    def construct(self):
        matrix = Matrix(["5", "3", "1", "0", "\\vdots"])
        matrix.to_edge(LEFT)
        self.play(Write(matrix))
        self.wait()

class IntroducePolynomialSpace(Scene):
    def construct(self):
        self.add_title()
        self.show_polynomial_cloud()
        self.split_individual_polynomial()
        self.list_basis_functions()
        self.show_example_coordinates()
        self.derivative_as_matrix()

    def add_title(self):
        title = TextMobject("Our current space: ", "All polynomials")
        title.to_edge(UP)
        title[1].set_color(BLUE)
        self.play(Write(title))
        self.wait()
        self.title = title

    def show_polynomial_cloud(self):
        cloud = ThoughtBubble()[-1]
        cloud.stretch_to_fit_height(6)
        cloud.center()
        

        polys = VGroup(
            TexMobject("x^2", "+", "3", "x", "+", "5"),
            TexMobject("4x^7-5x^2"),
            TexMobject("x^{100}+2x^{99}+3x^{98}"),
            TexMobject("3x-7"),
            TexMobject("x^{1{,}000{,}000{,}000}+1"),
            TexMobject("\\vdots"),
        )
        polys.set_color_by_gradient(BLUE_B, BLUE_D)
        polys.arrange(DOWN, buff = MED_SMALL_BUFF)
        polys.next_to(cloud.get_top(), DOWN, buff = MED_LARGE_BUFF)

        self.play(ShowCreation(cloud))
        for poly in polys:
            self.play(Write(poly), run_time = 1)
        self.wait()
        self.poly1, self.poly2 = polys[0], polys[1]
        polys.remove(self.poly1)
        self.play(
            FadeOut(cloud),
            FadeOut(polys),
            self.poly1.next_to, ORIGIN, LEFT,
            self.poly1.set_color, WHITE
        )

    def split_individual_polynomial(self):
        leading_coef = TexMobject("1")
        leading_coef.next_to(self.poly1[0], LEFT, aligned_edge = DOWN)
        self.poly1.add_to_back(leading_coef)
        one = TexMobject("\\cdot", "1")
        one.next_to(self.poly1[-1], RIGHT, aligned_edge = DOWN)
        self.poly1.add(one)
        for mob in leading_coef, one:
            mob.set_color(BLACK)

        brace = Brace(self.poly1)
        brace.text = brace.get_text("Already written as \\\\ a linear combination")

        index_to_color = {
            0 : WHITE,
            1 : Z_COLOR,
            4 : Y_COLOR,
            7 : X_COLOR,
        }
        self.play(
            GrowFromCenter(brace),
            Write(brace.text),
            *[
                ApplyMethod(self.poly1[index].set_color, color)
                for index, color in list(index_to_color.items())
            ]
        )
        self.wait()
        self.brace = brace

    def list_basis_functions(self):
        title = TextMobject("Basis functions")
        title.next_to(self.title, DOWN, buff = MED_SMALL_BUFF)
        title.to_edge(RIGHT)
        h_line = Line(ORIGIN, RIGHT).scale(title.get_width())
        h_line.next_to(title, DOWN)

        x_cubed = TexMobject("x^3")
        x_cubed.set_color(MAROON_B)
        x_cubed.to_corner(DOWN+RIGHT).shift(2*(DOWN+RIGHT))
        basis_group = VGroup(
            self.poly1[7][1],
            self.poly1[4],
            self.poly1[1],
            x_cubed
        ).copy()
        basis_group.generate_target()
        basis_group.target.arrange(
            DOWN, buff = 0.75*LARGE_BUFF, aligned_edge = LEFT
        )
        basis_group.target.to_edge(RIGHT, buff = MED_LARGE_BUFF)
        dots = TexMobject("\\vdots")
        dots.next_to(basis_group.target, DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT)

        basis_functions = [
            TexMobject("b_%d(x)"%i, "=")
            for i in range(len(list(basis_group)))
        ]
        for basis_func, term in zip(basis_functions, basis_group.target):
            basis_func.set_color(term.get_color())
            basis_func.next_to(term, LEFT)
        for i in 2, 3:
            basis_functions[i].shift(SMALL_BUFF*DOWN)

        self.play(
            FadeIn(title),
            ShowCreation(h_line),
            MoveToTarget(basis_group),
            Write(dots)
        )
        for basis_func in basis_functions:
            self.play(Write(basis_func, run_time = 1))
        self.play(Write(dots))
        self.wait()
        self.basis = basis_group
        self.basis_functions = basis_functions

    def show_example_coordinates(self):
        coords = Matrix(["5", "3", "1", "0", "0", "\\vdots"])
        for i, color in enumerate([X_COLOR, Y_COLOR, Z_COLOR]):
            coords[i].set_color(color)
        self.poly1.generate_target()
        equals = TexMobject("=").next_to(coords, LEFT)
        self.poly1.target.next_to(equals, LEFT)
        entries = coords.get_entries()
        entries.save_state()
        entries.set_fill(opacity = 0)

        self.play(
            MoveToTarget(self.poly1),
            Write(equals),
            FadeOut(self.brace),
            FadeOut(self.brace.text)
        )
        for entry, index in zip(entries, [6, 3, 0]):
            entry.move_to(self.poly1[index])
        self.play(Write(coords.get_brackets()))
        self.play(
            entries.restore,
            lag_ratio = 0.5,
            run_time = 3
        )
        self.wait()
        target = self.poly1.copy()
        terms = [
            VGroup(*target[6:8]),
            VGroup(target[5], *target[3:5]),
            VGroup(target[2], *target[0:2]),
        ]
        target[5].next_to(target[3], LEFT)
        target[2].next_to(target[0], LEFT)
        more_terms = [
            TexMobject("+0", "x^3").set_color_by_tex("x^3", MAROON_B),
            TexMobject("+0", "x^4").set_color_by_tex("x^4", YELLOW),
            TexMobject("\\vdots")
        ]        
        for entry, term in zip(entries, terms+more_terms):
            term.next_to(entry, LEFT, buff = LARGE_BUFF)
        more_terms[-1].shift(MED_SMALL_BUFF*LEFT)

        self.play(Transform(self.poly1, target))
        self.wait()
        self.play(FadeIn(
            VGroup(*more_terms), 
            lag_ratio = 0.5,
            run_time = 2
        ))
        self.wait()

        self.play(*list(map(FadeOut, [self.poly1]+more_terms)))
        self.poly2.next_to(equals, LEFT)
        self.poly2.shift(MED_SMALL_BUFF*UP)
        self.poly2.set_color(WHITE)
        self.poly2[0].set_color(TEAL)
        VGroup(*self.poly2[3:5]).set_color(Z_COLOR)
        new_coords = Matrix(["0", "0", "-5", "0", "0", "0", "0", "4", "\\vdots"])
        new_coords.get_entries()[2].set_color(Z_COLOR)
        new_coords.get_entries()[7].set_color(TEAL)
        new_coords.set_height(6)
        new_coords.move_to(coords, aligned_edge = LEFT)
        self.play(
            Write(self.poly2),
            Transform(coords, new_coords)
        )
        self.wait()
        for i, mob in (2, VGroup(*self.poly2[3:5])), (7, self.poly2[0]):
            self.play(
                new_coords.get_entries()[i].scale_in_place, 1.3,
                mob.scale_in_place, 1.3,
                rate_func = there_and_back
            )
            self.remove(*self.get_mobjects_from_last_animation())
            self.add(self.poly2)
            self.wait()
        self.play(*list(map(FadeOut, [self.poly2, coords, equals])))

    def derivative_as_matrix(self):
        matrix = Matrix([
            [
                str(j) if j == i+1 else "0" 
                for j in range(4)
            ] + ["\\cdots"]
            for i in range(4)
        ] + [
            ["\\vdots"]*4 + ["\\ddots"]
        ])
        matrix.shift(2*LEFT)
        diag_entries = VGroup(*[
            matrix.get_mob_matrix()[i, i+1]
            for i in range(3)
        ])
        ##Horrible
        last_col = VGroup(*matrix.get_mob_matrix()[:,-1])
        last_col_top = last_col.get_top()
        last_col.arrange(DOWN, buff = 0.83)
        last_col.move_to(last_col_top, aligned_edge = UP+RIGHT)
        ##End horrible
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR, MAROON_B)

        deriv = TexMobject("\\dfrac{d}{dx}")
        equals = TexMobject("=")
        equals.next_to(matrix, LEFT)
        deriv.next_to(equals, LEFT)

        self.play(FadeIn(deriv), FadeIn(equals))
        self.play(Write(matrix))
        self.wait()
        diag_entries.save_state()
        diag_entries.generate_target()
        diag_entries.target.scale_in_place(1.2)
        diag_entries.target.set_color(YELLOW)
        for anim in MoveToTarget(diag_entries), diag_entries.restore:
            self.play(
                anim,
                lag_ratio = 0.5,
                run_time = 1.5,
            )
        self.wait()
        matrix.generate_target()
        matrix.target.to_corner(DOWN+LEFT).shift(0.25*UP)
        deriv.generate_target()
        deriv.target.next_to(
            matrix.target, UP, 
            buff = MED_SMALL_BUFF,
            aligned_edge = LEFT
        )
        deriv.target.shift(0.25*RIGHT)
        self.play(
            FadeOut(equals),
            *list(map(MoveToTarget, [matrix, deriv]))
        )

        poly = TexMobject(
            "(", "1", "x^3", "+",
            "5", "x^2", "+",
            "4", "x", "+",
            "5", ")"
        )
        coefs = VGroup(*np.array(poly)[[10, 7, 4, 1]])
        VGroup(*poly[1:3]).set_color(MAROON_B)
        VGroup(*poly[4:6]).set_color(Z_COLOR)
        VGroup(*poly[7:9]).set_color(Y_COLOR)
        VGroup(*poly[10:11]).set_color(X_COLOR)
        poly.next_to(deriv)
        self.play(FadeIn(poly))

        array = Matrix(list(coefs.copy()) + [TexMobject("\\vdots")])
        array.next_to(matrix, RIGHT)
        self.play(Write(array.get_brackets()))
        to_remove = []
        for coef, entry in zip(coefs, array.get_entries()):
            self.play(Transform(coef.copy(), entry))
            to_remove += self.get_mobjects_from_last_animation()
        self.play(Write(array.get_entries()[-1]))
        to_remove += self.get_mobjects_from_last_animation()        
        self.remove(*to_remove)
        self.add(array)

        eq1, eq2 = TexMobject("="), TexMobject("=")
        eq1.next_to(poly)
        eq2.next_to(array)
        
        poly_result = TexMobject(
            "3", "x^2", "+",
            "10", "x", "+",
            "4"
        )
        poly_result.next_to(eq1)
        brace = Brace(poly_result, buff = 0)

        self.play(*list(map(Write, [eq1, eq2, brace])))

        result_coefs = VGroup(*np.array(poly_result)[[6, 3, 0]])
        VGroup(*poly_result[0:2]).set_color(MAROON_B)
        VGroup(*poly_result[3:5]).set_color(Z_COLOR)
        VGroup(*poly_result[6:]).set_color(Y_COLOR)
        result_terms = [
            VGroup(*poly_result[6:]),
            VGroup(*poly_result[3:6]),
            VGroup(*poly_result[0:3]),
        ]
        relevant_entries = VGroup(*array.get_entries()[1:4])
        dots = [TexMobject("\\cdot") for x in range(3)]
        result_entries = []
        for entry, diag_entry, dot in zip(relevant_entries, diag_entries, dots):
            entry.generate_target()
            diag_entry.generate_target()
            group = VGroup(diag_entry.target, dot, entry.target)
            group.arrange()
            result_entries.append(group)
        result_array = Matrix(
            result_entries + [
                TexMobject("0"),
                TexMobject("\\vdots")
            ]
        )
        result_array.next_to(eq2)

        rects = [
            Rectangle(
                color = YELLOW
            ).replace(
                VGroup(*matrix.get_mob_matrix()[i,:]),
                stretch = True
            ).stretch_in_place(1.1, 0).stretch_in_place(1.3, 1)
            for i in range(3)
        ]
        vert_rect = Rectangle(color = YELLOW)
        vert_rect.replace(array.get_entries(), stretch = True)
        vert_rect.stretch_in_place(1.1, 1)
        vert_rect.stretch_in_place(1.5, 0)
        tuples = list(zip(
            relevant_entries,
            diag_entries,
            result_entries,
            rects,
            result_terms,
            coefs[1:]
        ))
        self.play(Write(result_array.get_brackets()))
        for entry, diag_entry, result_entry, rect, result_term, coef in tuples:
            self.play(FadeIn(rect), FadeIn(vert_rect))
            self.wait()
            self.play(
                entry.scale_in_place, 1.2,
                diag_entry.scale_in_place, 1.2,
            )
            diag_entry_target, dot, entry_target = result_entry
            self.play(
                Transform(entry.copy(), entry_target),
                Transform(diag_entry.copy(), diag_entry_target),
                entry.scale_in_place, 1/1.2,
                diag_entry.scale_in_place, 1/1.2,
                Write(dot)
            )
            self.wait()
            self.play(Transform(coef.copy(), VGroup(result_term)))
            self.wait()
            self.play(FadeOut(rect), FadeOut(vert_rect))
        self.play(*list(map(Write, result_array.get_entries()[3:])))
        self.wait()

class MatrixVectorMultiplicationAndDerivative(TeacherStudentsScene):
    def construct(self):
        mv_mult = VGroup(
            Matrix([[3, 1], [0, 2]]).set_column_colors(X_COLOR, Y_COLOR),
            Matrix(["x", "y"]).set_column_colors(YELLOW)
        )
        mv_mult.arrange()
        mv_mult.scale(0.75)
        arrow = TexMobject("\\Leftrightarrow")
        deriv = TexMobject("\\dfrac{df}{dx}")
        group = VGroup(mv_mult, arrow, deriv)
        group.arrange(buff = MED_SMALL_BUFF)
        arrow.set_color(BLACK)

        teacher = self.get_teacher()
        bubble = teacher.get_bubble(SpeechBubble, height = 4)
        bubble.add_content(group)

        self.play(
            teacher.change_mode, "speaking",
            ShowCreation(bubble),
            Write(group)
        )
        self.random_blink()
        group.generate_target()        
        group.target.scale(0.8)
        words = TextMobject("Linear transformations")
        h_line = Line(ORIGIN, RIGHT).scale(words.get_width())
        h_line.next_to(words, DOWN)
        group.target.next_to(h_line, DOWN, buff = MED_SMALL_BUFF)
        group.target[1].set_color(WHITE)
        new_group = VGroup(words, h_line, group.target)
        bubble.add_content(new_group)

        self.play(
            MoveToTarget(group),
            ShowCreation(h_line),
            Write(words),
            self.get_teacher().change_mode, "hooray"
        )
        self.change_student_modes(*["pondering"]*3)
        self.random_blink(3)

class CompareTermsInLinearAlgebraToFunction(Scene):
    def construct(self):
        l_title = TextMobject("Linear algebra \\\\ concepts")
        r_title = TextMobject("Alternate names when \\\\ applied to functions")
        for title, vect in (l_title, LEFT), (r_title, RIGHT):
            title.to_edge(UP)
            title.shift(vect*FRAME_X_RADIUS/2)
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        h_line.shift(
            VGroup(l_title, r_title).get_bottom()[1]*UP + SMALL_BUFF*DOWN
        )
        v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS)
        VGroup(h_line, v_line).set_color(BLUE)

        self.add(l_title, r_title)
        self.play(*list(map(ShowCreation, [h_line, v_line])))
        self.wait()

        lin_alg_concepts = VGroup(*list(map(TextMobject, [
            "Linear transformations",
            "Dot products",
            "Eigenvectors",
        ])))
        function_concepts = VGroup(*list(map(TextMobject, [
            "Linear operators",
            "Inner products",
            "Eigenfunctions",
        ])))
        for concepts, vect in (lin_alg_concepts, LEFT), (function_concepts, RIGHT):
            concepts.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT)
            concepts.next_to(h_line, DOWN, buff = LARGE_BUFF)
            concepts.shift(vect*FRAME_X_RADIUS/2)
            concepts.set_color_by_gradient(YELLOW_B, YELLOW_C)

            for concept in concepts:
                self.play(Write(concept, run_time = 1))
            self.wait()

class BackToTheQuestion(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            """
            Wait...so how does
            this relate to what vectors 
            really are?
            """,
            target_mode = "confused"
        )
        self.random_blink(2)
        self.teacher_says(
            """
            There are many different
            vector-ish things
            """
        )
        self.random_blink(2)

class YouAsAMathematician(Scene):
    def construct(self):
        mathy = Mathematician()
        mathy.to_corner(DOWN+LEFT)
        words = TextMobject("You as a mathematician")
        words.shift(2*UP)
        arrow = Arrow(words.get_bottom(), mathy.get_corner(UP+RIGHT))
        bubble = mathy.get_bubble()

        equations = self.get_content()
        bubble.add_content(equations)

        self.add(mathy)
        self.play(Write(words, run_time = 2))
        self.play(
            ShowCreation(arrow),
            mathy.change_mode, "wave_1",
            mathy.look, OUT
        )
        self.play(Blink(mathy))
        self.wait()
        self.play(
            FadeOut(words),
            FadeOut(arrow),
            mathy.change_mode, "pondering",
            ShowCreation(bubble),
        )
        self.play(Write(equations))
        self.play(Blink(mathy))
        self.wait()

        bubble.write("Does this make any sense \\\\ for functions too?")
        self.play(
            equations.next_to, mathy.eyes, RIGHT, 2*LARGE_BUFF,
            mathy.change_mode, "confused",
            mathy.look, RIGHT,
            Write(bubble.content)
        )
        self.wait()
        self.play(Blink(mathy))

    def get_content(self):
        v_tex = "\\vec{\\textbf{v}}"
        eigen_equation = TexMobject("A", v_tex, "=", "\\lambda", v_tex)
        v_ne_zero = TexMobject(v_tex, "\\ne \\vec{\\textbf{0}}")
        det_equation = TexMobject("\\det(A-", "\\lambda", "I)=0")
        arrow = TexMobject("\\Rightarrow")

        for tex in eigen_equation, v_ne_zero, det_equation:
            tex.set_color_by_tex(v_tex, YELLOW)
            tex.set_color_by_tex("\\lambda", MAROON_B)

        lhs = VGroup(eigen_equation, v_ne_zero)
        lhs.arrange(DOWN)
        group = VGroup(lhs, arrow, det_equation)
        group.arrange(buff = MED_SMALL_BUFF)
        return group

class ShowVectorSpaces(Scene):
    def construct(self):
        title = TextMobject("Vector spaces")
        title.to_edge(UP)
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        h_line.next_to(title, DOWN)

        v_lines = [
            Line(
                h_line.get_center(), FRAME_Y_RADIUS*DOWN
            ).shift(vect*FRAME_X_RADIUS/3.)
            for vect in (LEFT, RIGHT)
        ]
        vectors = self.get_vectors()
        vectors.shift(LEFT*FRAME_X_RADIUS*(2./3))
        arrays = self.get_arrays()
        functions = self.get_functions()
        functions.shift(RIGHT*FRAME_X_RADIUS*(2./3))

        self.add(h_line, *v_lines)
        self.play(ShowCreation(
            vectors,
            run_time = 3
        ))
        self.play(Write(arrays))
        self.play(Write(functions))
        self.wait()
        self.play(Write(title))

    def get_vectors(self, n_vectors = 10):
        vectors = VGroup(*[
            Vector(RIGHT).scale(scalar).rotate(angle)
            for scalar, angle in zip(
                2*np.random.random(n_vectors)+0.5,
                np.linspace(0, 6, n_vectors)
            )
        ])
        vectors.set_color_by_gradient(YELLOW, MAROON_B)
        return vectors

    def get_arrays(self):
        arrays = VGroup(*[
            VGroup(*[
                Matrix(np.random.randint(-9, 9, 2))
                for x in range(4)
            ])
            for x in range(3)
        ])
        for subgroup in arrays:
            subgroup.arrange(DOWN, buff = MED_SMALL_BUFF)
        arrays.arrange(RIGHT)
        arrays.scale(0.7)
        arrays.set_color_by_gradient(YELLOW, MAROON_B)
        return arrays

    def get_functions(self):
        axes = Axes()
        axes.scale(0.3)
        functions = VGroup(*[
            FunctionGraph(func, x_min = -4, x_max = 4)
            for func in [
                lambda x : x**3 - 9*x,
                lambda x : x**3 - 4*x,
                lambda x : x**2 - 1,
            ]
        ])
        functions.stretch_to_fit_width(FRAME_X_RADIUS/2.)
        functions.stretch_to_fit_height(6)
        functions.set_color_by_gradient(YELLOW, MAROON_B)
        functions.center()
        return VGroup(axes, functions)

class ToolsOfLinearAlgebra(Scene):
    def construct(self):
        words = VGroup(*list(map(TextMobject, [
            "Linear transformations",
            "Null space",
            "Eigenvectors",
            "Dot products",
            "$\\vdots$"
        ])))
        words.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF)
        words[-1].next_to(words[-2], DOWN)
        self.play(FadeIn(
            words,
            lag_ratio = 0.5,
            run_time = 3
        ))
        self.wait()

class MathematicianSpeakingToAll(Scene):
    def construct(self):
        mathy = Mathematician().to_corner(DOWN+LEFT)
        others = VGroup(*[
            Randolph().flip().set_color(color)
            for color in (BLUE_D, GREEN_E, GOLD_E, BLUE_C)
        ])
        others.arrange()
        others.scale(0.8)
        others.to_corner(DOWN+RIGHT)

        bubble = mathy.get_bubble(SpeechBubble)
        bubble.write("""
            I don't want to think
            about all y'all's crazy
            vector spaces
        """)

        self.add(mathy, others)
        self.play(
            ShowCreation(bubble),
            Write(bubble.content),
            mathy.change_mode, "sassy",
            mathy.look_at, others
        )
        self.play(Blink(others[3]))
        self.wait()
        thought_bubble = mathy.get_bubble(ThoughtBubble)
        self.play(
            FadeOut(bubble.content),
            Transform(bubble, thought_bubble),
            mathy.change_mode, "speaking",
            mathy.look_at, bubble,
            *[ApplyMethod(pi.look_at, bubble) for pi in others]
        )

        vect = -bubble.get_bubble_center()
        def func(point):
            centered = point+vect
            return 10*centered/get_norm(centered)
        self.play(*[
            ApplyPointwiseFunction(func, mob)
            for mob in self.get_mobjects()
        ])

class ListAxioms(Scene):
    def construct(self):
        title = TextMobject("Rules for vectors addition and scaling")
        title.to_edge(UP)
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        h_line.next_to(title, DOWN)
        self.add(title, h_line)

        u_tex, v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "uvw"]
        axioms = VGroup(*it.starmap(TexMobject, [
            (
                "1. \\,", 
                u_tex, "+", "(", v_tex, "+", w_tex, ")=(", 
                u_tex, "+", v_tex, ")+", w_tex
            ),
            (   "2. \\,", 
                v_tex, "+", w_tex, "=", w_tex, "+", v_tex
            ),
            (
                "3. \\,", 
                "\\text{There is a vector }", "\\textbf{0}",
                "\\text{ such that }", "\\textbf{0}+", v_tex,
                "=", v_tex, "\\text{ for all }", v_tex
            ),
            (
                "4. \\,", 
                "\\text{For every vector }", v_tex, 
                "\\text{ there is a vector }", "-", v_tex,
                "\\text{ so that }", v_tex, "+", "(-", v_tex, ")=\\textbf{0}"
            ),
            (   "5. \\,", 
                "a", "(", "b", v_tex, ")=(", "a", "b", ")", v_tex
            ),
            (  
                "6. \\,", 
                "1", v_tex, "=", v_tex
            ),
            (
                "7. \\,", 
                "a", "(", v_tex, "+", w_tex, ")", "=", 
                "a", v_tex, "+", "a", w_tex
            ),
            (
                "8. \\,", 
                "(", "a", "+", "b", ")", v_tex, "=",
                "a", v_tex, "+", "b", v_tex
            ),
        ]))
        tex_color_pairs = [
            (v_tex, YELLOW),
            (w_tex, MAROON_B),
            (u_tex, PINK),
            ("a", BLUE),
            ("b", GREEN)

        ]
        for axiom in axioms:
            for tex, color in tex_color_pairs:
                axiom.set_color_by_tex(tex, color)
        axioms.arrange(
            DOWN, buff = MED_LARGE_BUFF,
            aligned_edge = LEFT
        )
        axioms.set_width(FRAME_WIDTH-1)
        axioms.next_to(h_line, DOWN, buff = MED_SMALL_BUFF)

        self.play(FadeIn(
            axioms,
            lag_ratio = 0.5,
            run_time = 5
        ))
        self.wait()
        axioms_word = TextMobject("``Axioms''")
        axioms_word.set_color(YELLOW)
        axioms_word.scale(2)
        axioms_word.shift(FRAME_X_RADIUS*RIGHT/2, FRAME_Y_RADIUS*DOWN/2)
        self.play(Write(axioms_word, run_time = 3))
        self.wait()

class AxiomsAreInterface(Scene):
    def construct(self):
        mathy = Mathematician().to_edge(LEFT)
        mathy.change_mode("pondering")
        others = [
            Randolph().flip().set_color(color)
            for color in (BLUE_D, GREEN_E, GOLD_E, BLUE_C)
        ]
        others = VGroup(
            VGroup(*others[:2]),
            VGroup(*others[2:]),
        )
        for group in others:
            group.arrange(RIGHT)
        others.arrange(DOWN)
        others.scale(0.8)
        others.to_edge(RIGHT)
        VGroup(mathy, others).to_edge(DOWN)
        double_arrow = DoubleArrow(mathy, others)

        axioms, are, rules_of_nature = words = TextMobject(
            "Axioms", "are", "rules of nature"
        )
        words.to_edge(UP)
        axioms.set_color(YELLOW)
        an_interface = TextMobject("an interface")
        an_interface.next_to(rules_of_nature, DOWN)
        red_line = Line(
            rules_of_nature.get_left(),
            rules_of_nature.get_right(),
            color = RED
        )

        self.play(Write(words))
        self.wait()
        self.play(ShowCreation(red_line))
        self.play(Transform(
            rules_of_nature.copy(),
            an_interface
        ))
        self.wait()
        self.play(FadeIn(mathy))
        self.play(
            ShowCreation(double_arrow),
            FadeIn(others, lag_ratio = 0.5, run_time = 2)
        )
        self.play(axioms.copy().next_to, double_arrow, UP)
        self.play(Blink(mathy))
        self.wait()

class VectorSpaceOfPiCreatures(Scene):
    def construct(self):
        creatures = self.add_creatures()
        self.show_sum(creatures)

    def add_creatures(self):
        creatures = VGroup(*[
            VGroup(*[
                PiCreature()
                for x in range(4)
            ]).arrange(RIGHT, buff = 1.5)
            for y in range(4)
        ]).arrange(DOWN, buff = 1.5)
        creatures = VGroup(*it.chain(*creatures))
        creatures.set_height(FRAME_HEIGHT-1)
        for pi in creatures:
            pi.change_mode(random.choice([
                "pondering", "pondering",
                "happy", "happy", "happy",
                "confused", 
                "angry", "erm", "sassy", "hooray", 
                "speaking", "tired", 
                "plain", "plain"
            ]))
            if random.random() < 0.5:
                pi.flip()
            pi.shift(0.5*(random.random()-0.5)*RIGHT)
            pi.shift(0.5*(random.random()-0.5)*UP)
            pi.set_color(random.choice([
                BLUE_B, BLUE_C, BLUE_D, BLUE_E,
                MAROON_B, MAROON_C, MAROON_D, MAROON_E,
                GREY_BROWN, GREY_BROWN, GREY,
                YELLOW_C, YELLOW_D, YELLOW_E
            ]))
            pi.scale_in_place(random.random()+0.5)

        self.play(FadeIn(
            creatures,
            lag_ratio = 0.5,
            run_time = 3
        ))
        self.wait()
        return creatures

    def show_sum(self, creatures):
        def is_valid(pi1, pi2, pi3):
            if len(set([pi.get_color() for pi in (pi1, pi2, pi3)])) < 3:
                return False
            if pi1.is_flipped()^pi2.is_flipped():
                return False
            return True
        pi1, pi2, pi3 = pis = [random.choice(creatures) for x in range(3)]
        while not is_valid(pi1, pi2, pi3):
            pi1, pi2, pi3 = pis = [random.choice(creatures) for x in range(3)]
        creatures.remove(*pis)

        transform = Transform(pi1.copy(), pi2.copy())
        transform.update(0.5)
        sum_pi = transform.mobject
        sum_pi.set_height(pi1.get_height()+pi2.get_height())
        for pi in pis:
            pi.generate_target()
        plus, equals = TexMobject("+=")
        sum_equation = VGroup(
            pi1.target, plus, pi2.target,
            equals, sum_pi
        )
        sum_equation.arrange().center()

        scaled_pi3 = pi3.copy().scale(2)
        equals2 = TexMobject("=")
        two = TexMobject("2 \\cdot")
        scale_equation = VGroup(
            two, pi3.target, equals2, scaled_pi3
        )
        scale_equation.arrange()

        VGroup(sum_equation, scale_equation).arrange(
            DOWN, buff = MED_SMALL_BUFF
        )

        self.play(FadeOut(creatures))
        self.play(*it.chain(
            list(map(MoveToTarget, [pi1, pi2, pi3])),
            list(map(Write, [plus, equals, two, equals2])),
        ))
        self.play(
            Transform(pi1.copy(), sum_pi),
            Transform(pi2.copy(), sum_pi),
            Transform(pi3.copy(), scaled_pi3)
        )
        self.remove(*self.get_mobjects_from_last_animation())
        self.add(sum_pi, scaled_pi3)
        for pi in pi1, sum_pi, scaled_pi3, pi3:
            self.play(Blink(pi))

class MathematicianDoesntHaveToThinkAboutThat(Scene):
    def construct(self):
        mathy = Mathematician().to_corner(DOWN+LEFT)
        bubble = mathy.get_bubble(ThoughtBubble, height = 4)
        words = TextMobject("I don't have to worry", "\\\\ about that madness!")
        bubble.add_content(words)
        new_words = TextMobject("So long as I", "\\\\ work abstractly")
        bubble.add_content(new_words)

        self.play(
            mathy.change_mode, "hooray",
            ShowCreation(bubble),
            Write(words)
        )
        self.play(Blink(mathy))
        self.wait()
        self.play(
            mathy.change_mode, "pondering",
            Transform(words, new_words)
        )
        self.play(Blink(mathy))
        self.wait()

class TextbooksAreAbstract(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            """
            All the textbooks I found
            are pretty abstract.
            """,
            target_mode = "pleading"
        )
        self.random_blink(3)
        self.teacher_says(
            """
            For each new concept, 
            contemplate it for 2d space
            with grid lines...
            """
        )
        self.change_student_modes("pondering")
        self.random_blink(2)
        self.teacher_says(
            "...then in some different\\\\",
            "context, like a function space"
        )
        self.change_student_modes(*["pondering"]*2)
        self.random_blink()
        self.teacher_says(
            "Only then should you\\\\",
            "think from the axioms",
            target_mode = "surprised"
        )
        self.change_student_modes(*["pondering"]*3)
        self.random_blink()

class LastAskWhatAreVectors(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "So...what are vectors?",
            target_mode = "erm"
        )
        self.random_blink()
        self.teacher_says(
            """
            The form they take
            doesn't really matter
            """
        )
        self.random_blink()

class WhatIsThree(Scene):
    def construct(self):
        what_is, three, q_mark = words = TextMobject(
            "What is ", "3", "?",
            arg_separator = ""
        )
        words.scale(1.5)
        self.play(Write(words))
        self.wait()
        self.play(
            FadeOut(what_is),
            FadeOut(q_mark),
            three.center
        )

        triplets = [
            VGroup(*[
                PiCreature(color = color).scale(0.4)
                for color in (BLUE_E, BLUE_C, BLUE_D)
            ]),
            VGroup(*[HyperCube().scale(0.3) for x in range(3)]),
            VGroup(*[Vector(RIGHT) for x in range(3)]),
            TexMobject("""
                \\Big\\{
                    \\emptyset, 
                    \\{\\emptyset\\}, 
                    \\{\\{\\emptyset\\}, \\emptyset\\}
                \\Big\\}
            """)
        ]
        directions = [UP+LEFT, UP+RIGHT, DOWN+LEFT, DOWN+RIGHT]
        for group, vect in zip(triplets, directions):
            if isinstance(group, TexMobject):
                pass
            elif isinstance(group[0], Vector):
                group.arrange(RIGHT)
                group.set_color_by_gradient(YELLOW, MAROON_B)
            else:
                m1, m2, m3 = group
                m2.next_to(m1, buff = MED_SMALL_BUFF)
                m3.next_to(VGroup(m1, m2), DOWN, buff = MED_SMALL_BUFF)
            group.next_to(three, vect, buff = LARGE_BUFF)
            self.play(FadeIn(group))
        self.wait()
        self.play(*[
            Transform(
                trip, three, 
                lag_ratio = 0.5,
                run_time = 2
            )
            for trip in triplets
        ])

class IStillRecommendConcrete(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            I still recommend 
            thinking concretely
        """)
        self.random_blink(2)
        self.student_thinks("")
        self.zoom_in_on_thought_bubble()

class AbstractionIsThePrice(Scene):
    def construct(self):
        words = TextMobject(
            "Abstractness", "is the price\\\\"
            "of", "generality"
        )
        words.set_color_by_tex("Abstractness", YELLOW)
        words.set_color_by_tex("generality", BLUE)
        self.play(Write(words))
        self.wait()

class ThatsAWrap(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("That's all for now!")
        self.random_blink(2)

class GoodLuck(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Good luck with \\\\ your future learning!",
            target_mode = "hooray"
        )
        self.change_student_modes(*["happy"]*3)
        self.random_blink(3)




























