Some graph utilities implemented for Moser videos

This commit is contained in:
Grant Sanderson
2015-04-26 14:25:43 -07:00
parent 5aff0b6374
commit 8d5240e070
10 changed files with 565 additions and 50 deletions

View File

@ -11,7 +11,7 @@ from images2gif import writeGif
from helpers import * from helpers import *
from constants import * from constants import *
from mobject import Mobject from mobject import Mobject, Point
class Animation(object): class Animation(object):
def __init__(self, def __init__(self,
@ -179,6 +179,9 @@ class Transform(Animation):
run_time = DEFAULT_TRANSFORM_RUN_TIME, run_time = DEFAULT_TRANSFORM_RUN_TIME,
*args, **kwargs): *args, **kwargs):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
if count2 == 0:
mobject2 = Point((SPACE_WIDTH, SPACE_HEIGHT, 0))
count2 = mobject2.get_num_points()
Mobject.align_data(mobject1, mobject2) Mobject.align_data(mobject1, mobject2)
Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs) Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs)
self.ending_mobject = mobject2 self.ending_mobject = mobject2
@ -189,11 +192,13 @@ class Transform(Animation):
if count2 < count1: if count2 < count1:
#Ensure redundant pixels fade to black #Ensure redundant pixels fade to black
indices = self.non_redundant_m2_indices = \ indices = np.arange(
np.arange(0, count1-1, float(count1) / count2).astype('int') 0, count1-1, float(count1) / count2
).astype('int')
temp = np.zeros(mobject2.points.shape) temp = np.zeros(mobject2.points.shape)
temp[indices] = mobject2.rgbs[indices] temp[indices] = mobject2.rgbs[indices]
mobject2.rgbs = temp mobject2.rgbs = temp
self.non_redundant_m2_indices = indices
def update_mobject(self, alpha): def update_mobject(self, alpha):
Mobject.interpolate( Mobject.interpolate(
@ -222,6 +227,17 @@ class FadeToColor(Transform):
target = copy.deepcopy(mobject).highlight(color) target = copy.deepcopy(mobject).highlight(color)
Transform.__init__(self, mobject, target, *args, **kwargs) Transform.__init__(self, mobject, target, *args, **kwargs)
class Highlight(FadeToColor):
def __init__(self, mobject, color = "red",
run_time = DEFAULT_ANIMATION_RUN_TIME,
alpha_func = there_and_back, *args, **kwargs):
FadeToColor.__init__(
self, mobject, color,
run_time = run_time,
alpha_func = alpha_func,
*args, **kwargs
)
class ScaleInPlace(Transform): class ScaleInPlace(Transform):
def __init__(self, mobject, scale_factor, *args, **kwargs): def __init__(self, mobject, scale_factor, *args, **kwargs):
target = copy.deepcopy(mobject) target = copy.deepcopy(mobject)

View File

@ -1,7 +1,7 @@
import os import os
PRODUCTION_QUALITY = True PRODUCTION_QUALITY = True
GENERALLY_BUFF_POINTS = True GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20 DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50 DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50

View File

@ -59,6 +59,8 @@ def make_even_by_cycling(iterable_1, iterable_2):
def sigmoid(x): def sigmoid(x):
return 1.0/(1 + np.exp(-x)) return 1.0/(1 + np.exp(-x))
### Alpha Functions ###
def high_inflection_0_to_1(t, inflection = 10.0): def high_inflection_0_to_1(t, inflection = 10.0):
error = sigmoid(-inflection / 2) error = sigmoid(-inflection / 2)
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error) return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
@ -67,6 +69,10 @@ def there_and_back(t, inflection = 10.0):
new_t = 2*t if t < 0.5 else 2*(1 - t) new_t = 2*t if t < 0.5 else 2*(1 - t)
return high_inflection_0_to_1(new_t, inflection) return high_inflection_0_to_1(new_t, inflection)
def not_quite_there(t, proportion = 0.7):
return proportion*high_inflection_0_to_1(t)
### Functional Functions ###
def composition(func_list): def composition(func_list):
""" """
@ -124,8 +130,7 @@ def z_to_vector(vector):
return np.dot(rotation_about_z(theta), phi_down) return np.dot(rotation_about_z(theta), phi_down)
def rotate_vector(vector, angle, axis): def rotate_vector(vector, angle, axis):
#Slightly hacky, changes vector in place return np.dot(rotation_matrix(angle, axis), vector)
vector[:3] = np.dot(rotation_matrix(angle, axis), vector)
def angle_between(v1, v2): def angle_between(v1, v2):
return np.arccos(np.dot( return np.arccos(np.dot(

View File

@ -86,7 +86,7 @@ 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)
#Purely redundant function to make singulars and plurals sensible
def tex_mobject(expression, size = "\HUGE"): def tex_mobject(expression, size = "\HUGE"):
return tex_mobjects(expression, size) return tex_mobjects(expression, size)

View File

@ -89,13 +89,14 @@ class Mobject(object):
self.shift(-self.get_center()) self.shift(-self.get_center())
return self return self
def get_center(self):
return np.apply_along_axis(np.mean, 0, self.points)
def scale(self, scale_factor): def scale(self, scale_factor):
self.points *= scale_factor self.points *= scale_factor
return self 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): def add(self, *mobjects):
for mobject in mobjects: for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs) self.add_points(mobject.points, mobject.rgbs)
@ -146,6 +147,17 @@ class Mobject(object):
self.rgbs = self.rgbs[to_eliminate] self.rgbs = self.rgbs[to_eliminate]
return self 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): def should_buffer_points(self):
# potentially changed in subclasses # potentially changed in subclasses
return GENERALLY_BUFF_POINTS return GENERALLY_BUFF_POINTS
@ -228,6 +240,7 @@ class Point(Mobject):
self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3) self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3)
class Arrow(Mobject1D): class Arrow(Mobject1D):
DEFAULT_COLOR = "white"
NUNGE_DISTANCE = 0.1 NUNGE_DISTANCE = 0.1
def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0), def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0),
length = 1, tip_length = 0.25, length = 1, tip_length = 0.25,
@ -246,7 +259,7 @@ class Arrow(Mobject1D):
]) ])
tips_dir = np.array(-self.direction), np.array(-self.direction) tips_dir = np.array(-self.direction), np.array(-self.direction)
for i, sgn in zip([0, 1], [-1, 1]): for i, sgn in zip([0, 1], [-1, 1]):
rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal) tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
self.add_points([ self.add_points([
[x, x, x] * tips_dir[i] + self.point [x, x, x] * tips_dir[i] + self.point
for x in np.arange(0, self.tip_length, self.epsilon) for x in np.arange(0, self.tip_length, self.epsilon)
@ -271,13 +284,13 @@ class Dot(Mobject1D): #Use 1D density, even though 2D
raise Exception("Center must have 2 or 3 coordinates!") raise Exception("Center must have 2 or 3 coordinates!")
elif center.size == 2: elif center.size == 2:
center = np.append(center, [0]) center = np.append(center, [0])
self.center = center self.center_point = center
self.radius = radius self.radius = radius
Mobject1D.__init__(self, *args, **kwargs) Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self): def generate_points(self):
self.add_points([ self.add_points([
np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center 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 t in np.arange(0, self.radius, self.epsilon)
for theta in np.arange(0, 2 * np.pi, self.epsilon) for theta in np.arange(0, 2 * np.pi, self.epsilon)
]) ])
@ -305,6 +318,18 @@ class Line(Mobject1D):
for t in np.arange(0, 1, self.epsilon) 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): class CubeWithFaces(Mobject2D):
def generate_points(self): def generate_points(self):

112
moser/graphs.py Normal file
View File

@ -0,0 +1,112 @@
import itertools as it
import numpy as np
CUBE_GRAPH = {
"name" : "CubeGraph",
# 6 4
# 20
# 31
# 7 5
"vertices" : [
(x, y, 0)
for r in (1, 2)
for x, y in it.product([-r,r], [-r, r])
],
"edges" : [
(0, 1),
(0, 2),
(3, 1),
(3, 2),
(4, 5),
(4, 6),
(7, 5),
(7, 6),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
],
"region_cycles" : [
[0, 2, 3, 1],
[4, 0, 1, 5],
[4, 6, 2, 0],
[6, 7, 3, 2],
[7, 5, 1, 3],
[4, 6, 7, 5],#By convention, last region will be "outside"
]
}
SAMPLE_GRAPH = {
"name" : "SampleGraph",
# 4 2 3
# 0 1
#
# 5
"vertices" :[
( 0, 0, 0),
( 2, 0, 0),
( 1, 1, 0),
( 3, 1, 0),
(-1, 1, 0),
(-2,-2, 0),
],
"edges" : [
(0, 1),
(1, 2),
(1, 3),
(3, 2),
(2, 4),
(4, 0),
(2, 0),
(4, 5),
(0, 5),
(1, 5),
],
"region_cycles" : [
(0, 1, 2),
(1, 3, 2),
(2, 4, 0),
(4, 5, 0),
(0, 5, 1),
(4, 5, 1, 3),
]
}
OCTOHEDRON_GRAPH = {
"name" : "OctohedronGraph",
# 3
#
# 1 0
# 2
#4 5
"vertices" : [
(r*np.cos(angle), r*np.sin(angle)-1, 0)
for r, s in [(1, 0), (3, 3)]
for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s])
],
"edges" : [
(0, 1),
(1, 2),
(2, 0),
(5, 0),
(0, 3),
(3, 5),
(3, 1),
(3, 4),
(1, 4),
(4, 2),
(4, 5),
(5, 2),
],
"region_cycles" : [
(0, 1, 2),
(0, 5, 3),
(3, 1, 0),
(3, 4, 1),
(1, 4, 2),
(2, 4, 5),
(5, 0, 2),
(3, 4, 5),
]
}

