diff --git a/constants.py b/constants.py index b0417208..843e5f88 100644 --- a/constants.py +++ b/constants.py @@ -29,8 +29,12 @@ SPACE_HEIGHT = 4.0 SPACE_WIDTH = SPACE_HEIGHT * DEFAULT_WIDTH / DEFAULT_HEIGHT -DEFAULT_MOBJECT_TO_EDGE_BUFFER = 0.5 -DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = 0.2 +SMALL_BUFF = 0.1 +MED_BUFF = 0.5 +LARGE_BUFF = 1 + +DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_BUFF +DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_BUFF #All in seconds diff --git a/eola/chapter7.py b/eola/chapter7.py new file mode 100644 index 00000000..4d57b78e --- /dev/null +++ b/eola/chapter7.py @@ -0,0 +1,69 @@ +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( + "\\small Calvin:", + "You know, I don't think math is a science, I think it's a religion.", + "\\\\Hobbes:", + "A religion?", + "\\\\Calvin:" , + "Yeah. All these equations are like miracles." + "You take two numbers and when you add them, " + "they magically become one NEW number!" + ) + words.scale_to_fit_width(2*SPACE_WIDTH - 1) + words.to_edge(UP) + words[0].highlight(YELLOW) + words[2].highlight("#fd9c2b") + words[4].highlight(YELLOW) + + self.play(FadeIn(words)) + self.dither(2) + +class TraditionalOrdering(RandolphScene): + def construct(self): + title = TextMobject("Traditional ordering:") + title.highlight(YELLOW).to_edge(UP) + topics = VMobject(*map(TextMobject, [ + "Topic 1: Vectors", + "Topic 2: Dot product", + "\\vdots", + "(everything else)", + "\\vdots", + ])) + topics.arrange_submobjects(DOWN) + topics.next_to(title, DOWN) + + self.play( + Write(title), + FadeIn(topics, submobject_mode = "lagged_start") + ) + self.play(topics[1].highlight, PINK) + self.dither() + +class ThisSeriesOrdering(RandolphScene): + def construct(self): + pass \ No newline at end of file diff --git a/eola/footnote2.py b/eola/footnote2.py index 265a1433..ad6f010b 100644 --- a/eola/footnote2.py +++ b/eola/footnote2.py @@ -63,9 +63,14 @@ class ColumnsRepresentBasisVectors(Scene): i_hat_words.next_to(ORIGIN, LEFT).to_edge(UP) j_hat_words.highlight(Y_COLOR) j_hat_words.next_to(ORIGIN, RIGHT).to_edge(UP) + question = TextMobject("How to interpret?") + question.next_to(matrix, UP) + question.highlight(YELLOW) self.add(matrix) + self.play(Write(question, run_time = 2)) self.dither() + self.play(FadeOut(question)) for i, words in enumerate([i_hat_words, j_hat_words]): arrow = Arrow( words.get_bottom(), @@ -80,6 +85,557 @@ class ColumnsRepresentBasisVectors(Scene): for m in matrix.get_mob_matrix()[:,i] ] ) + self.dither(2) + self.put_in_thought_bubble() + + def put_in_thought_bubble(self): + everything = VMobject(*self.get_mobjects()) + randy = Randolph().to_corner() + bubble = randy.get_bubble() + + self.play(FadeIn(randy)) + self.play( + ApplyFunction( + lambda m : bubble.position_mobject_inside( + m.scale_to_fit_height(2.5) + ), + everything + ), + ShowCreation(bubble), + randy.change_mode, "pondering" + ) + self.play(Blink(randy)) + self.dither() + self.play(randy.change_mode, "surprised") + self.dither() + +class Symbolic2To3DTransform(Scene): + def construct(self): + func = TexMobject("L(", "\\vec{\\textbf{v}}", ")") + input_array = Matrix([2, 7]) + input_array.highlight(YELLOW) + in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color()) + func[1].highlight(input_array.get_color()) + output_array = Matrix([1, 8, 2]) + output_array.highlight(PINK) + out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color()) + VMobject( + input_array, in_arrow, func, out_arrow, output_array + ).arrange_submobjects(RIGHT, buff = SMALL_BUFF) + + input_brace = Brace(input_array, DOWN) + input_words = input_brace.get_text("2d input") + output_brace = Brace(output_array, UP) + output_words = output_brace.get_text("3d output") + input_words.highlight(input_array.get_color()) + output_words.highlight(output_array.get_color()) + + + self.add(func, input_array) + self.play( + GrowFromCenter(input_brace), + Write(input_words) + ) + mover = input_array.copy() + self.play( + Transform(mover, Dot().move_to(func)), + ShowCreation(in_arrow), + rate_func = rush_into + ) + self.play( + Transform(mover, output_array), + ShowCreation(out_arrow), + rate_func = rush_from + ) + self.play( + GrowFromCenter(output_brace), + Write(output_words) + ) + self.dither() + +class PlaneStartState(LinearTransformationScene): + def construct(self): + self.add_title("Input space") + labels = self.get_basis_vector_labels() + self.play(*map(Write, labels)) + self.dither() + +class OutputIn3dWords(Scene): + def construct(self): + words = TextMobject("Output in 3d") + words.scale(1.5) + self.play(Write(words)) + self.dither() + +class OutputIn3d(Scene): + pass + +class ShowSideBySide2dTo3d(Scene): + pass + +class AnimationLaziness(Scene): + def construct(self): + self.add(TextMobject("But there is some animation laziness...")) + +class DescribeColumnsInSpecificTransformation(Scene): + def construct(self): + matrix = Matrix([ + [2, 0], + [-1, 1], + [-2, 1], + ]) + matrix.highlight_columns(X_COLOR, Y_COLOR) + mob_matrix = matrix.get_mob_matrix() + i_col, j_col = [VMobject(*mob_matrix[:,i]) for i in 0, 1] + for col, char, vect in zip([i_col, j_col], ["i", "j"], [UP, DOWN]): + color = col[0].get_color() + col.words = TextMobject("Where $\\hat\\%smath$ lands"%char) + col.words.next_to(matrix, vect, buff = LARGE_BUFF) + col.words.highlight(color) + col.arrow = Arrow( + col.words.get_edge_center(-vect), + col.get_edge_center(vect), + color = color + ) + + self.play(Write(matrix.get_brackets())) + self.dither() + for col in i_col, j_col: + self.play( + Write(col), + ShowCreation(col.arrow), + Write(col.words, run_time = 1) + ) + self.dither() + +class CountRowsAndColumns(Scene): + def construct(self): + matrix = Matrix([ + [2, 0], + [-1, 1], + [-2, 1], + ]) + matrix.highlight_columns(X_COLOR, Y_COLOR) + rows_brace = Brace(matrix, LEFT) + rows_words = rows_brace.get_text("3", "rows") + rows_words.highlight(PINK) + cols_brace = Brace(matrix, UP) + cols_words = cols_brace.get_text("2", "columns") + cols_words.highlight(TEAL) + title = TexMobject("3", "\\times", "2", "\\text{ matrix}") + title.to_edge(UP) + + self.add(matrix) + self.play( + GrowFromCenter(rows_brace), + Write(rows_words, run_time = 2) + ) + self.play( + GrowFromCenter(cols_brace), + Write(cols_words, run_time = 2) + ) + self.dither() + self.play( + rows_words[0].copy().move_to, title[0], + cols_words[0].copy().move_to, title[2], + Write(VMobject(title[1], title[3])) + ) + self.dither() + +class WriteColumnSpaceDefinition(Scene): + def construct(self): + matrix = Matrix([ + [2, 0], + [-1, 1], + [-2, 1], + ]) + matrix.highlight_columns(X_COLOR, Y_COLOR) + + brace = Brace(matrix) + words = VMobject( + TextMobject("Span", "of columns"), + TexMobject("\\Updownarrow"), + TextMobject("``Column space''") + ) + words.arrange_submobjects(DOWN, buff = 0.1) + words.next_to(brace, DOWN) + words[0][0].highlight(PINK) + words[2].highlight(TEAL) + words[0].add_background_rectangle() + words[2].add_background_rectangle() + VMobject(matrix, brace, words).center() + + self.add(matrix) + self.play( + GrowFromCenter(brace), + Write(words, run_time = 2) + ) + self.dither() + +class MatrixInTheWild(Scene): + def construct(self): + randy = Randolph(color = PINK) + randy.look(LEFT) + randy.to_corner() + matrix = Matrix([ + [3, 1], + [4, 1], + [5, 9], + ]) + matrix.next_to(randy, RIGHT, buff = LARGE_BUFF, aligned_edge = DOWN) + bubble = randy.get_bubble(height = 4) + bubble.make_green_screen() + VMobject(randy, bubble, matrix).to_corner(UP+LEFT, buff = MED_BUFF) + + self.add(randy) + self.play(Write(matrix)) + self.play(randy.look, RIGHT, run_time = 0.5) + self.play(randy.change_mode, "sassy") + self.play(Blink(randy)) + self.play( + ShowCreation(bubble), + randy.change_mode, "pondering" + ) + # self.play(matrix.highlight_columns, X_COLOR, Y_COLOR) + self.dither() + for x in range(3): + self.play(Blink(randy)) + self.dither(2) + new_matrix = Matrix([[3, 1, 4], [1, 5, 9]]) + new_matrix.move_to(matrix, side_to_align = UP+LEFT) + self.play( + Transform(matrix, new_matrix), + FadeOut(bubble) + ) + self.remove(matrix) + matrix = new_matrix + self.add(matrix) + self.play(randy.look, DOWN+RIGHT, run_time = 0.5) + self.play(randy.change_mode, "confused") + self.dither() + self.play(Blink(randy)) + self.dither() + + top_brace = Brace(matrix, UP) + top_words = top_brace.get_text("3 basis vectors") + top_words.submobject_gradient_highlight(GREEN, RED, BLUE) + side_brace = Brace(matrix, RIGHT) + side_words = side_brace.get_text(""" + 2 coordinates for + each landing spots + """) + side_words.highlight(YELLOW) + + self.play( + GrowFromCenter(top_brace), + Write(top_words), + matrix.highlight_columns, X_COLOR, Y_COLOR, Z_COLOR + ) + self.play(randy.change_mode, "happy") + self.play( + GrowFromCenter(side_brace), + Write(side_words, run_time = 2) + ) + self.play(Blink(randy)) + self.dither() + +class ThreeDToTwoDInput(Scene): + pass + +class ThreeDToTwoDInputWords(Scene): + def construct(self): + words = TextMobject("3d input") + words.scale(2) + self.play(Write(words)) + self.dither() + +class ThreeDToTwoDOutput(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False, + "foreground_plane_kwargs" : { + "color" : GREY, + "x_radius" : SPACE_WIDTH, + "y_radius" : SPACE_HEIGHT, + "secondary_line_ratio" : 0 + }, + } + def construct(self): + title = TextMobject("Output in 2d") + title.to_edge(UP, buff = SMALL_BUFF) + subwords = TextMobject(""" + (only showing basis vectors, + full 3d grid would be a mess) + """) + subwords.scale(0.75) + subwords.next_to(title, DOWN) + for words in title, subwords: + words.add_background_rectangle() + + self.add(title) + i, j, k = it.starmap(self.add_vector, [ + ([3, 1], X_COLOR), + ([1, 2], Y_COLOR), + ([-2, -2], Z_COLOR) + ]) + pairs = [ + (i, "L(\\hat\\imath)"), + (j, "L(\\hat\\jmath)"), + (k, "L(\\hat k)") + ] + for v, tex in pairs: + self.label_vector(v, tex) + self.play(Write(subwords)) + self.dither() + +class ThreeDToTwoDSideBySide(Scene): + pass + +class Symbolic2To1DTransform(Scene): + def construct(self): + func = TexMobject("L(", "\\vec{\\textbf{v}}", ")") + input_array = Matrix([2, 7]) + input_array.highlight(YELLOW) + in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color()) + func[1].highlight(input_array.get_color()) + output_array = Matrix([1.8]) + output_array.highlight(PINK) + out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color()) + VMobject( + input_array, in_arrow, func, out_arrow, output_array + ).arrange_submobjects(RIGHT, buff = SMALL_BUFF) + + input_brace = Brace(input_array, DOWN) + input_words = input_brace.get_text("2d input") + output_brace = Brace(output_array, UP) + output_words = output_brace.get_text("1d output") + input_words.highlight(input_array.get_color()) + output_words.highlight(output_array.get_color()) + + + self.add(func, input_array) + self.play( + GrowFromCenter(input_brace), + Write(input_words) + ) + mover = input_array.copy() + self.play( + Transform(mover, Dot().move_to(func)), + ShowCreation(in_arrow), + rate_func = rush_into, + run_time = 0.5 + ) + self.play( + Transform(mover, output_array), + ShowCreation(out_arrow), + rate_func = rush_from, + run_time = 0.5 + ) + self.play( + GrowFromCenter(output_brace), + Write(output_words) + ) + self.dither() + +class TwoDTo1DTransform(LinearTransformationScene): + CONFIG = { + "include_background_plane" : False, + "foreground_plane_kwargs" : { + "x_radius" : SPACE_WIDTH, + "y_radius" : SPACE_HEIGHT, + "secondary_line_ratio" : 1 + }, + "t_matrix" : [[1, 0], [2, 0]], + } + def construct(self): + line = NumberLine() + plane_words = TextMobject("2d space") + plane_words.next_to(self.j_hat, UP, buff = MED_BUFF) + plane_words.add_background_rectangle() + line_words = TextMobject("1d space (number line)") + line_words.next_to(line, UP) + + + self.play(Write(plane_words)) + self.dither() + self.remove(plane_words) + mobjects = self.get_mobjects() + self.play( + *map(FadeOut, mobjects) + [ShowCreation(line)] + ) + self.play(Write(line_words)) + self.dither() + self.remove(line_words) + self.play(*map(FadeIn, mobjects)) + self.apply_transposed_matrix(self.t_matrix) + self.play(Write(VMobject(*line.get_number_mobjects()))) + self.dither() + self.show_matrix() + + def show_matrix(self): + for vect, char in zip([self.i_hat, self.j_hat], ["i", "j"]): + vect.words = TextMobject( + "$\\hat\\%smath$ lands on"%char, + str(int(vect.get_end()[0])) + ) + direction = UP if vect is self.i_hat else DOWN + vect.words.next_to(vect.get_end(), direction, buff = LARGE_BUFF) + vect.words.highlight(vect.get_color()) + matrix = Matrix([[1, 2]]) + matrix_words = TextMobject("Transformation matrix: ") + matrix_group = VMobject(matrix_words, matrix) + matrix_group.arrange_submobjects(buff = MED_BUFF) + matrix_group.to_edge(UP) + entries = matrix.get_entries() + + self.play(Write(matrix_words), Write(matrix.get_brackets())) + for i, vect in enumerate([self.i_hat, self.j_hat]): + self.play(vect.rotate, np.pi/12, rate_func = wiggle) + self.play(Write(vect.words)) + self.dither() + self.play(vect.words[1].copy().move_to, entries[i]) + self.dither() + +class TwoDTo1DTransformWithDots(TwoDTo1DTransform): + def construct(self): + line = NumberLine() + self.add(line, *self.get_mobjects()) + offset = LEFT+DOWN + vect = 2*RIGHT+UP + dots = VMobject(*[ + Dot(offset + a*vect, radius = 0.075) + for a in np.linspace(-2, 3, 18) + ]) + dots.submobject_gradient_highlight(YELLOW_B, YELLOW_C) + func = self.get_matrix_transformation(self.t_matrix) + new_dots = VMobject(*[ + Dot( + func(dot.get_center()), + color = dot.get_color(), + radius = dot.radius + ) + for dot in dots + ]) + + self.play(Write(dots)) + self.apply_transposed_matrix( + self.t_matrix, + added_anims = [Transform(dots, new_dots)] + ) + self.dither() + +class NextVideo(Scene): + def construct(self): + title = TextMobject(""" + Next video: Dot products and duality + """) + title.scale_to_fit_width(2*SPACE_WIDTH - 2) + title.to_edge(UP) + rect = Rectangle(width = 16, height = 9, color = BLUE) + rect.scale_to_fit_height(6) + rect.next_to(title, DOWN) + + self.add(title) + self.play(ShowCreation(rect)) + self.dither() + +class DotProductPreview(VectorScene): + CONFIG = { + "v_coords" : [3, 1], + "w_coords" : [2, -1], + "v_color" : YELLOW, + "w_color" : MAROON_C, + + } + def construct(self): + self.lock_in_faded_grid() + self.add_symbols() + self.add_vectors() + self.grow_number_line() + self.project_w() + self.show_scaling() + + + def add_symbols(self): + v = matrix_to_mobject(self.v_coords).highlight(self.v_color) + w = matrix_to_mobject(self.w_coords).highlight(self.w_color) + v.add_background_rectangle() + w.add_background_rectangle() + dot = TexMobject("\\cdot") + eq = VMobject(v, dot, w) + eq.arrange_submobjects(RIGHT, buff = SMALL_BUFF) + eq.to_corner(UP+LEFT) + self.play(Write(eq), run_time = 1) + + def add_vectors(self): + self.v = Vector(self.v_coords, color = self.v_color) + self.w = Vector(self.w_coords, color = self.w_color) + self.play(ShowCreation(self.v)) + self.play(ShowCreation(self.w)) + + def grow_number_line(self): + line = NumberLine(stroke_width = 2).add_numbers() + line.rotate(self.v.get_angle()) + self.play(Write(line), Animation(self.v)) + self.play( + line.highlight, self.v.get_color(), + Animation(self.v), + rate_func = there_and_back + ) + self.dither() + + def project_w(self): + dot_product = np.dot(self.v.get_end(), self.w.get_end()) + v_norm, w_norm = [ + np.linalg.norm(vect.get_end()) + for vect in self.v, self.w + ] + projected_w = Vector( + self.v.get_end()*dot_product/(v_norm**2), + color = self.w.get_color() + ) + projection_line = Line( + self.w.get_end(), projected_w.get_end(), + color = GREY + ) + + self.play(ShowCreation(projection_line)) + self.add(self.w.copy().fade()) + self.play(Transform(self.w, projected_w)) + self.dither() + + def show_scaling(self): + dot_product = np.dot(self.v.get_end(), self.w.get_end()) + start_brace, interim_brace, final_brace = braces = [ + Brace( + Line(ORIGIN, norm*RIGHT), + UP + ) + for norm in 1, self.v.get_length(), dot_product + ] + length_texs = list(it.starmap(TexMobject, [ + ("1",), + ("\\text{Scale by }", "||\\vec{\\textbf{v}}||",), + ("\\text{Length of}", "\\text{ scaled projection}",), + ])) + length_texs[1][1].highlight(self.v_color) + length_texs[2][1].highlight(self.w_color) + for brace, tex_mob in zip(braces, length_texs): + tex_mob.add_background_rectangle() + brace.put_at_tip(tex_mob, buff = SMALL_BUFF) + brace.add(tex_mob) + brace.rotate(self.v.get_angle()) + new_w = self.w.copy().scale(self.v.get_length()) + + self.play(Write(start_brace)) + self.dither() + self.play( + Transform(start_brace, interim_brace), + Transform(self.w, new_w) + ) + self.dither() + self.play( + Transform(start_brace, final_brace) + ) self.dither() @@ -92,3 +648,10 @@ class ColumnsRepresentBasisVectors(Scene): + + + + + + + diff --git a/eola/two_d_space.py b/eola/two_d_space.py index c09fb074..69930899 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -90,12 +90,14 @@ class VectorScene(Scene): def get_vector_label(self, vector, label, direction = "left", rotate = False, - color = WHITE, + color = None, label_scale_factor = VECTOR_LABEL_SCALE_FACTOR): if not isinstance(label, TexMobject): if len(label) == 1: label = "\\vec{\\textbf{%s}}"%label label = TexMobject(label) + if color is None: + color = vector.get_color() label.highlight(color) label.scale(label_scale_factor) label.add_background_rectangle() @@ -257,7 +259,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]]), + "t_matrix" : [[3, 0], [1, 2]], } def setup(self): if hasattr(self, "has_setup"): diff --git a/topics/characters.py b/topics/characters.py index d6ccf603..2c1f46ae 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -294,6 +294,22 @@ class ThoughtBubble(Bubble): return self +class RandolphScene(Scene): + CONFIG = { + "randy_mode" : "plain" + } + def setup(self): + self.randy = Randolph(mode = self.randy_mode) + self.randy.to_corner() + self.add(self.randy) + + def dither(self, blink = True): + if blink: + self.play(Blink(self.randy)) + else: + Scene.dither(self) + return self + class TeacherStudentsScene(Scene): def setup(self): self.teacher = Mortimer() diff --git a/topics/number_line.py b/topics/number_line.py index bcd9a20e..3f99320e 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -13,7 +13,7 @@ class NumberLine(VMobject): "x_max" : SPACE_WIDTH, "space_unit_to_num" : 1, "tick_size" : 0.1, - "tick_frequency" : 0.5, + "tick_frequency" : 1, "leftmost_tick" : None, #Defaults to ceil(x_min) "numbers_with_elongated_ticks" : [0], "longer_tick_multiple" : 2, @@ -63,7 +63,7 @@ class NumberLine(VMobject): return self.x_min + dist_from_left def default_numbers_to_display(self): - return self.get_tick_numbers()[::2] + return np.arange(self.leftmost_tick, self.x_max, 1) def get_vertical_number_offset(self, direction = DOWN): return 4*direction*self.tick_size @@ -75,7 +75,7 @@ class NumberLine(VMobject): result = [] for number in numbers: mob = TexMobject(str(int(number))) - mob.scale_to_fit_height(2*self.tick_size) + mob.scale_to_fit_height(3*self.tick_size) mob.shift( self.number_to_point(number), self.get_vertical_number_offset(**kwargs) @@ -128,7 +128,6 @@ class NumberPlane(VMobject): "written_coordinate_nudge" : 0.1*(DOWN+RIGHT), "num_pair_at_center" : (0, 0), "propogate_style_to_family" : False, - "submobject_partial_creation_mode" : "smoothed_lagged_start", } def generate_points(self):