diff --git a/animation/simple_animations.py b/animation/simple_animations.py index aa3049b8..0bdb0894 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -50,7 +50,6 @@ class ShowCreation(ShowPartial): class Write(ShowCreation): CONFIG = { - "run_time" : 3, "rate_func" : None, "submobject_mode" : "lagged_start", } @@ -59,8 +58,20 @@ class Write(ShowCreation): mobject = TextMobject(mob_or_text) else: mobject = mob_or_text + if "run_time" not in kwargs: + self.establish_run_time(mobject) ShowCreation.__init__(self, mobject, **kwargs) + def establish_run_time(self, mobject): + num_subs = len(mobject.family_members_with_points()) + if num_subs < 5: + self.run_time = 1 + elif num_subs < 15: + self.run_time = 2 + else: + self.run_time = 3 + + class ShowPassingFlash(ShowPartial): CONFIG = { "time_width" : 0.1 diff --git a/animation/transform.py b/animation/transform.py index 95abab45..66d80bbc 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -14,6 +14,7 @@ class Transform(Animation): CONFIG = { "path_arc" : 0, "path_func" : None, + "submobject_mode" : "all_at_once", } def __init__(self, mobject, ending_mobject, **kwargs): #Copy ending_mobject so as to not mess with caller diff --git a/eola/chapter3.py b/eola/chapter3.py index d14e7447..2e6204de 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -80,43 +80,324 @@ class Introduction(TeacherStudentsScene): ) ) -class ThreePerspectives(Scene): +class MatrixVectorMechanicalMultiplication(NumericalMatrixMultiplication): + CONFIG = { + "left_matrix" : [[1, -3], [2, 4]], + "right_matrix" : [[5], [7]] + } + +class PostponeHigherDimensions(TeacherStudentsScene): def construct(self): - title = TextMobject("Linear transformations") + self.setup() + self.student_says("What about 3 dimensions?") + self.random_blink() + self.teacher_says("All in due time,\\\\ young padawan") + self.random_blink() + +class DescribeTransformation(Scene): + def construct(self): + self.add_title() + self.show_function() + + def add_title(self): + title = TextMobject(["Linear", "transformation"]) title.to_edge(UP) - title.highlight(YELLOW) - self.add(title) + linear, transformation = title.split() + brace = Brace(transformation, DOWN) + function = TextMobject("function").next_to(brace, DOWN) + function.highlight(YELLOW) - words = VMobject(*map(TextMobject, [ - "1. Geometric perspective", - "2. Numerical computations", - "3. Abstract definition" - ])) - words.arrange_submobjects(DOWN, buff = 0.5, aligned_edge = LEFT) - words.to_edge(LEFT, buff = 2) - for word in words.split(): - self.play(Write(word), run_time = 2) - self.dither() - for word in words.split(): - all_else = self.get_mobjects() - all_else.remove(word) - all_else_copies = [mob.copy() for mob in all_else] - self.play(*[ApplyMethod(mob.fade, 0.7) for mob in all_else]) - self.dither() - self.play(*[Transform(*pair) for pair in zip(all_else, all_else_copies)]) + self.play(Write(title)) + self.dither() + self.play( + GrowFromCenter(brace), + Write(function), + ApplyMethod(linear.fade) + ) -class ShowGridCreation(Scene): + def show_function(self): + f_of_x = TexMobject("f(x)") + L_of_v = TexMobject("L(\\vec{\\textbf{v}})") + nums = [5, 2, -3] + num_inputs = VMobject(*map(TexMobject, map(str, nums))) + num_outputs = VMobject(*[ + TexMobject(str(num**2)) + for num in nums + ]) + for mob in num_inputs, num_outputs: + mob.arrange_submobjects(DOWN, buff = 1) + num_inputs.next_to(f_of_x, LEFT, buff = 1) + num_outputs.next_to(f_of_x, RIGHT, buff = 1) + f_point = VectorizedPoint(f_of_x.get_center()) + + input_vect = Matrix([5, 7]) + input_vect.next_to(L_of_v, LEFT, buff = 1) + output_vect = Matrix([2, -3]) + output_vect.next_to(L_of_v, RIGHT, buff = 1) + + vector_input_words = TextMobject("Vector input") + vector_input_words.highlight(MAROON_C) + vector_input_words.next_to(input_vect, DOWN) + vector_output_words = TextMobject("Vector output") + vector_output_words.highlight(BLUE) + vector_output_words.next_to(output_vect, DOWN) + + self.play(Write(f_of_x, run_time = 1)) + self.play(Write(num_inputs, run_time = 2)) + self.dither() + for mob in f_point, num_outputs: + self.play(Transform( + num_inputs, mob, + submobject_mode = "lagged_start" + )) + self.dither() + + self.play( + FadeOut(num_inputs), + Transform(f_of_x, L_of_v) + ) + self.play( + Write(input_vect), + Write(vector_input_words) + ) + self.dither() + for mob in f_point, output_vect: + self.play(Transform( + input_vect, mob, + submobject_mode = "lagged_start" + )) + self.play(Write(vector_output_words)) + self.dither() + +class WhyConfuseWithTerminology(TeacherStudentsScene): def construct(self): - plane = NumberPlane() - coords = VMobject(*plane.get_coordinate_labels()) - self.play(ShowCreation(plane, run_time = 3)) - self.play(Write(coords, run_time = 3)) + self.setup() + self.student_says("Why confuse us with \\\\ redundant terminology?") + self.play(*[ + ApplyMethod(self.get_students()[i].change_mode, "confused") + for i in 0, 2 + ]) + self.random_blink() + self.dither() + +class ThinkinfOfFunctionsAsGraphs(VectorScene): + def construct(self): + axes = self.add_axes() + graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2) + name = TexMobject("f(x) = x^2") + name.next_to(graph, RIGHT).to_edge(UP) + point = Dot(graph.point_from_proportion(0.8)) + point_label = TexMobject("(2, f(2))") + point_label.next_to(point.get_center(), DOWN+RIGHT, buff = 0.1) + + self.play(ShowCreation(graph)) + self.play(Write(name, run_time = 1)) + self.play( + ShowCreation(point), + Write(point_label), + run_time = 1 + ) + self.dither() + + def collapse_func(p): + return np.dot(p, [RIGHT, RIGHT, OUT]) + (SPACE_HEIGHT+1)*DOWN + self.play( + ApplyPointwiseFunction(collapse_func, axes), + ApplyPointwiseFunction(collapse_func, graph), + ApplyMethod(point.shift, 10*DOWN), + ApplyMethod(point_label.shift, 10*DOWN), + ApplyPointwiseFunction(collapse_func, name), + run_time = 2 + ) + self.clear() + words = TextMobject(["Instead think about", "\\emph{movement}"]) + words.split()[-1].highlight(YELLOW) + self.play(Write(words)) + self.dither() + +class TransformJustOneVector(VectorScene): + def construct(self): + self.lock_in_faded_grid() + v1_coords = [-3, 1] + t_matrix = [[0, -1], [2, -1]] + v1 = Vector(v1_coords) + v2 = Vector( + np.dot(np.array(t_matrix).transpose(), v1_coords), + color = PINK + ) + for v, word in (v1, "Input"), (v2, "Output"): + v.label = TextMobject("%s vector"%word) + v.label.next_to(v.get_end(), UP) + v.label.highlight(v.get_color()) + self.play(ShowCreation(v)) + self.play(Write(v.label)) + self.dither() + self.remove(v2) + self.play( + Transform( + v1.copy(), v2, + path_arc = -np.pi/2, run_time = 3 + ), + ApplyMethod(v1.fade) + ) + self.dither() + +class TransformManyVectors(VectorScene): + CONFIG = { + "transposed_matrix" : [[2, 1], [1, 2]], + "use_dots" : False, + } + def construct(self): + self.lock_in_faded_grid() + vectors = VMobject(*[ + Vector([x, y]) + for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5) + for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5) + ]) + vectors.submobject_gradient_highlight(PINK, YELLOW) + t_matrix = self.transposed_matrix + transformed_vectors = VMobject(*[ + Vector( + np.dot(np.array(t_matrix).transpose(), v.get_end()[:2]), + color = v.get_color() + ) + for v in vectors.split() + ]) + + self.play(ShowCreation(vectors, submobject_mode = "lagged_start")) + self.dither() + if self.use_dots: + self.play(Transform( + vectors, self.vectors_to_dots(vectors), + run_time = 3, + submobject_mode = "lagged_start" + )) + transformed_vectors = self.vectors_to_dots(transformed_vectors) + self.dither() + self.play(Transform( + vectors, transformed_vectors, + run_time = 3, + path_arc = -np.pi/2 + )) + self.dither() + if self.use_dots: + self.play(Transform( + vectors, self.dots_to_vectors(vectors), + run_time = 2, + submobject_mode = "lagged_start" + )) + self.dither() + + def vectors_to_dots(self, vectors): + return VMobject(*[ + Dot(v.get_end(), color = v.get_color()) + for v in vectors.split() + ]) + + def dots_to_vectors(self, dots): + return VMobject(*[ + Vector(dot.get_center(), color = dot.get_color()) + for dot in dots.split() + ]) + +class TransformManyVectorsAsPoints(TransformManyVectors): + CONFIG = { + "use_dots" : True + } + +class TransformInfiniteGrid(LinearTransformationScene): + CONFIG = { + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + }, + "show_basis_vectors" : False + } + def construct(self): + self.setup() + self.play(ShowCreation( + self.plane, run_time = 3, submobject_mode = "lagged_start" + )) + self.dither() + self.apply_transposed_matrix([[2, 1], [1, 2]]) + self.dither() + +class TransformInfiniteGridWithBackground(TransformInfiniteGrid): + CONFIG = { + "include_background_plane" : True, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + "secondary_line_ratio" : 0 + }, + + } + +class ApplyComplexFunction(LinearTransformationScene): + CONFIG = { + "function" : lambda z : 0.5*z**2, + "show_basis_vectors" : False, + "foreground_plane_kwargs" : { + "x_radius" : SPACE_WIDTH, + "y_radius" : SPACE_HEIGHT, + "secondary_line_ratio" : 0 + }, + } + def construct(self): + self.setup() + self.plane.prepare_for_nonlinear_transform(100) + self.dither() + self.play(ApplyMethod( + self.plane.apply_complex_function, self.function, + run_time = 5, + path_arc = np.pi/2 + )) + self.dither() + +class ExponentialTransformation(ApplyComplexFunction): + CONFIG = { + "function" : np.exp, + } + +class CrazyTransformation(ApplyComplexFunction): + CONFIG = { + "function" : lambda z : np.sin(z)**2 + np.sinh(z) + } + +class LookToWordLinear(Scene): + def construct(self): + title = TextMobject(["Linear ", "transformations"]) + title.to_edge(UP) + faded_title = title.copy().fade() + linear, transformation = title.split() + faded_linear, faded_transformation = faded_title.split() + linear_brace = Brace(linear, DOWN) + transformation_brace = Brace(transformation, DOWN) + function = TextMobject("function") + function.highlight(YELLOW) + function.next_to(transformation_brace, DOWN) + new_sub_word = TextMobject("What does this mean?") + new_sub_word.highlight(BLUE) + new_sub_word.next_to(linear_brace, DOWN) + + self.add( + faded_linear, transformation, + transformation_brace, function + ) + self.dither() + self.play( + Transform(faded_linear, linear), + Transform(transformation, faded_transformation), + Transform(transformation_brace, linear_brace), + Transform(function, new_sub_word), + submobject_mode = "lagged_start" + ) self.dither() class IntroduceLinearTransformations(LinearTransformationScene): CONFIG = { "show_basis_vectors" : False, - "include_background_plane" : False } def construct(self): self.setup() @@ -135,12 +416,10 @@ class IntroduceLinearTransformations(LinearTransformationScene): rule.add_background_rectangle() self.play( - # FadeIn(lines_rule_rect), Write(lines_rule, run_time = 2), ) self.dither() self.play( - # FadeIn(origin_rule_rect), Write(origin_rule, run_time = 2), ShowCreation(arrow), GrowFromCenter(dot) @@ -216,7 +495,7 @@ class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation): self.play(ShowCreation(diag)) self.add_transformable_mobject(diag) -class AnotherLinearTransformation(SimpleLinearTransformationScene): +class GridLinesRemainParallel(SimpleLinearTransformationScene): CONFIG = { "transposed_matrix" : [ [3, 0], @@ -257,123 +536,31 @@ class YetAnotherLinearTransformation(SimpleLinearTransformationScene): [3, 2], ] } - -class MoveAroundAllVectors(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "focus_on_one_vector" : False, - "include_background_plane" : False, - } def construct(self): - self.setup() - vectors = VMobject(*[ - Vector([x, y]) - for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5) - for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5) - ]) - vectors.submobject_gradient_highlight(PINK, YELLOW) - dots = self.get_dots(vectors) - - self.dither() - self.play(ShowCreation(dots)) - self.dither() - self.play(Transform(dots, vectors)) - self.dither() - self.remove(dots) - if self.focus_on_one_vector: - vector = vectors.split()[43]#yeah, great coding Grant - self.remove(vectors) - self.add_vector(vector) - self.play(*[ - FadeOut(v) - for v in vectors.split() - if v is not vector - ]) - self.dither() - self.add(vector.copy().highlight(DARK_GREY)) - else: - for vector in vectors.split(): - self.add_vector(vector, animate = False) - self.apply_transposed_matrix([[3, 0], [1, 2]]) - self.dither() - dots = self.get_dots(vectors) - self.play(Transform(vectors, dots)) - self.dither() - - def get_dots(self, vectors): - return VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - -# class MoveAroundJustOneVector(MoveAroundAllVectors): -# CONFIG = { -# "focus_on_one_vector" : True, -# } - -class ReasonForThinkingAboutArrows(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - self.plane.fade() - v_color = MAROON_C - w_color = BLUE - - v = self.add_vector([3, 1], color = v_color) - w = self.add_vector([1, -2], color = w_color) - vectors = VMobject(v, w) - - self.to_and_from_dots(vectors) - self.scale_and_add(vectors) - self.apply_transposed_matrix([[1, 1], [-1, 0]]) - self.scale_and_add(vectors) - - def to_and_from_dots(self, vectors): - vectors_copy = vectors.copy() - dots = VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - - self.dither() - self.play(Transform(vectors, dots)) - self.dither() - self.play(Transform(vectors, vectors_copy)) - self.dither() - - def scale_and_add(self, vectors): - vectors_copy = vectors.copy() - v, w, = vectors.split() - scaled_v = Vector(0.5*v.get_end(), color = v.get_color()) - scaled_w = Vector(1.5*w.get_end(), color = w.get_color()) - shifted_w = scaled_w.copy().shift(scaled_v.get_end()) - sum_vect = Vector(shifted_w.get_end(), color = PINK) - - self.play( - ApplyMethod(v.scale, 0.5), - ApplyMethod(w.scale, 1.5), + SimpleLinearTransformationScene.construct(self) + words = TextMobject(""" + How would you describe + one of these numerically? + """ ) - self.play(ApplyMethod(w.shift, v.get_end())) - self.add_vector(sum_vect) - self.dither() - self.play(Transform( - vectors, vectors_copy, - submobject_mode = "all_at_once" - )) - self.dither() + words.add_background_rectangle() + words.to_edge(UP) + words.highlight(GREEN) + formula = TexMobject( + matrix_to_tex_string(["x", "y"]) + "\\rightarrow" + \ + matrix_to_tex_string(["-1x+3y", "1x + 2y"]) + ) + formula.add_background_rectangle() -class LinearTransformationWithOneVector(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - } - def construct(self): - self.setup() - v = self.add_vector([3, 1]) - self.vector_to_coords(v) - self.apply_transposed_matrix([[-1, 1], [-2, -1]]) - self.vector_to_coords(v) + self.play(Write(words)) + self.dither() + self.play( + ApplyMethod(self.plane.fade, 0.7), + ApplyMethod(self.background_plane.fade, 0.7), + Write(formula, run_time = 2), + Animation(words) + ) + self.dither() class FollowIHatJHat(LinearTransformationScene): CONFIG = { @@ -596,12 +783,7 @@ class MatrixVectorMultiplication(LinearTransformationScene): i_brackets = i_coords.get_brackets() j_brackets = j_coords.get_brackets() for coords in i_coords, j_coords: - rect = Rectangle( - color = BLACK, - stroke_width = 0, - fill_opacity = 0.75 - ) - rect.replace(coords, stretch = True) + rect = BackgroundRectangle(coords) coords.rect = rect abstract_matrix = np.append( @@ -616,8 +798,8 @@ class MatrixVectorMultiplication(LinearTransformationScene): concrete_matrix.to_edge(UP) matrix_brackets = concrete_matrix.get_brackets() - self.play(FadeIn(i_coords.rect), Write(i_coords)) - self.play(FadeIn(j_coords.rect), Write(j_coords)) + self.play(ShowCreation(i_coords.rect), Write(i_coords)) + self.play(ShowCreation(j_coords.rect), Write(j_coords)) self.dither() self.remove(i_coords.rect, j_coords.rect) self.play( @@ -773,6 +955,243 @@ class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication): "abstract" : True, } +class ColumnsToBasisVectors(LinearTransformationScene): + def construct(self): + self.setup() + transposed_matrix = [[3, 1], [1, 2]] + vector_coords = [-1, 2] + + vector = self.move_matrix_columns(transposed_matrix, vector_coords) + self.scale_and_add(vector, vector_coords) + + def move_matrix_columns(self, transposed_matrix, vector_coords = None): + matrix = np.array(transposed_matrix).transpose() + matrix_mob = Matrix(matrix) + matrix_mob.to_corner(UP+LEFT) + col1 = VMobject(*matrix_mob.get_mob_matrix()[:,0]) + col1.highlight(X_COLOR) + col2 = VMobject(*matrix_mob.get_mob_matrix()[:,1]) + col2.highlight(Y_COLOR) + matrix_brackets = matrix_mob.get_brackets() + matrix_background = BackgroundRectangle(matrix_mob) + self.add_foreground_mobject(matrix_background, matrix_mob) + + if vector_coords is not None: + vector = Matrix(vector_coords) + VMobject(*vector.get_mob_matrix().flatten()).highlight(YELLOW) + vector.scale_to_fit_height(matrix_mob.get_height()) + vector.next_to(matrix_mob, RIGHT) + vector_background = BackgroundRectangle(vector) + self.add_foreground_mobject(vector_background, vector) + + new_i = Vector(matrix[:,0]) + new_j = Vector(matrix[:,1]) + i_label = vector_coordinate_label(new_i).highlight(X_COLOR) + j_label = vector_coordinate_label(new_j).highlight(Y_COLOR) + i_coords = VMobject(*i_label.get_mob_matrix().flatten()) + j_coords = VMobject(*j_label.get_mob_matrix().flatten()) + i_brackets = i_label.get_brackets() + j_brackets = j_label.get_brackets() + i_label_background = BackgroundRectangle(i_label) + j_label_background = BackgroundRectangle(j_label) + i_coords_start = VMobject( + matrix_background.copy(), + col1.copy(), + matrix_brackets.copy() + ) + i_coords_end = VMobject( + i_label_background, + i_coords, + i_brackets, + ) + j_coords_start = VMobject( + matrix_background.copy(), + col2.copy(), + matrix_brackets.copy() + ) + j_coords_end = VMobject( + j_label_background, + j_coords, + j_brackets, + ) + + transform_matrix1 = np.array(matrix) + transform_matrix1[:,1] = [0, 1] + transform_matrix2 = np.dot( + matrix, + np.linalg.inv(transform_matrix1) + ) + + self.dither() + self.apply_transposed_matrix( + transform_matrix1.transpose(), + added_anims = [Transform(i_coords_start, i_coords_end)], + path_arc = np.pi/2 + ) + self.add_foreground_mobject(i_coords_start) + self.apply_transposed_matrix( + transform_matrix2.transpose(), + added_anims = [Transform(j_coords_start, j_coords_end) ], + path_arc = np.pi/2 + ) + self.add_foreground_mobject(j_coords_start) + self.dither() + return vector if vector_coords is not None else None + + + def scale_and_add(self, vector, vector_coords): + i_copy = self.i_hat.copy() + j_copy = self.j_hat.copy() + i_target = i_copy.copy().scale(vector_coords[0]).fade(0.3) + j_target = j_copy.copy().scale(vector_coords[1]).fade(0.3) + + coord1, coord2 = vector.copy().get_mob_matrix().flatten() + coord1.add_background_rectangle() + coord2.add_background_rectangle() + + self.play( + Transform(i_copy, i_target), + ApplyMethod(coord1.next_to, i_target.get_center(), DOWN) + ) + self.play( + Transform(j_copy, j_target), + ApplyMethod(coord2.next_to, j_target.get_center(), LEFT) + ) + j_copy.add(coord2) + self.play(ApplyMethod(j_copy.shift, i_copy.get_end())) + self.add_vector(j_copy.get_end()) + self.dither() + +class Describe90DegreeRotation(LinearTransformationScene): + CONFIG = { + "transposed_matrix" : [[0, 1], [-1, 0]], + "title" : "$90^\\circ$ rotation counterclockwise", + } + def construct(self): + self.setup() + title = TextMobject(self.title) + title.shift(DOWN) + title.add_background_rectangle() + matrix = Matrix(np.array(self.transposed_matrix).transpose()) + matrix.to_corner(UP+LEFT) + matrix_background = BackgroundRectangle(matrix) + col1 = VMobject(*matrix.get_mob_matrix()[:,0]) + col2 = VMobject(*matrix.get_mob_matrix()[:,1]) + col1.highlight(X_COLOR) + col2.highlight(Y_COLOR) + self.add_foreground_mobject(matrix_background, matrix.get_brackets()) + + self.dither() + self.apply_transposed_matrix(self.transposed_matrix) + self.dither() + self.play(Write(title)) + self.add_foreground_mobject(title) + + for vect, color, col in [(self.i_hat, X_COLOR, col1), (self.j_hat, Y_COLOR, col2)]: + label = vector_coordinate_label(vect) + label.highlight(color) + background = BackgroundRectangle(label) + coords = VMobject(*label.get_mob_matrix().flatten()) + brackets = label.get_brackets() + + self.play(ShowCreation(background), Write(label)) + self.dither() + self.play( + ShowCreation(background, rate_func = lambda t : smooth(1-t)), + ApplyMethod(coords.replace, col), + FadeOut(brackets), + ) + self.remove(label) + self.add_foreground_mobject(coords) + self.dither() + self.show_vector(matrix) + + def show_vector(self, matrix): + vector = Matrix(["x", "y"]) + VMobject(*vector.get_mob_matrix().flatten()).highlight(YELLOW) + vector.scale_to_fit_height(matrix.get_height()) + vector.next_to(matrix, RIGHT) + v_background = BackgroundRectangle(vector) + + matrix = np.array(self.transposed_matrix).transpose() + inv = np.linalg.inv(matrix) + self.apply_transposed_matrix(inv.transpose(), run_time = 0.5) + self.add_vector([1, 2]) + self.dither() + self.apply_transposed_matrix(self.transposed_matrix) + self.play(ShowCreation(v_background), Write(vector)) + self.dither() + +class DescribeShear(Describe90DegreeRotation): + CONFIG = { + "transposed_matrix" : [[1, 0], [1, 1]], + "title" : "``Shear''", + } + +class DeduceTransformationFromMatrix(ColumnsToBasisVectors): + def construct(self): + self.setup() + self.move_matrix_columns([[1, 2], [3, 1]]) + +class LinearlyDependentColumns(ColumnsToBasisVectors): + def construct(self): + self.setup() + title = TextMobject("Linearly dependent") + subtitle = TextMobject("columns") + title.add_background_rectangle() + subtitle.add_background_rectangle() + subtitle.next_to(title, DOWN) + title.add(subtitle) + title.shift(UP).to_edge(LEFT) + title.highlight(YELLOW) + self.add_foreground_mobject(title) + self.move_matrix_columns([[2, 1], [-2, -1]]) + +class NextVideo(Scene): + def construct(self): + title = TextMobject("Next video: Matrix multiplication as composition") + title.to_edge(UP) + rect = Rectangle(width = 16, height = 9, color = BLUE) + rect.scale_to_fit_height(6) + rect.next_to(title, DOWN) + + self.add(title) + self.play(ShowCreation(rect)) + self.dither() + + +class FinalSlide(Scene): + def construct(self): + text = TextMobject(""" + \\footnotesize + Technically, the definition of ``linear'' is as follows: + A transformation L is linear if it satisfies these + two properties: + + \\begin{align*} + L(\\vec{\\textbf{v}} + \\vec{\\textbf{w}}) + &= L(\\vec{\\textbf{v}}) + L(\\vec{\\textbf{w}}) + & & \\text{``Additivity''} \\\\ + L(c\\vec{\\textbf{v}}) &= c L(\\vec{\\textbf{v}}) + & & \\text{``Scaling''} + \\end{align*} + + I'll talk about these properties later on, but I'm a big + believer in first understanding things visually. + Once you do, it becomes much more intuitive why these + two properties make sense. So for now, you can + feel fine thinking of linear transformations as those + which keep grid lines parallel and evenly spaced + (and which fix the origin in place), since this visual + definition is actually equivalent to the two properties + above. + """, enforce_new_line_structure = False) + text.scale_to_fit_height(2*SPACE_HEIGHT - 2) + text.to_edge(UP) + self.add(text) + self.dither() + +### Old scenes class RotateIHat(LinearTransformationScene): CONFIG = { @@ -1070,9 +1489,125 @@ class SecondAdditivityExample(AdditivityProperty): "proclaim_sum" : False, } - - - +class ShowGridCreation(Scene): + def construct(self): + plane = NumberPlane() + coords = VMobject(*plane.get_coordinate_labels()) + self.play(ShowCreation(plane, run_time = 3)) + self.play(Write(coords, run_time = 3)) + self.dither() + +class MoveAroundAllVectors(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False, + "focus_on_one_vector" : False, + "include_background_plane" : False, + } + def construct(self): + self.setup() + vectors = VMobject(*[ + Vector([x, y]) + for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5) + for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5) + ]) + vectors.submobject_gradient_highlight(PINK, YELLOW) + dots = self.get_dots(vectors) + + self.dither() + self.play(ShowCreation(dots)) + self.dither() + self.play(Transform(dots, vectors)) + self.dither() + self.remove(dots) + if self.focus_on_one_vector: + vector = vectors.split()[43]#yeah, great coding Grant + self.remove(vectors) + self.add_vector(vector) + self.play(*[ + FadeOut(v) + for v in vectors.split() + if v is not vector + ]) + self.dither() + self.add(vector.copy().highlight(DARK_GREY)) + else: + for vector in vectors.split(): + self.add_vector(vector, animate = False) + self.apply_transposed_matrix([[3, 0], [1, 2]]) + self.dither() + dots = self.get_dots(vectors) + self.play(Transform(vectors, dots)) + self.dither() + + def get_dots(self, vectors): + return VMobject(*[ + Dot(v.get_end(), color = v.get_color()) + for v in vectors.split() + ]) + +class ReasonForThinkingAboutArrows(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False + } + def construct(self): + self.setup() + self.plane.fade() + v_color = MAROON_C + w_color = BLUE + + v = self.add_vector([3, 1], color = v_color) + w = self.add_vector([1, -2], color = w_color) + vectors = VMobject(v, w) + + self.to_and_from_dots(vectors) + self.scale_and_add(vectors) + self.apply_transposed_matrix([[1, 1], [-1, 0]]) + self.scale_and_add(vectors) + + def to_and_from_dots(self, vectors): + vectors_copy = vectors.copy() + dots = VMobject(*[ + Dot(v.get_end(), color = v.get_color()) + for v in vectors.split() + ]) + + self.dither() + self.play(Transform(vectors, dots)) + self.dither() + self.play(Transform(vectors, vectors_copy)) + self.dither() + + def scale_and_add(self, vectors): + vectors_copy = vectors.copy() + v, w, = vectors.split() + scaled_v = Vector(0.5*v.get_end(), color = v.get_color()) + scaled_w = Vector(1.5*w.get_end(), color = w.get_color()) + shifted_w = scaled_w.copy().shift(scaled_v.get_end()) + sum_vect = Vector(shifted_w.get_end(), color = PINK) + + self.play( + ApplyMethod(v.scale, 0.5), + ApplyMethod(w.scale, 1.5), + ) + self.play(ApplyMethod(w.shift, v.get_end())) + self.add_vector(sum_vect) + self.dither() + self.play(Transform( + vectors, vectors_copy, + submobject_mode = "all_at_once" + )) + self.dither() + +class LinearTransformationWithOneVector(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False, + } + def construct(self): + self.setup() + v = self.add_vector([3, 1]) + self.vector_to_coords(v) + self.apply_transposed_matrix([[-1, 1], [-2, -1]]) + self.vector_to_coords(v) diff --git a/eola/matrix.py b/eola/matrix.py index 9ef58309..940097fe 100644 --- a/eola/matrix.py +++ b/eola/matrix.py @@ -221,7 +221,8 @@ class NumericalMatrixMultiplication(Scene): Transform( start_parts, result_entry.copy().highlight(YELLOW), - path_arc = -np.pi/2 + path_arc = -np.pi/2, + submobject_mode = "all_at_once", ), *lagging_anims ) diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 48f71c33..8ddb03b5 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -1,6 +1,6 @@ from vectorized_mobject import VMobject from svg_mobject import SVGMobject, VMobjectFromSVGPathstring -from topics.geometry import Rectangle +from topics.geometry import BackgroundRectangle from helpers import * import collections @@ -103,21 +103,9 @@ class TexMobject(SVGMobject): ) def add_background_rectangle(self, color = BLACK, opacity = 0.75): - self.rectangle = Rectangle( - color = color, - stroke_width = 0, - fill_opacity = opacity - ) - self.rectangle.replace(self, stretch = True) + rect = BackgroundRectangle(self) letters = VMobject(*self.submobjects) - self.submobjects = [self.rectangle, letters] - ##Hacky stuff to fix later ...TODO - def rect_become_partial(mob, a, b): - return self.rectangle.set_fill(opacity = b*opacity) - self.rectangle.pointwise_become_partial = rect_become_partial - def rect_set_style_data(*args, **kwargs): - return self.rectangle #Do nothing - self.rectangle.set_style_data = rect_set_style_data + self.submobjects = [rect, letters] return self class TextMobject(TexMobject): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 733158ac..a48f6c82 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -346,6 +346,7 @@ class VMobject(Mobject): return self + class VectorizedPoint(VMobject): CONFIG = { "color" : BLACK, diff --git a/topics/geometry.py b/topics/geometry.py index 01199da1..8550f25e 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -267,6 +267,30 @@ class Square(Rectangle): **kwargs ) +class BackgroundRectangle(Rectangle): + CONFIG = { + "color" : BLACK, + "stroke_width" : 0, + "fill_opacity" : 0.75, + } + def __init__(self, mobject, **kwargs): + self.lock_style = False + Rectangle.__init__(self, **kwargs) + self.lock_style = True + self.replace(mobject, stretch = True) + self.original_fill_opacity = self.fill_opacity + + def pointwise_become_partial(self, mobject, a, b): + self.lock_style = False + self.set_fill(opacity = b*self.original_fill_opacity) + self.lock_style = True + return self + + def set_style_data(self, *args, **kwargs): + if self.lock_style: + return self #Do nothing + return Rectangle.set_style_data(self, *args, **kwargs) + class Grid(VMobject): CONFIG = {