Preliminary script wrapper implemented and applied to moser/main.py, more moser/main.py functions as well

This commit is contained in:
Grant Sanderson
2015-05-06 17:58:34 -07:00
parent e230b48adb
commit ed7061e7cf
8 changed files with 493 additions and 80 deletions

View File

@ -177,6 +177,7 @@ class FadeIn(Animation):
class Transform(Animation):
def __init__(self, mobject1, mobject2,
run_time = DEFAULT_TRANSFORM_RUN_TIME,
black_out_extra_points = True,
*args, **kwargs):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
if count2 == 0:
@ -190,7 +191,7 @@ class Transform(Animation):
self.reference_mobjects.append(mobject2)
self.name += "To" + str(mobject2)
if count2 < count1:
if black_out_extra_points and count2 < count1:
#Ensure redundant pixels fade to black
indices = np.arange(
0, count1-1, float(count1) / count2
@ -222,6 +223,21 @@ class Transform(Animation):
)[self.non_redundant_m2_indices]
)
class SemiCircleTransform(Transform):
def update_mobject(self, alpha):
sm, em = self.starting_mobject, self.ending_mobject
midpoints = (sm.points + em.points) / 2
angle = alpha * np.pi
rotation_matrix = np.matrix([
[np.cos(angle), np.sin(angle), 0],
[-np.sin(angle), np.cos(angle), 0],
[0, 0, 1],
])
self.mobject.points = np.dot(
sm.points - midpoints,
np.transpose(rotation_matrix)
) + midpoints
class FadeToColor(Transform):
def __init__(self, mobject, color, *args, **kwargs):
target = copy.deepcopy(mobject).highlight(color)

View File

@ -7,7 +7,7 @@ DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50
DEFAULT_HEIGHT = 1440 if PRODUCTION_QUALITY else 480
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 640
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 840
#All in seconds
DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1
DEFAULT_ANIMATION_RUN_TIME = 3.0

View File

@ -22,9 +22,9 @@ def get_pixels(image_array):
def paint_region(region, image_array = None, color = None):
pixels = get_pixels(image_array)
# assert region.shape == pixels.shape[:2]
assert region.shape == pixels.shape[:2]
if color is None:
rgb = np.random.random(3)
rgb = 0.5 * np.random.random(3) #Random darker colors
else:
rgb = np.array(Color(color).get_rgb())
pixels[region.bool_grid] = (255*rgb).astype('uint8')

View File

@ -4,27 +4,31 @@ from PIL import Image
from colour import Color
from random import random
import string
import re
from constants import *
def hash_args(args):
args = map(lambda arg : arg.__name__ if type(arg) == type(hash_args) else arg, args)
return str(hash(str(args))%1000) if args else ""
def random_color():
color = Color()
color.set_rgb([1 - 0.5 * random() for x in range(3)])
return color
################################################
def to_cammel_case(name):
parts = name.split("_")
parts = [
return "".join([
filter(
lambda c : c not in string.punctuation + string.whitespace, part
).capitalize()
for part in parts
]
return "".join(parts)
for part in name.split("_")
])
def initials(name, sep_values = [" ", "_"]):
return "".join([
s[0] for s in re.split("|".join(sep_values), name)
])
################################################
def drag_pixels(frames):
curr = frames[0]

View File