View File

@ -28,7 +28,7 @@ def logo_to_circle():
) )
big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS)
sc.add(small_circle) sc.add(small_circle)
sc.dither() sc.dither()`
sc.animate(Transform(small_circle, big_circle)) sc.animate(Transform(small_circle, big_circle))
return sc return sc

View File

@ -4,6 +4,7 @@ import numpy as np
import itertools as it import itertools as it
import operator as op import operator as op
from copy import deepcopy from copy import deepcopy
from random import random
from animation import * from animation import *
@ -14,10 +15,14 @@ from region import *
from scene import Scene from scene import Scene
from moser_helpers import * from moser_helpers import *
from graphs import *
RADIUS = SPACE_HEIGHT - 0.1 RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
movie_prefix = "moser/"
############################################
class CircleScene(Scene): class CircleScene(Scene):
def __init__(self, radians, *args, **kwargs): def __init__(self, radians, *args, **kwargs):
@ -32,6 +37,34 @@ class CircleScene(Scene):
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)] self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
self.add(self.circle, *self.dots + self.lines) 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
################################################## ##################################################
@ -72,7 +105,7 @@ def count_lines(*radians):
else: else:
anims.append(FadeOut(mob)) anims.append(FadeOut(mob))
sc.animate(*anims, run_time = 1) sc.animate(*anims, run_time = 1)
return sc sc.write_to_movie(movie_prefix + "CountLines%dPoints"%len(radians))
def count_intersection_points(*radians): def count_intersection_points(*radians):
@ -84,7 +117,7 @@ def count_intersection_points(*radians):
for p in it.combinations(sc.points, 4) for p in it.combinations(sc.points, 4)
] ]
intersection_dots = [Dot(point) for point in intersection_points] intersection_dots = [Dot(point) for point in intersection_points]
text_center = (sc.radius + 1, sc.radius -0.5, 0) text_center = (sc.radius + 0.5, sc.radius -0.5, 0)
size = r"\large" size = r"\large"
scale_factor = 0.4 scale_factor = 0.4
text = tex_mobject(r"\text{How Many Intersection Points?}", size = size) text = tex_mobject(r"\text{How Many Intersection Points?}", size = size)
@ -107,7 +140,7 @@ def count_intersection_points(*radians):
# ]) # ])
sc.add(text) sc.add(text)
sc.count(intersection_dots, "show_creation", num_offset = (0, 0, 0)) sc.count(intersection_dots, "show", num_offset = (0, 0, 0))
sc.dither() sc.dither()
# sc.animate(Transform(intersection_dots, new_dots)) # sc.animate(Transform(intersection_dots, new_dots))
anims = [] anims = []
@ -116,9 +149,11 @@ def count_intersection_points(*radians):
anims.append(Transform(mob, answer)) anims.append(Transform(mob, answer))
else: else:
anims.append(FadeOut(mob)) anims.append(FadeOut(mob))
anims.append(FadeIn(formula)) #Put here to so they are foreground anims.append(Animation(formula))
sc.animate(*anims, run_time = 1) sc.animate(*anims, run_time = 1)
return sc
name = "CountIntersectionPoints%dPoints"%len(radians)
sc.write_to_movie(movie_prefix + name)
def non_general_position(): def non_general_position():
radians = np.arange(1, 7) radians = np.arange(1, 7)
@ -151,7 +186,7 @@ def non_general_position():
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME) Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects) for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects)
]) ])
return sc1 sc1.write_to_movie(movie_prefix + "NonGeneralPosition")
def line_corresponds_with_pair(radians, r1, r2): def line_corresponds_with_pair(radians, r1, r2):
sc = CircleScene(radians) sc = CircleScene(radians)
@ -165,40 +200,319 @@ def line_corresponds_with_pair(radians, r1, r2):
sc.dots.remove(dot0) sc.dots.remove(dot0)
sc.dots.remove(dot1) sc.dots.remove(dot1)
sc.dither() sc.dither()
sc.animate(*[FadeOut(mob) for mob in sc.lines + sc.dots]) sc.animate(*[
FadeOut(mob, alpha_func = not_quite_there)
for mob in sc.lines + sc.dots
])
sc.add(sc.circle) sc.add(sc.circle)
sc.animate(*[ sc.animate(*[
ScaleInPlace(mob, 3, alpha_func = there_and_back) ScaleInPlace(mob, 3, alpha_func = there_and_back)
for mob in (dot0, dot1) for mob in (dot0, dot1)
]) ])
sc.animate(Transform(line, dot0)) sc.animate(Transform(line, dot0))
return sc name = "LineCorrspondsWithPair%d%d"%(dot0_index, dot1_index)
sc.write_to_movie(movie_prefix + name)
def illustrate_n_choose_2(n): def illustrate_n_choose_k(n, k):
#TODO, maybe make this snazzy
sc = Scene() sc = Scene()
nrange = range(1, n+1) nrange = range(1, n+1)
nrange_im = tex_mobject(str(nrange)) tuples = list(it.combinations(nrange, k))
pairs_str = str(list(it.combinations(nrange, 2))) nrange_mobs = tex_mobjects([str(n) + r'\;' for n in nrange])
exp = tex_mobject(r"{{%d \choose 2} = %d \text{ total pairs}}"%(n, choose(n, 2))) tuple_mobs = tex_mobjects(
pairs_im = tex_mobject(r"\underbrace{%s}"%pairs_str, size=r"\tiny") [
nrange_im.shift((0, 2, 0)) (r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p)
pairs_im.scale(0.7) for p, c in zip(tuples, it.count())
exp.shift((0, -2, 0)) ],
sc.add(nrange_im) size = r"\small"
)
tuple_terms = {
2 : "pairs",
3 : "triplets",
4 : "quadruplets",
}
tuple_term = tuple_terms[k] if k in tuple_terms else "tuples"
form1, count, form2 = tex_mobject([
r"{%d \choose %d} = "%(n, k),
"%d"%choose(n, k),
r" \text{ total %s}"%tuple_term
])
for mob in nrange_mobs:
mob.shift((0, 2, 0))
for mob in form1, count, form2:
mob.shift((0, -SPACE_HEIGHT + 1, 0))
count_center = count.get_center()
for mob in tuple_mobs:
mob.scale(0.6)
sc.add(*nrange_mobs)
sc.dither() sc.dither()
sc.animate(FadeIn(pairs_im), FadeIn(exp)) run_time = 6.0
sc.add(pairs_im) frame_time = run_time / len(tuples)
return sc for tup, count in zip(tuples, it.count()):
count_mob = tex_mobject(str(count+1))
count_mob.center().shift(count_center)
sc.add(count_mob)
tuple_copy = CompoundMobject(*[nrange_mobs[index-1] for index in tup])
tuple_copy.highlight()
sc.add(tuple_copy)
sc.add(tuple_mobs[count])
sc.dither(frame_time)
sc.remove(count_mob)
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))
def intersection_point_correspondances(radians, indices):
assert(len(indices) == 4)
indices.sort()
sc = CircleScene(radians)
intersection_point = intersection(
(sc.points[indices[0]], sc.points[indices[2]]),
(sc.points[indices[1]], sc.points[indices[3]])
)
intersection_point = tuple(list(intersection_point) + [0])
intersection_dot = Dot(intersection_point)
intersection_dot_arrow = Arrow(intersection_point).nudge()
sc.add(intersection_dot)
pairs = list(it.combinations(range(len(radians)), 2))
lines_to_save = [
sc.lines[pairs.index((indices[p0], indices[p1]))]
for p0, p1 in [(0, 2), (1, 3)]
]
dots_to_save = [
sc.dots[p]
for p in indices
]
line_statement = tex_mobject(r"\text{Pair of Lines}")
dots_statement = tex_mobject(r"&\text{Quadruplet of} \\ &\text{outer dots}")
for mob in line_statement, dots_statement:
mob.center()
mob.scale(0.7)
mob.shift((SPACE_WIDTH-2, SPACE_HEIGHT - 1, 0))
fade_outs = []
line_highlights = []
dot_highlights = []
dot_pointers = []
for mob in sc.mobjects:
if mob in lines_to_save:
line_highlights.append(Highlight(mob))
elif mob in dots_to_save:
dot_highlights.append(Highlight(mob))
dot_pointers.append(Arrow(mob.get_center()).nudge())
elif mob != intersection_dot:
fade_outs.append(FadeOut(mob, alpha_func = not_quite_there))
sc.add(intersection_dot_arrow)
sc.animate(Highlight(intersection_dot))
sc.remove(intersection_dot_arrow)
sc.animate(*fade_outs)
sc.dither()
sc.add(line_statement)
sc.animate(*line_highlights)
sc.remove(line_statement)
sc.dither()
sc.add(dots_statement, *dot_pointers)
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)
def lines_intersect_outside(radians, indices):
assert(len(indices) == 4)
indices.sort()
sc = CircleScene(radians)
intersection_point = intersection(
(sc.points[indices[0]], sc.points[indices[1]]),
(sc.points[indices[2]], sc.points[indices[3]])
)
intersection_point = tuple(list(intersection_point) + [0])
intersection_dot = Dot(intersection_point)
pairs = list(it.combinations(range(len(radians)), 2))
lines_to_save = [
sc.lines[pairs.index((indices[p0], indices[p1]))]
for p0, p1 in [(0, 1), (2, 3)]
]
sc.animate(*[
FadeOut(mob, alpha_func = not_quite_there)
for mob in sc.mobjects if mob not in lines_to_save
])
sc.animate(*[
Transform(
Line(sc.points[indices[p0]], sc.points[indices[p1]]),
Line(sc.points[indices[p0]], intersection_point))
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)
def quadruplets_to_intersections(*radians):
sc = CircleScene(radians)
quadruplets = it.combinations(range(len(radians)), 4)
frame_time = 1.0
for quad in quadruplets:
intersection_dot = Dot(intersection(
(sc.points[quad[0]], sc.points[quad[2]]),
(sc.points[quad[1]], sc.points[quad[3]])
)).repeat(3)
dot_quad = [deepcopy(sc.dots[i]) for i in quad]
for dot in dot_quad:
dot.scale_in_place(2)
# arrows = [Arrow(d.get_center()) for d in dot_quad]
dot_quad = CompoundMobject(*dot_quad)
# arrows = CompoundMobject(*arrows)
dot_quad.highlight()
# sc.add(arrows)
sc.add(dot_quad)
sc.dither(frame_time / 3)
sc.animate(Transform(
dot_quad,
intersection_dot,
run_time = 3*frame_time/2
))
# sc.remove(arrows)
name = "QuadrupletsToIntersections" + len(radians)
sc.write_to_movie(movie_prefix + name)
def defining_graph(graph):
gs = GraphScene(graph)
dots, lines = gs.vertices, gs.edges
gs.remove(*dots + lines)
all_dots = CompoundMobject(*dots)
gs.animate(ShowCreation(all_dots))
gs.remove(all_dots)
gs.add(*dots)
gs.dither()
gs.animate(*[
ShowCreation(line) for line in lines
])
#Move to new graph
new_graph = deepcopy(graph)
new_graph["vertices"] = [
(v[0] + 3*random(), v[1] + 3*random(), 0)
for v in new_graph["vertices"]
]
ngs = GraphScene(new_graph)
gs.animate(*[
Transform(m[0], m[1])
for m in zip(gs.mobjects, ngs.mobjects)
], run_time = 7.0)
name = "DefiningGraph" + graph["name"]
gs.write_to_movie(movie_prefix + name)
def doubled_edges(graph):
gs = GraphScene(graph)
lines_to_double = gs.edges[:9:3]
crazy_lines = [
(
line,
Line(line.end, line.start),
CurvedLine(line.start, line.end) ,
CurvedLine(line.end, line.start)
)
for line in lines_to_double
]
anims = []
outward_curved_lines = []
kwargs = {"run_time" : 3.0}
for straight, backwards, inward, outward in crazy_lines:
anims += [
Transform(straight, inward, **kwargs),
Transform(backwards, outward, **kwargs),
]
outward_curved_lines.append(outward)
gs.animate(*anims)
gs.dither()
gs.remove(*outward_curved_lines)
name = "DoubledEdges" + graph["name"]
gs.write_to_movie(movie_prefix + name)
def eulers_formula(graph):
gs = GraphScene(graph)
terms = "V - E + F =2".split(" ")
form = dict([
(key, mob)
for key, mob in zip(terms, tex_mobjects(terms))
])
for mob in form.values():
mob.shift((0, SPACE_HEIGHT-1.5, 0))
formula = CompoundMobject(*form.values())
new_form = dict([
(key, deepcopy(mob).shift((0, -0.7, 0)))
for key, mob in zip(form.keys(), form.values())
])
gs.add(formula)
colored_dots = [
deepcopy(d).scale_in_place(1.5).highlight("red")
for d in gs.dots
]
colored_edges = [
deepcopy(e).highlight("red")
for e in gs.edges
]
frame_time = 0.3
gs.generate_regions()
parameters = [
(colored_dots, "V", "mobject", "-", "show_creation"),
(colored_edges, "E", "mobject", "+", "show_creation"),
(gs.regions, "F", "region", "=2", "show_all")
]
for items, letter, item_type, symbol, mode in parameters:
gs.count(
items,
item_type = item_type,
mode = mode,
num_offset = new_form[letter].get_center(),
run_time = frame_time*len(items)
)
gs.dither()
if item_type == "mobject":
gs.remove(*items)
gs.add(new_form[symbol])
gs.reset_background()
name = "EulersFormula" + graph["name"]
gs.write_to_movie(movie_prefix + name)
################################################## ##################################################
if __name__ == '__main__': if __name__ == '__main__':
radians = np.arange(0, 6, 6.0/7) radians = np.arange(0, 6, 6.0/7)
# count_lines(*radians).write_to_movie("moser/CountLines") # count_lines(*radians)
count_intersection_points(*radians).write_to_movie("moser/CountIntersectionPoints") # count_lines(*radians[:4])
# non_general_position().write_to_movie("moser/NonGeneralPosition") # count_intersection_points(*radians[:4])
# line_corresponds_with_pair(radians, radians[3], radians[4]).write_to_movie("moser/LineCorrespondsWithPair34") # count_intersection_points(*radians[:6])
# line_corresponds_with_pair(radians, radians[2], radians[5]).write_to_movie("moser/LineCorrespondsWithPair25") # count_intersection_points(*radians)
# illustrate_n_choose_2(6).write_to_movie("moser/IllustrateNChoose2with6") # 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)

View File

@ -76,6 +76,12 @@ class HalfPlane(Region):
return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0) return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0)
Region.__init__(self, condition, *args, **kwargs) Region.__init__(self, condition, *args, **kwargs)
def region_from_line_boundary(*lines):
reg = Region()
for line in lines:
reg.intersect(HalfPlane(line))
return reg
def plane_partition(*lines): def plane_partition(*lines):
""" """
A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)] A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)]

View File

@ -16,6 +16,9 @@ from image_mobject import *
from animation import * from animation import *
import displayer as disp import displayer as disp
DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
DEFAULT_COUNT_RUN_TIME = 5.0
class Scene(object): class Scene(object):
def __init__(self, def __init__(self,
name = None, name = None,
@ -99,19 +102,29 @@ class Scene(object):
self.add(*moving_mobjects) self.add(*moving_mobjects)
progress_bar.finish() progress_bar.finish()
def count(self, mobjects, mode = "highlight", def count(self, items, item_type = "mobject", *args, **kwargs):
color = "red", if item_type == "mobject":
num_offset = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0), self.count_mobjects(items, *args, **kwargs)
run_time = 5.0): elif item_type == "region":
self.count_regions(items, *args, **kwargs)
def count_mobjects(
self, mobjects, mode = "highlight",
color = "red",
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME):
""" """
Note: Leaves scene with a "number" attribute Note: Leaves scene with a "number" attribute
for the final number mobject for the final number mobject.
mode can be "highlight", "show_creation" or "show", otherwise
a warning is given and nothing is animating during the count
""" """
if len(mobjects) > 50: #TODO if len(mobjects) > 50: #TODO
raise Exception("I don't know if you should be counting \ raise Exception("I don't know if you should be counting \
too many mobjects...") too many mobjects...")
if mode not in ["highlight", "show_creation"]: if mode not in ["highlight", "show_creation", "show"]:
raise Exception("Invalid mode") raise Warning("Unknown mode")
frame_time = run_time / len(mobjects) frame_time = run_time / len(mobjects)
if mode == "highlight": if mode == "highlight":
self.add(*mobjects) self.add(*mobjects)
@ -126,10 +139,34 @@ class Scene(object):
mob.highlight(original_color) mob.highlight(original_color)
if mode == "show_creation": if mode == "show_creation":
self.animate(ShowCreation(mob, run_time = frame_time)) self.animate(ShowCreation(mob, run_time = frame_time))
if mode == "show":
self.add(mob)
self.dither(frame_time)
self.remove(num_mob) self.remove(num_mob)
self.add(num_mob) self.add(num_mob)
self.number = num_mob self.number = num_mob
def count_regions(self, regions,
mode = "one_at_a_time",
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME,
**unused_kwargsn):
if mode not in ["one_at_a_time", "show_all"]:
raise Warning("Unknown mode")
frame_time = run_time / (len(regions))
for region, count in zip(regions, it.count(1)):
num_mob = tex_mobject(str(count))
num_mob.center().shift(num_offset)
self.add(num_mob)
self.highlight_region(region)
self.dither(frame_time)
if mode == "one_at_a_time":
self.reset_background()
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
def get_frame(self): def get_frame(self):
frame = self.background frame = self.background
for mob in self.mobjects: for mob in self.mobjects:
@ -147,7 +184,7 @@ class Scene(object):
self.dither(end_dither_time) self.dither(end_dither_time)
disp.write_to_movie(self, name or str(self)) disp.write_to_movie(self, name or str(self))
def show_frame(self): def show(self):
Image.fromarray(self.get_frame()).show() Image.fromarray(self.get_frame()).show()