From 25efbbf0b07e9c1fdc67e9bec5266df7c982a25f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 25 Aug 2016 17:15:48 -0700 Subject: [PATCH] A nice small commit capturing one atomic change, as usual --- animation/transform.py | 1 + eola/chapter7.py | 53 +- eola/chapter8.py | 458 ++++++++++++++++-- eola/two_d_space.py | 6 +- extract_scene.py | 6 +- .../generate_logo.py | 0 scene/scene.py | 12 +- topics/characters.py | 9 +- topics/geometry.py | 16 +- 9 files changed, 499 insertions(+), 62 deletions(-) rename generate_logo.py => old_projects/generate_logo.py (100%) diff --git a/animation/transform.py b/animation/transform.py index 72dfb48c..1a291eea 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -123,6 +123,7 @@ class FadeIn(Transform): mobject.fade(1) if isinstance(mobject, VMobject): mobject.set_stroke(width = 0) + mobject.set_fill(opacity = 0) Transform.__init__(self, mobject, target, **kwargs) diff --git a/eola/chapter7.py b/eola/chapter7.py index b3ab3bff..713ce52a 100644 --- a/eola/chapter7.py +++ b/eola/chapter7.py @@ -780,7 +780,7 @@ class Symbolic2To1DTransform(Scene): class RecommendChapter3(Scene): def construct(self): title = TextMobject(""" - Definite watch Chapter 3 + Definitely watch Chapter 3 if you haven't already """) title.to_edge(UP) @@ -2316,7 +2316,56 @@ class NextVideo(Scene): self.play(ShowCreation(rect)) self.dither() - +class CrossAndDualWords(Scene): + def construct(self): + from eola.chapter5 import get_det_text + + v_tex, u_tex, w_tex = [ + "\\vec{\\textbf{%s}}"%s + for s in "vuw" + ] + vector_word = TextMobject("Vector:") + transform_word = TextMobject("Dual transform:") + + cross = TexMobject( + v_tex, "=", u_tex, "\\times", w_tex + ) + for tex, color in zip([v_tex, u_tex, w_tex], [V_COLOR, U_COLOR, W_COLOR]): + cross.highlight_by_tex(tex, color) + input_array_tex = matrix_to_tex_string(["x", "y", "z"]) + func = TexMobject("L\\left(%s\\right) = "%input_array_tex) + matrix = Matrix(np.array([ + ["x", "y", "z"], + ["u_1", "u_2", "u_3"], + ["w_1", "w_2", "w_3"], + ]).T) + matrix.highlight_columns(WHITE, U_COLOR, W_COLOR) + det_text = get_det_text(matrix) + det_text.add(matrix) + dot_with_cross = TexMobject( + "%s \\cdot ("%input_array_tex, + u_tex, "\\times", w_tex, ")" + ) + dot_with_cross.highlight_by_tex(u_tex, U_COLOR) + dot_with_cross.highlight_by_tex(w_tex, W_COLOR) + transform = Group(func, det_text) + transform.arrange_submobjects() + + Group(transform, dot_with_cross).scale(0.7) + vector_word.to_corner(UP+LEFT) + transform_word.next_to(vector_word, DOWN, buff = MED_BUFF, aligned_edge = LEFT) + cross.next_to(vector_word, buff = MED_BUFF) + transform.next_to(transform_word, DOWN, buff = MED_BUFF, aligned_edge = LEFT) + dot_with_cross.next_to(func, RIGHT) + + self.add(vector_word) + self.play(Write(cross)) + self.dither() + self.play(FadeIn(transform_word)) + self.play(Write(transform)) + self.dither() + self.play(Transform(det_text, dot_with_cross)) + self.dither() diff --git a/eola/chapter8.py b/eola/chapter8.py index bd2f08e9..6d42b0f5 100644 --- a/eola/chapter8.py +++ b/eola/chapter8.py @@ -22,66 +22,454 @@ from eola.two_d_space import * from eola.chapter5 import get_det_text -V_COLOR = RED U_COLOR = ORANGE +V_COLOR = YELLOW W_COLOR = MAROON_B class OpeningQuote(Scene): def construct(self): words = TextMobject( - "To ask the", - "right question\\\\", - "is harder than to answer it." + "From [Grothendieck], I have also learned not" + "to take glory in the ", + "difficulty of a proof:", + "difficulty means we have not understood." + "The idea is to be able to", + "paint a landscape", + "in which the proof is obvious.", + arg_separator = " " ) + words.highlight_by_tex("difficulty of a proof:", RED) + words.highlight_by_tex("paint a landscape", GREEN) + words.scale_to_fit_width(2*SPACE_WIDTH - 2) words.to_edge(UP) - words[1].highlight(BLUE) - author = TextMobject("-Georg Cantor") + author = TextMobject("-Pierre Deligne") author.highlight(YELLOW) author.next_to(words, DOWN, buff = 0.5) self.play(FadeIn(words)) - self.dither(2) + self.dither(4) self.play(Write(author, run_time = 3)) self.dither() -class CrossAndDualWords(Scene): +class DoTheSameForCross(TeacherStudentsScene): def construct(self): - v_tex, u_tex, w_tex = [ - "\\vec{\\textbf{%s}}"%s - for s in "vuw" + words = TextMobject("Let's do the same \\\\ for", "cross products") + words.highlight_by_tex("cross products", YELLOW) + self.teacher_says(words, pi_creature_target_mode = "surprised") + self.random_blink(2) + self.change_student_modes("pondering") + self.random_blink() + +class ListSteps(RandolphScene): + CONFIG = { + "randy_corner" : DOWN+RIGHT + } + def construct(self): + title = TextMobject("Two parts") + title.highlight(YELLOW) + title.to_edge(UP) + h_line = Line(LEFT, RIGHT).scale(SPACE_WIDTH) + h_line.next_to(title, DOWN) + + step_1 = TextMobject("1. Standard introduction") + step_2 = TextMobject("2. Deeper understanding with ", "linear transformations") + step_2.highlight_by_tex("linear transformations", BLUE) + steps = Group(step_1, step_2) + steps.arrange_submobjects(DOWN, aligned_edge = LEFT, buff = LARGE_BUFF) + steps.next_to(self.randy, UP) + steps.to_edge(LEFT) + + self.play( + FadeIn(step_1), + self.randy.change_mode, "happy" + ) + self.dither() + self.play( + Write(step_2), + self.randy.change_mode, "pondering" + ) + self.dither() + +class ContrastDotAndCross(Scene): + def construct(self): + self.add_t_chart() + self.add_dot_products() + self.add_cross_product() + self.add_2d_cross_product() + self.emphasize_output_type() + + def add_t_chart(self): + for word, vect, color in ("Dot", LEFT, BLUE_C), ("Cross", RIGHT, YELLOW): + title = TextMobject("%s product"%word) + title.shift(vect*SPACE_WIDTH/2) + title.to_edge(UP) + title.highlight(color) + self.add(title) + v_line = Line(UP, DOWN).scale(SPACE_HEIGHT) + l_h_line = Line(LEFT, ORIGIN).scale(SPACE_WIDTH) + r_h_line = Line(ORIGIN, RIGHT).scale(SPACE_WIDTH) + r_h_line.next_to(title, DOWN) + l_h_line.next_to(r_h_line, LEFT, buff = 0) + self.add(v_line, l_h_line, r_h_line) + self.l_h_line, self.r_h_line = l_h_line, r_h_line + + def add_dot_products(self, max_width = SPACE_WIDTH-1, dims = [2, 5]): + colors = [X_COLOR, Y_COLOR, Z_COLOR, MAROON_B, TEAL] + last_mob = self.l_h_line + for dim in dims: + arrays = [ + [random.randint(0, 9) for in_count in range(dim)] + for out_count in range(2) + ] + m1, m2 = map(Matrix, arrays) + for matrix in m1, m2: + for entry, color in zip(matrix.get_entries(), colors): + entry.highlight(color) + syms = map(TexMobject, ["="] + ["+"]*(dim-1)) + result = Group(*it.chain(*zip( + syms, + [ + Group( + e1, TexMobject("\\cdot"), e2 + ).arrange_submobjects() + for e1, e2 in zip(*[m.copy().get_entries() for m in m1, m2]) + ] + ))) + result.arrange_submobjects(RIGHT) + dot_prod = Group( + m1, TexMobject("\\cdot"), m2, result + ) + dot_prod.arrange_submobjects(RIGHT) + if dot_prod.get_width() > max_width: + dot_prod.scale_to_fit_width(max_width) + dot_prod.next_to(last_mob, DOWN, buff = MED_BUFF) + last_mob = dot_prod + dot_prod.to_edge(LEFT) + self.play(Write(dot_prod)) + + def add_cross_product(self): + colors = [X_COLOR, Y_COLOR, Z_COLOR] + + arrays = [ + [random.randint(0, 9) for in_count in range(3)] + for out_count in range(2) ] - vector_word = TextMobject("Vector:") - transform_word = TextMobject("Dual transform:") + matrices = map(Matrix, arrays) + for matrix in matrices: + for entry, color in zip(matrix.get_entries(), colors): + entry.highlight(color) + m1, m2 = matrices + cross_product = Group(m1, TexMobject("\\times"), m2) + cross_product.arrange_submobjects() - cross = TexMobject( - v_tex, "=", u_tex, "\\times", w_tex + index_to_cross_enty = {} + syms = Group() + movement_sets = [] + for a, b, c in it.permutations(range(3)): + e1, e2 = m1.get_entries()[b], m2.get_entries()[c] + for e in e1, e2: + e.target = e.copy() + movement_sets.append([e1, e1.target, e2, e2.target]) + dot = TexMobject("\\cdot") + syms.add(dot) + cross_entry = Group(e1.target, dot, e2.target) + cross_entry.arrange_submobjects() + if a not in index_to_cross_enty: + index_to_cross_enty[a] = [] + index_to_cross_enty[a].append(cross_entry) + result_entries = [] + for a in range(3): + prod1, prod2 = index_to_cross_enty[a] + if a == 1: + prod1, prod2 = prod2, prod1 + prod2.arrange_submobjects(LEFT) + minus = TexMobject("-") + syms.add(minus) + entry = Group(prod1, minus, prod2) + entry.arrange_submobjects(RIGHT) + result_entries.append(entry) + + result = Matrix(result_entries) + full_cross_product = Group( + cross_product, TexMobject("="), result ) - for tex, color in zip([v_tex, u_tex, w_tex], [V_COLOR, U_COLOR, W_COLOR]): - cross.highlight_by_tex(tex, color) - input_array_tex = matrix_to_tex_string(["x", "y", "z"]) - func = TexMobject("f\\left(%s\\right) = "%input_array_tex) + full_cross_product.arrange_submobjects() + full_cross_product.scale(0.75) + full_cross_product.next_to(self.r_h_line, DOWN, buff = MED_BUFF/2) + full_cross_product.remove(result) + self.play( + Write(full_cross_product), + Write(syms) + ) + movements = [] + for e1, e1_target, e2, e2_target in movement_sets: + movements += [ + e1.copy().move_to, e1_target, + e2.copy().move_to, e2_target, + ] + self.play( + Write(result.get_brackets()), + *movements, + run_time = 2 + ) + self.dither() + + brace = Brace(cross_product) + brace_text = brace.get_text("Only 3d") + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + + self.cross_result = result + self.only_3d_text = brace_text + + def add_2d_cross_product(self): + h_line = DashedLine(ORIGIN, SPACE_WIDTH*RIGHT) + h_line.next_to(self.only_3d_text, DOWN, buff = MED_BUFF/2) + h_line.to_edge(RIGHT, buff = 0) + arrays = np.random.randint(0, 9, (2, 2)) + m1, m2 = matrices = map(Matrix, arrays) + for m in matrices: + for e, color in zip(m.get_entries(), [X_COLOR, Y_COLOR]): + e.highlight(color) + cross_product = Group(m1, TexMobject("\\times"), m2) + cross_product.arrange_submobjects() + (x1, x2), (x3, x4) = tuple(m1.get_entries()), tuple(m2.get_entries()) + entries = [x1, x2, x3, x4] + for entry in entries: + entry.target = entry.copy() + eq, dot1, minus, dot2 = syms = map(TexMobject, + ["=", "\\cdot", "-", "\\cdot"] + ) + result = Group( + eq, x1.target, dot1, x4.target, + minus, x3.target, dot2, x2.target, + ) + result.arrange_submobjects(RIGHT) + full_cross_product = Group(cross_product, result) + full_cross_product.arrange_submobjects(RIGHT) + full_cross_product.next_to(h_line, DOWN, buff = MED_BUFF/2) + + self.play(ShowCreation(h_line)) + self.play(Write(cross_product)) + self.play( + Write(Group(*syms)), + *[ + Transform(entry.copy(), entry.target) + for entry in entries + ] + ) + self.dither() + self.two_d_result = Group(*result[1:]) + + def emphasize_output_type(self): + three_d_brace = Brace(self.cross_result) + two_d_brace = Brace(self.two_d_result) + vector = three_d_brace.get_text("Vector") + number = two_d_brace.get_text("Number") + + self.play( + GrowFromCenter(three_d_brace), + Write(vector) + ) + self.dither() + self.play( + GrowFromCenter(two_d_brace), + Write(number) + ) + self.dither() + +class PrereqDeterminant(Scene): + def construct(self): + title = TextMobject(""" + Prerequisite: Understanding determinants + """) + 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 Define2dCrossProduct(LinearTransformationScene): + CONFIG = { + "show_basis_vectors" : False, + "v_coords" : [3, 1], + "w_coords" : [2, -1], + } + def construct(self): + self.initial_definition() + self.show_transformation() + self.transform_square() + self.show_orientation_rule() + + + def initial_definition(self): + self.plane.save_state() + self.plane.fade() + v = self.add_vector(self.v_coords, color = V_COLOR) + w = self.add_vector(self.w_coords, color = W_COLOR) + self.moving_vectors.remove(v) + self.moving_vectors.remove(w) + for vect, name in (v, "v"), (w, "w"): + color = vect.get_color() + vect.label = self.label_vector(vect, name, color = color) + vect.coord_array = self.write_vector_coordinates( + vect, color = color + ) + vect.coords = vect.coord_array.get_entries() + movers = [v.label, w.label, v.coords, w.coords] + for mover in movers: + mover.target = mover.copy() + times = TexMobject("\\times") + cross_product = Group( + v.label.target, times, w.label.target + ) + + cross_product.arrange_submobjects() matrix = Matrix(np.array([ - ["x", "y", "z"], - ["u_1", "u_2", "u_3"], - ["w_1", "w_2", "w_3"], + list(v.coords.target), + list(w.coords.target) ]).T) - matrix.highlight_columns(WHITE, U_COLOR, W_COLOR) det_text = get_det_text(matrix) - det_text.add(matrix) - equals_dot = TexMobject( - "= %s \\cdot"%input_array_tex, v_tex + full_det = Group(det_text, matrix) + equals = TexMobject("=") + equation = Group(cross_product, equals, full_det) + equation.arrange_submobjects() + equation.to_corner(UP+LEFT) + + matrix_background = BackgroundRectangle(matrix) + cross_background = BackgroundRectangle(cross_product) + + disclaimer = TextMobject("$^*$ See ``Note on conventions'' in description") + disclaimer.scale(0.7) + disclaimer.highlight(RED) + disclaimer.next_to( + det_text.get_corner(UP+RIGHT), RIGHT, buff = 0 ) - equals_dot.highlight_by_tex(v_tex, V_COLOR) - transform = Group(func, det_text) - transform.arrange_submobjects() + disclaimer.add_background_rectangle() + + self.play( + FadeIn(cross_background), + Transform(v.label.copy(), v.label.target), + Transform(w.label.copy(), w.label.target), + Write(times), + ) + self.dither() + self.play(Transform(v.coords.copy(), v.coords.target)) + self.play(Transform(w.coords.copy(), w.coords.target)) + self.play( + ShowCreation(matrix_background), + Write(matrix.get_brackets()), + Animation(matrix.get_entries()), + run_time = 1 + ) + matrix.add_to_back(matrix_background) + self.dither() + self.play( + Write(equals), + Write(det_text), + Animation(matrix), + ) + self.dither() + self.play(FadeIn(disclaimer)) + self.dither() + self.play(FadeOut(disclaimer)) + self.dither() + + self.matrix = matrix + self.det_text = det_text + + def show_transformation(self): + matrix = self.matrix + everything = self.get_mobjects() + everything.remove(self.plane) + everything.remove(self.background_plane) + everything.remove(matrix) + self.play( + *map(FadeOut, everything) + [ + Animation(self.background_plane), + self.plane.restore, + Animation(matrix), + ]) + side_brace = Brace(matrix, RIGHT) + transform_words = side_brace.get_text("Linear transformation") + transform_words.add_background_rectangle() + + col1, col2 = [ + Group(*matrix.get_mob_matrix()[:,i]) + for i in 0, 1 + ] + + both_words = [] + for char, color, col in ("i", X_COLOR, col1), ("j", Y_COLOR, col2): + words = TextMobject("Where $\\hat\\%smath$ lands"%char) + words.highlight(color) + words.add_background_rectangle() + words.next_to(col, DOWN, buff = LARGE_BUFF) + words.arrow = Arrow(words.get_top(), col.get_bottom(), color = color) + both_words.append(words) + i_words, j_words = both_words + + self.play( + GrowFromCenter(side_brace), + Write(transform_words) + ) + self.play( + Write(i_words), + ShowCreation(i_words.arrow), + col1.highlight, X_COLOR + ) + self.dither() + self.play( + Transform(i_words, j_words), + Transform(i_words.arrow, j_words.arrow), + col2.highlight, Y_COLOR + ) + self.dither() + self.play(*map(FadeOut, [i_words, i_words.arrow])) + + i_hat, j_hat = self.get_basis_vectors() + basis_labels = self.get_basis_vector_labels() + self.play( + ShowCreation(i_hat), + ShowCreation(j_hat), + Write(basis_labels) + ) + self.dither() + self.play(FadeOut(basis_labels)) + self.add_vector(i_hat) + self.add_vector(j_hat) + self.play(*map(FadeOut, [side_brace, transform_words])) + self.add_foreground_mobject(matrix) + self.apply_transposed_matrix([self.v_coords, self.w_coords]) + self.dither() + + + def transform_square(self): + pass + + def show_orientation_rule(self): + pass + + + + + + + + + + + + + - vector_word.next_to(transform_word, UP, buff = LARGE_BUFF, aligned_edge = LEFT) - cross.next_to(vector_word, buff = MED_BUFF) - transform.next_to(transform_word, buff = MED_BUFF) - equals_dot.next_to(det_text, DOWN, aligned_edge = LEFT) - self.add_mobjects_among(locals().values()) - self.show_frame() diff --git a/eola/two_d_space.py b/eola/two_d_space.py index 69930899..8b4cfdaa 100644 --- a/eola/two_d_space.py +++ b/eola/two_d_space.py @@ -2,7 +2,7 @@ import numpy as np from scene import Scene from mobject import Mobject -from mobject.vectorized_mobject import VMobject +from mobject.vectorized_mobject import VMobject, Group from mobject.tex_mobject import TexMobject, TextMobject from animation import Animation from animation.transform import ApplyPointwiseFunction, Transform, \ @@ -75,7 +75,7 @@ class VectorScene(Scene): def get_basis_vector_labels(self, **kwargs): i_hat, j_hat = self.get_basis_vectors() - return [ + return Group(*[ self.get_vector_label( vect, label, color = color, label_scale_factor = 1, @@ -85,7 +85,7 @@ class VectorScene(Scene): (i_hat, "\\hat{\\imath}", X_COLOR), (j_hat, "\\hat{\\jmath}", Y_COLOR), ] - ] + ]) def get_vector_label(self, vector, label, direction = "left", diff --git a/extract_scene.py b/extract_scene.py index d9230902..6b0eb773 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -79,7 +79,8 @@ def get_configuration(sys_argv): #By default, write to file actions = ["write", "preview", "save_image"] if not any([config[key] for key in actions]): - config["write"] = True + config["write"] = True + config["skip_animations"] = config["save_image"] and not config["write"] if len(args) == 0: print HELP_MESSAGE @@ -197,7 +198,8 @@ def main(): ) config["movie_prefix"] = config["file"].replace(".py", "") scene_kwargs = { - "camera_config" : config["camera_config"] + "camera_config" : config["camera_config"], + "skip_animations" : config["skip_animations"], } for SceneClass in get_scene_classes(scene_names_to_classes, config): for args in get_scene_args(SceneClass, config): diff --git a/generate_logo.py b/old_projects/generate_logo.py similarity index 100% rename from generate_logo.py rename to old_projects/generate_logo.py diff --git a/scene/scene.py b/scene/scene.py index b4a8ec20..0f4dc315 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -20,9 +20,10 @@ from animation.transform import ApplyMethod class Scene(object): CONFIG = { - "camera_config" : {}, - "frame_duration" : DEFAULT_FRAME_DURATION, - "construct_args" : [], + "camera_config" : {}, + "frame_duration" : DEFAULT_FRAME_DURATION, + "construct_args" : [], + "skip_animations" : False, } def __init__(self, **kwargs): digest_config(self, kwargs) @@ -214,6 +215,9 @@ class Scene(object): def play(self, *args, **kwargs): if len(args) == 0: raise Exception("Called Scene.play with no animations") + if self.skip_animations: + kwargs["run_time"] = 0 + animations = self.compile_play_args_to_animation_list(*args) self.num_plays += 1 @@ -274,6 +278,8 @@ class Scene(object): return self def dither(self, duration = DEFAULT_DITHER_TIME): + if self.skip_animations: + return self self.update_frame() self.frames += [self.get_frame()]*int(duration / self.frame_duration) return self diff --git a/topics/characters.py b/topics/characters.py index f1e0a944..ed41e52b 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -296,11 +296,14 @@ class ThoughtBubble(Bubble): class RandolphScene(Scene): CONFIG = { - "randy_mode" : "plain" + "randy_kwargs" : {}, + "randy_corner" : DOWN+LEFT } def setup(self): - self.randy = Randolph(mode = self.randy_mode) - self.randy.to_corner() + self.randy = Randolph(**self.randy_kwargs) + self.randy.to_corner(self.randy_corner) + if self.randy_corner[0] > 0: + self.randy.flip() self.add(self.randy) def dither(self, time = 1, blink = True): diff --git a/topics/geometry.py b/topics/geometry.py index 103b4b1b..0ac671b0 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -313,28 +313,16 @@ class BackgroundRectangle(Rectangle): "fill_opacity" : 0.75, } def __init__(self, mobject, **kwargs): - self.lock_style = False Rectangle.__init__(self, **kwargs) - self.lock_style = True self.replace(mobject, stretch = True) self.original_fill_opacity = self.fill_opacity def pointwise_become_partial(self, mobject, a, b): - self.lock_style = False self.set_fill(opacity = b*self.original_fill_opacity) - self.lock_style = True return self - def fade_to(self, *args, **kwargs): - self.lock_style = False - Rectangle.fade_to(self, *args, **kwargs) - self.lock_style = True - return self - - def set_style_data(self, *args, **kwargs): - if self.lock_style: - return self #Do nothing - return Rectangle.set_style_data(self, *args, **kwargs) + def get_fill_color(self): + return Color(self.color) class Grid(VMobject):