@ -4,7 +4,8 @@ import numpy as np
import itertools as it
import operator as op
from copy import deepcopy
from random import random
from random import random, randint
import sys
from animation import *
@ -13,14 +14,12 @@ from image_mobject import *
from constants import *
from region import *
from scene import Scene
from script_wrapper import create_scene
from moser_helpers import *
from graphs import *
movie_prefix = "moser/"
def count_lines(*radians):
def count_lines(radians):
#TODO, Count things explicitly?
sc = CircleScene(radians)
text_center = (sc.radius + 1, sc.radius -1, 0)
@ -57,10 +56,10 @@ def count_lines(*radians):
else:
anims.append(FadeOut(mob))
sc.animate(*anims, run_time = 1)
sc.write_to_movie(movie_prefix + "CountLines%dPoints"%len(radians))
return sc
def count_intersection_points(*radians):
def count_intersection_points(radians):
radians = [r % (2*np.pi) for r in radians]
radians.sort()
sc = CircleScene(radians)
@ -104,8 +103,7 @@ def count_intersection_points(*radians):
anims.append(Animation(formula))
sc.animate(*anims, run_time = 1)
name = "CountIntersectionPoints%dPoints"%len(radians)
sc.write_to_movie(movie_prefix + name)
return sc
def non_general_position():
radians = np.arange(1, 7)
@ -138,15 +136,15 @@ def non_general_position():
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects)
])
sc1.write_to_movie(movie_prefix + "NonGeneralPosition")
def line_corresponds_with_pair(radians, r1, r2):
return sc1
def line_corresponds_with_pair(radians, dot0_index, dot1_index):
sc = CircleScene(radians)
#Remove from sc.lines list, so they won't be faded out
assert r1 in radians and r2 in radians
line_index = list(it.combinations(radians, 2)).index((r1, r2))
radians = list(radians)
dot0_index, dot1_index = radians.index(r1), radians.index(r2)
r1, r2 = radians[dot0_index], radians[dot1_index]
line_index = list(it.combinations(radians, 2)).index((r1, r2))
line, dot0, dot1 = sc.lines[line_index], sc.dots[dot0_index], sc.dots[dot1_index]
sc.lines.remove(line)
sc.dots.remove(dot0)
@ -162,8 +160,8 @@ def line_corresponds_with_pair(radians, r1, r2):
for mob in (dot0, dot1)
])
sc.animate(Transform(line, dot0))
name = "LineCorrspondsWithPair%d%d"%(dot0_index, dot1_index)
sc.write_to_movie(movie_prefix + name)
return sc
def illustrate_n_choose_k(n, k):
sc = Scene()
@ -213,7 +211,8 @@ def illustrate_n_choose_k(n, k):
sc.remove(tuple_copy)
sc.add(count_mob)
sc.animate(FadeIn(CompoundMobject(form1, form2)))
sc.write_to_movie(movie_prefix + "Illustrate%dChoose%d"%(n, k))
return sc
def intersection_point_correspondances(radians, indices):
assert(len(indices) == 4)
@ -268,10 +267,7 @@ def intersection_point_correspondances(radians, indices):
sc.animate(*dot_highlights)
sc.remove(dots_statement, *dot_pointers)
name = "IntersectionPointCorrespondances"
for ind in indices:
name += str(ind)
sc.write_to_movie(movie_prefix + name)
return sc
def lines_intersect_outside(radians, indices):
assert(len(indices) == 4)
@ -299,12 +295,9 @@ def lines_intersect_outside(radians, indices):
for p0, p1 in [(0, 1), (3, 2)]
] + [ShowCreation(intersection_dot)])
name = "LinesIntersectOutside"
for ind in indices:
name += str(ind)
sc.write_to_movie(movie_prefix + name)
return sc
def quadruplets_to_intersections(*radians):
def quadruplets_to_intersections(radians):
sc = CircleScene(radians)
quadruplets = it.combinations(range(len(radians)), 4)
frame_time = 1.0
@ -330,8 +323,7 @@ def quadruplets_to_intersections(*radians):
))
# sc.remove(arrows)
name = "QuadrupletsToIntersections%d"%len(radians)
sc.write_to_movie(movie_prefix + name)
return sc
def defining_graph(graph):
gs = GraphScene(graph)
@ -358,8 +350,7 @@ def defining_graph(graph):
for m in zip(gs.mobjects, ngs.mobjects)
], run_time = 7.0)
name = "DefiningGraph" + graph["name"]
gs.write_to_movie(movie_prefix + name)
return gs
def doubled_edges(graph):
gs = GraphScene(graph)
@ -386,8 +377,7 @@ def doubled_edges(graph):
gs.dither()
gs.remove(*outward_curved_lines)
name = "DoubledEdges" + graph["name"]
gs.write_to_movie(movie_prefix + name)
return gs
def eulers_formula(graph):
@ -435,10 +425,9 @@ def eulers_formula(graph):
gs.add(new_form[symbol])
gs.reset_background()
name = "EulersFormula" + graph["name"]
gs.write_to_movie(movie_prefix + name)
return gs
def apply_euler_to_moser(*radians):
def cannot_directly_apply_euler_to_moser(radians):
cs = CircleScene(radians)
cs.remove(cs.n_equals)
n_equals, intersection_count = tex_mobjects([
@ -479,11 +468,9 @@ def apply_euler_to_moser(*radians):
ShowCreation(dot) for dot in cs.intersection_dots
])
return cs
name = "ApplyEulerToMoser%d"%len(radians)
cs.write_to_movie(movie_prefix + name)
def show_moser_graph_lines(*radians):
def show_moser_graph_lines(radians):
radians = list(set(map(lambda x : x%(2*np.pi), radians)))
radians.sort()
@ -509,38 +496,306 @@ def show_moser_graph_lines(*radians):
])
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)
return cs
def apply_euler_to_moser():
#Boy is this an ugly implementation..., maybe you should
#make a generic formula manipuating module
sc = Scene()
expressions = []
for i in range(4):
V_exp = "V" if i < 2 else r"\left(n + {n \choose 4} \right)"
E_exp = "E" if i < 3 else r"\left({n \choose 2} + 2{n \choose 4}\right)"
if i == 0:
form = [V_exp, "-", E_exp, "+", "F", "=", "2"]
else:
form = ["F", "&=", E_exp, "-", V_exp, "+", "2"]
if i == 3:
form += [r"\\&=",r"{n \choose 4} + {n \choose 2}+", "2"]
expressions.append(tex_mobjects(form))
final_F_pos = (-SPACE_WIDTH+1, 0, 0)
for exp in expressions:
shift_val = final_F_pos - exp[0].get_center()
for mob in exp:
mob.shift(shift_val)
#rearange first expression
expressions[0] = [
expressions[0][x]
for x in [4, 5, 2, 1, 0, 3, 6] #TODO, Better way in general for rearrangements?
]
for i in range(3):
sc.remove(*sc.mobjects)
sc.add(*expressions[i])
sc.dither()
sc.animate(*[
SemiCircleTransform(x, y, run_time = 2) if i == 0 else Transform(x, y)
for x, y in zip(expressions[i], expressions[i+1])
])
sc.dither()
equals, simplified_exp = expressions[-1][-3], expressions[-1][-2:]
sc.animate(*[
FadeIn(mob)
for mob in [equals] + simplified_exp
])
sc.remove(*sc.mobjects)
shift_val = -CompoundMobject(*simplified_exp).get_center()
sc.animate(*[
ApplyMethod((Mobject.shift, shift_val), mob)
for mob in simplified_exp
])
sc.dither()
one, two = tex_mobject("1"), simplified_exp[-1]
one.center().shift(two.get_center())
two.highlight()
sc.dither()
sc.animate(SemiCircleTransform(two, one))
return sc
def draw_pascals_triangle(nrows):
pts = PascalsTriangleScene(nrows)
pts.remove(*pts.mobjects)
pts.add(pts.coords_to_mobs[0][0])
for n in range(1, nrows):
starts = [deepcopy(pts.coords_to_mobs[n-1][0])]
starts += [
CompoundMobject(
pts.coords_to_mobs[n-1][k-1],
pts.coords_to_mobs[n-1][k]
)
for k in range(1, n)
]
starts.append(deepcopy(pts.coords_to_mobs[n-1][n-1]))
pts.animate(*[
Transform(starts[i], pts.coords_to_mobs[n][i],
run_time = 1.5, black_out_extra_points = False)
for i in range(n+1)
])
return pts
def pascal_rule_example(nrows):
assert(nrows > 1)
pts = PascalsTriangleScene(nrows)
pts.dither()
n = randint(2, nrows-1)
k = randint(1, n-1)
pts.coords_to_mobs[n][k].highlight("green")
pts.dither()
plus = tex_mobject("+").scale(0.5)
nums_above = [pts.coords_to_mobs[n-1][k-1], pts.coords_to_mobs[n-1][k]]
plus.center().shift(sum(map(Mobject.get_center, nums_above)) / 2)
pts.add(plus)
for mob in nums_above + [plus]:
mob.highlight("yellow")
pts.dither()
return pts
def pascals_triangle_with_n_choose_k(nrows):
pts = PascalsTriangleScene(nrows)
pts.generate_n_choose_k_mobs()
mob_dicts = (pts.coords_to_mobs, pts.coords_to_n_choose_k)
for i in [0, 1]:
pts.dither()
pts.remove(*pts.mobjects)
pts.animate(*[
SemiCircleTransform(
deepcopy(mob_dicts[i][n][k]),
mob_dicts[1-i][n][k]
)
for n, k in pts.coords
])
pts.remove(*pts.mobjects)
pts.add(*[mob_dicts[1-i][n][k] for n, k in pts.coords])
return pts
def pascals_triangle_sum_rows(nrows):
pts = PascalsTriangleScene(nrows)
pluses = []
powers_of_two = []
equalses = []
powers_of_two_symbols = []
plus = tex_mobject("+")
desired_plus_width = pts.coords_to_mobs[0][0].get_width()
if plus.get_width() > desired_plus_width:
plus.scale(desired_plus_width / plus.get_width())
for n, k in pts.coords:
if k == 0:
continue
new_plus = deepcopy(plus)
new_plus.center().shift(pts.coords_to_mobs[n][k].get_center())
new_plus.shift((-pts.cell_width / 2.0, 0, 0))
pluses.append(new_plus)
equals = tex_mobject("=")
equals.scale(min(1, 0.7 * pts.cell_height / equals.get_width()))
for n in range(nrows):
new_equals = deepcopy(equals)
pof2 = tex_mobjects(str(2**n))
symbol = tex_mobject("2^{%d}"%n)
desired_center = np.array((
pts.diagram_width / 2.0,
pts.coords_to_mobs[n][0].get_center()[1],
0
))
new_equals.shift(desired_center - new_equals.get_center())
desired_center += (1.5*equals.get_width(), 0, 0)
scale_factor = pts.coords_to_mobs[0][0].get_height() / pof2.get_height()
for mob in pof2, symbol:
mob.center().scale(scale_factor).shift(desired_center)
symbol.shift((0, 0.5*equals.get_height(), 0)) #FUAH! Stupid
powers_of_two.append(pof2)
equalses.append(new_equals)
powers_of_two_symbols.append(symbol)
pts.animate(FadeIn(CompoundMobject(*pluses)))
run_time = 0.5
to_remove = []
for n in range(nrows):
start = CompoundMobject(*[pts.coords_to_mobs[n][k] for k in range(n+1)])
to_remove.append(start)
pts.animate(
Transform(start, powers_of_two[n]),
FadeIn(equalses[n]),
run_time = run_time
)
pts.dither()
pts.remove(*to_remove)
pts.add(*powers_of_two)
for n in range(nrows):
pts.animate(SemiCircleTransform(
powers_of_two[n], powers_of_two_symbols[n],
run_time = run_time
))
pts.remove(powers_of_two[n])
pts.add(powers_of_two_symbols[n])
return pts
##################################################
if __name__ == '__main__':
if __name__ == "__main__":
movie_prefix = "moser/"
radians = np.arange(0, 6, 6.0/7)
# count_lines(*radians)
# count_lines(*radians[:4])
# count_intersection_points(*radians[:4])
# count_intersection_points(*radians[:6])
# count_intersection_points(*radians)
# non_general_position()
# line_corresponds_with_pair(radians, radians[3], radians[4])
# line_corresponds_with_pair(radians, radians[2], radians[5])
# illustrate_n_choose_k(7, 2)
# illustrate_n_choose_k(6, 4)
# intersection_point_correspondances(radians, range(0, 7, 2))
# lines_intersect_outside(radians, [2, 4, 5, 6])
# quadruplets_to_intersections(*radians[:6])
# defining_graph(SAMPLE_GRAPH)
# doubled_edges(CUBE_GRAPH)
# eulers_formula(CUBE_GRAPH)
# eulers_formula(SAMPLE_GRAPH)
# eulers_formula(OCTOHEDRON_GRAPH)
# apply_euler_to_moser(*radians)
show_moser_graph_lines(*radians[:6])
n_pascal_rows = 7
big_n_pascal_rows = 11
def int_list_to_string(int_list):
return "-".join(map(str, int_list))
function_tuples = [
(
count_lines,
[
(radians),
(radians[:4]),
],
lambda args : str(len(args[0]))
),
(
count_intersection_points,
[
(radians[:4]),
(radians[:6]),
(radians),
],
lambda args : str(len(args[0]))
),
(
non_general_position,
[()],
None,
),
(
line_corresponds_with_pair,
[(radians, 2, 5)],
lambda args : "%d-%d"%(args[1], args[2])
),
(
illustrate_n_choose_k,
[
(7, 2),
(6, 4),
],
int_list_to_string
),
(
intersection_point_correspondances,
[(radians, range(0, 7, 2))],
lambda args : int_list_to_string(args[1])
),
(
lines_intersect_outside,
[(radians, [2, 4, 5, 6])],
lambda args : int_list_to_string(args[1])
),
(
quadruplets_to_intersections,
[(radians[:6])],
lambda args : str(len(args[0]))
),
(
defining_graph,
[(SAMPLE_GRAPH)],
lambda args : args[0]["name"]
),
(
doubled_edges,
[(CUBE_GRAPH)],
lambda args : args[0]["name"]
),
(
eulers_formula,
[
(CUBE_GRAPH),
(SAMPLE_GRAPH),
(OCTOHEDRON_GRAPH),
],
lambda args : args[0]["name"],
),
(
cannot_directly_apply_euler_to_moser,
[(radians)],
lambda args : str(len(args[0]))
),
(
show_moser_graph_lines,
[(radians[:6])],
lambda args : str(len(args[0]))
),
(
apply_euler_to_moser,
(),
None,
),
(
draw_pascals_triangle,
[(n_pascal_rows)],
lambda args : str(args[0])
),
(
pascal_rule_example,
[(n_pascal_rows)],
lambda args : str(args[0]),
),
(
pascals_triangle_with_n_choose_k,
[(n_pascal_rows)],
lambda args : str(args[0]),
),
(
pascals_triangle_sum_rows,
[(n_pascal_rows)],
lambda args : str(args[0])
),
(
pascals_triangle_sum_rows,
[(big_n_pascal_rows)],
lambda args : str(args[0])
),
]
create_scene(sys.argv[1:], function_tuples, movie_prefix)

