diff --git a/eola/chapter3.py b/eola/chapter3.py index 66e210cd..b6871ec4 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -441,6 +441,22 @@ class IntroduceLinearTransformations(LinearTransformationScene): ) self.dither() +class ToThePedants(Scene): + def construct(self): + words = TextMobject([ + "To the pedants:\\\\", + """ + Yeah yeah, I know that's not the formal definition + for linear transformations, as seen in textbooks, + but I'm building visual intuition here, and what + I've said is equivalent to the formal definition + (which I'll get to later in the series). + """]) + words.split()[0].highlight(RED) + words.to_edge(UP) + self.add(words) + self.dither() + class SimpleLinearTransformationScene(LinearTransformationScene): CONFIG = { "show_basis_vectors" : False, @@ -1138,13 +1154,13 @@ class ColumnsToBasisVectors(LinearTransformationScene): self.apply_transposed_matrix( transform_matrix1.transpose(), added_anims = [Transform(i_coords_start, i_coords_end)], - path_arc = np.pi/2 + 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 + path_arc = np.pi/2, ) self.add_foreground_mobject(j_coords_start) self.dither() diff --git a/eola/chapter4.py b/eola/chapter4.py index 16148ce8..4bbfa288 100644 --- a/eola/chapter4.py +++ b/eola/chapter4.py @@ -687,7 +687,7 @@ class GeneralMultiplication(MoreComplicatedExampleNumerically): for entry, char in zip(m1_entries.split(), "efgh") ]) - words = TextMobject("This method works genearlly") + words = TextMobject("This method works generally") self.play(Write(words, run_time = 2)) self.play(Transform( m1_entries, m1_entries_target, diff --git a/eola/chapter5.py b/eola/chapter5.py index 2c4f2bf1..71bea4e0 100644 --- a/eola/chapter5.py +++ b/eola/chapter5.py @@ -348,6 +348,11 @@ class BreakBlobIntoGridSquaresGranular(BreakBlobIntoGridSquares): "square_size" : 0.25 } +class BreakBlobIntoGridSquaresMoreGranular(BreakBlobIntoGridSquares): + CONFIG = { + "square_size" : 0.15 + } + class BreakBlobIntoGridSquaresVeryGranular(BreakBlobIntoGridSquares): CONFIG = { "square_size" : 0.1 @@ -409,6 +414,11 @@ class NameDeterminant(LinearTransformationScene): det_text.remove(det_text.split()[-1]) return matrix_background, matrix, det_text +class SecondDeterminantExample(NameDeterminant): + CONFIG = { + "t_matrix" : [[-1, -1], [1, -1]] + } + class DeterminantIsThree(NameDeterminant): CONFIG = { "t_matrix" : [[0, -1.5], [2, 1]] @@ -429,6 +439,12 @@ class DeterminantIsZero(NameDeterminant): "t_matrix" : [[4, 2], [2, 1]], } +class SecondDeterminantIsZeroExamlpe(NameDeterminant): + CONFIG = { + "t_matrix" : [[0, 0], [0, 0]], + "show_basis_vectors" : False + } + class NextFewVideos(Scene): def construct(self): icon = SVGMobject("video_icon") @@ -447,6 +463,16 @@ class NextFewVideos(Scene): ) self.dither() +class UnderstandingBeforeApplication(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says(""" + Just the visual + understanding for now + """) + self.random_blink() + self.dither() + class WhatIveSaidSoFar(TeacherStudentsScene): def construct(self): self.setup() @@ -807,6 +833,10 @@ class RightHandRule(Scene): i_hat = Vector([-1.75, 0.5]) j_hat = Vector([-1.4, -0.7]) k_hat = Vector([0, 1.7]) + vects = [i_hat, j_hat, k_hat] + if self.flip: + VMobject(hand, *vects).flip() + i_label, j_label, k_label = [ TexMobject("\\hat{%s}"%s).scale(1.5) for s in "\\imath", "\\jmath", "k" @@ -815,12 +845,9 @@ class RightHandRule(Scene): j_label.next_to(j_hat.get_end(), DOWN) k_label.next_to(k_hat.get_end(), UP) - vects = [i_hat, j_hat, k_hat] labels = [i_label, j_label, k_label] colors = [X_COLOR, Y_COLOR, Z_COLOR] - if self.flip: - VMobject(hand, *vects+labels).flip() # self.add(NumberPlane()) self.play( @@ -841,6 +868,16 @@ class LeftHandRule(RightHandRule): "flip" : True } +class AskHowToCompute(TeacherStudentsScene): + def construct(self): + self.setup() + student = self.get_students()[1] + self.student_says("How do you \\\\ compute this?") + self.play(student.change_mode, "confused") + self.random_blink() + self.dither() + self.random_blink() + class TwoDDeterminantFormula(Scene): def construct(self): eq = TextMobject("=") @@ -853,7 +890,7 @@ class TwoDDeterminantFormula(Scene): VMobject(matrix, det_text).next_to(eq, LEFT) formula = TexMobject(list("ad-bc")) formula.next_to(eq, RIGHT) - formula.shift(0.2*UP) + formula.shift(0.1*UP) a, d, minus, b, c = formula.split() VMobject(a, c).highlight(X_COLOR) @@ -878,7 +915,7 @@ class TwoDDeterminantFormula(Scene): for m in mb, mc, b, c ]) self.dither() - for pair in (mc, c), (mb, b): + for pair in (mb, b), (mc, c): self.play(*[ Transform(m, m.original) for m in pair @@ -957,9 +994,9 @@ class FullFormulaExplanation(LinearTransformationScene): d = self.j_hat.get_end()[1]*UP shapes_colors_and_tex = [ - (Polygon(ORIGIN, a, a+c), TEAL, "bd/2"), - (Polygon(ORIGIN, d+b, d), TEAL, "\\dfrac{bd}{2}"), - (Polygon(a+c, a+b+c, a+b+c+d), MAROON, "\\dfrac{ac}{2}"), + (Polygon(ORIGIN, a, a+c), MAROON, "ac/2"), + (Polygon(ORIGIN, d+b, d, d), TEAL, "\\dfrac{bd}{2}"), + (Polygon(a+c, a+b+c, a+b+c, a+b+c+d), TEAL, "\\dfrac{bd}{2}"), (Polygon(b+d, a+b+c+d, b+c+d), MAROON, "ac/2"), (Polygon(a, a+b, a+b+c, a+c), PINK, "bc"), (Polygon(d, d+b, d+b+c, d+c), PINK, "bc"), @@ -991,8 +1028,8 @@ class FullFormulaExplanation(LinearTransformationScene): (a, a+b, DOWN, "b"), (a+b, a+b+c, RIGHT, "c"), (a+b+c, a+b+c+d, RIGHT, "d"), - (a+b+c+d, a+c+d, UP, "a"), - (a+c+d, d+c, UP, "b"), + (a+b+c+d, b+c+d, UP, "a"), + (b+c+d, d+c, UP, "b"), (d+c, d, LEFT, "c"), (d, ORIGIN, LEFT, "d"), ] diff --git a/eola/chapter6.py b/eola/chapter6.py new file mode 100644 index 00000000..805fb4e7 --- /dev/null +++ b/eola/chapter6.py @@ -0,0 +1,814 @@ +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 ka_playgrounds.circuits import Resistor, Source, LongResistor + +class OpeningQuote(Scene): + def construct(self): + words = TextMobject([ + "The question you raise, ", + "``how can such a formulation lead to computations?''", + "doesn't bother me in the least! Throughout my whole life " + "as a mathematician, the possibility of making explicit, " + "elegant computations has always come out by itself, as a " + "byproduct of a ", + "thorough conceptual understanding." + ], separate_list_arg_with_spaces = False) + words.scale_to_fit_width(2*SPACE_WIDTH - 2) + words.to_edge(UP) + words.split()[1].highlight(BLUE) + words.split()[3].highlight(GREEN) + author = TextMobject(["-Grothendieck", "(a hero of mine)"]) + author.split()[0].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 ListTerms(Scene): + def construct(self): + title = TextMobject("Under the light of linear transformations") + title.highlight(YELLOW) + title.to_edge(UP) + randy = Randolph().to_corner() + words = VMobject(*map(TextMobject, [ + "Inverse matrices", + "Column space", + "Rank", + "Null space", + ])) + words.arrange_submobjects(DOWN, aligned_edge = LEFT) + words.next_to(title, DOWN, aligned_edge = LEFT) + words.shift(RIGHT) + + self.add(title, randy) + for i, word in enumerate(words.split()): + self.play(Write(word), run_time = 1) + if i%2 == 0: + self.play(Blink(randy)) + else: + self.dither() + self.dither() + +class NoComputations(TeacherStudentsScene): + def construct(self): + self.setup() + self.student_says( + "Will you cover \\\\ computations?", + pi_creature_target_mode = "raise_left_hand" + ) + self.random_blink() + self.teacher_says( + "Well...uh...no", + pi_creature_target_mode = "guilty", + ) + self.play(*[ + ApplyMethod(student.change_mode, mode) + for student, mode in zip( + self.get_students(), + ["dejected", "confused", "angry"] + ) + ]) + self.random_blink() + self.dither() + new_words = self.teacher.bubble.position_mobject_inside( + TextMobject([ + "Search", + "``Gaussian elimination'' \\\\", + "and", + "``Row echelon form''", + ]) + ) + new_words.split()[1].highlight(YELLOW) + new_words.split()[3].highlight(GREEN) + self.play( + Transform(self.teacher.bubble.content, new_words), + self.teacher.change_mode, "speaking" + ) + self.play(*[ + ApplyMethod(student.change_mode, "pondering") + for student in self.get_students() + ]) + self.random_blink() + +class UsefulnessOfMatrices(Scene): + def construct(self): + title = TextMobject("Usefulness of matrices") + title.highlight(YELLOW) + title.to_edge(UP) + self.add(title) + self.dither(3) #Play some 3d linear transform over this + + equations = TexMobject(""" + 6x - 3y + 2z &= 7 \\\\ + x + 2y + 5z &= 0 \\\\ + 2x - 8y - z &= -2 \\\\ + """) + equations.to_edge(RIGHT, buff = 2) + syms = VMobject(*np.array(equations.split())[[1, 4, 7]]) + new_syms = VMobject(*[ + m.copy().highlight(c) + for m, c in zip(syms.split(), [X_COLOR, Y_COLOR, Z_COLOR]) + ]) + new_syms.arrange_submobjects(RIGHT, buff = 0.5) + new_syms.next_to(equations, LEFT, buff = 3) + sym_brace = Brace(new_syms, DOWN) + unknowns = sym_brace.get_text("Unknown variables") + eq_brace = Brace(equations, DOWN) + eq_words = eq_brace.get_text("Equations") + + self.play(Write(equations)) + self.dither() + self.play(Transform(syms.copy(), new_syms, path_arc = np.pi/2)) + for brace, words in (sym_brace, unknowns), (eq_brace, eq_words): + self.play( + GrowFromCenter(brace), + Write(words) + ) + self.dither() + +class CircuitDiagram(Scene): + def construct(self): + self.add(TextMobject("Voltages").to_edge(UP)) + + source = Source() + p1, p2 = source.get_top(), source.get_bottom() + r1 = Resistor(p1, p1+2*RIGHT) + r2 = LongResistor(p1+2*RIGHT, p2+2*RIGHT) + r3 = Resistor(p1+2*RIGHT, p1+2*2*RIGHT) + l1 = Line(p1+2*2*RIGHT, p2+2*2*RIGHT) + l2 = Line(p2+2*2*RIGHT, p2) + circuit = VMobject(source, r1, r2, r3, l1, l2) + circuit.center() + v1 = TexMobject("v_1").next_to(r1, UP) + v2 = TexMobject("v_2").next_to(r2, RIGHT) + v3 = TexMobject("v_3").next_to(r3, UP) + unknowns = VMobject(v1, v2, v3) + unknowns.highlight(BLUE) + + self.play(ShowCreation(circuit)) + self.dither() + self.play(Write(unknowns)) + self.dither() + +class StockLine(VMobject): + CONFIG = { + "num_points" : 15, + "step_range" : 2 + } + def generate_points(self): + points = [ORIGIN] + for x in range(self.num_points): + step_size = self.step_range*(random.random() - 0.5) + points.append(points[-1] + 0.5*RIGHT + step_size*UP) + self.set_anchor_points(points, mode = "corners") + +class StockPrices(Scene): + def construct(self): + self.add(TextMobject("Stock prices").to_edge(UP)) + + x_axis = Line(ORIGIN, SPACE_WIDTH*RIGHT) + y_axis = Line(ORIGIN, SPACE_HEIGHT*UP) + everyone = VMobject(x_axis, y_axis) + stock_lines = [] + for color in TEAL, PINK, YELLOW, RED, BLUE: + sl = StockLine(color = color) + sl.move_to(y_axis.get_center(), side_to_align = LEFT) + everyone.add(sl) + stock_lines.append(sl) + everyone.center() + + self.add(x_axis, y_axis) + self.play(ShowCreation( + VMobject(*stock_lines), + run_time = 3, + submobject_mode = "lagged_start" + )) + self.dither() + +class MachineLearningNetwork(Scene): + def construct(self): + self.add(TextMobject("Machine learning parameters").to_edge(UP)) + + layers = [] + for i, num_nodes in enumerate([3, 4, 4, 1]): + layer = VMobject(*[ + Circle(radius = 0.5, color = YELLOW) + for x in range(num_nodes) + ]) + for j, mob in enumerate(layer.split()): + sym = TexMobject("x_{%d, %d}"%(i, j)) + sym.move_to(mob) + mob.add(sym) + layer.arrange_submobjects(DOWN, buff = 0.5) + layer.center() + layers.append(layer) + VMobject(*layers).arrange_submobjects(RIGHT, buff = 1.5) + lines = VMobject() + for l_layer, r_layer in zip(layers, layers[1:]): + for l_node, r_node in it.product(l_layer.split(), r_layer.split()): + lines.add(Line(l_node, r_node)) + lines.submobject_gradient_highlight(BLUE_E, BLUE_A) + for mob in VMobject(*layers), lines: + self.play(Write(mob), run_time = 2) + self.dither() + +class SystemOfEquations(Scene): + def construct(self): + equations = self.get_equations() + self.show_linearity_rules(equations) + self.describe_organization(equations) + self.factor_into_matrix(equations) + + def get_equations(self): + matrix = Matrix([ + [2, 5, 3], + [4, 0, 8], + [1, 3, 0] + ]) + mob_matrix = matrix.get_mob_matrix() + rhs = map(TexMobject, map(str, [-3, 0, 2])) + variables = map(TexMobject, list("xyz")) + for v, color in zip(variables, [X_COLOR, Y_COLOR, Z_COLOR]): + v.highlight(color) + equations = VMobject() + for row in mob_matrix: + equation = VMobject(*it.chain(*zip( + row, + [v.copy() for v in variables], + map(TexMobject, list("++=")) + ))) + equation.arrange_submobjects( + RIGHT, buff = 0.1, + aligned_edge = DOWN + ) + equation.split()[4].shift(0.1*DOWN) + equation.split()[-1].next_to(equation.split()[-2], RIGHT) + equations.add(equation) + equations.arrange_submobjects(DOWN, aligned_edge = RIGHT) + for eq, rhs_elem in zip(equations.split(), rhs): + rhs_elem.next_to(eq, RIGHT) + eq.add(rhs_elem) + equations.center() + self.play(Write(equations)) + self.add(equations) + return equations + + def show_linearity_rules(self, equations): + top_equation = equations.split()[0] + other_equations = VMobject(*equations.split()[1:]) + other_equations.save_state() + scaled_vars = VMobject(*[ + VMobject(*top_equation.split()[3*i:3*i+2]) + for i in range(3) + ]) + scaled_vars.save_state() + isolated_scaled_vars = scaled_vars.copy() + isolated_scaled_vars.scale(1.5) + isolated_scaled_vars.next_to(top_equation, UP) + scalars = VMobject(*[m.split()[0] for m in scaled_vars.split()]) + plusses = np.array(top_equation.split())[[2, 5]] + + self.play(other_equations.fade, 0.7) + self.play(Transform(scaled_vars, isolated_scaled_vars)) + self.play(scalars.highlight, YELLOW, submobject_mode = "lagged_start") + self.play(*[ + ApplyMethod(m.scale_in_place, 1.2, rate_func = there_and_back) + for m in scalars.split() + ]) + self.dither() + self.remove(scalars) + self.play(scaled_vars.restore) + self.play(*[ + ApplyMethod(p.scale_in_place, 1.5, rate_func = there_and_back) + for p in plusses + ]) + self.dither() + self.show_nonlinearity_examples() + self.play(other_equations.restore) + + def show_nonlinearity_examples(self): + squared = TexMobject("x^2") + squared.split()[0].highlight(X_COLOR) + sine = TexMobject("\\sin(x)") + sine.split()[-2].highlight(X_COLOR) + product = TexMobject("xy") + product.split()[0].highlight(X_COLOR) + product.split()[1].highlight(Y_COLOR) + + + words = TextMobject("Not allowed!") + words.highlight(RED) + words.to_corner(UP+LEFT, buff = 1) + arrow = Vector(RIGHT, color = RED) + arrow.next_to(words, RIGHT) + for mob in squared, sine, product: + mob.scale(1.7) + mob.next_to(arrow.get_end(), RIGHT, buff = 0.5) + circle_slash = Circle(color = RED) + line = Line(LEFT, RIGHT, color = RED) + line.rotate(np.pi/4) + circle_slash.add(line) + circle_slash.next_to(arrow, RIGHT) + def draw_circle_slash(mob): + circle_slash.replace(mob) + circle_slash.scale_in_place(1.4) + self.play(ShowCreation(circle_slash), run_time = 0.5) + self.dither(0.5) + self.play(FadeOut(circle_slash), run_time = 0.5) + + self.play( + Write(squared), + Write(words, run_time = 1), + ShowCreation(arrow), + ) + draw_circle_slash(squared) + for mob in sine, product: + self.play(Transform(squared, mob)) + draw_circle_slash(mob) + self.play(*map(FadeOut, [words, arrow, squared])) + self.dither() + + + def describe_organization(self, equations): + variables = VMobject(*it.chain(*[ + eq.split()[:-2] + for eq in equations.split() + ])) + variables.words = "Throw variables on the left" + constants = VMobject(*[ + eq.split()[-1] + for eq in equations.split() + ]) + constants.words = "Lingering constants on the right" + xs, ys, zs = [ + VMobject(*[ + eq.split()[i] + for eq in equations.split() + ]) + for i in 1, 4, 7 + ] + ys.words = "Vertically align variables" + colors = [PINK, YELLOW, BLUE_B, BLUE_C, BLUE_D] + for mob, color in zip([variables, constants, xs, ys, zs], colors): + mob.square = Square(color = color) + mob.square.replace(mob, stretch = True) + mob.square.scale_in_place(1.1) + if hasattr(mob, "words"): + mob.words = TextMobject(mob.words) + mob.words.highlight(color) + mob.words.next_to(mob.square, UP) + ys.square.add(xs.square, zs.square) + zero_circles = VMobject(*[ + Circle().replace(mob).scale_in_place(1.3) + for mob in [ + VMobject(*equations.split()[i].split()[j:j+2]) + for i, j in (1, 3), (2, 6) + ] + ]) + zero_circles.highlight(PINK) + zero_circles.words = TextMobject("Add zeros as needed") + zero_circles.words.highlight(zero_circles.get_color()) + zero_circles.words.next_to(equations, UP) + + for mob in variables, constants, ys: + self.play( + FadeIn(mob.square), + FadeIn(mob.words) + ) + self.dither() + self.play(*map(FadeOut, [mob.square, mob.words])) + self.play( + ShowCreation(zero_circles), + Write(zero_circles.words, run_time = 1) + ) + self.dither() + self.play(*map(FadeOut, [zero_circles, zero_circles.words])) + self.dither() + title = TextMobject("``Linear system of equations''") + title.scale(1.5) + title.to_edge(UP) + self.play(Write(title)) + self.dither() + self.play(FadeOut(title)) + + + def factor_into_matrix(self, equations): + coefficients = np.array([ + np.array(eq.split())[[0, 3, 6]] + for eq in equations.split() + ]) + variable_arrays = np.array([ + np.array(eq.split())[[1, 4, 7]] + for eq in equations.split() + ]) + rhs_entries = np.array([ + eq.split()[-1] + for eq in equations.split() + ]) + + matrix = Matrix(copy.deepcopy(coefficients)) + x_array = Matrix(copy.deepcopy(variable_arrays[0])) + v_array = Matrix(copy.deepcopy(rhs_entries)) + equals = TexMobject("=") + ax_equals_v = VMobject(matrix, x_array, equals, v_array) + ax_equals_v.arrange_submobjects(RIGHT) + ax_equals_v.to_edge(RIGHT) + all_brackets = [ + mob.get_brackets() + for mob in matrix, x_array, v_array + ] + + self.play(equations.to_edge, LEFT) + arrow = Vector(RIGHT, color = YELLOW) + arrow.next_to(ax_equals_v, LEFT) + self.play(ShowCreation(arrow)) + self.play(*it.chain(*[ + [ + Transform( + m1.copy(), m2, + run_time = 2, + path_arc = -np.pi/2 + ) + for m1, m2 in zip( + start_array.flatten(), + matrix_mobject.get_entries().split() + ) + ] + for start_array, matrix_mobject in [ + (coefficients, matrix), + (variable_arrays[0], x_array), + (variable_arrays[1], x_array), + (variable_arrays[2], x_array), + (rhs_entries, v_array) + ] + ])) + self.play(*[ + Write(mob) + for mob in all_brackets + [equals] + ]) + self.dither() + self.label_matrix_product(matrix, x_array, v_array) + + def label_matrix_product(self, matrix, x_array, v_array): + matrix.words = "Coefficients" + matrix.symbol = "A" + x_array.words = "Variables" + x_array.symbol = "\\vec{\\textbf{x}}" + v_array.words = "Constants" + v_array.symbol = "\\vec{\\textbf{v}}" + parts = matrix, x_array, v_array + for mob in parts: + mob.brace = Brace(mob, UP) + mob.words = mob.brace.get_text(mob.words) + mob.words.shift_onto_screen() + mob.symbol = TexMobject(mob.symbol) + mob.brace.put_at_tip(mob.symbol) + x_array.words.submobject_gradient_highlight( + X_COLOR, Y_COLOR, Z_COLOR + ) + x_array.symbol.highlight(PINK) + v_array.symbol.highlight(YELLOW) + for mob in parts: + self.play( + GrowFromCenter(mob.brace), + FadeIn(mob.words) + ) + self.dither() + self.play(*map(FadeOut, [mob.brace, mob.words])) + self.dither() + for mob in parts: + self.play( + FadeIn(mob.brace), + Write(mob.symbol) + ) + compact_equation = VMobject(*[ + mob.symbol for mob in parts + ]) + compact_equation.submobjects.insert( + 2, TexMobject("=").next_to(x_array, RIGHT) + ) + compact_equation.target = compact_equation.copy() + compact_equation.target.arrange_submobjects(buff = 0.1) + compact_equation.target.to_edge(UP) + + self.play(Transform( + compact_equation.copy(), + compact_equation.target + )) + self.dither() + +class LinearSystemTransformationScene(LinearTransformationScene): + def setup(self): + LinearTransformationScene.setup(self) + equation = TexMobject([ + "A", + "\\vec{\\textbf{x}}", + "=", + "\\vec{\\textbf{v}}", + ]) + equation.scale(1.5) + equation.next_to(ORIGIN, LEFT).to_edge(UP) + equation.add_background_rectangle() + self.add_foreground_mobject(equation) + self.equation = equation + self.A, self.x, eq, self.v = equation.split()[1].split() + self.x.highlight(PINK) + self.v.highlight(YELLOW) + +class MentionThatItsATransformation(LinearSystemTransformationScene): + CONFIG = { + "t_matrix" : np.array([[2, 1], [2, 3]]) + } + def construct(self): + self.setup() + brace = Brace(self.A) + words = brace.get_text("Transformation") + words.add_background_rectangle() + self.play(GrowFromCenter(brace), Write(words, run_time = 1)) + self.add_foreground_mobject(words, brace) + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class LookForX(MentionThatItsATransformation): + CONFIG = { + "show_basis_vectors" : False + } + def construct(self): + self.setup() + v = [-4, - 1] + x = np.linalg.solve(self.t_matrix.T, v) + v = Vector(v, color = YELLOW) + x = Vector(x, color = PINK) + v_label = self.get_vector_label(v, "v", color = YELLOW) + x_label = self.get_vector_label(x, "x", color = PINK) + for label in x_label, v_label: + label.add_background_rectangle() + self.play( + ShowCreation(v), + Write(v_label) + ) + self.add_foreground_mobject(v_label) + x = self.add_vector(x, animate = False) + self.play( + ShowCreation(x), + Write(x_label) + ) + self.dither() + self.add(VMobject(x, x_label).copy().fade()) + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class SystemOfTwoEquationsTwoUnknowns(Scene): + def construct(self): + system = TexMobject(""" + 2x + 2y &= -4 \\\\ + 1x + 3y &= -1 + """) + system.to_edge(UP) + for indices, color in ((1, 9), X_COLOR), ((4, 12), Y_COLOR): + for i in indices: + system.split()[i].highlight(color) + matrix = Matrix([[2, 2], [1, 3]]) + v = Matrix([-4, -1]) + x = Matrix(["x", "y"]) + x.get_entries().submobject_gradient_highlight(X_COLOR, Y_COLOR) + matrix_system = VMobject( + matrix, x, TexMobject("="), v + ) + matrix_system.arrange_submobjects(RIGHT) + matrix_system.next_to(system, DOWN, buff = 1) + + self.add(system) + self.play(Write(matrix_system)) + self.dither() + +class ShowBijectivity(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False, + "t_matrix" : np.array([[0, -1], [2, 1]]) + } + def construct(self): + self.setup() + vectors = VMobject(*[ + Vector([x, y]) + for x, y in it.product(*[ + np.arange(-int(val)+0.5, int(val)+0.5) + for val in SPACE_WIDTH, SPACE_HEIGHT + ]) + ]) + vectors.submobject_gradient_highlight(BLUE_E, PINK) + dots = VMobject(*[ + Dot(v.get_end(), color = v.get_color()) + for v in vectors.split() + ]) + titles = [ + TextMobject([ + "Each vector lands on\\\\", + "exactly one vector" + ]), + TextMobject([ + "Every vector has \\\\", + "been landed on" + ]) + ] + for title in titles: + title.to_edge(UP) + background = BackgroundRectangle(VMobject(*titles)) + self.add_foreground_mobject(background, titles[0]) + + kwargs = { + "submobject_mode" : "lagged_start", + "run_time" : 2 + } + anims = map(Animation, self.foreground_mobjects) + self.play(ShowCreation(vectors, **kwargs), *anims) + self.play(Transform(vectors, dots, **kwargs), *anims) + self.dither() + self.add_transformable_mobject(vectors) + self.apply_transposed_matrix(self.t_matrix) + self.dither() + self.play(Transform(*titles)) + self.dither() + self.apply_transposed_matrix( + np.linalg.inv(self.t_matrix.T).T + ) + self.dither() + +class LabeledExample(LinearSystemTransformationScene): + CONFIG = { + "title" : "", + "t_matrix" : [[0, 0], [0, 0]], + } + def setup(self): + LinearSystemTransformationScene.setup(self) + title = TextMobject(self.title) + title.scale(1.5) + title.next_to(self.equation, DOWN) + title.add_background_rectangle() + self.add_foreground_mobject(title) + self.title = title + + def construct(self): + self.setup() + self.dither() + self.apply_transposed_matrix(self.t_matrix) + self.dither() + +class SquishExmapleWithWords(LabeledExample): + CONFIG = { + "title" : "$A$ squishes things to a lower dimension", + "t_matrix" : [[-2, -1], [2, 1]] + } + +class FullRankExmapleWithWords(LabeledExample): + CONFIG = { + "title" : "$A$ keeps things 2D", + "t_matrix" : [[3, 0], [2, 1]] + } + +class SquishExmapleDet(SquishExmapleWithWords): + CONFIG = { + "title" : "$\\det(A) = 0$", + } + +class FullRankExmapleDet(FullRankExmapleWithWords): + CONFIG = { + "title" : "$\\det(A) \\ne 0$", + } + +class PlayInReverse(FullRankExmapleDet): + CONFIG = { + "show_basis_vectors" : False + } + def construct(self): + FullRankExmapleDet.construct(self) + v = self.add_vector([-2, -2], color = YELLOW) + v_label = self.label_vector(v, "v", color = YELLOW) + self.add(v.copy()) + self.apply_inverse_transpose(self.t_matrix) + self.play(v.highlight, PINK) + self.label_vector(v, "x", color = PINK) + self.dither() + +class DescribeInverse(LinearTransformationScene): + CONFIG = { + "show_matrix" : False + } + def construct(self): + self.setup() + title = TextMobject("Transformation:") + new_title = TextMobject("Inverse transformation:") + if self.show_matrix: + matrix = Matrix(self.t_matrix.T) + inv_matrix = Matrix(np.linalg.inv(self.t_matrix.T).astype('int')) + else: + matrix, inv_matrix = map(TexMobject, ["A", "A^{-1}"]) + for m, text in (matrix, title), (inv_matrix, new_title): + m.rect = BackgroundRectangle(m) + m = VMobject(m.rect, m) + text.add_background_rectangle() + m.next_to(text, RIGHT) + text.add(m) + if text.get_width() > 2*SPACE_WIDTH-1: + text.scale_to_fit_width(2*SPACE_WIDTH-1) + text.center().to_edge(UP) + + self.add_foreground_mobject(title) + self.apply_transposed_matrix(self.t_matrix) + self.dither() + self.play(Transform(title, new_title)) + self.apply_inverse_transpose(self.t_matrix) + self.dither() + +class ClockwiseCounterclockwise(DescribeInverse): + CONFIG = { + "t_matrix" : [[0, 1], [-1, 0]], + "show_matrix" : True, + } + +class ShearInverseShear(DescribeInverse): + CONFIG = { + "t_matrix" : [[1, 0], [1, 1]], + "show_matrix" : True, + } + +class MultiplyToIdentity(LinearTransformationScene): + def construct(self): + self.setup() + lhs = TexMobject("A", "A^{-1}", "=") + lhs.scale(1.5) + A, A_inv, eq = lhs.split() + identity = Matrix([[1, 0], [0, 1]]) + identity.highlight_columns(X_COLOR, Y_COLOR) + identity.next_to(eq, RIGHT) + VMobject(lhs, identity).center().to_edge(UP) + lhs.add_background_rectangle() + identity.add_to_back(BackgroundRectangle(identity)) + + col1 = VMobject(*identity.get_mob_matrix[:,0]) + col2 = VMobject(*identity.get_mob_matrix[:,1]) + + A.text = "Transformation" + A_inv.text = "Inverse transformation" + product = VMobject(A, A_inv) + product.text = "Matrix multiplication" + identity.text = "The transformation that does nothing" + for mob in A, A_inv, product, identity: + mob.brace = Brace(mob) + mob.text = mob.brace.get_text(mob.text) + mob.text.add_background_rectangle() + + self.add(A, A_inv) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eola/thumbnails.py b/eola/thumbnails.py new file mode 100644 index 00000000..f9ed3d66 --- /dev/null +++ b/eola/thumbnails.py @@ -0,0 +1,96 @@ +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 * + +class Chapter0(LinearTransformationScene): + CONFIG = { + "include_background_plane" : False, + "t_matrix" : [[3, 1], [2, -1]] + } + def construct(self): + self.setup() + self.plane.fade() + for mob in self.get_mobjects(): + mob.set_stroke(width = 6) + self.apply_transposed_matrix(self.t_matrix, run_time = 0) + +class Chapter1(Scene): + def construct(self): + arrow = Vector(2*UP+RIGHT) + vs = TextMobject("vs.") + array = Matrix([1, 2]) + array.highlight(TEAL) + everyone = VMobject(arrow, vs, array) + everyone.arrange_submobjects(RIGHT, buff = 0.5) + everyone.scale_to_fit_height(4) + self.add(everyone) + +class Chapter2(LinearTransformationScene): + 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, BLUE_E) + words = TextMobject("Span") + words.scale(3) + words.to_edge(UP) + words.add_background_rectangle() + self.add(vectors, words) + + +class Chapter3(Chapter0): + CONFIG = { + "t_matrix" : [[3, 0], [2, -1]] + } + +class Chapter4p1(Chapter0): + CONFIG = { + "t_matrix" : [[1, 0], [1, 1]] + } + +class Chapter4p2(Chapter0): + CONFIG = { + "t_matrix" : [[1, 2], [-1, 1]] + } + + + + + + + + + + + + + + + + + + + + + diff --git a/eola/two_d_space.py b/eola/two_d_space.py index bc4fd47d..0fab2167 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -257,6 +257,7 @@ class LinearTransformationScene(VectorScene): "i_hat_color" : X_COLOR, "j_hat_color" : Y_COLOR, "leave_ghost_vectors" : False, + "t_matrix" : np.array([[3, 0], [1, 2]]), } def setup(self): self.background_mobjects = [] @@ -266,7 +267,7 @@ class LinearTransformationScene(VectorScene): self.transformable_labels = [] self.moving_mobjects = [] - + self.t_matrix = np.array(self.t_matrix) self.background_plane = NumberPlane( **self.background_plane_kwargs ) @@ -289,6 +290,7 @@ class LinearTransformationScene(VectorScene): ] ] + def add_special_mobjects(self, mob_list, *mobs_to_add): for mobject in mobs_to_add: if mobject not in mob_list: @@ -405,6 +407,10 @@ class LinearTransformationScene(VectorScene): kwargs["path_arc"] = net_rotation self.apply_function(func, **kwargs) + def apply_inverse_transpose(self, t_matrix, **kwargs): + t_inv = np.linalg.inv(np.array(t_matrix).T).T + self.apply_transposed_matrix(t_inv, **kwargs) + def apply_nonlinear_transformation(self, function, **kwargs): self.plane.prepare_for_nonlinear_transform() self.apply_function(function, **kwargs) diff --git a/mobject/mobject.py b/mobject/mobject.py index 26c550c4..3936d2d2 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -51,6 +51,11 @@ class Mobject(object): self.submobjects = list_update(self.submobjects, mobjects) return self + def add_to_back(self, *mobjects): + self.remove(*mobjects) + self.submobjects = list(mobjects) + self.submobjects + return self + def remove(self, *mobjects): for mobject in mobjects: if mobject in self.submobjects: @@ -298,17 +303,25 @@ class Mobject(object): def gradient_highlight(self, start_color, end_color): raise Exception("Not implemented") - def submobject_gradient_highlight(self, start_color, end_color, use_color_range_to = False): + def submobject_gradient_highlight(self, *colors): + if len(colors) == 0: + raise Exception("Need at least one color") + elif len(colors) == 1: + return self.highlight(*colors) + mobs = self.family_members_with_points() - 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): + rgbs = map(color_to_rgb, colors) + alphas = np.linspace(0, (len(rgbs) - 1), len(mobs)) + floors = alphas.astype('int') + alphas_mod1 = alphas % 1 + #End edge case + alphas_mod1[-1] = 1 + floors[-1] = len(rgbs) - 2 + new_colors = [ + Color(rgb = interpolate(rgbs[i], rgbs[i+1], alpha)) + for i, alpha in zip(floors, alphas_mod1) + ] + for mob, color in zip(mobs, new_colors): mob.highlight(color, family = False) return self @@ -337,6 +350,15 @@ class Mobject(object): return self.color ## + def save_state(self): + self.saved_state = self.copy() + return self + + def restore(self): + if not hasattr(self, "saved_state"): + raise Exception("Trying to restore without having saved") + self.__dict__.update(self.saved_state.__dict__) + return self def apply_complex_function(self, function): return self.apply_function( diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 51df553b..79daa7c9 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -39,26 +39,18 @@ class TexMobject(SVGMobject): "organize_left_to_right" : False, "propogate_style_to_family" : True, } - def __init__(self, expression, **kwargs): + def __init__(self, *args, **kwargs): digest_config(self, kwargs, locals()) - self.is_input_a_list = isinstance(expression, list) + ##TODO, Eventually remove this + if len(args) == 1 and isinstance(args[0], list): + args = args[0] + ## + assert(all([isinstance(a, str) for a in args])) VMobject.__init__(self, **kwargs) self.move_into_position() if self.organize_left_to_right: self.organize_submobjects_left_to_right() - def handle_input_type(self): - if isinstance(self.expression, str): - self.is_input_a_list = False - elif isinstance(self.expression, collections.Iterable): - self.is_input_a_list = True - self.expression = list(self.expression) - else: - raise Exception( - "TexMobject was expecting string or list, got " + \ - str(type(self.expression)) + \ - " instead." - ) def path_string_to_mobject(self, path_string): #Overwrite superclass default to use @@ -72,22 +64,22 @@ class TexMobject(SVGMobject): self.template_tex_file ) SVGMobject.generate_points(self) - if self.is_input_a_list: - self.handle_list_expression(self.expression) + if len(self.args) > 1: + self.handle_multiple_args() def get_modified_expression(self): separator = "" - if self.is_input_a_list and self.separate_list_arg_with_spaces: + if self.separate_list_arg_with_spaces: separator = " " - result = separator.join(self.expression) + result = separator.join(self.args) if self.enforce_new_line_structure: result = result.replace("\n", " \\\\ \n ") return result - def handle_list_expression(self, list_expression): + def handle_multiple_args(self): new_submobjects = [] curr_index = 0 - for expr in list_expression: + for expr in self.args: model = TexMobject(expr, **self.CONFIG) new_index = curr_index + len(model.submobjects) new_submobjects.append(VMobject( @@ -108,7 +100,7 @@ class TexMobject(SVGMobject): fill_opacity = opacity ) letters = VMobject(*self.submobjects) - self.submobjects = [rect, letters] + self.add_to_back(rect) return self class TextMobject(TexMobject): diff --git a/topics/characters.py b/topics/characters.py index 047129f9..45ba6cc2 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -5,6 +5,7 @@ from mobject.svg_mobject import SVGMobject from mobject.vectorized_mobject import VMobject from mobject.tex_mobject import TextMobject +from animation import Animation from animation.transform import Transform, ApplyMethod, FadeOut, FadeIn from animation.simple_animations import Write from scene import Scene @@ -107,6 +108,7 @@ class PiCreature(SVGMobject): pupil.shift(nudge_size*UP) return self + def get_looking_direction(self): return np.sign(np.round( self.pupils.get_center() - self.eyes.get_center(), @@ -166,6 +168,35 @@ class Blink(ApplyMethod): def __init__(self, pi_creature, **kwargs): ApplyMethod.__init__(self, pi_creature.blink, **kwargs) +class DoTheWave(Transform): + CONFIG = { + "run_time" : 2 + } + def __init__(self, pi_creature, **kwargs): + start_state = pi_creature.copy() + self.target_states = [ + pi_creature.copy().change_mode("wave_%d"%x) + for x in 1, 2, 3 + ] + [ + pi_creature.copy() + ] + Transform.__init__(self, pi_creature, self.target_states[0], **kwargs) + + def update_mobject(self, alpha): + scaled = alpha*len(self.target_states) + try: + if scaled-1 > 0: + self.starting_mobject = self.target_states[int(scaled)-1] + self.ending_mobject = self.target_states[int(scaled)] + except IndexError: + self.ending_mobject = self.target_states[-1] + Transform.update_mobject(self, scaled%1) + return self + + + + + class Bubble(SVGMobject): CONFIG = { "direction" : LEFT, @@ -275,6 +306,7 @@ class TeacherStudentsScene(Scene): self.students.arrange_submobjects(RIGHT) self.students.scale(0.8) self.students.to_corner(DOWN+LEFT) + self.students = self.students.split() for pi_creature in self.get_everyone(): pi_creature.bubble = None @@ -284,7 +316,7 @@ class TeacherStudentsScene(Scene): return self.teacher def get_students(self): - return self.students.split() + return self.students def get_everyone(self): return [self.get_teacher()] + self.get_students()