Chopping stuff up in Moser

This commit is contained in:
Grant Sanderson
2015-04-30 15:26:56 -07:00
parent 80f5c39b24
commit e230b48adb
8 changed files with 736 additions and 402 deletions

View File

@ -34,7 +34,8 @@ for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR, TMP_IMAGE_DIR, TEX_IMAGE_
PDF_DENSITY = 800 PDF_DENSITY = 800
SIZE_TO_REPLACE = "SizeHere" SIZE_TO_REPLACE = "SizeHere"
TEX_TEXT_TO_REPLACE = "YourTextHere" TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex") TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex")
TEMPLATE_TEXT_FILE = os.path.join(TEX_DIR, "text_template.tex")
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png") LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")

View File

@ -72,6 +72,9 @@ def there_and_back(t, inflection = 10.0):
def not_quite_there(t, proportion = 0.7): def not_quite_there(t, proportion = 0.7):
return proportion*high_inflection_0_to_1(t) return proportion*high_inflection_0_to_1(t)
def wiggle(t, wiggles = 2):
return there_and_back(t) * np.sin(wiggles*np.pi*t)
### Functional Functions ### ### Functional Functions ###
def composition(func_list): def composition(func_list):

View File

@ -86,11 +86,16 @@ class VideoIcon(ImageMobject):
ImageMobject.__init__(self, "video_icon", *args, **kwargs) ImageMobject.__init__(self, "video_icon", *args, **kwargs)
self.scale(0.3) self.scale(0.3)
def text_mobject(text, size = r"\Large"):
image = tex_to_image(text, size, TEMPLATE_TEXT_FILE)
assert(not isinstance(image, list))
return ImageMobject(image)
#Purely redundant function to make singulars and plurals sensible #Purely redundant function to make singulars and plurals sensible
def tex_mobject(expression, size = "\HUGE"): def tex_mobject(expression, size = r"\HUGE"):
return tex_mobjects(expression, size) return tex_mobjects(expression, size)
def tex_mobjects(expression, size = "\HUGE"): def tex_mobjects(expression, size = r"\HUGE"):
images = tex_to_image(expression, size) images = tex_to_image(expression, size)
if isinstance(images, list): if isinstance(images, list):
#TODO, is checking listiness really the best here? #TODO, is checking listiness really the best here?

511
mobject.py Normal file
View File