View File

@ -4,6 +4,7 @@ import itertools as it
from constants import *
from image_mobject import *
from region import *
from scene import Scene
RADIUS = SPACE_HEIGHT - 0.1
@ -122,6 +123,68 @@ class GraphScene(Scene):
regions[-1].complement()#Outer region painted outwardly...
self.regions = regions
class PascalsTriangleScene(Scene):
def __init__(self, nrows, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
diagram_height = 2*SPACE_HEIGHT - 1
diagram_width = 1.5*SPACE_WIDTH
cell_height = diagram_height / nrows
cell_width = diagram_width / nrows
portion_to_fill = 0.7
bottom_left = np.array(
(-cell_width * nrows / 2.0, -cell_height * nrows / 2.0, 0)
)
num_to_num_mob = {}
coords_to_mobs = {}
coords = [(n, k) for n in range(nrows) for k in range(n+1)]
for n, k in coords:
num = choose(n, k)
center = bottom_left + (
cell_width * (k+nrows/2.0 - n/2.0),
cell_height * (nrows - n),
0
)
if num not in num_to_num_mob:
num_to_num_mob[num] = tex_mobject(str(num))
num_mob = deepcopy(num_to_num_mob[num])
scale_factor = min(
1,
portion_to_fill * cell_height / num_mob.get_height(),
portion_to_fill * cell_width / num_mob.get_width(),
)
num_mob.center().scale(scale_factor).shift(center)
if n not in coords_to_mobs:
coords_to_mobs[n] = {}
coords_to_mobs[n][k] = num_mob
self.add(*[coords_to_mobs[n][k] for n, k in coords])
#Add attributes
self.nrows = nrows
self.coords = coords
self.diagram_height = diagram_height
self.diagram_width = diagram_width
self.cell_height = cell_height
self.cell_width = cell_width
self.portion_to_fill= portion_to_fill
self.coords_to_mobs = coords_to_mobs
def generate_n_choose_k_mobs(self):
self.coords_to_n_choose_k = {}
for n, k in self.coords:
nck_mob = tex_mobject(r"{%d \choose %d}"%(n, k))
scale_factor = min(
1,
self.portion_to_fill * self.cell_height / nck_mob.get_height(),
self.portion_to_fill * self.cell_width / nck_mob.get_width(),
)
center = self.coords_to_mobs[n][k].get_center()
nck_mob.center().scale(scale_factor).shift(center)
if n not in self.coords_to_n_choose_k:
self.coords_to_n_choose_k[n] = {}
self.coords_to_n_choose_k[n][k] = nck_mob
##################################################
def choose(n, r):

View File

@ -62,6 +62,8 @@ class Scene(object):
def remove(self, *mobjects):
for mobject in mobjects:
if not isinstance(mobject, Mobject):
raise Exception("Removing something which is not a mobject")
while mobject in self.mobjects:
self.mobjects.remove(mobject)

73
script_wrapper.py Normal file
View File

@ -0,0 +1,73 @@
import sys
import getopt
import imp
import itertools as it
from helpers import initials, to_cammel_case
def print_help_message():
print '<script name> -f <function name or initials>'
def get_scene_and_name(function_string, args_string,
function_tuples):
possible_func_args = []
for func, args_list, args_to_string in function_tuples:
if function_string in ("", func.__name__, initials(func.__name__)):
for args in args_list:
if not isinstance(args, tuple):
args = (args,)
if not args_to_string:
args_to_string = lambda x : ""
this_args_string = args_to_string(args)
if args_string in ("", this_args_string):
possible_func_args.append(
(func, args, this_args_string)
)
if len(possible_func_args) == 0:
print "No function_string arg_string pair \
matching \"%s\" and \"%s\""%(function_string, args_string)
sys.exit(0)
elif len(possible_func_args) == 1:
index = 0
else:
print "Multiple functions/arg pairs satisfying this " + \
"description, choose from the following list:"
count = 0
for func, args, args_string in possible_func_args:
print "%d: %s%s"%(
count,
to_cammel_case(func.__name__),
args_string
)
count += 1
index = int(raw_input("Choose a number from above: "))
function, args, args_string = possible_func_args[index]
scene_name = to_cammel_case(function.__name__) + args_string
print "Writing %s..."%scene_name
scene = function(*args)
return scene, scene_name
def create_scene(sys_argv, function_tuples, movie_prefix = ""):
try:
opts, args = getopt.getopt(sys_argv, "ho:vf:v")#TODO, Learn about this
except getopt.GetoptError as err:
print str(err)
sys.exit(2)
function_string = ""
args_extension = ""
for opt, arg in opts:
if opt == '-h':
print_help_message()
return
elif opt == '-f':
function_string = arg
elif opt == "-a":
args_extension = arg
scene, name = get_scene_and_name(
function_string,
args_extension,
function_tuples
)
print name