From ef8461bc73e7af4593d4c1b764f6f64888bb17b1 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 27 Jul 2016 13:10:45 -0700 Subject: [PATCH] Starting chapter 4 --- constants.py | 4 +- eola/chapter3.py | 167 ++++++++++++++++++++---- eola/chapter4.py | 311 ++++++++++++++++++++++++++++++++++++++++++++ eola/matrix.py | 5 +- eola/two_d_space.py | 2 + mobject/mobject.py | 11 +- topics/geometry.py | 6 + 7 files changed, 475 insertions(+), 31 deletions(-) create mode 100644 eola/chapter4.py diff --git a/constants.py b/constants.py index 1a48e8ca..b0417208 100644 --- a/constants.py +++ b/constants.py @@ -137,9 +137,9 @@ COLOR_MAP = { "GREEN_SCREEN": "#00FF00", } PALETTE = COLOR_MAP.values() -globals().update(COLOR_MAP) +locals().update(COLOR_MAP) for name in filter(lambda s : s.endswith("_C"), COLOR_MAP.keys()): - globals()[name.replace("_C", "")] = globals()[name] + locals()[name.replace("_C", "")] = locals()[name] diff --git a/eola/chapter3.py b/eola/chapter3.py index 2e6204de..e23748a2 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -35,7 +35,7 @@ class OpeningQuote(Scene): words.scale_to_fit_width(2*SPACE_WIDTH - 2) words.to_edge(UP) words.split()[1].highlight(GREEN) - words.split()[3].highlight(GREEN) + words.split()[3].highlight(BLUE) author = TextMobject("-Morpheus") author.highlight(YELLOW) author.next_to(words, DOWN, buff = 0.5) @@ -43,7 +43,6 @@ class OpeningQuote(Scene): (Surprisingly apt words on the importance of understanding matrix operations visually.) """) - comment.scale(0.7) comment.next_to(author, DOWN, buff = 1) self.play(FadeIn(words)) @@ -173,9 +172,25 @@ class WhyConfuseWithTerminology(TeacherStudentsScene): def construct(self): self.setup() self.student_says("Why confuse us with \\\\ redundant terminology?") + other_students = [self.get_students()[i] for i in 0, 2] self.play(*[ - ApplyMethod(self.get_students()[i].change_mode, "confused") - for i in 0, 2 + ApplyMethod(student.change_mode, "confused") + for student in other_students + ]) + self.random_blink() + self.dither() + statement = TextMobject([ + "The word", + "``transformation''", + "suggests \\\\ that you think using", + "movement", + ]) + statement.split()[1].highlight(BLUE) + statement.split()[-1].highlight(YELLOW) + self.teacher_says(statement, width = 10) + self.play(*[ + ApplyMethod(student.change_mode, "happy") + for student in other_students ]) self.random_blink() self.dither() @@ -242,7 +257,7 @@ class TransformJustOneVector(VectorScene): ) self.dither() -class TransformManyVectors(VectorScene): +class TransformManyVectors(LinearTransformationScene): CONFIG = { "transposed_matrix" : [[2, 1], [1, 2]], "use_dots" : False, @@ -546,10 +561,11 @@ class YetAnotherLinearTransformation(SimpleLinearTransformationScene): 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 = TexMobject([ + matrix_to_tex_string(["x_\\text{in}", "y_\\text{in}"]), + "\\rightarrow ???? \\rightarrow", + matrix_to_tex_string(["x_\\text{out}", "y_{\\text{out}}"]) + ]) formula.add_background_rectangle() self.play(Write(words)) @@ -569,9 +585,17 @@ class FollowIHatJHat(LinearTransformationScene): def construct(self): self.setup() i_hat = self.add_vector([1, 0], color = X_COLOR) - i_label = self.label_vector(i_hat, "\\hat{\\imath}") + i_label = self.label_vector( + i_hat, "\\hat{\\imath}", + color = X_COLOR, + label_scale_val = 1 + ) j_hat = self.add_vector([0, 1], color = Y_COLOR) - j_label = self.label_vector(j_hat, "\\hat{\\jmath}") + j_label = self.label_vector( + j_hat, "\\hat{\\jmath}", + color = Y_COLOR, + label_scale_val = 1 + ) self.dither() self.play(*map(FadeOut, [i_label, j_label])) @@ -742,17 +766,91 @@ class TrackBasisVectorsExample(LinearTransformationScene): self.play(Write(result)) self.dither() -class TrackBasisVectorsExampleGenerally(TrackBasisVectorsExample): - CONFIG = { - "v_coord_strings" : ["x", "y"], - "result_coords_string" : """ - = - \\left[ \\begin{array}{c} - 1x + 3y \\\\ - -2x + 0y - \\end{arary}\\right] - """ - } +class WatchManyVectorsMove(TransformManyVectors): + 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.vectors_to_dots(vectors) + self.play(ShowCreation(dots, submobject_mode = "lagged_start")) + self.play(Transform( + dots, vectors, + submobject_mode = "lagged_start", + run_time = 2 + )) + self.remove(dots) + for v in vectors.split(): + self.add_vector(v, animate = False) + self.apply_transposed_matrix([[1, -2], [3, 0]]) + self.dither() + self.play( + ApplyMethod(self.plane.fade), + FadeOut(vectors), + Animation(self.i_hat), + Animation(self.j_hat), + ) + self.dither() + +class NowWithoutWatching(Scene): + def construct(self): + text = TextMobject("Now without watching...") + text.to_edge(UP) + randy = Randolph(mode = "pondering") + self.add(randy) + self.play(Write(text, run_time = 1)) + self.play(ApplyMethod(randy.blink)) + self.dither(2) + +class DeduceResultWithGeneralCoordinates(Scene): + def construct(self): + i_hat_to = TexMobject("\\hat{\\imath} \\rightarrow") + j_hat_to = TexMobject("\\hat{\\jmath} \\rightarrow") + i_coords = Matrix([1, -2]) + j_coords = Matrix([3, 0]) + i_coords.next_to(i_hat_to, RIGHT, buff = 0.1) + j_coords.next_to(j_hat_to, RIGHT, buff = 0.1) + i_group = VMobject(i_hat_to, i_coords) + j_group = VMobject(j_hat_to, j_coords) + i_group.highlight(X_COLOR) + j_group.highlight(Y_COLOR) + i_group.next_to(ORIGIN, LEFT, buff = 1).to_edge(UP) + j_group.next_to(ORIGIN, RIGHT, buff = 1).to_edge(UP) + + vect = Matrix(["x", "y"]) + x, y = vect.get_mob_matrix().flatten() + VMobject(x, y).highlight(YELLOW) + rto = TexMobject("\\rightarrow") + equals = TexMobject("=") + plus = TexMobject("+") + row1 = TexMobject("1x + 3y") + row2 = TexMobject("-2x + 0y") + VMobject( + row1.split()[0], row2.split()[0], row2.split()[1] + ).highlight(X_COLOR) + VMobject( + row1.split()[1], row1.split()[4], row2.split()[2], row2.split()[5] + ).highlight(YELLOW) + VMobject( + row1.split()[3], row2.split()[4] + ).highlight(Y_COLOR) + result = Matrix([row1, row2]) + result.show() + vect_group = VMobject( + vect, rto, + x.copy(), i_coords.copy(), plus, + y.copy(), j_coords.copy(), equals, + result + ) + vect_group.arrange_submobjects(RIGHT, buff = 0.1) + + self.add(i_group, j_group) + for mob in vect_group.split(): + self.play(Write(mob)) + self.dither() class MatrixVectorMultiplication(LinearTransformationScene): CONFIG = { @@ -774,8 +872,8 @@ class MatrixVectorMultiplication(LinearTransformationScene): if self.abstract: new_i_coords = Matrix(["a", "c"]) new_j_coords = Matrix(["b", "d"]) - new_i_coords.scale(0.7).move_to(i_coords) - new_j_coords.scale(0.7).move_to(j_coords) + new_i_coords.move_to(i_coords) + new_j_coords.move_to(j_coords) i_coords = new_i_coords j_coords = new_j_coords i_coords.highlight(X_COLOR) @@ -796,6 +894,9 @@ class MatrixVectorMultiplication(LinearTransformationScene): add_background_rectangles = True ) concrete_matrix.to_edge(UP) + if self.abstract: + m = concrete_matrix.get_mob_matrix()[1, 0] + m.shift(m.get_height()*DOWN/2) matrix_brackets = concrete_matrix.get_brackets() self.play(ShowCreation(i_coords.rect), Write(i_coords)) @@ -916,7 +1017,6 @@ class MatrixVectorMultiplication(LinearTransformationScene): row2 = VMobject(*map(TexMobject, row2)) for row in row1, row2: row.arrange_submobjects(RIGHT, buff = 0.1) - row.scale(0.7) final_sum = Matrix([row1, row2]) row1, row2 = final_sum.get_mob_matrix().flatten() row1.split()[0].highlight(X_COLOR) @@ -933,6 +1033,7 @@ class MatrixVectorMultiplication(LinearTransformationScene): ) self.dither() + def reposition_matrix_and_vector(self, matrix, vector, formula): start_state = VMobject(matrix, vector) end_state = start_state.copy() @@ -940,6 +1041,10 @@ class MatrixVectorMultiplication(LinearTransformationScene): equals = TexMobject("=") equals.next_to(formula, LEFT) end_state.next_to(equals, LEFT) + brace = Brace(formula, DOWN) + brace_words = TextMobject("Where all the intuition is") + brace_words.next_to(brace, DOWN) + brace_words.highlight(YELLOW) self.play( Transform( @@ -949,6 +1054,12 @@ class MatrixVectorMultiplication(LinearTransformationScene): Write(equals, run_time = 1) ) self.dither() + self.play( + FadeIn(brace), + FadeIn(brace_words), + submobject_mode = "lagged_start" + ) + self.dither() class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication): CONFIG = { @@ -1128,6 +1239,11 @@ class DescribeShear(Describe90DegreeRotation): "title" : "``Shear''", } +class OtherWayAround(Scene): + def construct(self): + self.play(Write("What about the other way around?")) + self.dither(2) + class DeduceTransformationFromMatrix(ColumnsToBasisVectors): def construct(self): self.setup() @@ -1159,7 +1275,6 @@ class NextVideo(Scene): self.play(ShowCreation(rect)) self.dither() - class FinalSlide(Scene): def construct(self): text = TextMobject(""" diff --git a/eola/chapter4.py b/eola/chapter4.py new file mode 100644 index 00000000..29c2b8b1 --- /dev/null +++ b/eola/chapter4.py @@ -0,0 +1,311 @@ +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import VMobject + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.number_line import * +from topics.numerals import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * +from mobject.vectorized_mobject import * + +from eola.matrix import * +from eola.two_d_space import * +from eola.chapter3 import MatrixVectorMultiplicationAbstract + + +class OpeningQuote(Scene): + def construct(self): + words = TextMobject([ + "It is my experience that proofs involving", + "matrices", + "can be shortened by 50\\% if one", + "throws the matrices out." + ]) + words.scale_to_fit_width(2*SPACE_WIDTH - 2) + words.to_edge(UP) + words.split()[1].highlight(GREEN) + words.split()[3].highlight(BLUE) + author = TextMobject("-Emil Artin") + author.highlight(YELLOW) + author.next_to(words, DOWN, buff = 0.5) + + self.play(FadeIn(words)) + self.dither(2) + self.play(Write(author, run_time = 3)) + self.dither() + +class MatrixToBlank(Scene): + def construct(self): + matrix = Matrix([[3, 1], [0, 2]]) + arrow = Arrow(LEFT, RIGHT) + matrix.to_edge(LEFT) + arrow.next_to(matrix, LEFT) + matrix.add(arrow) + self.play(Write(matrix)) + self.dither() + +class RecapTime(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says("Quick recap time!") + self.random_blink() + self.dither() + student = self.get_students()[0] + everyone = self.get_mobjects() + everyone.remove(student) + everyone = VMobject(*everyone) + self.play( + ApplyMethod(everyone.fade, 0.7), + ApplyMethod(student.change_mode, "confused") + ) + self.play(Blink(student)) + self.dither() + self.play(ApplyFunction( + lambda m : m.change_mode("pondering").look(LEFT), + student + )) + self.play(Blink(student)) + self.dither() + +class DeterminedByTwoBasisVectors(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False + } + def construct(self): + self.setup() + i_hat = self.add_vector([1, 0], color = X_COLOR) + self.add_transformable_label( + i_hat, "\\hat{\\imath}", "\\hat{\\imath}", + color = X_COLOR + ) + j_hat = self.add_vector([0, 1], color = Y_COLOR) + self.add_transformable_label( + j_hat, "\\hat{\\jmath}", "\\hat{\\jmath}", + color = Y_COLOR + ) + + t_matrix = np.array([[2, 2], [-2, 1]]) + matrix = t_matrix.transpose() + matrix1 = np.array(matrix) + matrix1[:,1] = [0, 1] + matrix2 = np.dot(matrix, np.linalg.inv(matrix1)) + + self.dither() + self.apply_transposed_matrix(matrix1.transpose()) + self.apply_transposed_matrix(matrix2.transpose()) + self.dither() + +class FollowLinearCombination(LinearTransformationScene): + def construct(self): + vect_coords = [-1, 2] + t_matrix = np.array([[2, 2], [-2, 1]]) + + #Draw vectors + self.setup() + i_label = self.add_transformable_label( + self.i_hat, "\\hat{\\imath}", animate = False, + direction = "right", color = X_COLOR + ) + j_label = self.add_transformable_label( + self.j_hat, "\\hat{\\jmath}", animate = False, + direction = "right", color = Y_COLOR + ) + vect = self.add_vector(vect_coords) + vect_array = Matrix(["x", "y"], add_background_rectangles = True) + v_equals = TexMobject(["\\vec{\\textbf{v}}", "="]) + v_equals.split()[0].highlight(YELLOW) + v_equals.next_to(vect_array, LEFT) + vect_array.add(v_equals) + vect_array.to_edge(UP, buff = 0.2) + background_rect = BackgroundRectangle(vect_array) + vect_array.get_entries().highlight(YELLOW) + self.play(ShowCreation(background_rect), Write(vect_array)) + self.add_foreground_mobject(background_rect, vect_array) + + #Show scaled vectors + x, y = vect_array.get_entries().split() + scaled_i_label = VMobject(x.copy(), i_label) + scaled_j_label = VMobject(y.copy(), j_label) + scaled_i = self.i_hat.copy().scale(vect_coords[0]) + scaled_j = self.j_hat.copy().scale(vect_coords[1]) + for mob in scaled_i, scaled_j: + mob.fade(0.3) + scaled_i_label_target = scaled_i_label.copy() + scaled_i_label_target.arrange_submobjects(buff = 0.1) + scaled_i_label_target.next_to(scaled_i, DOWN) + scaled_j_label_target = scaled_j_label.copy() + scaled_j_label_target.arrange_submobjects(buff = 0.1) + scaled_j_label_target.next_to(scaled_j, LEFT) + + self.show_scaled_vectors(vect_array, vect_coords, i_label, j_label) + self.apply_transposed_matrix(t_matrix) + self.show_scaled_vectors(vect_array, vect_coords, i_label, j_label) + self.record_basis_coordinates(vect_array, vect) + + def show_scaled_vectors(self, vect_array, vect_coords, i_label, j_label): + x, y = vect_array.get_entries().split() + scaled_i_label = VMobject(x.copy(), i_label.copy()) + scaled_j_label = VMobject(y.copy(), j_label.copy()) + scaled_i = self.i_hat.copy().scale(vect_coords[0]) + scaled_j = self.j_hat.copy().scale(vect_coords[1]) + for mob in scaled_i, scaled_j: + mob.fade(0.3) + scaled_i_label_target = scaled_i_label.copy() + scaled_i_label_target.arrange_submobjects(buff = 0.1) + scaled_i_label_target.next_to(scaled_i.get_center(), DOWN) + scaled_j_label_target = scaled_j_label.copy() + scaled_j_label_target.arrange_submobjects(buff = 0.1) + scaled_j_label_target.next_to(scaled_j.get_center(), LEFT) + + self.play( + Transform(self.i_hat.copy(), scaled_i), + Transform(scaled_i_label, scaled_i_label_target) + ) + scaled_i = self.get_mobjects_from_last_animation()[0] + self.play( + Transform(self.j_hat.copy(), scaled_j), + Transform(scaled_j_label, scaled_j_label_target) + ) + scaled_j = self.get_mobjects_from_last_animation()[0] + self.play(*[ + ApplyMethod(mob.shift, scaled_i.get_end()) + for mob in scaled_j, scaled_j_label + ]) + self.dither() + self.play(*map(FadeOut, [ + scaled_i, scaled_j, scaled_i_label, scaled_j_label, + ])) + + def record_basis_coordinates(self, vect_array, vect): + i_label = vector_coordinate_label(self.i_hat) + i_label.highlight(X_COLOR) + j_label = vector_coordinate_label(self.j_hat) + j_label.highlight(Y_COLOR) + for mob in i_label, j_label: + mob.scale_in_place(0.8) + background = BackgroundRectangle(mob) + self.play(ShowCreation(background), Write(mob)) + + self.dither() + x, y = vect_array.get_entries().split() + pre_formula = VMobject( + x, i_label, TexMobject("+"), + y, j_label + ) + post_formula = pre_formula.copy() + pre_formula.split()[2].fade(1) + post_formula.arrange_submobjects(buff = 0.1) + post_formula.next_to(vect, DOWN) + background = BackgroundRectangle(post_formula) + everything = self.get_mobjects() + everything.remove(vect) + self.play(*[ + ApplyMethod(m.fade) for m in everything + ] + [ + ShowCreation(background, run_time = 2, rate_func = squish_rate_func(smooth, 0.5, 1)), + Transform(pre_formula.copy(), post_formula, run_time = 2), + ApplyMethod(vect.set_stroke, width = 7) + ]) + self.dither() + +class MatrixVectorMultiplicationCopy(MatrixVectorMultiplicationAbstract): + pass ## Here just for stage_animations.py purposes + +class RecapOver(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says("Recap over!") + +class TwoSuccessiveTransformations(LinearTransformationScene): + def construct(self): + self.setup() + self.apply_transposed_matrix([[2, 1],[1, 2]]) + self.apply_transposed_matrix([[-1, -0.5],[0, -0.5]]) + self.dither() + +class RotationThenShear(LinearTransformationScene): + CONFIG = { + "foreground_plane_kwargs" : { + "x_radius" : SPACE_WIDTH, + "y_radius" : 2*SPACE_WIDTH, + "secondary_line_ratio" : 0 + }, + } + def construct(self): + self.setup() + rot_words = TextMobject("$90^\\circ$ rotation counterclockwise") + shear_words = TextMobject("followed by a shear") + rot_words.highlight(YELLOW) + shear_words.highlight(PINK) + VMobject(rot_words, shear_words).arrange_submobjects(DOWN).to_edge(UP) + for words in rot_words, shear_words: + words.add_background_rectangle() + + self.play(Write(rot_words, run_time = 1)) + self.add_foreground_mobject(rot_words) + self.apply_transposed_matrix([[0, 1], [-1, 0]]) + + self.play(Write(shear_words, run_time = 1)) + self.add_foreground_mobject(shear_words) + self.apply_transposed_matrix([[1, 0], [1, 1]]) + self.dither() + +class IntroduceIdeaOfComposition(RotationThenShear): + def construct(self): + self.setup() + self.show_composition() + self.track_basis_vectors() + + def show_composition(self): + words = TextMobject([ + "``Composition''", + "of a", + "rotation", + "and a", + "shear" + ]) + words.split()[0].submobject_gradient_highlight(YELLOW, PINK, use_color_range_to = False) + words.split()[2].highlight(YELLOW) + words.split()[4].highlight(PINK) + words.add_background_rectangle() + words.to_edge(UP) + + self.apply_transposed_matrix([[0, 1], [-1, 0]], run_time = 2) + self.apply_transposed_matrix([[1, 0], [1, 1]], run_time = 2) + self.play(Write(words)) + self.dither() + + def track_basis_vectors(self): + pass + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eola/matrix.py b/eola/matrix.py index 940097fe..32fdb98a 100644 --- a/eola/matrix.py +++ b/eola/matrix.py @@ -12,7 +12,7 @@ from topics.geometry import Vector, Line, Circle, Arrow, Dot from helpers import * -VECTOR_LABEL_SCALE_VAL = 0.7 +VECTOR_LABEL_SCALE_VAL = 1.0 def matrix_to_tex_string(matrix): matrix = np.array(matrix).astype("string") @@ -107,6 +107,9 @@ class Matrix(VMobject): def get_mob_matrix(self): return self.mob_matrix + def get_entries(self): + return VMobject(*self.get_mob_matrix().flatten()) + def get_brackets(self): return self.brackets diff --git a/eola/two_d_space.py b/eola/two_d_space.py index 9b7eaffd..646e7a53 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -311,6 +311,8 @@ class LinearTransformationScene(VectorScene): label_mob.target_text = "L(%s)"%label_mob.expression label_mob.vector = vector label_mob.kwargs = kwargs + if "animate" in label_mob.kwargs: + label_mob.kwargs.pop("animate") self.transformable_labels.append(label_mob) return label_mob diff --git a/mobject/mobject.py b/mobject/mobject.py index e52ba0bd..93159a91 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -297,9 +297,16 @@ class Mobject(object): def gradient_highlight(self, start_color, end_color): raise Exception("Not implemented") - def submobject_gradient_highlight(self, start_color, end_color): + def submobject_gradient_highlight(self, start_color, end_color, use_color_range_to = False): mobs = self.family_members_with_points() - colors = Color(start_color).range_to(end_color, len(mobs)) + if use_color_range_to: + colors = Color(start_color).range_to(end_color, len(mobs)) + else: + rgb1, rgb2 = map(color_to_rgb, [start_color, end_color]) + colors = [ + Color(rgb = interpolate(rgb1, rgb2, alpha)) + for alpha in np.linspace(0, 1, len(mobs)) + ] for mob, color in zip(mobs, colors): mob.highlight(color, family = False) return self diff --git a/topics/geometry.py b/topics/geometry.py index 8550f25e..f3c9c24f 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -286,6 +286,12 @@ class BackgroundRectangle(Rectangle): self.lock_style = True return self + def fade_to(self, *args, **kwargs): + self.lock_style = False + Rectangle.fade_to(self, *args, **kwargs) + self.lock_style = True + return self + def set_style_data(self, *args, **kwargs): if self.lock_style: return self #Do nothing