@ -0,0 +1,511 @@
import numpy as np
import itertools as it
import os
from PIL import Image
from random import random
from copy import deepcopy
from colour import Color
from constants import *
from helpers import *
import displayer as disp
class Mobject(object):
"""
Mathematical Object
"""
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
DIM = 3
DEFAULT_COLOR = Color("skyblue")
SHOULD_BUFF_POINTS = GENERALLY_BUFF_POINTS
def __init__(self,
color = None,
name = None,
center = None,
):
self.color = Color(color) if color else Color(self.DEFAULT_COLOR)
if not hasattr(self, "name"):
self.name = name or self.__class__.__name__
self.has_normals = hasattr(self, 'unit_normal')
self.points = np.zeros((0, 3))
self.rgbs = np.zeros((0, 3))
if self.has_normals:
self.unit_normals = np.zeros((0, 3))
self.generate_points()
if center:
self.center().shift(center)
def __str__(self):
return self.name
def show(self):
Image.fromarray(disp.paint_mobject(self)).show()
def save_image(self, name = None):
Image.fromarray(disp.paint_mobject(self)).save(
os.path.join(MOVIE_DIR, (name or str(self)) + ".png")
)
def add_points(self, points, rgbs = None, color = None):
"""
points must be a Nx3 numpy array, as must rgbs if it is not None
"""
points = np.array(points)
num_new_points = points.shape[0]
self.points = np.append(self.points, points)
self.points = self.points.reshape((self.points.size / 3, 3))
if rgbs is None:
color = Color(color) if color else self.color
rgbs = np.array([color.get_rgb()] * num_new_points)
else:
if rgbs.shape != points.shape:
raise Exception("points and rgbs must have same shape")
self.rgbs = np.append(self.rgbs, rgbs).reshape(self.points.shape)
if self.has_normals:
self.unit_normals = np.append(
self.unit_normals,
np.array([self.unit_normal(point) for point in points])
).reshape(self.points.shape)
return self
def rotate(self, angle, axis = [0, 0, 1]):
t_rotation_matrix = np.transpose(rotation_matrix(angle, axis))
self.points = np.dot(self.points, t_rotation_matrix)
if self.has_normals:
self.unit_normals = np.dot(self.unit_normals, t_rotation_matrix)
return self
def rotate_in_place(self, angle, axis = [0, 0, 1]):
center = self.get_center()
self.shift(-center)
self.rotate(angle, axis)
self.shift(center)
return self
def shift(self, vector):
cycle = it.cycle(vector)
v = np.array([cycle.next() for x in range(self.points.size)]).reshape(self.points.shape)
self.points += v
return self
def center(self):
self.shift(-self.get_center())
return self
def scale(self, scale_factor):
self.points *= scale_factor
return self
def scale_in_place(self, scale_factor):
center = self.get_center()
return self.center().scale(scale_factor).shift(center)
def add(self, *mobjects):
for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs)
return self
def repeat(self, count):
#Can make transition animations nicer
points, rgbs = deepcopy(self.points), deepcopy(self.rgbs)
for x in range(count - 1):
self.add_points(points, rgbs)
return self
def get_num_points(self):
return self.points.shape[0]
def pose_at_angle(self):
self.rotate(np.pi / 7)
self.rotate(np.pi / 7, [1, 0, 0])
return self
def apply_function(self, function):
self.points = np.apply_along_axis(function, 1, self.points)
return self
def apply_complex_function(self, function):
def point_map((x, y, z)):
result = function(complex(x, y))
return (result.real, result.imag, 0)
return self.apply_function(point_map)
def highlight(self, color = "red", condition = lambda x : True):
"""
Condition is function which takes in one arguments, (x, y, z).
"""
#TODO, Should self.color change?
to_change = np.apply_along_axis(condition, 1, self.points)
self.rgbs[to_change, :] *= 0
self.rgbs[to_change, :] += Color(color).get_rgb()
return self
def fade(self, amount = 0.5):
self.rgbs *= amount
return self
def filter_out(self, condition):
to_eliminate = ~np.apply_along_axis(condition, 1, self.points)
self.points = self.points[to_eliminate]
self.rgbs = self.rgbs[to_eliminate]
return self
### Getters ###
def get_center(self):
return np.apply_along_axis(np.mean, 0, self.points)
def get_width(self):
return np.max(self.points[:, 0]) - np.min(self.points[:, 0])
def get_height(self):
return np.max(self.points[:, 1]) - np.min(self.points[:, 1])
### Stuff subclasses should deal with ###
def should_buffer_points(self):
# potentially changed in subclasses
return GENERALLY_BUFF_POINTS
def generate_points(self):
#Typically implemented in subclass, unless purposefully left blank
pass
### Static Methods ###
def align_data(mobject1, mobject2):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
if count1 == 0:
mobject1.add_points([(0, 0, 0)])
if count2 == 0:
mobject2.add_points([(0, 0, 0)])
if count1 == count2:
return
for attr in ['points', 'rgbs']:
new_arrays = make_even(getattr(mobject1, attr), getattr(mobject2, attr))
for array, mobject in zip(new_arrays, [mobject1, mobject2]):
setattr(mobject, attr, np.array(array))
def interpolate(mobject1, mobject2, target_mobject, alpha):
"""
Turns target_mobject into an interpolation between mobject1
and mobject2.
"""
Mobject.align_data(mobject1, mobject2)
for attr in ['points', 'rgbs']:
new_array = (1 - alpha) * getattr(mobject1, attr) + \
alpha * getattr(mobject2, attr)
setattr(target_mobject, attr, new_array)
class Mobject1D(Mobject):
def __init__(self, density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs):
self.epsilon = 1.0 / density
Mobject.__init__(self, *args, **kwargs)
class Mobject2D(Mobject):
def __init__(self, density = DEFAULT_POINT_DENSITY_2D, *args, **kwargs):
self.epsilon = 1.0 / density
Mobject.__init__(self, *args, **kwargs)
class CompoundMobject(Mobject):
def __init__(self, *mobjects):
Mobject.__init__(self)
for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs)
###### Concrete Mobjects ########
class Stars(Mobject):
DEFAULT_COLOR = "white"
SHOULD_BUFF_POINTS = False
def __init__(self, num_points = DEFAULT_NUM_STARS,
*args, **kwargs):
self.num_points = num_points
Mobject.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
(
r * np.sin(phi)*np.cos(theta),
r * np.sin(phi)*np.sin(theta),
r * np.cos(phi)
)
for x in range(self.num_points)
for r, phi, theta in [[
max(SPACE_HEIGHT, SPACE_WIDTH) * random(),
np.pi * random(),
2 * np.pi * random(),
]]
])
class Point(Mobject):
def __init__(self, point = (0, 0, 0), *args, **kwargs):
Mobject.__init__(self, *args, **kwargs)
self.points = np.array(point).reshape(1, 3)
self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3)
class Arrow(Mobject1D):
DEFAULT_COLOR = "white"
NUNGE_DISTANCE = 0.1
def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0),
length = 1, tip_length = 0.25,
normal = (0, 0, 1), *args, **kwargs):
self.point = np.array(point)
self.direction = np.array(direction) / np.linalg.norm(direction)
self.normal = np.array(normal)
self.length = length
self.tip_length = tip_length
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
[x, x, x] * self.direction + self.point
for x in np.arange(-self.length, 0, self.epsilon)
])
tips_dir = np.array(-self.direction), np.array(-self.direction)
for i, sgn in zip([0, 1], [-1, 1]):
tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
self.add_points([
[x, x, x] * tips_dir[i] + self.point
for x in np.arange(0, self.tip_length, self.epsilon)
for i in [0, 1]
])
def nudge(self):
return self.shift(-self.direction * self.NUNGE_DISTANCE)
class Vector(Arrow):
def __init__(self, point = (1, 0, 0), *args, **kwargs):
length = np.linalg.norm(point)
Arrow.__init__(self, point = point, direction = point,
length = length, tip_length = 0.2 * length,
*args, **kwargs)
class Dot(Mobject1D): #Use 1D density, even though 2D
DEFAULT_COLOR = "white"
def __init__(self, center = (0, 0, 0), radius = 0.05, *args, **kwargs):
center = np.array(center)
if center.size == 1:
raise Exception("Center must have 2 or 3 coordinates!")
elif center.size == 2:
center = np.append(center, [0])
self.center_point = center
self.radius = radius
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center_point
for t in np.arange(0, self.radius, self.epsilon)
for theta in np.arange(0, 2 * np.pi, self.epsilon)
])
class Cross(Mobject1D):
RADIUS = 0.3
DEFAULT_COLOR = "white"
def generate_points(self):
self.add_points([
(sgn * x, x, 0)
for x in np.arange(-self.RADIUS / 2, self.RADIUS/2, self.epsilon)
for sgn in [-1, 1]
])
class Line(Mobject1D):
def __init__(self, start, end, density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs):
self.start = np.array(start)
self.end = np.array(end)
density *= np.linalg.norm(self.start - self.end)
Mobject1D.__init__(self, density = density, *args, **kwargs)
def generate_points(self):
self.add_points([
t * self.end + (1 - t) * self.start
for t in np.arange(0, 1, self.epsilon)
])
class CurvedLine(Line):
def generate_points(self):
equidistant_point = rotate_vector(
self.end - self.start,
np.pi/3, [0,0,1]
) + self.start
self.add_points([
(1 - t*(1-t))*(t*self.end + (1-t)*self.start) \
+ t*(1-t)*equidistant_point
for t in np.arange(0, 1, self.epsilon)
])
self.ep = equidistant_point
class CubeWithFaces(Mobject2D):
def generate_points(self):
self.add_points([
sgn * np.array(coords)
for x in np.arange(-1, 1, self.epsilon)
for y in np.arange(x, 1, self.epsilon)
for coords in it.permutations([x, y, 1])
for sgn in [-1, 1]
])
self.pose_at_angle()
def unit_normal(self, coords):
return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords))
class Cube(Mobject1D):
DEFAULT_COLOR = "yellow"
def generate_points(self):
self.add_points([
([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]])
for p in [(0, 1, 2), (2, 0, 1), (1, 2, 0)]
for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon))
])
self.pose_at_angle()
class Sphere(Mobject2D):
def generate_points(self):
self.add_points([
(
np.sin(phi) * np.cos(theta),
np.sin(phi) * np.sin(theta),
np.cos(phi)
)
for phi in np.arange(self.epsilon, np.pi, self.epsilon)
for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi))
])
def unit_normal(self, coords):
return np.array(coords) / np.linalg.norm(coords)
class Circle(Mobject1D):
DEFAULT_COLOR = "red"
def generate_points(self):
self.add_points([
(np.cos(theta), np.sin(theta), 0)
for theta in np.arange(0, 2 * np.pi, self.epsilon)
])
class FunctionGraph(Mobject1D):
DEFAULT_COLOR = "lightblue"
def __init__(self, function, x_range = [-10, 10], *args, **kwargs):
self.function = function
self.x_min = x_range[0] / SPACE_WIDTH
self.x_max = x_range[1] / SPACE_WIDTH
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
scale_factor = 2.0 * SPACE_WIDTH / (self.x_max - self.x_min)
self.epsilon /= scale_factor
self.add_points([
np.array([x, self.function(x), 0])
for x in np.arange(self.x_min, self.x_max, self.epsilon)
])
self.scale(scale_factor)
class ParametricFunction(Mobject):
DEFAULT_COLOR = "lightblue"
def __init__(self,
function,
dim = 1,
expected_measure = 10.0,
density = None,
*args,
**kwargs):
self.function = function
self.dim = dim
self.expected_measure = expected_measure
if density:
self.epsilon = 1.0 / density
elif self.dim == 1:
self.epsilon = 1.0 / expected_measure / DEFAULT_POINT_DENSITY_1D
else:
self.epsilon = 1.0 / np.sqrt(expected_measure) / DEFAULT_POINT_DENSITY_2D
Mobject.__init__(self, *args, **kwargs)
def generate_points(self):
if self.dim == 1:
self.add_points([
self.function(t)
for t in np.arange(-1, 1, self.epsilon)
])
if self.dim == 2:
self.add_points([
self.function(s, t)
for t in np.arange(-1, 1, self.epsilon)
for s in np.arange(-1, 1, self.epsilon)
])
class Grid(Mobject1D):
DEFAULT_COLOR = "green"
def __init__(self,
radius = max(SPACE_HEIGHT, SPACE_WIDTH),
interval_size = 1.0,
subinterval_size = 0.5,
*args, **kwargs):
self.radius = radius
self.interval_size = interval_size
self.subinterval_size = subinterval_size
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
(sgns[0] * x, sgns[1] * y, 0)
for beta in np.arange(0, self.radius, self.interval_size)
for alpha in np.arange(0, self.radius, self.epsilon)
for sgns in it.product((-1, 1), (-1, 1))
for x, y in [(alpha, beta), (beta, alpha)]
])
if self.subinterval_size:
si = self.subinterval_size
color = Color(self.color)
color.set_rgb([x/2 for x in color.get_rgb()])
self.add_points([
(sgns[0] * x, sgns[1] * y, 0)
for beta in np.arange(0, self.radius, si)
if abs(beta % self.interval_size) > self.epsilon
for alpha in np.arange(0, self.radius, self.epsilon)
for sgns in it.product((-1, 1), (-1, 1))
for x, y in [(alpha, beta), (beta, alpha)]
], color = color)
class NumberLine(Mobject1D):
def __init__(self,
radius = SPACE_WIDTH,
interval_size = 0.5, tick_size = 0.1,
with_numbers = False, *args, **kwargs):
self.radius = int(radius) + 1
self.interval_size = interval_size
self.tick_size = tick_size
self.with_numbers = with_numbers
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
(x, 0, 0)
for x in np.arange(-self.radius, self.radius, self.epsilon)
])
self.add_points([
(0, y, 0)
for y in np.arange(-2*self.tick_size, 2*self.tick_size, self.epsilon)
])
self.add_points([
(x, y, 0)
for x in np.arange(-self.radius, self.radius, self.interval_size)
for y in np.arange(-self.tick_size, self.tick_size, self.epsilon)
])
if self.with_numbers:
#TODO, test
vertical_displacement = -0.3
nums = range(-self.radius, self.radius)
nums = map(lambda x : x / self.interval_size, nums)
mobs = tex_mobjects(*[str(num) for num in nums])
for num, mob in zip(nums, mobs):
mob.center().shift([num, vertical_displacement, 0])
self.add(*mobs)
# class ComplexPlane(Grid):
# def __init__(self, *args, **kwargs):
# Grid.__init__(self, *args, **kwargs)
# self.add(Dot())

