diff --git a/eola/chapter1.py b/eola/chapter1.py index a98312d5..513538eb 100644 --- a/eola/chapter1.py +++ b/eola/chapter1.py @@ -29,7 +29,7 @@ def plane_wave_homotopy(x, y, z, t): norm = np.linalg.norm([x, y]) tau = interpolate(5, -5, t) + norm/SPACE_WIDTH alpha = sigmoid(tau) - return [x, y + 0.5*np.sin(2*np.pi*alpha), z] + return [x, y + 0.5*np.sin(2*np.pi*alpha)-t*SMALL_BUFF/2, z] class Physicist(PiCreature): CONFIG = { diff --git a/eola/chapter10.py b/eola/chapter10.py index 92eaec1b..51e07f48 100644 --- a/eola/chapter10.py +++ b/eola/chapter10.py @@ -998,8 +998,8 @@ class NonZeroSolutionsVisually(LinearTransformationScene): equation.highlight_by_tex("\\lambda", MAROON_B) equation.highlight_by_tex("\\vec{\\textbf{v}}", YELLOW) equation.add_background_rectangle() - equation.next_to(ORIGIN, LEFT, buff = MED_BUFF) - equation.to_edge(UP) + equation.next_to(ORIGIN, DOWN, buff = MED_BUFF) + equation.to_edge(LEFT) det_equation = TexMobject( "\\text{Squishification} \\Rightarrow", @@ -1136,7 +1136,10 @@ class TweakLambda(LinearTransformationScene): self.add(*self.lambda_vals) new_det = DecimalNumber( - np.linalg.det(self.t_matrix - val*np.identity(2)) + np.linalg.det([ + self.i_hat.get_end()[:2], + self.j_hat.get_end()[:2], + ]) ) new_det.move_to(self.det, aligned_edge = LEFT) new_det.highlight(self.det.get_color()) diff --git a/eola/chapter11.py b/eola/chapter11.py new file mode 100644 index 00000000..9c010800 --- /dev/null +++ b/eola/chapter11.py @@ -0,0 +1,997 @@ +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 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.chapter1 import plane_wave_homotopy +from eola.chapter3 import ColumnsToBasisVectors +from eola.chapter5 import NameDeterminant +from eola.chapter9 import get_small_bubble +from 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.highlight_by_tex("axioms,", BLUE) + words.highlight_by_tex("difficult for the uninitiated", RED) + words.scale_to_fit_width(2*SPACE_WIDTH - 2) + words.to_edge(UP) + author = TextMobject("-Vladmir Arnold") + author.highlight(YELLOW) + author.next_to(words, DOWN, buff = 2*MED_BUFF) + + self.play(Write(words, run_time = 8)) + self.dither() + self.play(FadeIn(author)) + self.dither(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.highlight_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" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_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, two_d_space = words = [ + TextMobject( + "``Two-dimensional ", word, "''", arg_separator = "" + ).highlight_by_tex(word, color) + for word, color in ("vector", YELLOW), ("space", BLUE) + ] + for word, vect in zip(words, [LEFT, RIGHT]): + word.add_background_rectangle() + word.next_to(ORIGIN, vect, buff = MED_BUFF) + word.to_edge(UP) + + self.play( + Write(two_d_vector), + ShowCreation(v), + Write(coords), + run_time = 2 + ) + self.dither() + last_mobs = self.get_mobjects_from_last_animation() + self.play( + Homotopy(plane_wave_homotopy, self.plane), + Write(two_d_space, run_time = 2), + *map(Animation, last_mobs) + ) + self.dither() + 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 = 2*MED_BUFF) + student.look_at(v) + student.bubble = get_small_bubble( + student, height = 4, width = 4, + ) + self.play(*map(FadeIn, students)) + self.play(Blink(physics_student)) + self.dither() + 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_submobjects(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.dither() + 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_submobjects(DOWN) + group.scale_to_fit_height(coords.get_height()) + group.next_to(student.arrow, RIGHT) + student.q_marks = TexMobject("???") + student.q_marks.gradient_highlight(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.dither() + self.play(*it.chain( + 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], + map(Animation, [cs_student.bubble, cs_student.arrow]), + [mob.restore for mob in cs_student.v, cs_student.coords], + )) + bubble = cs_student.get_bubble("speech", 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.dither() + +class HigherDimensionalVectorsNumerically(Scene): + def construct(self): + words = VGroup(*map(TextMobject, [ + "4D vector", + "5D vector", + "100D vector", + ])) + words.arrange_submobjects(RIGHT, buff = LARGE_BUFF*2) + words.to_edge(UP) + vectors = VGroup(*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.highlight(color) + vector.highlight(color) + + for word in words: + self.play(FadeIn(word)) + self.play(Write(vectors)) + self.dither() + 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).highlight(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.dither() + +class AskAbout4DCSStudent(Scene): + def construct(self): + cs_student = CSStudent().to_edge(DOWN).shift(2*RIGHT) + asker = PiCreature(color = MAROON_D) + asker.to_edge(DOWN).shift(2*LEFT) + for pi1, pi2 in (cs_student, asker), (asker, cs_student): + pi1.speech_bubble = pi1.get_bubble("speech") + pi1.look_at(pi2.eyes) + + thought_bubble = get_small_bubble(cs_student) + thought_bubble.rotate(-np.pi/6) + thought_bubble.stretch_to_fit_height(4) + thought_bubble.next_to( + cs_student.get_corner(UP+RIGHT), UP, buff = 0 + ) + + vector = Matrix([3, 1, 4, 1]) + vector.highlight(YELLOW) + vector.scale_to_fit_height(2) + thought_bubble.add_content(vector) + thought_bubble.add(thought_bubble.content) + asker.speech_bubble.write(""" + What is this ``4d space'' + you speak of? + """) + cs_student.speech_bubble.write( + "The set of all", + "ordered \\\\ quadruplets", + "of real numbers", + ) + cs_student.speech_bubble.content[1].highlight(YELLOW) + for pi in asker, cs_student: + pi.speech_bubble.add(pi.speech_bubble.content) + + self.add(asker, cs_student) + self.play( + asker.change_mode, "erm", + Write(asker.speech_bubble), + ) + self.play(Blink(cs_student)) + self.play( + asker.change_mode, "plain", + cs_student.change_mode, "speaking", + FadeOut(asker.speech_bubble), + Write(cs_student.speech_bubble), + ) + self.play( + asker.change_mode, "pondering", + asker.look_at, thought_bubble, + Write(thought_bubble) + ) + self.play(Blink(asker)) + self.dither() + +class HyperCube(VMobject): + CONFIG = { + "color" : BLUE_C, + "color2" : BLUE_D, + "dims" : 4, + } + def generate_points(self): + corners = np.array(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.gradient_highlight(self.color, self.color2) + +class AskAbout4DPhysicsStudent(Scene): + def construct(self): + physy = PhysicsStudent().flip().to_edge(DOWN).shift(2*RIGHT) + asker = PiCreature(color = MAROON_D) + asker.to_edge(DOWN).shift(2*LEFT) + for pi1, pi2 in (physy, asker), (asker, physy): + pi1.speech_bubble = pi1.get_bubble("speech") + pi1.look_at(pi2.eyes) + + thought_bubble = get_small_bubble(physy) + thought_bubble.rotate(-np.pi/6) + thought_bubble.stretch_to_fit_height(5) + thought_bubble.stretch_to_fit_width(4) + thought_bubble.shift(RIGHT) + thought_bubble.next_to( + physy.get_corner(UP+RIGHT), UP, buff = 0 + ) + + vector = Vector([1, 2]) + line = Line(LEFT, RIGHT, color = BLUE_B) + square = Square(color = BLUE_C) + cube = HyperCube(color = BLUE_D, dims = 3) + hyper_cube = HyperCube() + thought_mobs = vector, line, square, cube, hyper_cube + for mob in thought_mobs: + mob.scale_to_fit_height(2) + thought_bubble.add_content(mob) + square.scale_in_place(0.7) + asker.speech_bubble.flip() + asker.speech_bubble.stretch_to_fit_width(4.5) + asker.speech_bubble.to_edge(LEFT) + asker.speech_bubble.write(""" + Well, what do \\emph{you} + think 4d space is? + """) + physy.speech_bubble.write(""" + Well, it's kind of like + 3d space, but with another + dimension, you know? + """) + for pi in asker, physy: + pi.speech_bubble.add(pi.speech_bubble.content) + + + self.add(asker, physy, thought_bubble, vector) + self.play( + asker.change_mode, "sassy", + Write(asker.speech_bubble), + ) + self.play(Blink(physy)) + self.play( + asker.change_mode, "plain", + physy.change_mode, "hooray", + FadeOut(asker.speech_bubble), + Write(physy.speech_bubble), + ) + self.play( + asker.change_mode, "confused", + asker.look_at, thought_bubble, + physy.look_at, thought_bubble, + ) + for mob in thought_mobs[1:]: + self.play(Transform(vector, mob)) + self.remove(vector) + vector = mob + self.add(vector) + self.dither() + self.play(Blink(asker)) + asker.speech_bubble.remove(asker.speech_bubble.content) + asker.speech_bubble.write("Is that real?") + asker.speech_bubble.add(asker.speech_bubble.content) + self.play( + FadeOut(physy.speech_bubble), + FadeOut(physy.speech_bubble), + Write(asker.speech_bubble, run_time = 2), + asker.change_mode, "sassy", + physy.change_mode, "plain", + physy.look_at, asker.eyes, + ) + self.play(Blink(physy)) + self.dither() + +class ManyCoordinateSystems(LinearTransformationScene): + CONFIG = { + "v_coords" : [2, 1], + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_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( + map(FadeOut, movers), + map(Animation, self.foreground_mobjects) + )) + for mover in movers: + mover.restore() + self.apply_transposed_matrix(t_matrix, run_time = 0) + self.play(*it.chain( + map(FadeIn, movers), + 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(map(DecimalNumber, coords)) + array.get_entries()[0].highlight(X_COLOR) + array.get_entries()[1].highlight(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.dither() + self.play(*map(MoveToTarget, [self.i_hat, coord1])) + self.play(*map(MoveToTarget, [self.j_hat, coord2])) + self.play(VGroup(self.j_hat, coord2).shift, self.i_hat.get_end()) + self.dither(2) + self.play( + self.i_hat.restore, + self.j_hat.restore, + *map(FadeOut, [array, coord1, coord2]) + ) + +class TransformationsWithCoordinates(ColumnsToBasisVectors): + CONFIG = { + "t_matrix" : [[3, 1], [1, 2]], + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + "secondary_line_ratio" : 1 + }, + } + def construct(self): + self.setup() + self.add_unit_square() + eigenvectors = VGroup(*self.get_eigenvectors()) + + for mob in self.square, eigenvectors: + mob.set_fill(opacity = 0) + mob.set_stroke(width = 0) + + det_word = TextMobject("Determinant") + det_word.highlight(YELLOW) + det_word.to_edge(UP) + eigen_word = TextMobject("Eigenvectors") + eigen_word.highlight(MAROON_B) + eigen_word.next_to(det_word, DOWN, aligned_edge = LEFT) + for word in det_word, eigen_word: + word.add_background_rectangle() + + self.move_matrix_columns(self.t_matrix) + faders = [self.matrix, self.i_coords, self.j_coords] + self.play(*map(FadeOut, faders)) + FadeOut(VGroup(*faders)).update(1) + self.play( + Write(det_word, run_time = 2), + self.square.set_fill, YELLOW, 0.3, + self.square.set_stroke, YELLOW, DEFAULT_POINT_THICKNESS, + *map(Animation, [self.i_hat, self.j_hat]) + ) + self.add_foreground_mobject(det_word) + self.play( + Write(eigen_word, run_time = 2), + FadeIn(eigenvectors) + ) + for eigenvector in eigenvectors: + self.add_vector(eigenvector, animate = False) + self.add_foreground_mobject(eigen_word) + self.apply_inverse_transpose( + self.t_matrix, rate_func = there_and_back + ) + self.dither() + + + + 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(14, 0, -2) + for u in -1, 1 + ]) + vectors.gradient_highlight(MAROON_B, YELLOW) + result += list(vectors) + return result + +class NameDeterminantCopy(NameDeterminant): + pass# + +class NameEigenvectorsAndEigenvaluesCopy(ExampleTranformationScene): + CONFIG = { + "show_basis_vectors" : False, + "include_background_plane" : False, + } + def construct(self): + self.remove(self.matrix) + self.foreground_mobjects.remove(self.matrix) + x_vectors = VGroup(*[ + self.add_vector(u*x*RIGHT, animate = False) + for x in range(int(SPACE_WIDTH)+1, 0, -1) + for u in -1, 1 + ]) + x_vectors.gradient_highlight(YELLOW, X_COLOR) + self.remove(x_vectors) + sneak_vectors = VGroup(*[ + self.add_vector(u*x*(LEFT+UP), animate = False) + for x in np.arange(int(SPACE_HEIGHT), 0, -0.5) + for u in -1, 1 + ]) + sneak_vectors.gradient_highlight(MAROON_B, YELLOW) + self.remove(sneak_vectors) + + x_words = TextMobject("Stretched by 3") + sneak_words = TextMobject("Stretched by 2") + for words in x_words, sneak_words: + words.add_background_rectangle() + words.next_to(x_vectors, DOWN) + words.next_to(words.get_center(), LEFT, buff = 1.5) + eigen_word = TextMobject("Eigenvectors") + eigen_word.add_background_rectangle() + eigen_word.replace(words) + words.target = eigen_word + eigen_val_words = TextMobject( + "with eigenvalue ", + "%s"%words.get_tex_string()[-1] + ) + eigen_val_words.add_background_rectangle() + eigen_val_words.next_to(words, DOWN, aligned_edge = RIGHT) + words.eigen_val_words = eigen_val_words + x_words.eigen_val_words.highlight(X_COLOR) + sneak_words.eigen_val_words.highlight(YELLOW) + + VGroup( + sneak_words, + sneak_words.target, + sneak_words.eigen_val_words, + ).rotate(sneak_vectors[0].get_angle()) + + final_words = TextMobject(""" + Result doesn't care about + the coordinate system + """) + final_words.add_background_rectangle() + final_words.to_corner(UP+RIGHT) + + for vectors in x_vectors, sneak_vectors: + self.play(ShowCreation(vectors, run_time = 1)) + self.dither() + for words in x_words, sneak_words: + self.play(Write(words, run_time = 1.5)) + self.add_foreground_mobject(words) + self.dither() + self.dither() + self.apply_transposed_matrix( + self.t_matrix, + path_arc = 0, + ) + self.dither(2) + self.play(*map(MoveToTarget, [x_words, sneak_words])) + self.dither() + for words in x_words, sneak_words: + self.play(Write(words.eigen_val_words), run_time = 2) + self.add_foreground_mobject(words.eigen_val_words) + self.dither() + fade_out = FadeOut(self.plane) + self.play(fade_out, Write(final_words)) + fade_out.update(1) + self.apply_inverse_transpose( + self.t_matrix, + rate_func = there_and_back, + path_arc = 0 + ) + +class ReallyWhatAreVectors(Scene): + def construct(self): + physy = PhysicsStudent() + physy.thought = Vector([1, 2]) + physy.words = "Nature seems to \\\\ disagree with you" + compy = CSStudent() + compy.thought = Matrix([1, 2]) + compy.words = "How else could you \\\\ possibly define them?" + for pi, vect in (physy, LEFT), (compy, RIGHT): + pi.to_edge(DOWN) + pi.shift(4*vect) + pi.bubble = get_small_bubble(pi) + pi.thought.highlight(YELLOW) + pi.thought.scale_to_fit_height(2) + pi.bubble.add_content(pi.thought) + pi.bubble.add(pi.thought) + + pi.speech_bubble = pi.get_bubble("speech", width = 5) + pi.speech_bubble.set_fill(BLACK, opacity = 1) + pi.speech_bubble.write(pi.words) + pi.speech_bubble.add(pi.speech_bubble.content) + self.add(pi) + physy.make_eye_contact(compy) + + hundred_d = Matrix(["3", "5", "\\vdots", "8", "6"]) + hundred_d.highlight(GREEN) + hundred_d.scale_to_fit_height(compy.thought.get_height()) + hundred_d.move_to(compy.thought) + + double_speech = DoubleSpeechBubble(width = 6) + double_speech.set_fill(BLACK, opacity = 1) + double_speech.write("What \\emph{are} vectors???") + double_speech.add(double_speech.content) + double_speech.next_to(VGroup(compy, physy), UP) + + self.play( + *[FadeIn(pi.bubble) for pi in physy, compy], + run_time = 1 + ) + self.play( + Write(physy.speech_bubble), + physy.change_mode, "sassy" + ) + self.play(Blink(compy)) + self.dither() + self.play( + FadeOut(physy.speech_bubble), + Write(compy.speech_bubble), + physy.change_mode, "pondering", + compy.change_mode, "pleading" + ) + self.play(Transform(compy.thought, hundred_d)) + self.play(Blink(physy)) + self.dither() + self.play( + FadeOut(compy.speech_bubble), + Write(double_speech), + compy.change_mode, "confused", + physy.change_mode, "confused", + ) + self.play(*map(Blink, [compy, physy])) + self.dither() + +class OtherVectorishThings(TeacherStudentsScene): + def construct(self): + words = TextMobject( + "There are other\\\\", + "vectorish", + "things..." + ) + words.highlight_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.highlight_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" : [MAROON_B, YELLOW, PINK], + "default_functions" : [ + lambda x : (x**3 - 9*x)/10., + 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.highlight(function_graph.get_color()) + return lines + + def add_lines(self, output_lines): + self.play(ShowCreation( + output_lines, + submobject_mode = "lagged_start", + 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.highlight(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) + for lines in f_lines, g_lines: + self.add_lines(lines) + self.play(*map(FadeOut, [f_graph, g_graph])) + self.dither() + + curr_x_point = f_lines[0].get_start() + sum_def = self.get_sum_definition(DecimalNumber(curr_x_point[0])) + # sum_def.scale_to_fit_width(SPACE_WIDTH-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.dither() + self.play(FadeOut(brace)) + fg_group = VGroup(*list(f_label)+list(g_label)) + self.play( + FadeIn(rect), + Animation(prefix), + Transform(fg_group, suffix), + run_time = 2 + ) + 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.dither() + + 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.dither() + + 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_submobjects() + result[0].highlight(self.graph_colors[2]) + VGroup(result[5], result[7]).highlight(self.graph_colors[0]) + VGroup(result[9], result[11]).highlight(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()) + 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.highlight(sum_lines[0].get_color()) + self.play(ShowCreation(dots)) + if len(list(g_lines)) == 1: + kwargs = {} + else: + kwargs = { + "submobject_mode" : "lagged_start", + "run_time" : 3 + } + self.play(*[ + MoveToTarget(mob, **kwargs) + for mob in g_lines, dots + ]) + self.dither() + self.play(*map(FadeOut, [f_lines, g_lines])) + self.add_lines(sum_lines) + self.dither() + +class ScaleFunction(FunctionGraphScene): + def construct(self): + graph = self.get_function_graph() + scaled_graph = self.get_function_graph( + lambda x : graph.get_function()(x)*2, + add = False + ) + graph_lines = self.get_output_lines(graph) + scaled_lines = self.get_output_lines(scaled_graph, nudge = False) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eola/chapter3.py b/eola/chapter3.py index c2d9b644..450ff359 100644 --- a/eola/chapter3.py +++ b/eola/chapter3.py @@ -1167,6 +1167,11 @@ class ColumnsToBasisVectors(LinearTransformationScene): ) self.add_foreground_mobject(j_coords_start) self.dither() + + self.matrix = VGroup(matrix_background, matrix_mob) + self.i_coords = i_coords_start + self.j_coords = j_coords_start + return vector if vector_coords is not None else None diff --git a/eola/chapter9.py b/eola/chapter9.py index c15466c3..38d93be3 100644 --- a/eola/chapter9.py +++ b/eola/chapter9.py @@ -36,19 +36,21 @@ class You(PiCreature): "flip_at_start" : True, } -def get_small_bubble(pi_creature): +def get_small_bubble(pi_creature, height = 4, width = 3): pi_center_x = pi_creature.get_center()[0] kwargs = { "height" : 4, "bubble_center_adjustment_factor" : 1./6, } bubble = ThoughtBubble(**kwargs) - bubble.stretch_to_fit_width(3) + bubble.stretch_to_fit_width(3)##Canonical width bubble.rotate(np.pi/4) + bubble.stretch_to_fit_width(width) + bubble.stretch_to_fit_height(height) if pi_center_x < 0: bubble.flip() bubble.next_to(pi_creature, UP, buff = MED_BUFF) - bubble.to_edge(pi_center_x*RIGHT, buff = SMALL_BUFF) + bubble.shift_onto_screen() bubble.set_fill(BLACK, opacity = 0.8) return bubble diff --git a/eola/thumbnails.py b/eola/thumbnails.py index 5320f29b..037a6c8f 100644 --- a/eola/thumbnails.py +++ b/eola/thumbnails.py @@ -114,6 +114,44 @@ class Chapter9(Scene): self.add(you, jenny, vector) +class Chapter10(LinearTransformationScene): + CONFIG = { + "foreground_plane_kwargs" : { + "x_radius" : 2*SPACE_WIDTH, + "y_radius" : 2*SPACE_HEIGHT, + "secondary_line_ratio" : 1 + + }, + "include_background_plane" : False, + } + + def construct(self): + v_tex = "\\vec{\\textbf{v}}" + eq = TexMobject("A", v_tex, "=", "\\lambda", v_tex) + eq.highlight_by_tex(v_tex, YELLOW) + eq.highlight_by_tex("\\lambda", MAROON_B) + eq.scale(3) + eq.add_background_rectangle() + eq.shift(2*DOWN) + + title = TextMobject( + "Eigen", "vectors \\\\", + "Eigen", "values" + , arg_separator = "") + title.scale(2.5) + title.to_edge(UP) + # title.highlight_by_tex("Eigen", MAROON_B) + title[0].highlight(YELLOW) + title[2].highlight(MAROON_B) + title.add_background_rectangle() + + + self.add_vector([-1, 1], color = YELLOW, animate = False) + self.apply_transposed_matrix([[3, 0], [1, 2]]) + self.plane.fade() + self.remove(self.j_hat) + self.add(eq, title) + diff --git a/mobject/mobject.py b/mobject/mobject.py index 21248548..e8332206 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -36,7 +36,7 @@ class Mobject(object): self.init_colors() def __str__(self): - return self.name + return str(self.name) def init_points(self): self.points = np.zeros((0, self.dim)) @@ -101,6 +101,10 @@ class Mobject(object): def copy(self): return deepcopy(self) + def generate_target(self): + self.target = self.copy() + return self.target + #### Transforming operations ###### def apply_to_family(self, func): @@ -247,6 +251,7 @@ class Mobject(object): dim = np.argmax(np.abs(vect)) if abs(self.get_edge_center(vect)[dim]) > space_lengths[dim]: self.to_edge(vect, **kwargs) + return self def stretch_to_fit(self, length, dim, stretch = True): old_length = self.length_over_dim(dim) @@ -622,7 +627,7 @@ class Mobject(object): of mobject to become. """ pass #To implement in subclasses - + #TODO, color? def pointwise_become_partial(self, mobject, a, b): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index f63616ad..0d6b87b2 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -234,7 +234,7 @@ class VMobject(Mobject): def point_from_proportion(self, alpha): num_cubics = self.get_num_anchor_points()-1 interpoint_alpha = num_cubics*(alpha % (1./num_cubics)) - index = 3*int(alpha*num_cubics) + index = min(3*int(alpha*num_cubics), 3*num_cubics) cubic = bezier(self.points[index:index+4]) return cubic(interpoint_alpha) diff --git a/topics/characters.py b/topics/characters.py index 396b0002..7848daa5 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -98,7 +98,7 @@ class PiCreature(SVGMobject): direction = direction/np.linalg.norm(direction) self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): - pupil_radius = pupil.get_width()/2. + pupil_radius = pupil.get_width()/2. eye_radius = eye.get_width()/2. pupil.move_to(eye) if direction[1] < 0: @@ -136,15 +136,16 @@ class PiCreature(SVGMobject): ) return self - def to_corner(self, vect = None): + def to_corner(self, vect = None, **kwargs): if vect is not None: - SVGMobject.to_corner(self, vect) + SVGMobject.to_corner(self, vect, **kwargs) else: self.scale(self.corner_scale_factor) - self.to_corner(DOWN+LEFT) + self.to_corner(DOWN+LEFT, **kwargs) return self def get_bubble(self, bubble_type = "thought", **kwargs): + #TODO, change bubble_type arg to have type Bubble if bubble_type == "thought": bubble = ThoughtBubble(**kwargs) elif bubble_type == "speech": @@ -154,6 +155,11 @@ class PiCreature(SVGMobject): bubble.pin_to(self) return bubble + def make_eye_contact(self, pi_creature): + self.look_at(pi_creature.eyes) + pi_creature.look_at(self.eyes) + return self + class Randolph(PiCreature): pass #Nothing more than an alternative name @@ -262,6 +268,12 @@ class SpeechBubble(Bubble): "height" : 4 } +class DoubleSpeechBubble(Bubble): + CONFIG = { + "file_name" : "Bubbles_double_speech.svg", + "height" : 4 + } + class ThoughtBubble(Bubble): CONFIG = { "file_name" : "Bubbles_thought.svg", @@ -429,14 +441,14 @@ class TeacherStudentsScene(Scene): ) - def zoom_in_on_thought_bubble(self, radius = SPACE_HEIGHT+SPACE_WIDTH): - bubble = None - for pi in self.get_everyone(): - if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): - bubble = pi.bubble - break + def zoom_in_on_thought_bubble(self, bubble = None, radius = SPACE_HEIGHT+SPACE_WIDTH): if bubble is None: - raise Exception("No pi creatures have a thought bubble") + for pi in self.get_everyone(): + if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): + bubble = pi.bubble + break + if bubble is None: + raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() def func(point): centered = point+vect diff --git a/topics/functions.py b/topics/functions.py index 8810bb3d..ae1796e1 100644 --- a/topics/functions.py +++ b/topics/functions.py @@ -21,6 +21,9 @@ class FunctionGraph(VMobject): for x in np.linspace(self.x_min, self.x_max, self.num_steps) ], mode = "smooth") + def get_function(self): + return self.function + class ParametricFunction(VMobject): CONFIG = {