Up to formal definition of linearity in EoLA chapter 3

This commit is contained in:
Grant Sanderson 2016-07-22 18:45:34 -07:00
parent 46e5dbfa14
commit 17505d4d9e
8 changed files with 244 additions and 77 deletions

View file

@ -98,12 +98,8 @@ class ApplyMethod(Transform):
"the method you want to animate" "the method you want to animate"
) )
assert(isinstance(method.im_self, Mobject)) assert(isinstance(method.im_self, Mobject))
Transform.__init__( target = copy.deepcopy(method)(*args)
self, Transform.__init__(self, method.im_self, target, **kwargs)
method.im_self,
copy.deepcopy(method)(*args),
**kwargs
)
class FadeOut(Transform): class FadeOut(Transform):
CONFIG = { CONFIG = {

View file

@ -20,6 +20,10 @@ from mobject.vectorized_mobject import *
from eola.matrix import * from eola.matrix import *
from eola.two_d_space import * from eola.two_d_space import *
def curvy_squish(point):
x, y, z = point
return (x+np.cos(y))*RIGHT + (y+np.sin(x))*UP
class OpeningQuote(Scene): class OpeningQuote(Scene):
def construct(self): def construct(self):
words = TextMobject([ words = TextMobject([
@ -49,7 +53,6 @@ class OpeningQuote(Scene):
self.play(Write(comment)) self.play(Write(comment))
self.dither() self.dither()
class Introduction(TeacherStudentsScene): class Introduction(TeacherStudentsScene):
def construct(self): def construct(self):
title = TextMobject("Matrices as linear transformations") title = TextMobject("Matrices as linear transformations")
@ -71,7 +74,6 @@ class Introduction(TeacherStudentsScene):
return (SPACE_WIDTH+SPACE_HEIGHT)*p/np.linalg.norm(p) return (SPACE_WIDTH+SPACE_HEIGHT)*p/np.linalg.norm(p)
self.play(ApplyPointwiseFunction(spread_out, everything)) self.play(ApplyPointwiseFunction(spread_out, everything))
class ShowGridCreation(Scene): class ShowGridCreation(Scene):
def construct(self): def construct(self):
plane = NumberPlane() plane = NumberPlane()
@ -114,7 +116,6 @@ class IntroduceLinearTransformations(LinearTransformationScene):
) )
self.dither() self.dither()
class SimpleLinearTransformationScene(LinearTransformationScene): class SimpleLinearTransformationScene(LinearTransformationScene):
CONFIG = { CONFIG = {
"show_basis_vectors" : False, "show_basis_vectors" : False,
@ -143,8 +144,7 @@ class SimpleNonlinearTransformationScene(LinearTransformationScene):
self.dither() self.dither()
def func(self, point): def func(self, point):
x, y, z = point return curvy_squish(point)
return (x+np.cos(y))*RIGHT + (y+np.sin(x))*UP
class MovingOrigin(SimpleNonlinearTransformationScene): class MovingOrigin(SimpleNonlinearTransformationScene):
CONFIG = { CONFIG = {
@ -185,7 +185,6 @@ class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation):
self.play(ShowCreation(diag)) self.play(ShowCreation(diag))
self.add_transformable_mobject(diag) self.add_transformable_mobject(diag)
class AnotherLinearTransformation(SimpleLinearTransformationScene): class AnotherLinearTransformation(SimpleLinearTransformationScene):
CONFIG = { CONFIG = {
"transposed_matrix" : [ "transposed_matrix" : [
@ -209,7 +208,6 @@ class AnotherLinearTransformation(SimpleLinearTransformationScene):
self.play(Write(text)) self.play(Write(text))
self.dither() self.dither()
class Rotation(SimpleLinearTransformationScene): class Rotation(SimpleLinearTransformationScene):
CONFIG = { CONFIG = {
"angle" : np.pi/3, "angle" : np.pi/3,
@ -221,8 +219,6 @@ class Rotation(SimpleLinearTransformationScene):
] ]
SimpleLinearTransformationScene.construct(self) SimpleLinearTransformationScene.construct(self)
class YetAnotherLinearTransformation(SimpleLinearTransformationScene): class YetAnotherLinearTransformation(SimpleLinearTransformationScene):
CONFIG = { CONFIG = {
"transposed_matrix" : [ "transposed_matrix" : [
@ -231,7 +227,6 @@ class YetAnotherLinearTransformation(SimpleLinearTransformationScene):
] ]
} }
class MoveAroundAllVectors(LinearTransformationScene): class MoveAroundAllVectors(LinearTransformationScene):
CONFIG = { CONFIG = {
"show_basis_vectors" : False, "show_basis_vectors" : False,
@ -269,17 +264,15 @@ class MoveAroundAllVectors(LinearTransformationScene):
self.add(vector.copy().highlight(DARK_GREY)) self.add(vector.copy().highlight(DARK_GREY))
else: else:
for vector in vectors.split(): for vector in vectors.split():
self.add_vector(vector) self.add_vector(vector, animate = False)
self.apply_transposed_matrix([[3, 0], [1, 2]]) self.apply_transposed_matrix([[3, 0], [1, 2]])
self.dither() self.dither()
class MoveAroundJustOneVector(MoveAroundAllVectors): class MoveAroundJustOneVector(MoveAroundAllVectors):
CONFIG = { CONFIG = {
"focus_on_one_vector" : True, "focus_on_one_vector" : True,
} }
class RotateIHat(LinearTransformationScene): class RotateIHat(LinearTransformationScene):
CONFIG = { CONFIG = {
"show_basis_vectors" : False "show_basis_vectors" : False
@ -297,8 +290,6 @@ class RotateIHat(LinearTransformationScene):
self.play(Write(j_label, run_time = 1)) self.play(Write(j_label, run_time = 1))
self.dither() self.dither()
class TransformationsAreFunctions(Scene): class TransformationsAreFunctions(Scene):
def construct(self): def construct(self):
title = TextMobject([ title = TextMobject([
@ -345,7 +336,6 @@ class TransformationsAreFunctions(Scene):
self.play(Write(v), ShowCreation(a), run_time = 1) self.play(Write(v), ShowCreation(a), run_time = 1)
self.dither() self.dither()
class UsedToThinkinfOfFunctionsAsGraphs(VectorScene): class UsedToThinkinfOfFunctionsAsGraphs(VectorScene):
def construct(self): def construct(self):
self.show_graph() self.show_graph()
@ -409,6 +399,158 @@ class UsedToThinkinfOfFunctionsAsGraphs(VectorScene):
) )
self.dither() self.dither()
class TryingToVisualizeFourDimensions(Scene):
def construct(self):
randy = Randolph().to_corner()
bubble = randy.get_bubble()
formula = TexMobject("""
L\\left(\\left[
\\begin{array}{c}
x \\\\
y
\\end{array}
\\right]\\right) =
\\left[
\\begin{array}{c}
2x + y \\\\
x + 2y
\\end{array}
\\right]
""")
formula.next_to(randy, RIGHT)
formula.split()[3].highlight(X_COLOR)
formula.split()[4].highlight(Y_COLOR)
VMobject(*formula.split()[9:9+4]).highlight(MAROON_C)
VMobject(*formula.split()[13:13+4]).highlight(BLUE)
thought = TextMobject("""
Do I imagine plotting
$(x, y, 2x+y, x+2y)$???
""")
thought.split()[-17].highlight(X_COLOR)
thought.split()[-15].highlight(Y_COLOR)
VMobject(*thought.split()[-13:-13+4]).highlight(MAROON_C)
VMobject(*thought.split()[-8:-8+4]).highlight(BLUE)
bubble.position_mobject_inside(thought)
thought.shift(0.2*UP)
self.add(randy)
self.play(
ApplyMethod(randy.look, DOWN+RIGHT),
Write(formula)
)
self.play(
ApplyMethod(randy.change_mode, "pondering"),
ShowCreation(bubble),
Write(thought)
)
self.play(Blink(randy))
self.dither()
self.remove(thought)
bubble.make_green_screen()
self.dither()
self.play(Blink(randy))
self.play(ApplyMethod(randy.change_mode, "confused"))
self.dither()
self.play(Blink(randy))
self.dither()
class ForgetAboutGraphs(Scene):
def construct(self):
self.play(Write("You must unlearn graphs"))
self.dither()
class ThinkAboutFunctionAsMovingVector(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"leave_ghost_vectors" : True,
}
def construct(self):
self.setup()
vector = self.add_vector([2, 1])
label = self.add_transformable_label(vector, "v")
self.dither()
self.apply_transposed_matrix([[1, 1], [-3, 1]])
self.dither()
class PrepareForFormalDefinition(TeacherStudentsScene):
def construct(self):
self.setup()
self.teacher_says("Get ready for a formal definition!")
self.dither(3)
bubble = self.student_thinks("")
bubble.make_green_screen()
self.dither(3)
class AdditivityProperty(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"give_title" : True,
"transposed_matrix" : [[2, 0], [1, 1]],
"nonlinear_transformation" : None,
"vector_v" : [2, 1],
"vector_w" : [1, -2],
}
def construct(self):
self.setup()
added_anims = []
if self.give_title:
title = TextMobject("""
First fundamental property of
linear transformations
""")
title.to_edge(UP)
title.add_background_rectangle()
self.play(Write(title))
added_anims.append(Animation(title))
self.dither()
# self.play(ApplyMethod(self.plane.fade))
v, w = self.draw_all_vectors()
self.apply_transformation()
self.show_final_sum(v, w)
def draw_all_vectors(self):
v = self.add_vector(self.vector_v, color = MAROON_C)
v_label = self.add_transformable_label(v, "v")
w = self.add_vector(self.vector_w, color = GREEN)
w_label = self.add_transformable_label(w, "w")
new_w = w.copy().fade(0.4)
self.play(ApplyMethod(new_w.shift, v.get_end()))
sum_vect = self.add_vector(new_w.get_end(), color = PINK)
sum_label = self.add_transformable_label(
sum_vect,
"%s + %s"%(v_label.expression, w_label.expression),
rotate = True
)
self.play(FadeOut(new_w))
return v, w
def apply_transformation(selfe):
if self.nonlinear_transformation:
self.apply_nonlinear_transformation(self.nonlinear_transformation)
else:
self.apply_transposed_matrix(
self.transposed_matrix,
added_anims = added_anims
)
self.dither()
def show_final_sum(self, v, w):
new_w = w.copy()
self.play(ApplyMethod(new_w.shift, v.get_end()))
self.dither()
class NonlinearLacksAdditivity(AdditivityProperty):
CONFIG = {
"give_title" : False,
"nonlinear_transformation" : curvy_squish,
"vector_w" : [2, -3],
}

View file

@ -84,28 +84,22 @@ class VectorScene(Scene):
direction = "left", direction = "left",
rotate = False, rotate = False,
color = WHITE, color = WHITE,
buff_factor = 2,
label_scale_val = VECTOR_LABEL_SCALE_VAL): label_scale_val = VECTOR_LABEL_SCALE_VAL):
if len(label) == 1: if len(label) == 1:
label = "\\vec{\\textbf{%s}}"%label label = "\\vec{\\textbf{%s}}"%label
label = TexMobject(label) label = TexMobject(label)
label.highlight(color) label.highlight(color)
label.scale(label_scale_val) label.scale(label_scale_val)
if rotate: label.add_background_rectangle()
label.rotate(vector.get_angle())
vector_vect = vector.get_end() - vector.get_start() angle = vector.get_angle()
if direction is "left": label.shift(label.get_height()*UP)
rot_angle = -np.pi/2 label.rotate(angle)
else: label.shift((vector.get_end() - vector.get_start())/2)
rot_angle = np.pi/2 if not rotate:
boundary_dir = -np.round(rotate_vector(vector_vect, rot_angle)) label.rotate_in_place(-angle)
boundary_point = label.get_critical_point(boundary_dir)
label.shift(buff_factor*boundary_point)
label.shift(vector_vect/2.)
return label return label
def label_vector(self, vector, label, animate = True, **kwargs): def label_vector(self, vector, label, animate = True, **kwargs):
label = self.get_vector_label(vector, label, **kwargs) label = self.get_vector_label(vector, label, **kwargs)
if animate: if animate:
@ -252,11 +246,13 @@ class LinearTransformationScene(VectorScene):
"show_basis_vectors" : True, "show_basis_vectors" : True,
"i_hat_color" : X_COLOR, "i_hat_color" : X_COLOR,
"j_hat_color" : Y_COLOR, "j_hat_color" : Y_COLOR,
"leave_ghost_vectors" : False,
} }
def setup(self): def setup(self):
self.background_mobjects = [] self.background_mobjects = []
self.transformable_mobject = [] self.transformable_mobjects = []
self.moving_vectors = [] self.moving_vectors = []
self.transformable_labels = []
self.background_plane = NumberPlane( self.background_plane = NumberPlane(
**self.background_plane_kwargs **self.background_plane_kwargs
@ -281,8 +277,8 @@ class LinearTransformationScene(VectorScene):
def add_transformable_mobject(self, *mobjects): def add_transformable_mobject(self, *mobjects):
for mobject in mobjects: for mobject in mobjects:
if mobject not in self.transformable_mobject: if mobject not in self.transformable_mobjects:
self.transformable_mobject.append(mobject) self.transformable_mobjects.append(mobject)
self.add(mobject) self.add(mobject)
def add_vector(self, vector, color = YELLOW, **kwargs): def add_vector(self, vector, color = YELLOW, **kwargs):
@ -292,6 +288,14 @@ class LinearTransformationScene(VectorScene):
self.moving_vectors.append(vector) self.moving_vectors.append(vector)
return vector return vector
def add_transformable_label(self, vector, label, **kwargs):
label_mob = self.label_vector(vector, label, **kwargs)
label_mob.target_text = "L(%s)"%label_mob.expression
label_mob.vector = vector
label_mob.kwargs = kwargs
self.transformable_labels.append(label_mob)
return label_mob
def get_matrix_transformation(self, transposed_matrix): def get_matrix_transformation(self, transposed_matrix):
transposed_matrix = np.array(transposed_matrix) transposed_matrix = np.array(transposed_matrix)
if transposed_matrix.shape == (2, 2): if transposed_matrix.shape == (2, 2):
@ -302,14 +306,24 @@ class LinearTransformationScene(VectorScene):
raise "Matrix has bad dimensions" raise "Matrix has bad dimensions"
return lambda point: np.dot(point, transposed_matrix) return lambda point: np.dot(point, transposed_matrix)
def get_piece_movement(self, pieces):
start = VMobject(*pieces)
target = VMobject(*[mob.target for mob in pieces])
if self.leave_ghost_vectors:
self.add(start.copy().fade(0.7))
return Transform(start, target, submobject_mode = "all_at_once")
def get_vector_movement(self, func): def get_vector_movement(self, func):
start = VMobject(*self.moving_vectors) for v in self.moving_vectors:
target = VMobject(*[ v.target = Vector(func(v.get_end()), color = v.get_color())
Vector(func(v.get_end()), color = v.get_color()) return self.get_piece_movement(self.moving_vectors)
for v in self.moving_vectors
]) def get_transformable_label_movement(self):
return Transform(start, target) for l in self.transformable_labels:
l.target = self.get_vector_label(
l.vector.target, l.target_text, **l.kwargs
)
return self.get_piece_movement(self.transformable_labels)
def apply_transposed_matrix(self, transposed_matrix, **kwargs): def apply_transposed_matrix(self, transposed_matrix, **kwargs):
func = self.get_matrix_transformation(transposed_matrix) func = self.get_matrix_transformation(transposed_matrix)
@ -322,18 +336,20 @@ class LinearTransformationScene(VectorScene):
self.apply_function(func, **kwargs) self.apply_function(func, **kwargs)
def apply_nonlinear_transformation(self, function, **kwargs): def apply_nonlinear_transformation(self, function, **kwargs):
self.plane.prepare_for_nonlinear_transform(100) self.plane.prepare_for_nonlinear_transform()
self.apply_function(function, **kwargs) self.apply_function(function, **kwargs)
def apply_function(self, function, **kwargs): def apply_function(self, function, added_anims = [], **kwargs):
if "run_time" not in kwargs: if "run_time" not in kwargs:
kwargs["run_time"] = 3 kwargs["run_time"] = 3
self.play( self.play(
ApplyPointwiseFunction( ApplyPointwiseFunction(
function, function,
VMobject(*self.transformable_mobject), VMobject(*self.transformable_mobjects),
), ),
self.get_vector_movement(function), self.get_vector_movement(function),
self.get_transformable_label_movement(),
*added_anims,
**kwargs **kwargs
) )

View file

@ -12,6 +12,7 @@ from scipy import linalg
from constants import * from constants import *
CLOSED_THRESHOLD = 0.01 CLOSED_THRESHOLD = 0.01
STRAIGHT_PATH_THRESHOLD = 0.01
def get_smooth_handle_points(points): def get_smooth_handle_points(points):
num_handles = len(points) - 1 num_handles = len(points) - 1
@ -262,7 +263,7 @@ def path_along_arc(arc_angle):
If vect is vector from start to end, [vect[:,1], -vect[:,0]] is If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
perpendicualr to vect in the left direction. perpendicualr to vect in the left direction.
""" """
if arc_angle == 0: if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
return straight_path return straight_path
def path(start_points, end_points, alpha): def path(start_points, end_points, alpha):
vects = end_points - start_points vects = end_points - start_points

View file

@ -314,7 +314,7 @@ class Mobject(object):
start = color_to_rgb(mob.get_color()) start = color_to_rgb(mob.get_color())
end = color_to_rgb(color) end = color_to_rgb(color)
new_rgb = interpolate(start, end, alpha) new_rgb = interpolate(start, end, alpha)
mob.highlight(Color(rgb = new_rgb)) mob.highlight(Color(rgb = new_rgb), family = False)
return self return self
def fade(self, darkness = 0.5): def fade(self, darkness = 0.5):

View file

@ -45,22 +45,22 @@ class VMobject(Mobject):
for mob in mobs: for mob in mobs:
if stroke_color is not None: if stroke_color is not None:
mob.stroke_rgb = color_to_rgb(stroke_color) mob.stroke_rgb = color_to_rgb(stroke_color)
if stroke_width is not None:
mob.stroke_width = stroke_width
if fill_color is not None: if fill_color is not None:
mob.fill_rgb = color_to_rgb(fill_color) mob.fill_rgb = color_to_rgb(fill_color)
if stroke_width is not None:
mob.stroke_width = stroke_width
if fill_opacity is not None: if fill_opacity is not None:
mob.fill_opacity = fill_opacity mob.fill_opacity = fill_opacity
probably_meant_to_change_opacity = reduce(op.and_, [
fill_color is not None,
fill_opacity is None,
mob.fill_opacity == 0
])
if probably_meant_to_change_opacity:
mob.fill_opacity = 1
return self return self
def set_fill(self, color = None, opacity = None, family = True): def set_fill(self, color = None, opacity = None, family = True):
probably_meant_to_change_opacity = reduce(op.and_, [
color is not None,
opacity is None,
self.fill_opacity == 0
])
if probably_meant_to_change_opacity:
opacity = 1
return self.set_style_data( return self.set_style_data(
fill_color = color, fill_color = color,
fill_opacity = opacity, fill_opacity = opacity,
@ -75,8 +75,11 @@ class VMobject(Mobject):
) )
def highlight(self, color, family = True): def highlight(self, color, family = True):
self.set_fill(color = color, family = family) self.set_style_data(
self.set_stroke(color = color, family = family) stroke_color = color,
fill_color = color,
family = family
)
return self return self
# def fade(self, darkness = 0.5): # def fade(self, darkness = 0.5):
@ -203,6 +206,13 @@ class VMobject(Mobject):
self.submobjects self.submobjects
) )
def apply_function(self, function, maintain_smoothness = True):
Mobject.apply_function(self, function)
if maintain_smoothness:
self.make_smooth()
return self
## Information about line ## Information about line
def component_curves(self): def component_curves(self):

View file

@ -226,12 +226,9 @@ class Bubble(SVGMobject):
return mobject return mobject
def add_content(self, mobject): def add_content(self, mobject):
if self.content in self.submobjects:
self.submobjects.remove(self.content)
self.position_mobject_inside(mobject) self.position_mobject_inside(mobject)
self.content = mobject self.content = mobject
self.add(self.content) return self.content
return self
def write(self, text): def write(self, text):
self.add_content(TextMobject(text)) self.add_content(TextMobject(text))
@ -296,11 +293,12 @@ class TeacherStudentsScene(Scene):
bubble.add_content(content) bubble.add_content(content)
if pi_creature.bubble: if pi_creature.bubble:
content_intro_anims = [ content_intro_anims = [
Transform(pi_creature.bubble, bubble) Transform(pi_creature.bubble, bubble),
Transform(pi_creature.bubble.content, bubble.content)
] ]
else: else:
content_intro_anims = [ content_intro_anims = [
FadeIn(bubble.split()[0]), FadeIn(bubble),
Write(content), Write(content),
] ]
pi_creature.bubble = bubble pi_creature.bubble = bubble
@ -324,7 +322,10 @@ class TeacherStudentsScene(Scene):
for p in self.get_everyone(): for p in self.get_everyone():
if p.bubble and p is not pi_creature: if p.bubble and p is not pi_creature:
added_anims.append(FadeOut(p.bubble)) added_anims += [
FadeOut(p.bubble),
FadeOut(p.bubble.content)
]
p.bubble = None p.bubble = None
added_anims.append(ApplyMethod(p.change_mode, "plain")) added_anims.append(ApplyMethod(p.change_mode, "plain"))
@ -335,24 +336,25 @@ class TeacherStudentsScene(Scene):
), ),
] ]
self.play(*anims) self.play(*anims)
return pi_creature.bubble
def teacher_says(self, content, **kwargs): def teacher_says(self, content, **kwargs):
self.introduce_bubble( return self.introduce_bubble(
content, "speech", self.get_teacher(), **kwargs content, "speech", self.get_teacher(), **kwargs
) )
def student_says(self, content, student_index = 1, **kwargs): def student_says(self, content, student_index = 1, **kwargs):
student = self.get_students()[student_index] student = self.get_students()[student_index]
self.introduce_bubble(content, "speech", student, **kwargs) return self.introduce_bubble(content, "speech", student, **kwargs)
def teacher_thinks(self, content, **kwargs): def teacher_thinks(self, content, **kwargs):
self.introduce_bubble( return self.introduce_bubble(
content, "thought", self.get_teacher(), **kwargs content, "thought", self.get_teacher(), **kwargs
) )
def student_thinks(self, content, student_index = 1, **kwargs): def student_thinks(self, content, student_index = 1, **kwargs):
student = self.get_students()[student_index] student = self.get_students()[student_index]
self.introduce_bubble(content, "thought", student, **kwargs) return self.introduce_bubble(content, "thought", student, **kwargs)
def random_blink(self, num_times = 1): def random_blink(self, num_times = 1):
for x in range(num_times): for x in range(num_times):

View file

@ -240,11 +240,11 @@ class NumberPlane(VMobject):
arrow = Arrow(ORIGIN, coords, **kwargs) arrow = Arrow(ORIGIN, coords, **kwargs)
return arrow return arrow
def prepare_for_nonlinear_transform(self, num_inserted_anchor_points = 40): def prepare_for_nonlinear_transform(self, num_inserted_anchor_points = 50):
for mob in self.submobject_family(): for mob in self.family_members_with_points():
if mob.get_num_points() > 0: mob.insert_n_anchor_points(num_inserted_anchor_points)
mob.insert_n_anchor_points(num_inserted_anchor_points) mob.make_smooth()
mob.change_anchor_mode("smooth") return self