View File

@ -1,345 +0,0 @@
from PIL import Image
from colour import Color
import numpy as np
import warnings
import time
import os
import copy
import progressbar
import inspect
from images2gif import writeGif
from helpers import *
from mobject import *
import displayer as disp
class MobjectMovement(object):
def __init__(self,
mobject,
run_time = DEFAULT_ANIMATION_RUN_TIME,
alpha_func = high_inflection_0_to_1,
name = None):
if isinstance(mobject, type) and issubclass(mobject, Mobject):
self.mobject = mobject()
elif isinstance(mobject, Mobject):
self.mobject = mobject
else:
raise Exception("Invalid mobject parameter, must be \
subclass or instance of Mobject")
self.starting_mobject = copy.deepcopy(self.mobject)
self.reference_mobjects = [self.starting_mobject]
self.alpha_func = alpha_func or (lambda x : x)
self.run_time = run_time
#TODO, Adress the idea of filtering the mobmov
self.filter_functions = []
self.restricted_height = SPACE_HEIGHT
self.restricted_width = SPACE_WIDTH
self.spacial_center = np.zeros(3)
self.name = name or self.__class__.__name__ + str(self.mobject)
def __str__(self):
return self.name
def get_points_and_rgbs(self):
"""
It is the responsibility of this class to only emit points within
the space. Returns np array of points and corresponding np array
of rgbs
"""
#TODO, I don't think this should be necessary. This should happen
#under the individual mobjects.
points = self.mobject.points
rgbs = self.mobject.rgbs
#Filters out what is out of bounds.
admissibles = (abs(points[:,0]) < self.restricted_width) * \
(abs(points[:,1]) < self.restricted_height)
for filter_function in self.filter_functions:
admissibles *= ~filter_function(points)
if any(self.spacial_center):
points += self.spacial_center
#Filter out points pushed off the edge
admissibles *= (abs(points[:,0]) < SPACE_WIDTH) * \
(abs(points[:,1]) < SPACE_HEIGHT)
if rgbs.shape[0] < points.shape[0]:
#TODO, this shouldn't be necessary, find what's happening.
points = points[:rgbs.shape[0], :]
admissibles = admissibles[:rgbs.shape[0]]
return points[admissibles, :], rgbs[admissibles, :]
def update(self, alpha):
if alpha < 0:
alpha = 0
if alpha > 1:
alpha = 1
self.update_mobject(self.alpha_func(alpha))
def filter_out(self, *filter_functions):
self.filter_functions += filter_functions
return self
def restrict_height(self, height):
self.restricted_height = min(height, SPACE_HEIGHT)
return self
def restrict_width(self, width):
self.restricted_width = min(width, SPACE_WIDTH)
return self
def shift(self, vector):
self.spacial_center += vector
return self
def set_run_time(self, time):
self.run_time = time
return self.reload()
def set_alpha_func(self, alpha_func):
if alpha_func is None:
alpha_func = lambda x : x
self.alpha_func = alpha_func
return self
def set_name(self, name):
self.name = name
return self
# def drag_pixels(self):
# self.frames = drag_pixels(self.get_frames())
# return self
# def reverse(self):
# self.get_frames().reverse()
# self.name = 'Reversed' + str(self)
# return self
def update_mobject(self, alpha):
#Typically ipmlemented by subclass
pass
def clean_up(self):
pass
###### Concrete MobjectMovement ########
class Rotating(MobjectMovement):
def __init__(self,
mobject,
axis = None,
axes = [[0, 0, 1], [0, 1, 0]],
radians = 2 * np.pi,
run_time = 20.0,
alpha_func = None,
*args, **kwargs):
MobjectMovement.__init__(
self, mobject,
run_time = run_time,
alpha_func = alpha_func,
*args, **kwargs
)
self.axes = [axis] if axis else axes
self.radians = radians
def update_mobject(self, alpha):
self.mobject.points = self.starting_mobject.points
for axis in self.axes:
self.mobject.rotate(
self.radians * alpha,
axis
)
class RotationAsTransform(Rotating):
def __init__(self, mobject, radians, axis = (0, 0, 1), axes = None,
run_time = DEFAULT_ANIMATION_RUN_TIME,
alpha_func = high_inflection_0_to_1,
*args, **kwargs):
Rotating.__init__(
self,
mobject,
axis = axis,
axes = axes,
run_time = run_time,
radians = radians,
alpha_func = alpha_func,
)
class FadeOut(MobjectMovement):
def update_mobject(self, alpha):
self.mobject.rgbs = self.starting_mobject.rgbs * (1 - alpha)
class Reveal(MobjectMovement):
def update_mobject(self, alpha):
self.mobject.rgbs = self.starting_mobject.rgbs * alpha
if self.mobject.points.shape != self.starting_mobject.points.shape:
self.mobject.points = self.starting_mobject.points
#TODO, Why do you need to do this? Shouldn't points always align?
class Transform(MobjectMovement):
def __init__(self, mobject1, mobject2,
run_time = DEFAULT_TRANSFORM_RUN_TIME,
*args, **kwargs):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
Mobject.align_data(mobject1, mobject2)
MobjectMovement.__init__(self, mobject1, run_time = run_time, *args, **kwargs)
self.ending_mobject = mobject2
self.mobject.SHOULD_BUFF_POINTS = \
mobject1.SHOULD_BUFF_POINTS and mobject2.SHOULD_BUFF_POINTS
self.reference_mobjects.append(mobject2)
self.name += "To" + str(mobject2)
if count2 < count1:
#Ensure redundant pixels fade to black
indices = self.non_redundant_m2_indices = \
np.arange(0, count1-1, float(count1) / count2).astype('int')
temp = np.zeros(mobject2.points.shape)
temp[indices] = mobject2.rgbs[indices]
mobject2.rgbs = temp
def update_mobject(self, alpha):
Mobject.interpolate(
self.starting_mobject,
self.ending_mobject,
self.mobject,
alpha
)
def clean_up(self):
if hasattr(self, "non_redundant_m2_indices"):
#Reduce mobject (which has become identical to mobject2), as
#well as mobject2 itself
for mobject in [self.mobject, self.ending_mobject]:
for attr in ['points', 'rgbs']:
setattr(
mobject, attr,
getattr(
self.ending_mobject,
attr
)[self.non_redundant_m2_indices]
)
class ApplyMethod(Transform):
def __init__(self, method, mobject, *args, **kwargs):
"""
Method is a method of Mobject
"""
method_args = ()
if isinstance(method, tuple):
method, method_args = method[0], method[1:]
if not inspect.ismethod(method):
raise "Not a valid Mobject method"
Transform.__init__(
self,
mobject,
method(copy.deepcopy(mobject), *method_args),
*args, **kwargs
)
class ApplyFunction(Transform):
def __init__(self, function, mobject, run_time = DEFAULT_ANIMATION_RUN_TIME,
*args, **kwargs):
map_image = copy.deepcopy(mobject)
map_image.points = np.array(map(function, map_image.points))
Transform.__init__(self, mobject, map_image, run_time = run_time,
*args, **kwargs)
self.name = "".join([
"Apply",
"".join([s.capitalize() for s in function.__name__.split("_")]),
"To" + str(mobject)
])
class ComplexFunction(ApplyFunction):
def __init__(self, function, *args, **kwargs):
def point_map(point):
x, y, z = point
c = np.complex(x, y)
c = function(c)
return c.real, c.imag, z
if len(args) > 0:
args = list(args)
mobject = args.pop(0)
elif "mobject" in kwargs:
mobject = kwargs.pop("mobject")
else:
mobject = Grid()
ApplyFunction.__init__(self, point_map, mobject, *args, **kwargs)
self.name = "ComplexFunction" + to_cammel_case(function.__name__)
#Todo, abstract away function naming'
class Homotopy(MobjectMovement):
def __init__(self, homotopy, *args, **kwargs):
"""
Homotopy a function from (x, y, z, t) to (x', y', z')
"""
self.homotopy = homotopy
MobjectMovement.__init__(self, *args, **kwargs)
def update_mobject(self, alpha):
self.mobject.points = np.array([
self.homotopy((x, y, z, alpha))
for x, y, z in self.starting_mobject.points
])
class ComplexHomotopy(Homotopy):
def __init__(self, complex_homotopy, *args, **kwargs):
"""
Complex Hootopy a function (z, t) to z'
"""
def homotopy((x, y, z, t)):
c = complex_homotopy((complex(x, y), t))
return (c.real, c.imag, z)
if len(args) > 0:
args = list(args)
mobject = args.pop(0)
elif "mobject" in kwargs:
mobject = kwargs["mobject"]
else:
mobject = Grid()
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
self.name = "ComplexHomotopy" + \
to_cammel_case(complex_homotopy.__name__)
class ShowCreation(MobjectMovement):
def update_mobject(self, alpha):
#TODO, shoudl I make this more efficient?
new_num_points = int(alpha * self.starting_mobject.points.shape[0])
for attr in ["points", "rgbs"]:
setattr(
self.mobject,
attr,
getattr(self.starting_mobject, attr)[:new_num_points, :]
)
class Flash(MobjectMovement):
def __init__(self, mobject, color = "white", slow_factor = 0.01,
run_time = 0.1, alpha_func = None,
*args, **kwargs):
MobjectMovement.__init__(self, mobject, run_time = run_time,
alpha_func = alpha_func,
*args, **kwargs)
self.intermediate = Mobject(color = color)
self.intermediate.add_points([
point + (x, y, 0)
for point in self.mobject.points
for x in [-1, 1]
for y in [-1, 1]
])
self.reference_mobjects.append(self.intermediate)
self.slow_factor = slow_factor
def update_mobject(self, alpha):
#Makes alpha go from 0 to slow_factor to 0 instead of 0 to 1
alpha = self.slow_factor * (1.0 - 4 * (alpha - 0.5)**2)
Mobject.interpolate(
self.starting_mobject,
self.intermediate,
self.mobject,
alpha
)

