diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 54e1dae3..e1f10fb7 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -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, diff --git a/constants.py b/constants.py index 16a8ce32..40fdaf68 100644 --- a/constants.py +++ b/constants.py @@ -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"] diff --git a/mobject/function_graphs.py b/mobject/function_graphs.py index fbf216b4..08e8ebbc 100644 --- a/mobject/function_graphs.py +++ b/mobject/function_graphs.py @@ -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 = { diff --git a/sample_script.py b/sample_script.py index cf44a9db..0f1c64c7 100644 --- a/sample_script.py +++ b/sample_script.py @@ -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() \ No newline at end of file diff --git a/scene/number_line.py b/scene/number_line.py index e858c0e3..9b1a3c1a 100644 --- a/scene/number_line.py +++ b/scene/number_line.py @@ -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 + ]) + + diff --git a/scene/scene.py b/scene/scene.py index 9da4bcd3..bae92d1e 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -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): diff --git a/scripts/matrix_as_transform_2d.py b/scripts/matrix_as_transform_2d.py index bc3b0ac5..2477016f 100644 --- a/scripts/matrix_as_transform_2d.py +++ b/scripts/matrix_as_transform_2d.py @@ -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()