Material needed for matrix as transform article

This commit is contained in:
Grant Sanderson
2015-10-08 11:14:55 -07:00
parent 9e22e51591
commit 5b986b9a9e
7 changed files with 310 additions and 48 deletions

View File

@ -57,7 +57,7 @@ class Rotating(Animation):
class RotationAsTransform(Rotating):
DEFAULT_CONFIG = {
"axes" : [IN],
"axes" : [OUT],
"radians" : np.pi / 2,
"run_time" : DEFAULT_ANIMATION_RUN_TIME,
"alpha_func" : smooth,

View File

@ -18,7 +18,7 @@ LOW_QUALITY_DISPLAY_CONFIG = {
DEFAULT_POINT_DENSITY_2D = 25
DEFAULT_POINT_DENSITY_1D = 150
DEFAULT_POINT_DENSITY_1D = 200
#TODO, Make sure these are not needd
DEFAULT_HEIGHT = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]

View File

@ -65,13 +65,15 @@ class NumberLine(Mobject1D):
"unit_length_to_spatial_width" : 1,
"tick_size" : 0.1,
"tick_frequency" : 0.5,
"leftmost_tick" : -int(SPACE_WIDTH),
"leftmost_tick" : None,
"number_at_center" : 0,
"numbers_with_elongated_ticks" : [0],
"longer_tick_multiple" : 2,
}
def __init__(self, **kwargs):
digest_config(self, NumberLine, kwargs)
if self.leftmost_tick is None:
self.leftmost_tick = -int(self.numerical_radius)
self.left_num = self.number_at_center - self.numerical_radius
self.right_num = self.number_at_center + self.numerical_radius
Mobject1D.__init__(self, **kwargs)
@ -128,20 +130,12 @@ class NumberLine(Mobject1D):
return 4*DOWN*self.tick_size
def get_number_mobjects(self, *numbers):
#TODO, handle decimals
if len(numbers) == 0:
numbers = self.default_numbers_to_display()
log_spacing = int(np.log10(self.tick_frequency))
if log_spacing < 0:
num_decimal_places = 2-log_spacing
else:
num_decimal_places = 1+log_spacing
result = []
for number in numbers:
if number < 0:
num_string = str(number)[:num_decimal_places+1]
else:
num_string = str(number)[:num_decimal_places]
mob = tex_mobject(num_string)
mob = tex_mobject(str(int(number)))
vert_scale = 2*self.tick_size/mob.get_height()
hori_scale = self.tick_frequency*self.unit_length_to_spatial_width/mob.get_width()
mob.scale(min(vert_scale, hori_scale))
@ -152,10 +146,12 @@ class NumberLine(Mobject1D):
def add_numbers(self, *numbers):
self.add(*self.get_number_mobjects(*numbers))
return self
def remove_numbers(self):
self.points = self.points[:self.number_of_points_without_numbers]
self.rgbs = self.rgbs[:self.number_of_points_without_numbers]
return self
class UnitInterval(NumberLine):
DEFAULT_CONFIG = {

View File

@ -10,18 +10,16 @@ from animation import *
from mobject import *
from constants import *
from region import *
from scene import Scene, NumberLineScene
from scene import Scene
from script_wrapper import command_line_create_scene
class SampleScene(NumberLineScene):
class SampleScene(Scene):
def construct(self):
NumberLineScene.construct(self)
arrow = Arrow(2*RIGHT+UP, 2*RIGHT)
self.add(arrow)
self.dither(2)
self.zoom_in_on(2.4, zoom_factor = 10)
self.dither(2)
plane = NumberPlane(density = 400)
arrow1 = Arrow(ORIGIN, UP, color = "green")
arrow2 = Arrow(ORIGIN, LEFT, color = "Red")
self.add(plane, arrow1, arrow2)
if __name__ == "__main__":
command_line_create_scene()

View File

@ -73,6 +73,20 @@ class NumberLineScene(Scene):
self.add(self.number_line, *self.number_mobs)
self.add(*additional_mobjects)
def show_multiplication(self, num, **kwargs):
if "interpolation_function" not in kwargs:
if num > 0:
kwargs["interpolation_function"] = straight_path
else:
kwargs["interpolation_function"] = counterclockwise_path
self.play(*[
ApplyMethod(self.number_line.stretch, num, 0, **kwargs)
]+[
ApplyMethod(mob.shift, (num-1)*mob.get_center()[0]*RIGHT, **kwargs)
for mob in self.number_mobs
])

View File

@ -43,6 +43,8 @@ class Scene(object):
pass #To be implemented in subclasses
def __str__(self):
if hasattr(self, "name"):
return self.name
return self.__class__.__name__
def set_name(self, name):

View File

@ -38,6 +38,7 @@ class ShowMultiplication(NumberLineScene):
(-3, False),
(-3, True),
(2, True),
(6, True),
]
@staticmethod
def args_to_string(num, show_original_line):
@ -45,30 +46,17 @@ class ShowMultiplication(NumberLineScene):
return str(num) + end_string
def construct(self, num, show_original_line):
NumberLineScene.construct(self, density = abs(num)*DEFAULT_POINT_DENSITY_1D)
config = {"density" : abs(num)*DEFAULT_POINT_DENSITY_1D}
if abs(num) < 1:
config["numerical_radius"] = SPACE_WIDTH/num
NumberLineScene.construct(self, **config)
if show_original_line:
self.copy_original_line()
kwargs = {
"run_time" : 2.0,
"interpolation_function" : straight_path if num > 0 else counterclockwise_path
}
self.dither()
new_number_line = deepcopy(self.number_line)
new_number_line.stretch(num, 0)
self.play(
Transform(self.number_line, new_number_line, **kwargs),
*[
ApplyFunction(
lambda m : m.do_in_place(m.stretch, 1.0/num, 0).stretch(num, 0),
mobject,
**kwargs
)
for mobject in self.number_mobs
]
)
self.show_multiplication(num, run_time = 2.0)
self.dither()
def copy_original_line(self):
copied_line = deepcopy(self.number_line)
copied_num_mobs = deepcopy(self.number_mobs)
@ -83,15 +71,76 @@ class ShowMultiplication(NumberLineScene):
)
self.dither()
class ExamplesOfOneDimensionalLinearTransforms(ShowMultiplication):
args_list = []
@staticmethod
def args_to_string():
return ""
def construct(self):
for num in [2, 0.5, -3]:
ShowMultiplication.construct(self, num, False)
self.clear()
class ExamplesOfNonlinearOneDimensionalTransforms(NumberLineScene):
def construct(self):
def sinx_plux_x((x, y, z)):
return (np.sin(x) + 1.2*x, y, z)
def shift_zero((x, y, z)):
return (2*x+4, y, z)
self.nonlinear = text_mobject("Not a Linear Transform")
self.nonlinear.highlight("red").to_edge(UP)
pairs = [
(sinx_plux_x, "numbers don't remain evenly spaced"),
(shift_zero, "zero does not remain fixed")
]
for func, explanation in pairs:
self.dither()
self.run_function(func, explanation)
self.dither()
def run_function(self, function, explanation):
self.clear()
self.add(self.nonlinear)
NumberLineScene.construct(self)
words = text_mobject(explanation).highlight("red")
words.next_to(self.nonlinear, DOWN, buff = 0.5)
self.add(words)
self.play(
ApplyPointwiseFunction(function, self.number_line),
*[
ApplyMethod(
mob.shift,
function(mob.get_center()) - mob.get_center()
)
for mob in self.number_mobs
],
run_time = 2.0
)
class ShowTwoThenThree(ShowMultiplication):
args_list = []
@staticmethod
def args_to_string():
return ""
def construct(self):
NumberLineScene.construct(self, density = 10*DEFAULT_POINT_DENSITY_1D)
self.copy_original_line()
self.show_multiplication(2)
self.dither()
self.show_multiplication(3)
self.dither()
########################################################
class TransformScene2D(Scene):
def add_number_plane(self, density_factor, use_faded_lines = True):
def add_number_plane(self, density_factor = 1, use_faded_lines = True):
config = {
"x_radius" : 2*SPACE_WIDTH,
"y_radius" : 2*SPACE_HEIGHT,
@ -131,13 +180,12 @@ class TransformScene2D(Scene):
class ShowMatrixTransform(TransformScene2D):
args_list = [
([[1, 2], [3, 4]], True, False),
([[1, 3], [-2, 0]], False, False),
([[1, 3], [-2, 0]], True, True),
([[0, -1], [1, 0]], True, False),
([[0, -1], [1, 0]], False, False),
([[1, 0.5], [0.5, 1]], True, False),
([[2, 0], [0, 2]], True, False),
([[0.5, 0], [0, 0.5]], True, False),
([[-1, 0], [0, -1]], True, False),
([[-1, 0], [0, -1]], False, False),
([[0, 1], [1, 0]], True, False),
([[-2, 0], [-1, -1]], True, False),
]
@staticmethod
def args_to_string(matrix, with_background, show_matrix):
@ -157,6 +205,7 @@ class ShowMatrixTransform(TransformScene2D):
self.add_x_y_arrows()
else:
self.add_number_plane(**number_plane_config)
self.save_image()
if show_matrix:
self.add(matrix_mobject(matrix).to_corner(UP+LEFT))
def func(mobject):
@ -184,6 +233,199 @@ class ShowMatrixTransform(TransformScene2D):
anims.append(Transform(arrow, new_arrow, **kwargs))
self.play(*anims)
self.dither()
self.set_name(str(self) + self.args_to_string(matrix, with_background, show_matrix))
self.save_image(os.path.join(MOVIE_DIR, MOVIE_PREFIX, "images"))
def get_density_factor(self, matrix):
max_norm = max([
abs(np.linalg.norm(column))
for column in np.transpose(matrix)
])
return max(max_norm, 1)
def get_interpolation_function(self, matrix):
def toggled_sign(n, i):
return int(i%2 == 0)^int(n >= 0)
rotational_components = [
sign*np.arccos(matrix[i,i]/np.linalg.norm(matrix[:,i]))
for i in [0, 1]
for sign in [toggled_sign(matrix[1-i, i], i)]
]
average_rotation = sum(rotational_components)/2
if abs(average_rotation) < np.pi / 2:
return straight_path
elif average_rotation > 0:
return counterclockwise_path
else:
return clockwise_path
class ExamplesOfTwoDimensionalLinearTransformations(ShowMatrixTransform):
args_list = []
@staticmethod
def args_to_string():
return ""
def construct(self):
matrices = [
[[1, 0.5],
[0.5, 1]],
[[0, -1],
[2, 0]],
[[1, 3],
[-2, 0]],
]
for matrix in matrices:
self.clear()
ShowMatrixTransform.construct(self, matrix, False, False)
class ExamplesOfNonlinearTwoDimensionalTransformations(Scene):
def construct(self):
Scene.construct(self)
def squiggle((x, y, z)):
return (x+np.sin(y), y+np.cos(x), z)
def shift_zero((x, y, z)):
return (2*x + 3*y + 4, -1*x+y+2, z)
self.nonlinear = text_mobject("Nonlinear Transform")
self.nonlinear.highlight("red").to_edge(UP)
pairs = [
(squiggle, "lines to not remain straight"),
(shift_zero, "the origin does not remain fixed")
]
for function, explanation in pairs:
self.apply_function(function, explanation)
def apply_function(self, function, explanation):
self.clear()
number_plane = NumberPlane(
x_radius = 1.5*SPACE_WIDTH,
y_radius = 1.5*SPACE_HEIGHT,
density = 3*DEFAULT_POINT_DENSITY_1D,
)
numbers = number_plane.get_coordinate_labels()
words = text_mobject(explanation).highlight("red")
words.next_to(self.nonlinear, DOWN, buff = 0.5)
self.add(number_plane, self.nonlinear, words, *numbers)
self.dither()
self.play(
ApplyPointwiseFunction(function, number_plane),
*[
ApplyMethod(
mob.shift,
function(mob.get_center())-mob.get_center()
)
for mob in numbers
],
run_time = 2.0
)
self.dither(2)
class TrickyExamplesOfNonlinearTwoDimensionalTransformations(Scene):
def construct(self):
number_plane = NumberPlane()
phrase1, phrase2 = text_mobject([
"These might look like they keep lines straight...",
"but diagonal lines get curved"
]).to_edge(UP).split()
phrase2.highlight("red")
diagonal = Line(
DOWN*SPACE_HEIGHT+LEFT*SPACE_WIDTH,
UP*SPACE_HEIGHT+RIGHT*SPACE_WIDTH
)
def sunrise((x, y, z)):
return ((SPACE_HEIGHT+y)*x, y, z)
def squished((x, y, z)):
return (x + np.sin(x), y+np.sin(y), z)
self.add(phrase1)
self.run_function(sunrise, number_plane)
self.run_function(squished, number_plane)
self.add(phrase2)
self.play(ShowCreation(diagonal))
self.remove(diagonal)
number_plane.add(diagonal)
self.run_function(sunrise, number_plane)
self.run_function(squished, number_plane)
def run_function(self, function, plane):
number_plane = deepcopy(plane)
self.add(number_plane)
self.dither()
self.play(ApplyPointwiseFunction(function, number_plane, run_time = 2.0))
self.dither(3)
self.remove(number_plane)
############# HORRIBLE! ##########################
class ShowMatrixTransformHack(TransformScene2D):
args_list = [
([[1, 3], [-2, 0]], True, False),
]
@staticmethod
def args_to_string(matrix, with_background, show_matrix):
background_string = "WithBackground" if with_background else "WithoutBackground"
show_string = "ShowingMatrix" if show_matrix else ""
return matrix_to_string(matrix) + background_string + show_string
def construct(self, matrix, with_background, show_matrix):
matrix = np.array(matrix)
number_plane_config = {
"density_factor" : self.get_density_factor(matrix)
}
if with_background:
self.add_background()
number_plane_config["use_faded_lines"] = False
self.add_number_plane(**number_plane_config)
self.add_x_y_arrows()
else:
self.add_number_plane(**number_plane_config)
if show_matrix:
self.add(matrix_mobject(matrix).to_corner(UP+LEFT))
def func(mobject):
mobject.points[:, :2] = np.dot(mobject.points[:, :2], np.transpose(matrix))
return mobject
dot = Dot((-1, 2, 0), color = "yellow")
x_arrow_copy = deepcopy(self.x_arrow)
y_arrow_copy = Arrow(LEFT, LEFT+2*UP, color = "red")
self.number_plane.add(dot)
self.play(ApplyMethod(x_arrow_copy.rotate, np.pi))
self.play(ShowCreation(y_arrow_copy))
self.dither()
self.remove(x_arrow_copy, y_arrow_copy)
kwargs = {
"run_time" : 2.0,
"interpolation_function" : self.get_interpolation_function(matrix)
}
anims = [ApplyFunction(func, self.number_plane, **kwargs)]
if hasattr(self, "x_arrow") and hasattr(self, "y_arrow"):
for arrow, index in (self.x_arrow, 0), (self.y_arrow, 1):
new_arrow = Arrow(
ORIGIN,
self.number_plane.num_pair_to_point(matrix[:,index]),
color = arrow.get_color()
)
arrow.remove_tip()
new_arrow.remove_tip()
Mobject.align_data(arrow, new_arrow)
arrow.add_tip()
new_arrow.add_tip()
anims.append(Transform(arrow, new_arrow, **kwargs))
self.play(*anims)
self.dither()
x_arrow_copy = deepcopy(self.x_arrow)
y_arrow_copy = Arrow(LEFT+2*UP, 5*RIGHT+2*UP, color = "red")
self.play(ApplyMethod(x_arrow_copy.rotate, np.pi))
self.play(ShowCreation(y_arrow_copy))
self.remove(x_arrow_copy, y_arrow_copy)
self.dither(3)
def get_density_factor(self, matrix):
max_norm = max([
@ -207,8 +449,18 @@ class ShowMatrixTransform(TransformScene2D):
return clockwise_path
class Show90DegreeRotation(TransformScene2D):
def construct(self):
self.add_number_plane()
self.add_background()
self.add_x_y_arrows()
self.dither()
self.play(*[
RotationAsTransform(mob, run_time = 2.0)
for mob in self.number_plane, self.x_arrow, self.y_arrow
])
self.dither()