View File

@ -17,57 +17,9 @@ from scene import Scene
from moser_helpers import * from moser_helpers import *
from graphs import * from graphs import *
RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
movie_prefix = "moser/" movie_prefix = "moser/"
############################################
class CircleScene(Scene):
def __init__(self, radians, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
self.radius = RADIUS
self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius)
self.points = [
(self.radius * np.cos(angle), self.radius * np.sin(angle), 0)
for angle in radians
]
self.dots = [Dot(point) for point in self.points]
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
self.add(self.circle, *self.dots + self.lines)
class GraphScene(Scene):
#Note, the placement of vertices in this is pretty hard coded, be
#warned if you want to change it.
def __init__(self, graph, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
#See CUBE_GRAPH above for format of graph
self.graph = graph
self.points = map(np.array, graph["vertices"])
self.vertices = self.dots = [Dot(p) for p in self.points]
self.edges = [
Line(self.points[i], self.points[j])
for i, j in graph["edges"]
]
self.add(*self.dots + self.edges)
def generate_regions(self):
regions = [
region_from_line_boundary(*[
[
self.points[rc[i]],
self.points[rc[(i+1)%len(rc)]]
]
for i in range(len(rc))
])
for rc in self.graph["region_cycles"]
]
regions[-1].complement()#Outer region painted outwardly...
self.regions = regions
##################################################
def count_lines(*radians): def count_lines(*radians):
#TODO, Count things explicitly? #TODO, Count things explicitly?
sc = CircleScene(radians) sc = CircleScene(radians)
@ -378,7 +330,7 @@ def quadruplets_to_intersections(*radians):
)) ))
# sc.remove(arrows) # sc.remove(arrows)
name = "QuadrupletsToIntersections" + len(radians) name = "QuadrupletsToIntersections%d"%len(radians)
sc.write_to_movie(movie_prefix + name) sc.write_to_movie(movie_prefix + name)
def defining_graph(graph): def defining_graph(graph):
@ -447,7 +399,7 @@ def eulers_formula(graph):
]) ])
for mob in form.values(): for mob in form.values():
mob.shift((0, SPACE_HEIGHT-1.5, 0)) mob.shift((0, SPACE_HEIGHT-1.5, 0))
formula = CompoundMobject(*form.values()) formula = CompoundMobject(*[form[k] for k in form.keys() if k != "=2"])
new_form = dict([ new_form = dict([
(key, deepcopy(mob).shift((0, -0.7, 0))) (key, deepcopy(mob).shift((0, -0.7, 0)))
for key, mob in zip(form.keys(), form.values()) for key, mob in zip(form.keys(), form.values())
@ -486,6 +438,81 @@ def eulers_formula(graph):
name = "EulersFormula" + graph["name"] name = "EulersFormula" + graph["name"]
gs.write_to_movie(movie_prefix + name) gs.write_to_movie(movie_prefix + name)
def apply_euler_to_moser(*radians):
cs = CircleScene(radians)
cs.remove(cs.n_equals)
n_equals, intersection_count = tex_mobjects([
r"&n = %d\\"%len(radians),
r"&{%d \choose 4} = %d"%(len(radians), choose(len(radians), 4))
])
shift_val = cs.n_equals.get_center() - n_equals.get_center()
for mob in n_equals, intersection_count:
mob.shift(shift_val)
cs.add(n_equals)
yellow_dots = [d.highlight("yellow") for d in deepcopy(cs.dots)]
yellow_lines = [l.highlight("yellow") for l in deepcopy(cs.lines)]
cs.animate(*[
ShowCreation(dot) for dot in yellow_dots
], run_time = 1.0)
cs.dither()
cs.remove(*yellow_dots)
cs.animate(*[
ShowCreation(line) for line in yellow_lines
], run_time = 1.0)
cs.dither()
cs.remove(yellow_lines)
cannot_intersect = text_mobject(r"""
Euler's formula does not apply to \\
graphs whose edges intersect!
"""
)
cannot_intersect.center()
for mob in cs.mobjects:
mob.fade(0.3)
cs.add(cannot_intersect)
cs.dither()
cs.remove(cannot_intersect)
for mob in cs.mobjects:
mob.fade(1/0.3)
cs.generate_intersection_dots()
cs.animate(FadeIn(intersection_count), *[
ShowCreation(dot) for dot in cs.intersection_dots
])
name = "ApplyEulerToMoser%d"%len(radians)
cs.write_to_movie(movie_prefix + name)
def show_moser_graph_lines(*radians):
radians = list(set(map(lambda x : x%(2*np.pi), radians)))
radians.sort()
cs = CircleScene(radians)
cs.chop_lines_at_intersection_points()
cs.add(*cs.intersection_dots)
small_lines = [
deepcopy(line).scale_in_place(0.5)
for line in cs.lines
]
cs.animate(*[
Transform(line, small_line, run_time = 3.0)
for line, small_line in zip(cs.lines, small_lines)
])
cs.count(cs.lines, color = "yellow",
run_time = 9.0, num_offset = (0, 0, 0))
cs.dither()
cs.remove(cs.number)
cs.chop_circle_at_points()
cs.animate(*[
Transform(p, sp, run_time = 3.0)
for p, sp in zip(cs.circle_pieces, cs.smaller_circle_pieces)
])
cs.count(cs.circle_pieces, color = "yellow",
run_time = 2.0, num_offset = (0, 0, 0))
name = "ShowMoserGraphLines%d"%len(radians)
cs.write_to_movie(movie_prefix + name)
################################################## ##################################################
@ -503,13 +530,14 @@ if __name__ == '__main__':
# illustrate_n_choose_k(6, 4) # illustrate_n_choose_k(6, 4)
# intersection_point_correspondances(radians, range(0, 7, 2)) # intersection_point_correspondances(radians, range(0, 7, 2))
# lines_intersect_outside(radians, [2, 4, 5, 6]) # lines_intersect_outside(radians, [2, 4, 5, 6])
quadruplets_to_intersections(*radians[:6]) # quadruplets_to_intersections(*radians[:6])
# defining_graph(SAMPLE_GRAPH) # defining_graph(SAMPLE_GRAPH)
# doubled_edges(CUBE_GRAPH) # doubled_edges(CUBE_GRAPH)
# eulers_formula(CUBE_GRAPH) # eulers_formula(CUBE_GRAPH)
# eulers_formula(SAMPLE_GRAPH) # eulers_formula(SAMPLE_GRAPH)
# eulers_formula(OCTOHEDRON_GRAPH) # eulers_formula(OCTOHEDRON_GRAPH)
# apply_euler_to_moser(*radians)
show_moser_graph_lines(*radians[:6])

View File

@ -4,6 +4,125 @@ import itertools as it
from constants import * from constants import *
from image_mobject import * from image_mobject import *
from scene import Scene
RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
############################################
class CircleScene(Scene):
def __init__(self, radians, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
self.radius = RADIUS
self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius)
self.points = [
(self.radius * np.cos(angle), self.radius * np.sin(angle), 0)
for angle in radians
]
self.dots = [Dot(point) for point in self.points]
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
self.n_equals = tex_mobject(
"n=%d"%len(radians),
size = r"\small"
).shift((-SPACE_WIDTH+1, SPACE_HEIGHT-1.5, 0))
self.add(self.circle, self.n_equals, *self.dots + self.lines)
def generate_intersection_dots(self):
"""
Generates and adds attributes intersection_points and
intersection_dots, but does not yet add them to the scene
"""
self.intersection_points = [
intersection((p[0], p[2]), (p[1], p[3]))
for p in it.combinations(self.points, 4)
]
self.intersection_dots = [
Dot(point) for point in self.intersection_points
]
def chop_lines_at_intersection_points(self):
if not hasattr(self, "intersection_dots"):
self.generate_intersection_dots()
self.remove(*self.lines)
self.lines = []
for point_pair in it.combinations(self.points, 2):
int_points = filter(
lambda p : is_on_line(p, *point_pair),
self.intersection_points
)
points = list(point_pair) + int_points
points = map(lambda p : (p[0], p[1], 0), points)
points.sort(cmp = lambda x,y: cmp(x[0], y[0]))
self.lines += [
Line(points[i], points[i+1])
for i in range(len(points)-1)
]
self.add(*self.lines)
def chop_circle_at_points(self):
self.remove(self.circle)
self.circle_pieces = []
self.smaller_circle_pieces = []
for i in range(len(self.points)):
pp = self.points[i], self.points[(i+1)%len(self.points)]
transform = np.array([
[pp[0][0], pp[1][0], 0],
[pp[0][1], pp[1][1], 0],
[0, 0, 1]
])
circle = deepcopy(self.circle)
smaller_circle = deepcopy(self.circle)
for c in circle, smaller_circle:
c.points = np.dot(
c.points,
np.transpose(np.linalg.inv(transform))
)
c.filter_out(
lambda p : p[0] < 0 or p[1] < 0
)
if c == smaller_circle:
c.filter_out(
lambda p : p[0] > 4*p[1] or p[1] > 4*p[0]
)
c.points = np.dot(
c.points,
np.transpose(transform)
)
self.circle_pieces.append(circle)
self.smaller_circle_pieces.append(smaller_circle)
self.add(*self.circle_pieces)
class GraphScene(Scene):
#Note, the placement of vertices in this is pretty hard coded, be
#warned if you want to change it.
def __init__(self, graph, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
#See CUBE_GRAPH above for format of graph
self.graph = graph
self.points = map(np.array, graph["vertices"])
self.vertices = self.dots = [Dot(p) for p in self.points]
self.edges = [
Line(self.points[i], self.points[j])
for i, j in graph["edges"]
]
self.add(*self.dots + self.edges)
def generate_regions(self):
regions = [
region_from_line_boundary(*[
[
self.points[rc[i]],
self.points[rc[(i+1)%len(rc)]]
]
for i in range(len(rc))
])
for rc in self.graph["region_cycles"]
]
regions[-1].complement()#Outer region painted outwardly...
self.regions = regions
##################################################
def choose(n, r): def choose(n, r):
if n < r: return 0 if n < r: return 0
@ -15,6 +134,16 @@ def choose(n, r):
def moser_function(n): def moser_function(n):
return choose(n, 4) + choose(n, 2) + 1 return choose(n, 4) + choose(n, 2) + 1
def is_on_line(p0, p1, p2, threshold = 0.01):
"""
Returns true of p0 is on the line between p1 and p2
"""
p0, p1, p2 = map(lambda tup : np.array(tup[:2]), [p0, p1, p2])
p1 -= p0
p2 -= p0
return abs((p1[0] / p1[1]) - (p2[0] / p2[1])) < threshold
def intersection(line1, line2): def intersection(line1, line2):
""" """
A "line" should come in the form [(x0, y0), (x1, y1)] for two A "line" should come in the form [(x0, y0), (x1, y1)] for two

View File

@ -3,7 +3,9 @@ import itertools as it
from PIL import Image from PIL import Image
from constants import * from constants import *
def tex_to_image(expression, size = "\HUGE"): def tex_to_image(expression,
size = "\HUGE",
template_tex_file = TEMPLATE_TEX_FILE):
""" """
Returns list of images for correpsonding with a list of expressions Returns list of images for correpsonding with a list of expressions
""" """
@ -27,7 +29,7 @@ def tex_to_image(expression, size = "\HUGE"):
"at size %s to "%(size), "at size %s to "%(size),
filestem, filestem,
]) ])
with open(TEMPLATE_TEX_FILE, "r") as infile: with open(template_tex_file, "r") as infile:
body = infile.read() body = infile.read()
body = body.replace(SIZE_TO_REPLACE, size) body = body.replace(SIZE_TO_REPLACE, size)
body = body.replace(TEX_TEXT_TO_REPLACE, expression) body = body.replace(TEX_TEXT_TO_REPLACE, expression)