diff --git a/animation/transform.py b/animation/transform.py index 55e466f7..095b4cf9 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -92,14 +92,19 @@ class ApplyMethod(Transform): **kwargs ) -class FadeOut(ApplyMethod): +class FadeOut(Transform): CONFIG = { "remover" : True, } def __init__(self, mobject, **kwargs): - ApplyMethod.__init__(self, mobject.fade, 1, **kwargs) + target = mobject.copy() + target.fade(1) + if isinstance(mobject, VMobject): + target.set_stroke(width = 0) + target.set_fill(opacity = 0) + Transform.__init__(self, mobject, target, **kwargs) - def cleanup(self): + def clean_up(self): self.update(0) class FadeIn(Transform): diff --git a/eola/chapter1.py b/eola/chapter1.py index 094b886f..1fc36631 100644 --- a/eola/chapter1.py +++ b/eola/chapter1.py @@ -25,6 +25,12 @@ from eola.chapter0 import UpcomingSeriesOfVidoes import random +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] + class Physicist(PiCreature): CONFIG = { "color" : PINK, @@ -319,7 +325,7 @@ class DifferentConceptions(Scene): v_array, TexMobject("+"), w_array, TexMobject("="), sum_array ) arrays.arrange_submobjects(RIGHT) - arrays.scale(0.5) + arrays.scale(0.75) arrays.to_edge(RIGHT).shift(UP) v_sym = TexMobject("\\vec{\\textbf{v}}") @@ -331,8 +337,7 @@ class DifferentConceptions(Scene): statement = TextMobject("We'll ignore him \\\\ for now") statement.highlight(PINK) statement.scale_to_fit_width(arrays.get_width()) - statement.next_to(arrays, DOWN, buff = 2) - arrow_to_mathy = Arrow(statement, mathy, color = PINK, buff = 0) + statement.next_to(arrays, DOWN, buff = 1.5) circle = Circle() circle.shift(syms.get_bottom()) @@ -349,7 +354,6 @@ class DifferentConceptions(Scene): self.play(Blink(mathy)) self.add_scaling(arrows, syms, arrays) self.play(Write(statement)) - self.play(ShowCreation(arrow_to_mathy, submobject_mode = "one_at_a_time")) self.play(ApplyMethod(mathy.change_mode, "sad")) self.dither() self.play( @@ -375,7 +379,7 @@ class DifferentConceptions(Scene): matrix_to_mobject(["2(3)", "2(-5)"]) ) s_arrays.arrange_submobjects(RIGHT) - s_arrays.scale(0.5) + s_arrays.scale(0.75) s_arrays.next_to(arrays, DOWN) s_syms = TexMobject(["2", "\\vec{\\textbf{v}}"]) @@ -797,7 +801,7 @@ class VectorAdditionNumerically(VectorScene): x_axis, y_axis = axes.split() v1 = self.add_vector([1, 2]) - coords1, x_line1, y_line1 = self.vector_to_coords(v1, cleanup = False) + coords1, x_line1, y_line1 = self.vector_to_coords(v1, clean_up = False) self.play(ApplyFunction( lambda m : m.next_to(y_axis, RIGHT).to_edge(UP), coords1 @@ -805,7 +809,7 @@ class VectorAdditionNumerically(VectorScene): plus.next_to(coords1, RIGHT) v2 = self.add_vector([3, -1], color = MAROON_B) - coords2, x_line2, y_line2 = self.vector_to_coords(v2, cleanup = False) + coords2, x_line2, y_line2 = self.vector_to_coords(v2, clean_up = False) self.dither() self.play( ApplyMethod(coords2.next_to, plus, RIGHT), @@ -1020,7 +1024,7 @@ class ScalingNumerically(VectorScene): equals = TexMobject("=") self.add_axes() v = self.add_vector([3, 1]) - v_coords, vx_line, vy_line = self.vector_to_coords(v, cleanup = False) + v_coords, vx_line, vy_line = self.vector_to_coords(v, clean_up = False) self.play(ApplyMethod(v_coords.to_edge, UP)) two_dot.next_to(v_coords, LEFT) equals.next_to(v_coords, RIGHT) @@ -1031,7 +1035,7 @@ class ScalingNumerically(VectorScene): Write(two_dot, run_time = 1) ) two_v_coords, two_v_x_line, two_v_y_line = self.vector_to_coords( - two_v, cleanup = False + two_v, clean_up = False ) self.play( ApplyMethod(two_v_coords.next_to, equals, RIGHT), @@ -1233,12 +1237,6 @@ class ManipulateSpace(LinearTransformationScene): pi_creature.shift(-pi_creature.get_corner(DOWN+LEFT)) self.plane.prepare_for_nonlinear_transform() - def 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] - self.play(ShowCreation( self.plane, submobject_mode = "one_at_a_time", @@ -1247,7 +1245,7 @@ class ManipulateSpace(LinearTransformationScene): self.play(FadeIn(pi_creature)) self.play(Blink(pi_creature)) self.plane.add(pi_creature) - self.play(Homotopy(homotopy, self.plane, run_time = 3)) + self.play(Homotopy(plane_wave_homotopy, self.plane, run_time = 3)) self.dither(2) self.apply_matrix([[2, 1], [1, 2]]) self.dither() diff --git a/eola/chapter2.py b/eola/chapter2.py index 26308307..2663411b 100644 --- a/eola/chapter2.py +++ b/eola/chapter2.py @@ -18,6 +18,7 @@ from mobject.tex_mobject import * from mobject.vectorized_mobject import * from eola.matrix import * +from eola.chapter1 import plane_wave_homotopy from eola.two_d_space import * class OpeningQuote(Scene): @@ -56,7 +57,8 @@ class CoordinatesAsScalars(VectorScene): } def construct(self): - self.axes = self.add_axes() + self.lock_in_dim_grid() + vector = self.add_vector(self.vector_coords) array, x_line, y_line = self.vector_to_coords(vector) self.add(array) @@ -127,7 +129,6 @@ class CoordinatesAsScalars(VectorScene): return new_array def scale_basis_vectors(self, new_array): - self.play(ApplyMethod(self.axes.highlight, GREY)) i_hat, j_hat = self.get_basis_vectors() self.add_vector(i_hat) i_hat_label = self.label_vector( @@ -193,7 +194,8 @@ class CoordinatesAsScalarsExample2(CoordinatesAsScalars): } def construct(self): - self.add_axes() + self.lock_in_dim_grid() + basis_vectors = self.get_basis_vectors() labels = self.get_basis_vector_labels() self.add(*basis_vectors) @@ -236,12 +238,15 @@ class ShowVaryingLinearCombinations(VectorScene): (-1, -1.5), (1, -1.13), (1.25, 0.5), - (-0.6, 1.3), + (-0.8, 1.3), ], - "finish_with_standard_basis_comparison" : True + "leave_sum_vector_copies" : False, + "start_with_non_sum_scaling" : True, + "finish_with_standard_basis_comparison" : True, + "finish_by_drawing_lines" : False, } def construct(self): - # self.add_axes() + self.lock_in_dim_grid() v1 = self.add_vector(self.vector1, color = self.vector1_color) v2 = self.add_vector(self.vector2, color = self.vector2_color) v1_label = self.label_vector( @@ -257,12 +262,16 @@ class ShowVaryingLinearCombinations(VectorScene): for v, label in (v1, v1_label), (v2, v2_label) ] scalar_anims = self.get_scalar_anims(v1, v2, v1_label, v2_label) + self.last_scalar_pair = (1, 1) - self.initial_scaling(v1, v2, label_anims, scalar_anims) + if self.start_with_non_sum_scaling: + self.initial_scaling(v1, v2, label_anims, scalar_anims) self.show_sum(v1, v2, label_anims, scalar_anims) self.scale_with_sum(v1, v2, label_anims, scalar_anims) if self.finish_with_standard_basis_comparison: - self.standard_basis_comparison(scalar_anims) + self.standard_basis_comparison(label_anims, scalar_anims) + if self.finish_by_drawing_lines: + self.draw_lines(v1, v2, label_anims, scalar_anims) def get_scalar_anims(self, v1, v2, v1_label, v2_label): def get_val_func(vect): @@ -320,13 +329,7 @@ class ShowVaryingLinearCombinations(VectorScene): self.dither() def scale_with_sum(self, v1, v2, label_anims, scalar_anims): - v2_anim = UpdateFromFunc( - v2, lambda m : m.shift(v1.get_end()-m.get_start()) - ) - sum_anim = UpdateFromFunc( - self.sum_vector, - lambda v : v.put_start_and_end_on(v1.get_start(), v2.get_end()) - ) + v2_anim, sum_anim = self.get_sum_animations(v1, v2) while self.scalar_pairs: scalar_pair = self.scalar_pairs.pop(0) anims = [ @@ -340,31 +343,65 @@ class ShowVaryingLinearCombinations(VectorScene): ] anims += [v2_anim, sum_anim] + label_anims + scalar_anims self.play(*anims, **{"run_time" : 2}) + if self.leave_sum_vector_copies: + self.add(self.sum_vector.copy()) self.dither() self.last_scalar_pair = scalar_pair - def standard_basis_comparison(self, scalar_anims): - everything = VMobject(*self.get_mobjects()) + def get_sum_animations(self, v1, v2): + v2_anim = UpdateFromFunc( + v2, lambda m : m.shift(v1.get_end()-m.get_start()) + ) + sum_anim = UpdateFromFunc( + self.sum_vector, + lambda v : v.put_start_and_end_on(v1.get_start(), v2.get_end()) + ) + return v2_anim, sum_anim + + def standard_basis_comparison(self, label_anims, scalar_anims): + everything = self.get_mobjects() + everything.remove(self.sum_vector) + everything = VMobject(*everything) alt_coords = [a.mobject for a in scalar_anims] - array = Matrix([m.copy() for m in alt_coords]) - array.scale(VECTOR_LABEL_SCALE_VAL) + array = Matrix([ + mob.copy().highlight(color) + for mob, color in zip( + alt_coords, + [self.vector1_color, self.vector2_color] + ) + ]) + array.scale(0.8) array.to_edge(UP) array.shift(RIGHT) brackets = array.get_brackets() - self.play(*[ + anims = [ Transform(*pair) for pair in zip(alt_coords, array.get_mob_matrix().flatten()) - ] + [Write(brackets)]) + ] + # anims += [ + # FadeOut(a.mobject) + # for a in label_anims + # ] + self.play(*anims + [Write(brackets)]) self.dither() self.remove(brackets, *alt_coords) self.add(array) - self.play(FadeOut(everything), Animation(self.sum_vector)) + self.play( + FadeOut(everything), + Animation(array), + ) self.add_axes(animate = True) ij_array, x_line, y_line = self.vector_to_coords( self.sum_vector, integer_labels = False ) + self.add(ij_array, x_line, y_line) + x, y = ij_array.get_mob_matrix().flatten() + self.play( + ApplyMethod(x.highlight, X_COLOR), + ApplyMethod(y.highlight, Y_COLOR), + ) neq = TexMobject("\\neq") neq.next_to(array) self.play( @@ -373,11 +410,869 @@ class ShowVaryingLinearCombinations(VectorScene): ) self.dither() - - - - - + def draw_lines(self, v1, v2, label_anims, scalar_anims): + sum_anims = self.get_sum_animations(v1, v2) + aux_anims = list(sum_anims) + label_anims + scalar_anims + + scale_val = 2 + for w1, w2 in (v2, v1), (v1, v2): + w1_vect = w1.get_end()-w1.get_start() + w2_vect = w2.get_end()-w2.get_start() + for num in scale_val, -1, -1./scale_val: + curr_tip = self.sum_vector.get_end() + line = Line(ORIGIN, curr_tip) + self.play( + ApplyMethod(w2.scale, num), + UpdateFromFunc( + line, lambda l : l.put_start_and_end_on(curr_tip, self.sum_vector.get_end()) + ), + *aux_anims + ) + self.add(line, v2) + self.dither() + + + +class AltShowVaryingLinearCombinations(ShowVaryingLinearCombinations): + CONFIG = { + "scalar_pairs" : [ + (1.5, 0.3), + (0.64, 1.3), + (-1, -1.5), + (1, 1.13), + (1.25, 0.5), + (-0.8, 1.14), + ], + "finish_with_standard_basis_comparison" : False + } + + +class NameLinearCombinations(Scene): + def construct(self): + v_color = MAROON_C + w_color = BLUE + words = TextMobject([ + "``Linear combination'' of", + "$\\vec{\\textbf{v}}$", + "and", + "$\\vec{\\textbf{w}}$" + ]) + words.split()[1].highlight(v_color) + words.split()[3].highlight(w_color) + words.scale_to_fit_width(2*SPACE_WIDTH - 1) + words.to_edge(UP) + + equation = TexMobject([ + "a", "\\vec{\\textbf{v}}", "+", "b", "\\vec{\\textbf{w}}" + ]) + equation.arrange_submobjects(buff = 0.1, aligned_edge = DOWN) + equation.split()[1].highlight(v_color) + equation.split()[4].highlight(w_color) + a, b = np.array(equation.split())[[0, 3]] + equation.scale(2) + equation.next_to(words, DOWN, buff = 1) + + scalars_word = TextMobject("Scalars") + scalars_word.scale(1.5) + scalars_word.next_to(equation, DOWN, buff = 2) + arrows = [ + Arrow(scalars_word, letter) + for letter in a, b + ] + + self.add(equation) + self.play(Write(words)) + self.play( + ShowCreation(VMobject(*arrows)), + Write(scalars_word) + ) + self.dither(2) + + +class LinearCombinationsDrawLines(ShowVaryingLinearCombinations): + CONFIG = { + "scalar_pairs" : [ + (1.5, 0.6), + (0.7, 1.3), + (1, 1), + ], + "start_with_non_sum_scaling" : False, + "finish_with_standard_basis_comparison" : False, + "finish_by_drawing_lines" : True, + } + + +class LinearCombinationsWithSumCopies(ShowVaryingLinearCombinations): + CONFIG = { + "scalar_pairs" : [ + (1.5, 0.6), + (0.7, 1.3), + (-1, -1.5), + (1, -1.13), + (1.25, 0.5), + (-0.8, 1.3), + (-0.9, 1.4), + (0.9, 2), + ], + "leave_sum_vector_copies" : True, + "start_with_non_sum_scaling" : False, + "finish_with_standard_basis_comparison" : False, + "finish_by_drawing_lines" : False, + } + + + +class LinearDependentVectors(ShowVaryingLinearCombinations): + CONFIG = { + "vector1" : [1, 2], + "vector2" : [0.5, 1], + "vector1_color" : MAROON_C, + "vector2_color" : BLUE, + "vector1_label" : "v", + "vector2_label" : "w", + "sum_color" : PINK, + "scalar_pairs" : [ + (1.5, 0.6), + (0.7, 1.3), + (-1, -1.5), + (1.25, 0.5), + (-0.8, 1.3), + (-0.9, 1.4), + (0.9, 2), + ], + "leave_sum_vector_copies" : False, + "start_with_non_sum_scaling" : False, + "finish_with_standard_basis_comparison" : False, + "finish_by_drawing_lines" : False, + } + + def get_sum_animations(self, v1, v2): + v2_anim, sum_anim = ShowVaryingLinearCombinations.get_sum_animations(self, v1, v2) + self.remove(self.sum_vector) + return v2_anim, Animation(VMobject()) + +class WhenVectorsLineUp(LinearDependentVectors): + CONFIG = { + "vector1" : [2, 1], + "vector2" : [1, 0.5], + "scalar_pairs" : [ + (1.5, 0.6), + (0.7, 1.3), + ], + } + +class AnimationUnderSpanDefinition(ShowVaryingLinearCombinations): + CONFIG = { + "scalar_pairs" : [ + (1.5, 0.6), + (0.7, 1.3), + (-1, -1.5), + (1.25, 0.5), + (0.8, 1.3), + (0.93, -1.4), + (-2, -0.5), + ], + "leave_sum_vector_copies" : True, + "start_with_non_sum_scaling" : False, + "finish_with_standard_basis_comparison" : False, + "finish_by_drawing_lines" : False, + } + + +class BothVectorsCouldBeZero(VectorScene): + def construct(self): + plane = self.add_plane() + plane.fade(0.7) + v1 = self.add_vector([1, 2], color = MAROON_C) + v2 = self.add_vector([3, -1], color = BLUE) + self.play(Transform(v1, Dot(ORIGIN))) + self.play(Transform(v2, Dot(ORIGIN))) + self.dither() + + +class DefineSpan(Scene): + def construct(self): + v_color = MAROON_C + w_color = BLUE + + definition = TextMobject(""" + The ``span'' of $\\vec{\\textbf{v}}$ and + $\\vec{\\textbf{w}}$ is the \\\\ set of all their + linear combinations. + """) + definition.scale_to_fit_width(2*SPACE_WIDTH-1) + definition.to_edge(UP) + def_mobs = np.array(definition.split()) + VMobject(*def_mobs[4:4+4]).highlight(PINK) + VMobject(*def_mobs[11:11+2]).highlight(v_color) + VMobject(*def_mobs[16:16+2]).highlight(w_color) + VMobject(*def_mobs[-19:-1]).highlight(YELLOW) + + equation = TexMobject([ + "a", "\\vec{\\textbf{v}}", "+", "b", "\\vec{\\textbf{w}}" + ]) + equation.arrange_submobjects(buff = 0.1, aligned_edge = DOWN) + equation.split()[1].highlight(v_color) + equation.split()[4].highlight(w_color) + a, b = np.array(equation.split())[[0, 3]] + equation.scale(2) + equation.next_to(definition, DOWN, buff = 1) + + vary_words = TextMobject( + "Let $a$ and $b$ vary \\\\ over all real numbers" + ) + vary_words.scale(1.5) + vary_words.next_to(equation, DOWN, buff = 2) + arrows = [ + Arrow(vary_words, letter) + for letter in a, b + ] + + self.play(Write(definition)) + self.play(Write(equation)) + self.dither() + self.play( + FadeIn(vary_words), + ShowCreation(VMobject(*arrows)) + ) + self.dither() + + +class VectorsVsPoints(Scene): + def construct(self): + self.play(Write("Vectors vs. Points")) + self.dither(2) + + +class VectorsToDotsScene(VectorScene): + CONFIG = { + "num_vectors" : 16, + "start_color" : PINK, + "end_color" : BLUE_E, + } + def construct(self): + self.lock_in_dim_grid() + + vectors = self.get_vectors() + colors = Color(self.start_color).range_to( + self.end_color, len(vectors) + ) + for vect, color in zip(vectors, colors): + vect.highlight(color) + prototype_vector = vectors[3*len(vectors)/4] + + vector_group = VMobject(*vectors) + self.play( + ShowCreation( + vector_group, + submobject_mode = "one_at_a_time", + run_time = 3 + ) + ) + vectors.sort(lambda v1, v2 : int(np.sign(v2.get_length() - v1.get_length()))) + self.add(*vectors) + def v_to_dot(vector): + return Dot(vector.get_end(), fill_color = vector.get_stroke_color()) + self.dither() + vectors.remove(prototype_vector) + self.play(*map(FadeOut, vectors)+[Animation(prototype_vector)]) + self.remove(vector_group) + self.add(prototype_vector) + self.dither() + self.play(Transform(prototype_vector, v_to_dot(prototype_vector))) + self.dither() + self.play(*map(FadeIn, vectors) + [Animation(prototype_vector)]) + rate_functions = [ + squish_rate_func(smooth, float(x)/(len(vectors)+2), 1) + for x in range(len(vectors)) + ] + self.play(*[ + Transform(v, v_to_dot(v), rate_func = rf, run_time = 2) + for v, rf in zip(vectors, rate_functions) + ]) + self.dither() + self.remove(prototype_vector) + self.play_final_animation(vectors, rate_functions) + self.dither() + + def get_vectors(self): + raise Exception("Not implemented") + + def play_final_animation(self, vectors, rate_functions): + raise Exception("Not implemented") + + +class VectorsOnALine(VectorsToDotsScene): + def get_vectors(self): + return [ + Vector(a*np.array([1.5, 1])) + for a in np.linspace( + -SPACE_HEIGHT, SPACE_HEIGHT, self.num_vectors + ) + ] + + def play_final_animation(self, vectors, rate_functions): + line_copies = [ + Line(vectors[0].get_end(), vectors[-1].get_end()) + for v in vectors + ] + self.play(*[ + Transform(v, mob, rate_func = rf, run_time = 2) + for v, mob, rf in zip(vectors, line_copies, rate_functions) + ]) + + +class VectorsInThePlane(VectorsToDotsScene): + CONFIG = { + "num_vectors" : 16, + "start_color" : PINK, + "end_color" : BLUE_E, + } + def get_vectors(self): + return [ + 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) + ] + + def play_final_animation(self, vectors, rate_functions): + h_line = Line( + SPACE_WIDTH*RIGHT, SPACE_WIDTH*LEFT, + stroke_width = 0.5, + color = BLUE_E + ) + v_line = Line( + SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN, + stroke_width = 0.5, + color = BLUE_E + ) + line_pairs = [ + VMobject(h_line.copy().shift(y), v_line.copy().shift(x)) + for v in vectors + for x, y, z in [v.get_center()] + ] + plane = NumberPlane() + self.play( + ShowCreation(plane), + *[ + Transform(v, p, rate_func = rf) + for v, p, rf in zip(vectors, line_pairs, rate_functions) + ] + ) + self.remove(*vectors) + self.dither() + self.play(Homotopy(plane_wave_homotopy, plane, run_time = 3)) + self.play(Transform(plane, NumberPlane(), rate_func = rush_from)) + self.dither() + + +class HowToThinkVectorsVsPoint(Scene): + def construct(self): + randy = Randolph().to_corner() + bubble = randy.get_bubble(height = 3.8) + text1 = TextMobject("Think of individual vectors as arrows") + text2 = TextMobject("Think of sets of vectors as points") + for text in text1, text2: + text.to_edge(UP) + + single_vector = Vector([2, 1]) + vector_group = VMobject(*[ + Vector([x, 1]) + for x in np.linspace(-2, 2, 5) + ]) + bubble.position_mobject_inside(single_vector) + bubble.position_mobject_inside(vector_group) + dots = VMobject(*[ + Dot(v.get_end()) + for v in vector_group.split() + ]) + + self.add(randy) + self.play( + ApplyMethod(randy.change_mode, "pondering"), + ShowCreation(bubble) + ) + self.play(FadeIn(text1)) + self.play(ShowCreation(single_vector)) + self.dither(3) + self.play( + Transform(text1, text2), + Transform(single_vector, vector_group) + ) + self.remove(single_vector) + self.add(vector_group) + self.dither() + self.play(Transform(vector_group, dots)) + self.dither() + + +class IntroduceThreeDSpan(Scene): + pass + +class AskAboutThreeDSpan(Scene): + def construct(self): + self.play(Write("What does the span of two 3d vectors look like?")) + self.dither(2) + +class ThreeDVectorSpan(Scene): + pass + + +class LinearCombinationOfThreeVectors(Scene): + pass + +class VaryingLinearCombinationOfThreeVectors(Scene): + pass + +class LinearCombinationOfThreeVectorsText(Scene): + def construct(self): + text = TextMobject(""" + Linear combination of + $\\vec{\\textbf{v}}$, + $\\vec{\\textbf{w}}$, and + $\\vec{\\textbf{u}}$: + """) + VMobject(*text.split()[-12:-10]).highlight(MAROON_C) + VMobject(*text.split()[-9:-7]).highlight(BLUE) + VMobject(*text.split()[-3:-1]).highlight(RED_C) + VMobject(*text.split()[:17]).highlight(GREEN) + text.scale_to_fit_width(2*SPACE_WIDTH - 1) + text.to_edge(UP) + + equation = TextMobject("""$ + a\\vec{\\textbf{v}} + + b\\vec{\\textbf{w}} + + c\\vec{\\textbf{u}} + $""") + VMobject(*equation.split()[-10:-8]).highlight(MAROON_C) + VMobject(*equation.split()[-6:-4]).highlight(BLUE) + VMobject(*equation.split()[-2:]).highlight(RED_C) + + a, b, c = np.array(equation.split())[[0, 4, 8]] + + equation.scale(1.5) + equation.next_to(text, DOWN, buff = 1) + + span_comment = TextMobject("For span, let these constants vary") + span_comment.scale(1.5) + span_comment.next_to(equation, DOWN, buff = 2) + VMobject(*span_comment.split()[3:7]).highlight(YELLOW) + arrows = VMobject(*[ + Arrow(span_comment, var) + for var in a, b, c + ]) + + self.play(Write(text)) + self.play(Write(equation)) + self.dither() + self.play( + ShowCreation(arrows, submobject_mode = "one_at_a_time"), + Write(span_comment) + ) + self.dither() + + +class ThirdVectorOnSpanOfFirstTwo(Scene): + pass + +class ThirdVectorOutsideSpanOfFirstTwo(Scene): + pass + +class SpanCasesWords(Scene): + def construct(self): + words1 = TextMobject(""" + Case 1: $\\vec{\\textbf{u}}$ is in the span of + $\\vec{\\textbf{v}}$ and $\\vec{\\textbf{u}}$ + """) + VMobject(*words1.split()[6:8]).highlight(RED_C) + VMobject(*words1.split()[-7:-5]).highlight(MAROON_C) + VMobject(*words1.split()[-2:]).highlight(BLUE) + + words2 = TextMobject(""" + Case 2: $\\vec{\\textbf{u}}$ is not in the span of + $\\vec{\\textbf{v}}$ and $\\vec{\\textbf{u}}$ + """) + VMobject(*words2.split()[6:8]).highlight(RED_C) + VMobject(*words2.split()[-7:-5]).highlight(MAROON_C) + VMobject(*words2.split()[-2:]).highlight(BLUE) + VMobject(*words2.split()[10:13]).highlight(RED) + + for words in words1, words2: + words.scale_to_fit_width(2*SPACE_WIDTH - 1) + self.play(Write(words1)) + self.dither() + self.play(Transform(words1, words2)) + self.dither() + + + +class LinearDependentWords(Scene): + def construct(self): + words1 = TextMobject([ + "$\\vec{\\textbf{v}}$", + "and", + "$\\vec{\\textbf{w}}$", + "are", + "``Linearly dependent'' ", + ]) + v, _and, w, are, rest = words1.split() + v.highlight(MAROON_C) + w.highlight(BLUE) + rest.highlight(YELLOW) + + words2 = TextMobject([ + "$\\vec{\\textbf{v}}$,", + "$\\vec{\\textbf{w}}$", + "and", + "$\\vec{\\textbf{u}}$", + "are", + "``Linearly dependent'' ", + ]) + v, w, _and, u, are, rest = words2.split() + v.highlight(MAROON_C) + w.highlight(BLUE) + u.highlight(RED_C) + rest.highlight(YELLOW) + + for words in words1, words2: + words.scale_to_fit_width(2*SPACE_WIDTH - 1) + + self.play(Write(words1)) + self.dither() + self.play(Transform(words1, words2)) + self.dither() + + +class LinearDependentEquations(Scene): + def construct(self): + title = TextMobject("``Linearly dependent'' ") + title.highlight(YELLOW) + title.scale(2) + title.to_edge(UP) + self.add(title) + + equation1 = TexMobject([ + "\\vec{\\textbf{w}}", + "=", + "a", + "\\vec{\\textbf{v}}", + ]) + w, eq, a, v = equation1.split() + w.highlight(BLUE) + v.highlight(MAROON_C) + equation1.scale(2) + eq1_copy = equation1.copy() + + low_words1 = TextMobject("For some value of $a$") + low_words1.scale(2) + low_words1.to_edge(DOWN) + arrow = Arrow(low_words1, a) + arrow_copy = arrow.copy() + + equation2 = TexMobject([ + "\\vec{\\textbf{u}}", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + ]) + u, eq, a, v, plus, b, w = equation2.split() + u.highlight(RED) + w.highlight(BLUE) + v.highlight(MAROON_C) + equation2.scale(2) + eq2_copy = equation2.copy() + + low_words2 = TextMobject("For some values of a and b") + low_words2.scale(2) + low_words2.to_edge(DOWN) + arrows = VMobject(*[ + Arrow(low_words2, var) + for var in a, b + ]) + + self.play(Write(equation1)) + self.play( + ShowCreation(arrow), + Write(low_words1) + ) + self.dither() + + self.play( + Transform(equation1, equation2), + Transform(low_words1, low_words2), + Transform(arrow, arrows) + ) + self.dither(2) + + new_title = TextMobject("``Linearly independent'' ") + new_title.highlight(GREEN) + new_title.replace(title) + + for eq_copy in eq1_copy, eq2_copy: + neq = TexMobject("\\neq") + neq.replace(eq_copy.submobjects[1]) + eq_copy.submobjects[1] = neq + + new_low_words1 = TextMobject(["For", "all", "values of a"]) + new_low_words2 = TextMobject(["For", "all", "values of a and b"]) + for low_words in new_low_words1, new_low_words2: + low_words.split()[1].highlight(GREEN) + low_words.scale(2) + low_words.to_edge(DOWN) + + self.play( + Transform(title, new_title), + Transform(equation1, eq1_copy), + Transform(arrow, arrow_copy), + Transform(low_words1, new_low_words1) + ) + self.dither() + self.play( + Transform(equation1, eq2_copy), + Transform(arrow, arrows), + Transform(low_words1, new_low_words2) + ) + self.dither() + + + +class AlternateDefOfLinearlyDependent(Scene): + def construct(self): + title1 = TextMobject([ + "$\\vec{\\textbf{v}}$,", + "$\\vec{\\textbf{w}}$", + "and", + "$\\vec{\\textbf{u}}$", + "are", + "linearly dependent", + "if", + ]) + title2 = TextMobject([ + "$\\vec{\\textbf{v}}$,", + "$\\vec{\\textbf{w}}$", + "and", + "$\\vec{\\textbf{u}}$", + "are", + "linearly independent", + "if", + ]) + for title in title1, title2: + v, w, _and, u, are, ld, _if = title.split() + v.highlight(MAROON_C) + w.highlight(BLUE) + u.highlight(RED_C) + title.to_edge(UP) + title1.split()[-2].highlight(YELLOW) + title2.split()[-2].highlight(GREEN) + + subtitle = TextMobject("the only solution to") + subtitle.next_to(title2, DOWN, aligned_edge = LEFT) + + self.add(title1) + + equations = self.get_equations() + added_words1 = TextMobject( + "where at least one of $a$, $b$ and $c$ is not $0$" + ) + added_words2 = TextMobject( + "is a = b = c = 0" + ) + + scalar_specification = TextMobject( + "For some choice of $a$ and $b$" + ) + scalar_specification.shift(1.5*DOWN) + scalar_specification.add(*[ + Arrow(scalar_specification, equations[0].split()[i]) + for i in 2, 5 + ]) + + brace = Brace(VMobject(*equations[2].split()[2:])) + brace_words = TextMobject("Linear combination") + brace_words.next_to(brace, DOWN) + + equation = equations[0] + for added_words in added_words1, added_words2: + added_words.next_to(title, DOWN, buff = 3.5, aligned_edge = LEFT) + self.play(Write(equation)) + for i, new_eq in enumerate(equations): + if i == 0: + self.play(FadeIn(scalar_specification)) + self.dither(2) + self.play(FadeOut(scalar_specification)) + elif i == 3: + self.play( + GrowFromCenter(brace), + Write(brace_words) + ) + self.dither(3) + self.play(FadeOut(brace), FadeOut(brace_words)) + self.play(Transform( + equation, new_eq, + path_arc = (np.pi/2 if i == 1 else 0) + )) + self.dither(3) + self.play(Write(added_words1)) + self.dither(2) + self.play( + Transform(title1, title2), + Write(subtitle), + Transform(added_words1, added_words2), + ) + self.dither(3) + everything = VMobject(*self.get_mobjects()) + self.play(ApplyFunction( + lambda m : m.scale(0.5).to_corner(UP+LEFT), + everything + )) + self.dither() + + + def get_equations(self): + equation1 = TexMobject([ + "\\vec{\\textbf{u}}", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + ]) + u = equation1.split()[0] + equation1.submobjects = list(it.chain( + [VectorizedPoint(u.get_center())], + equation1.submobjects[1:], + [VectorizedPoint(u.get_left())], + [u] + )) + equation2 = TexMobject([ + "\\left[\\begin{array}{c} 0 \\\\ 0 \\\\ 0 \\end{array} \\right]", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + "-", + "\\vec{\\textbf{u}}", + ]) + equation3 = TexMobject([ + "\\vec{\\textbf{0}}", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + "-", + "\\vec{\\textbf{u}}", + ]) + equation4 = TexMobject([ + "\\vec{\\textbf{0}}", + "=", + "0", + "\\vec{\\textbf{v}}", + "+", + "0", + "\\vec{\\textbf{w}}", + "+0", + "\\vec{\\textbf{u}}", + ]) + equation5 = TexMobject([ + "\\vec{\\textbf{0}}", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + "+(-1)", + "\\vec{\\textbf{u}}", + ]) + equation5.split()[-2].highlight(YELLOW) + equation6 = TexMobject([ + "\\vec{\\textbf{0}}", + "=", + "a", + "\\vec{\\textbf{v}}", + "+", + "b", + "\\vec{\\textbf{w}}", + "+c", + "\\vec{\\textbf{u}}", + ]) + result = [equation1, equation2, equation3, equation4, equation5, equation6] + for eq in result: + eq.split()[3].highlight(MAROON_C) + eq.split()[6].highlight(BLUE) + eq.split()[-1].highlight(RED_C) + eq.scale(1.5) + eq.shift(UP) + return result + + + +class MathematiciansLikeToConfuse(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says(""" + We wouldn't want things to \\\\ + be \\emph{understandable} would we? + """) + modes = "pondering", "sassy", "confused" + self.play(*[ + ApplyMethod(student.change_mode, mode) + for student, mode in zip(self.get_students(), modes) + ]) + self.dither(2) + +class CheckYourUnderstanding(TeacherStudentsScene): + def construct(self): + self.setup() + self.teacher_says("Quiz time!") + self.random_blink() + self.dither() + self.random_blink() + + + +class TechnicalDefinitionOfBasis(Scene): + def construct(self): + title = TextMobject("Technical definition of basis:") + title.to_edge(UP) + definition = TextMobject([ + "The", + "basis", + "of a vector space is a set of", + "linearly independent", + "vectors that", + "span", + "the full space", + ]) + t, b, oavsiaso, li, vt, s, tfs = definition.split() + b.highlight(BLUE) + li.highlight(GREEN) + s.highlight(YELLOW) + definition.scale_to_fit_width(2*SPACE_WIDTH-1) + + self.add(title) + self.play(Write(definition)) + self.dither() + +class NextVideo(Scene): + def construct(self): + title = TextMobject("Next video: Matrices as linear transformations") + title.to_edge(UP) + rect = Rectangle(width = 16, height = 9, color = BLUE) + rect.scale_to_fit_height(6) + rect.next_to(title, DOWN) + + self.add(title) + self.play(ShowCreation(rect)) + self.dither() diff --git a/eola/two_d_space.py b/eola/two_d_space.py index be6df19f..7ac827a7 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -127,6 +127,15 @@ class VectorScene(Scene): self.add(axes) return axes + def lock_in_dim_grid(self, dimness = 0.7, axes_dimness = 0.5): + plane = self.add_plane() + axes = plane.get_axes() + plane.fade(dimness) + axes.highlight(WHITE) + axes.fade(axes_dimness) + self.add(axes) + self.freeze_background() + def add_vector(self, vector, animate = True, color = YELLOW): if not isinstance(vector, Arrow): vector = Vector(vector, color = color) @@ -202,7 +211,7 @@ class VectorScene(Scene): y_coord.highlight(Y_COLOR) return y_coord - def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, cleanup = True): + def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, clean_up = True): starting_mobjects = list(self.mobjects) array = Matrix(vector) array.shift(coords_start) @@ -231,11 +240,11 @@ class VectorScene(Scene): self.play(ShowCreation(y_line)) self.play(ShowCreation(arrow)) self.dither() - if cleanup: + if clean_up: self.clear() self.add(*starting_mobjects) - def vector_to_coords(self, vector, integer_labels = True, cleanup = True): + def vector_to_coords(self, vector, integer_labels = True, clean_up = True): starting_mobjects = list(self.mobjects) show_creation = False if isinstance(vector, Arrow): @@ -281,7 +290,7 @@ class VectorScene(Scene): self.remove(x_coord_start, y_coord_start, brackets) self.add(array) - if cleanup: + if clean_up: self.clear() self.add(*starting_mobjects) return array, x_line, y_line diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 285dcf9b..dd94cb52 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -31,8 +31,7 @@ class TexMobject(SVGMobject): "fill_opacity" : 1.0, "fill_color" : WHITE, "should_center" : True, - "next_to_direction" : RIGHT, - "next_to_buff" : 0.25, + "separate_list_arg_with_spaces" : True, "initial_scale_val" : TEX_MOB_SCALE_VAL, "organize_left_to_right" : False, "propogate_style_to_family" : True, @@ -50,31 +49,29 @@ class TexMobject(SVGMobject): return TexSymbol(path_string) - def generate_points(self): - if isinstance(self.expression, list): - self.handle_list_expression() - else: - self.svg_file = tex_to_svg_file( - "".join(self.expression), - self.template_tex_file - ) - SVGMobject.generate_points(self) + def generate_points(self): + is_list = isinstance(self.expression, list) + separator = "" + if is_list and self.separate_list_arg_with_spaces: + separator = " " + expression = separator.join(self.expression) + self.svg_file = tex_to_svg_file(expression, self.template_tex_file) + SVGMobject.generate_points(self) + if is_list: + self.handle_list_expression(self.expression) - def handle_list_expression(self): - #TODO, next_to not sufficient? - subs = [ - TexMobject(expr) - for expr in self.expression - ] - self.initial_scale_val = 1 - for sm1, sm2 in zip(subs, subs[1:]): - sm2.next_to( - sm1, - self.next_to_direction, - buff = self.next_to_buff - ) - self.submobjects = subs + def handle_list_expression(self, list_expression): + new_submobjects = [] + curr_index = 0 + for expr in list_expression: + model = TexMobject(expr, **self.CONFIG) + new_index = curr_index + len(model.submobjects) + new_submobjects.append(VMobject( + *self.submobjects[curr_index:new_index] + )) + curr_index = new_index + self.submobjects = new_submobjects return self def organize_submobjects_left_to_right(self): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 47df0a32..00a640eb 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -93,8 +93,7 @@ class VMobject(Mobject): def get_stroke_color(self): try: - self.stroke_rgb[self.stroke_rgb<0] = 0 - self.stroke_rgb[self.stroke_rgb>1] = 1 + self.stroke_rgb = np.clip(self.stroke_rgb, 0, 1) return Color(rgb = self.stroke_rgb) except: return Color(WHITE) diff --git a/scene/scene.py b/scene/scene.py index 25a4f6bc..189053c6 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -104,7 +104,12 @@ class Scene(object): mobjects = list(it.chain(*[m.submobject_family() for m in mobjects])) if len(mobjects) == 0: return - self.mobjects = filter(lambda m : m not in mobjects, self.mobjects) + self.mobjects = filter( + lambda m : not set( + m.family_members_with_points() + ).issubset(mobjects), + self.mobjects + ) return self def bring_to_front(self, mobject): diff --git a/topics/numerals.py b/topics/numerals.py index 750eb438..26769b1d 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -1,12 +1,13 @@ +from mobject.vectorized_mobject import VMobject from mobject.tex_mobject import TexMobject from animation import Animation from helpers import * -class DecimalNumber(TexMobject): +class DecimalNumber(VMobject): CONFIG = { "num_decimal_points" : 2, "digit_to_digit_buff" : 0.05 @@ -14,7 +15,10 @@ class DecimalNumber(TexMobject): def __init__(self, float_num, **kwargs): digest_config(self, kwargs) num_string = '%.*f' % (self.num_decimal_points, float_num) - TexMobject.__init__(self, list(num_string)) + VMobject.__init__(self, *[ + TexMobject(char) + for char in num_string + ], **kwargs) self.arrange_submobjects( buff = self.digit_to_digit_buff, aligned_edge = DOWN