diff --git a/animation/simple_animations.py b/animation/simple_animations.py index e2ac650b..75881bf4 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -132,6 +132,8 @@ class ComplexHomotopy(Homotopy): to_cammel_case(complex_homotopy.__name__) +####### Pi Creature Stuff ############# + class WalkPiCreature(Animation): def __init__(self, pi_creature, destination, *args, **kwargs): self.final = deepcopy(pi_creature).move_to(destination) @@ -153,15 +155,35 @@ class WalkPiCreature(Animation): self.mobject, 2*alpha - 1 ) + + +class BlinkPiCreature(Transform): + def __init__(self, pi_creature, run_time = 0.2, *args, **kwargs): + blinked = deepcopy(pi_creature).blink() + Transform.__init__( + self, pi_creature, blinked, + alpha_func = there_and_back, + run_time = run_time, + *args, **kwargs + ) - -###### Something different ############### -def pi_creature_step(scene, pi_creature, destination): - final = deepcopy(pi_creature).move_to(destination) - intermediate = pi_creature.get_step_intermediate(final) - scene.animate(Transform(pi_creature, intermediate)) - scene.animate(Transform(pi_creature, final)) +class WaveArm(Transform): + def __init__(self, pi_creature, *args, **kwargs): + final = deepcopy(pi_creature) + body_to_arm = pi_creature.arm.get_center()-pi_creature.get_center() + if body_to_arm[0] < 0: + wag_direction = LEFT + else: + wag_direction = RIGHT + final.arm.wag(0.7*UP, wag_direction, 2.0) + final.rewire_part_attributes(self_from_parts = True) + Transform.__init__( + self, pi_creature, final, + alpha_func = there_and_back, + *args, **kwargs + ) + diff --git a/mobject/creatures.py b/mobject/creatures.py index 6c120b88..33cc0e07 100644 --- a/mobject/creatures.py +++ b/mobject/creatures.py @@ -104,14 +104,28 @@ class PiCreature(Mobject): vect = pi_creature.get_center() - self.get_center() result = deepcopy(self).shift(vect / 2.0) result.rewire_part_attributes() - if vect[0] < 0: - result.right_leg.wag(-vect/2.0, DOWN) + left_forward = vect[0] > 0 + if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]: + #For Mortimer's case + left_forward = not left_forward + if left_forward: result.left_leg.wag(vect/2.0, DOWN) + result.right_leg.wag(-vect/2.0, DOWN) else: - result.left_leg.wag(-vect/2.0, DOWN) result.right_leg.wag(vect/2.0, DOWN) + result.left_leg.wag(-vect/2.0, DOWN) return result + def blink(self): + for eye in self.left_eye, self.right_eye: + bottom = min(eye.points[:,1]) + eye.apply_function( + lambda (x, y, z) : (x, bottom, z) + ) + self.rewire_part_attributes(self_from_parts = True) + return self + + class Randolph(PiCreature): pass #Nothing more than an alternative name diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 8022c67e..641bfa5c 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -67,24 +67,54 @@ class ImageMobject(Mobject2D): return False class SpeechBubble(ImageMobject): - def __init__(self, *args, **kwargs): + def __init__(self, direction = LEFT, *args, **kwargs): ImageMobject.__init__(self, "speech_bubble", *args, **kwargs) - self.scale(0.25) + self.direction = direction + self.scale(0.4) + self.center() + if direction[0] > 0: + self.rotate(np.pi, UP) + self.reload_tip() + + def reload_tip(self): + #TODO, perhaps make this resiliant to different point orderings + self.tip = self.points[-1] + + def speak_from(self, mobject): + dest = mobject.get_center() + dest += self.direction * mobject.get_width()/2 + dest += UP * mobject.get_height()/2 + self.shift(dest - self.tip) + self.reload_tip() + return self + + def write(self, text): + smidgeon = 0.1*UP + 0.2*self.direction + self.text = text_mobject(text) + self.text.scale(0.75*self.get_width() / self.text.get_width()) + self.text.shift(self.get_center() + smidgeon) + class ThoughtBubble(ImageMobject): def __init__(self, *args, **kwargs): ImageMobject.__init__(self, "thought_bubble", *args, **kwargs) self.scale(0.5) + self.center() -class SimpleFace(ImageMobject): - def __init__(self, *args, **kwargs): - ImageMobject.__init__(self, "simple_face", *args, **kwargs) +class Face(ImageMobject): + def __init__(self, mode = "simple", *args, **kwargs): + """ + Mode can be "simple", "talking", "straight" + """ + ImageMobject.__init__(self, mode + "_face", *args, **kwargs) self.scale(0.5) + self.center() class VideoIcon(ImageMobject): def __init__(self, *args, **kwargs): ImageMobject.__init__(self, "video_icon", *args, **kwargs) self.scale(0.3) + self.center() def text_mobject(text, size = "\\Large"): return tex_mobjects(text, size, TEMPLATE_TEXT_FILE) diff --git a/mobject/mobject.py b/mobject/mobject.py index d105439e..d665fc6c 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -95,10 +95,12 @@ class Mobject(object): self.points += v return self - def wag(self, wag_direction = (0, 1, 0), wag_axis = (-1, 0, 0)): + def wag(self, wag_direction = RIGHT, wag_axis = DOWN, + wag_factor = 1.0): alphas = np.dot(self.points, np.transpose(wag_axis)) alphas -= min(alphas) alphas /= max(alphas) + alphas = alphas**wag_factor self.points += np.dot( alphas.reshape((len(alphas), 1)), np.array(wag_direction).reshape((1, 3)) diff --git a/mobject/simple_mobjects.py b/mobject/simple_mobjects.py index 619615f8..55f7c716 100644 --- a/mobject/simple_mobjects.py +++ b/mobject/simple_mobjects.py @@ -18,12 +18,11 @@ class Arrow(Mobject1D): tail = None, length = 1, tip_length = 0.25, normal = (0, 0, 1), *args, **kwargs): self.point = np.array(point) - if tail == None: - self.direction = np.array(direction) / np.linalg.norm(direction) - self.length = length - else: - self.direction = self.point - tail - self.length = np.linalg.norm(self.direction) + if tail is not None: + direction = self.point - tail + length = np.linalg.norm(direction) + self.direction = np.array(direction) / np.linalg.norm(direction) + self.length = length self.normal = np.array(normal) self.tip_length = tip_length Mobject1D.__init__(self, *args, **kwargs) diff --git a/sample_script.py b/sample_script.py index e860fec2..4ec78064 100644 --- a/sample_script.py +++ b/sample_script.py @@ -10,22 +10,19 @@ from animation import * from mobject import * from constants import * from region import * -from scene import Scene, GraphScene +from scene import Scene from script_wrapper import command_line_create_scene -class SampleScene(GraphScene): +class SampleScene(Scene): def construct(self): - GraphScene.construct(self) - self.generate_regions() - self.generate_dual_graph() - self.generate_spanning_tree() - self.add(self.spanning_tree) - for count in range(len(self.regions)): - self.add(tex_mobject(str(count)).shift(self.dual_points[count])) - for count in range(len(self.edges)): - self.add(tex_mobject(str(count)).shift(self.edges[count].get_center())) - + randy = Randolph() + self.add(randy) + self.dither() + self.animate(BlinkPiCreature(randy)) + self.dither() + self.animate(WaveArm(randy)) + self.dither() diff --git a/scene/graphs.py b/scene/graphs.py index 8538ed80..f141aedb 100644 --- a/scene/graphs.py +++ b/scene/graphs.py @@ -11,165 +11,197 @@ from region import * from constants import * from helpers import * -CUBE_GRAPH = { - "name" : "CubeGraph", - # 5 7 - # 12 - # 03 - # 4 6 - "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" - ] -} +class Graph(): + def __init__(self): + #List of points in R^3 + vertices = [] + #List of pairs of indices of vertices + edges = [] + #List of tuples of indices of vertices. The last should + #be a cycle whose interior is the entire graph, and when + #regions are computed its complement will be taken. + region_cycles = [] -SAMPLE_GRAPH = { - "name" : "SampleGraph", - # 4 2 3 8 - # 0 1 - # 7 - # 5 6 - "vertices" :[ - ( 0, 0, 0), - ( 2, 0, 0), - ( 1, 2, 0), - ( 3, 2, 0), - (-1, 2, 0), - (-2,-2, 0), - ( 2,-2, 0), - ( 4,-1, 0), - ( 6, 2, 0), - ], - "edges" : [ - (0, 1), - (1, 2), - (1, 3), - (3, 2), - (2, 4), - (4, 0), - (2, 0), - (4, 5), - (0, 5), - (1, 5), - (5, 6), - (6, 7), - (7, 1), - (7, 8), - (8, 3), - ], - "region_cycles" : [ - (0, 1, 2), - (1, 3, 2), - (2, 4, 0), - (4, 5, 0), - (0, 5, 1), - (1, 5, 6, 7), - (1, 7, 8, 3), - (4, 5, 6, 7, 8, 3, 2), - ] + self.construct() -} + def construct(self): + pass -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), - ] -} + def __str__(self): + return self.__class__.__name__ + + +class CubeGraph(Graph): + """ + 5 7 + 12 + 03 + 4 6 + """ + def construct(self): + self.vertices = [ + (x, y, 0) + for r in (1, 2) + for x, y in it.product([-r,r], [-r, r]) + ] + self.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), + ] + self.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" + ] + + +class SampleGraph(Graph): + """ + 4 2 3 8 + 0 1 + 7 + 5 6 + """ + def construct(self): + self.vertices = [ + ( 0, 0, 0), + ( 2, 0, 0), + ( 1, 2, 0), + ( 3, 2, 0), + (-1, 2, 0), + (-2,-2, 0), + ( 2,-2, 0), + ( 4,-1, 0), + ( 6, 2, 0), + ] + self.edges = [ + (0, 1), + (1, 2), + (1, 3), + (3, 2), + (2, 4), + (4, 0), + (2, 0), + (4, 5), + (0, 5), + (1, 5), + (5, 6), + (6, 7), + (7, 1), + (7, 8), + (8, 3), + ] + self.region_cycles = [ + (0, 1, 2), + (1, 3, 2), + (2, 4, 0), + (4, 5, 0), + (0, 5, 1), + (1, 5, 6, 7), + (1, 7, 8, 3), + (4, 5, 6, 7, 8, 3, 2), + ] + + +class OctohedronGraph(Graph): + """ + 3 + + 1 0 + 2 + 4 5 + """ + def construct(self): + self.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]) + ] + self.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), + ] + self.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), + ] + + +class CompleteGraph(Graph): + def __init__(self, num_vertices, radius = 3): + self.num_vertices = num_vertices + self.radius = radius + Graph.__init__(self) + + def construct(self): + self.vertices = [ + (self.radius*np.cos(theta), self.radius*np.sin(theta), 0) + for x in range(self.num_vertices) + for theta in [2*np.pi*x / self.num_vertices] + ] + self.edges = it.combinations(range(self.num_vertices), 2) + + def __str__(self): + return Graph.__str__(self) + str(self.num_vertices) -def complete_graph(n, radius = 3): - return { - "name" : "Complete%d"%n, - "vertices" : [ - (radius*np.cos(theta), radius*np.sin(theta), 0) - for x in range(n) - for theta in [2*np.pi*x / n] - ], - "edges" : it.combinations(range(n), 2) - } class GraphScene(Scene): args_list = [ - (CUBE_GRAPH,), - (SAMPLE_GRAPH,), - (OCTOHEDRON_GRAPH,), + (CubeGraph(),), + (SampleGraph(),), + (OctohedronGraph(),), ] @staticmethod def args_to_string(*args): - return args[0]["name"] + return str(args[0]) def __init__(self, graph, *args, **kwargs): - #See CUBE_GRAPH above for format of graph + #See CubeGraph() above for format of graph self.graph = graph Scene.__init__(self, *args, **kwargs) def construct(self): - self.points = map(np.array, self.graph["vertices"]) + self.points = map(np.array, self.graph.vertices) self.vertices = self.dots = [Dot(p) for p in self.points] self.edges = self.lines = [ Line(self.points[i], self.points[j]) - for i, j in self.graph["edges"] + for i, j in self.graph.edges ] self.add(*self.dots + self.edges) def generate_regions(self): regions = [ self.region_from_cycle(cycle) - for cycle in self.graph["region_cycles"] + for cycle in self.graph.region_cycles ] regions[-1].complement()#Outer region painted outwardly... self.regions = regions @@ -242,7 +274,7 @@ class GraphScene(Scene): def trace_cycle(self, cycle = None, color = "yellow", run_time = 2.0): if cycle == None: - cycle = self.graph["region_cycles"][0] + cycle = self.graph.region_cycles[0] time_per_edge = run_time / len(cycle) next_in_cycle = it.cycle(cycle) next_in_cycle.next()#jump one ahead @@ -257,7 +289,7 @@ class GraphScene(Scene): def generate_spanning_tree(self, root = 0, color = "yellow"): self.spanning_tree_root = 0 - pairs = deepcopy(self.graph["edges"]) + pairs = deepcopy(self.graph.edges) pairs += [tuple(reversed(pair)) for pair in pairs] self.spanning_tree_index_pairs = [] curr = root @@ -339,7 +371,7 @@ class GraphScene(Scene): def generate_dual_graph(self): point_at_infinity = np.array([np.inf]*3) - cycles = self.graph["region_cycles"] + cycles = self.graph.region_cycles self.dual_points = [ center_of_mass([ self.points[index] @@ -355,7 +387,7 @@ class GraphScene(Scene): self.dual_points[-1] = point_at_infinity self.dual_edges = [] - for pair in self.graph["edges"]: + for pair in self.graph.edges: dual_point_pair = [] for cycle in cycles: if not (pair[0] in cycle and pair[1] in cycle): diff --git a/scripts/ecf_graph_scenes.py b/scripts/ecf_graph_scenes.py index 5305be47..ccfac609 100644 --- a/scripts/ecf_graph_scenes.py +++ b/scripts/ecf_graph_scenes.py @@ -21,7 +21,139 @@ RANDOLPH_SCALE_VAL = 0.3 EDGE_ANNOTATION_SCALE_VAL = 0.7 DUAL_CYCLE = [3, 4, 5, 6, 1, 0, 2, 3] -class IntroduceGraphs(GraphScene): +class EulersFormulaWords(Scene): + def construct(self): + self.add(tex_mobject("V-E+F=2")) + +class TheTheoremWords(Scene): + def construct(self): + self.add(text_mobject("The Theorem:")) + +class ProofAtLastWords(Scene): + def construct(self): + self.add(text_mobject("The Proof At Last...")) + +class DualSpanningTreeWords(Scene): + def construct(self): + self.add(text_mobject("Spanning trees have duals too!")) + +class PreferOtherProofDialogue(Scene): + def construct(self): + teacher = Face("talking").shift(2*LEFT) + student = Face("straight").shift(2*RIGHT) + teacher_bubble = SpeechBubble(LEFT).speak_from(teacher) + student_bubble = SpeechBubble(RIGHT).speak_from(student) + teacher_bubble.write("Look at this \\\\ elegant proof!") + student_bubble.write("I prefer the \\\\ other proof.") + + self.add(student, teacher, teacher_bubble, teacher_bubble.text) + self.dither(2) + self.animate(Transform( + Dot(student_bubble.tip).highlight("black"), + CompoundMobject(student_bubble, student_bubble.text) + )) + self.dither(2) + self.remove(teacher_bubble.text) + teacher_bubble.write("Does that make this \\\\ any less elegant?") + self.add(teacher_bubble.text) + self.dither(2) + +class IllustrateDuality(GraphScene): + def construct(self): + GraphScene.construct(self) + self.generate_dual_graph() + + self.add(text_mobject("Duality").to_edge(UP)) + self.remove(*self.vertices) + def special_alpha(t): + if t > 0.5: + t = 1 - t + if t < 0.25: + return high_inflection_0_to_1(4*t) + else: + return 1 + kwargs = { + "run_time" : 5.0, + "alpha_func" : special_alpha + } + self.animate(*[ + Transform(*edge_pair, **kwargs) + for edge_pair in zip(self.edges, self.dual_edges) + ] + [ + Transform( + CompoundMobject(*[ + self.vertices[index] + for index in cycle + ]), + dv, + **kwargs + ) + for cycle, dv in zip( + self.graph.region_cycles, + self.dual_vertices + ) + ]) + self.dither() + +class IntroduceGraph(GraphScene): + def construct(self): + GraphScene.construct(self) + tweaked_graph = deepcopy(self.graph) + for index in 2, 4: + tweaked_graph.vertices[index] += 2.8*RIGHT + 1.8*DOWN + tweaked_self = GraphScene(tweaked_graph) + edges_to_remove = [ + self.edges[self.graph.edges.index(pair)] + for pair in [(4, 5), (0, 5), (1, 5), (7, 1), (8, 3)] + ] + + connected, planar, graph = CompoundMobject(*text_mobject([ + "Connected ", "Planar ", "Graph" + ])).to_edge(UP).split() + not_okay = text_mobject("Not Okay").highlight("red") + planar_explanation = text_mobject(""" + (``Planar'' just means we can draw it without + intersecting lines) + """, size = "\\small") + planar_explanation.shift(planar.get_center() + 0.5*DOWN) + + self.draw_vertices() + self.draw_edges() + self.clear() + self.add(*self.vertices + self.edges) + self.dither() + self.add(graph) + self.dither() + kwargs = { + "alpha_func" : there_and_back, + "run_time" : 5.0 + } + self.add(not_okay) + self.animate(*[ + Transform(*pair, **kwargs) + for pair in zip( + self.edges + self.vertices, + tweaked_self.edges + tweaked_self.vertices, + ) + ]) + self.remove(not_okay) + self.add(planar, planar_explanation) + self.dither(2) + self.remove(planar_explanation) + self.add(not_okay) + self.remove(*edges_to_remove) + self.animate(ShowCreation( + CompoundMobject(*edges_to_remove), + alpha_func = lambda t : 1 - t, + run_time = 1.0 + )) + self.dither(2) + self.remove(not_okay) + self.add(connected, *edges_to_remove) + self.dither() + + +class OldIntroduceGraphs(GraphScene): def construct(self): GraphScene.construct(self) self.draw_vertices() @@ -29,7 +161,7 @@ class IntroduceGraphs(GraphScene): self.dither() self.clear() self.add(*self.edges) - self.replace_vertices_with(SimpleFace().scale(0.4)) + self.replace_vertices_with(Face().scale(0.4)) friends = text_mobject("Friends").scale(EDGE_ANNOTATION_SCALE_VAL) self.annotate_edges(friends.shift((0, friends.get_height()/2, 0))) self.animate(*[ @@ -55,9 +187,9 @@ class PlanarGraphDefinition(Scene): graphs = [ CompoundMobject(*GraphScene(g).mobjects) for g in [ - CUBE_GRAPH, - complete_graph(5), - OCTOHEDRON_GRAPH + CubeGraph(), + CompleteGraph(5), + OctohedronGraph() ] ] @@ -84,7 +216,7 @@ class PlanarGraphDefinition(Scene): class TerminologyFromPolyhedra(GraphScene): - args_list = [(CUBE_GRAPH,)] + args_list = [(CubeGraph(),)] def construct(self): GraphScene.construct(self) rot_kwargs = { @@ -97,7 +229,7 @@ class TerminologyFromPolyhedra(GraphScene): ] cube = CompoundMobject(*[ Line(vertices[edge[0]], vertices[edge[1]]) - for edge in self.graph["edges"] + for edge in self.graph.edges ]) cube.rotate(-np.pi/3, [0, 0, 1]) cube.rotate(-np.pi/3, [0, 1, 0]) @@ -106,10 +238,11 @@ class TerminologyFromPolyhedra(GraphScene): regions_to_faces = text_mobject("Regions $\\to$ Faces").to_corner() self.clear() - self.animate(TransformAnimations( - Rotating(Dodecahedron(), **rot_kwargs), - Rotating(cube, **rot_kwargs), - )) + # self.animate(TransformAnimations( + # Rotating(Dodecahedron(), **rot_kwargs), + # Rotating(cube, **rot_kwargs) + # )) + self.animate(Rotating(cube, **rot_kwargs)) self.clear() self.animate(*[ Transform(l1, l2) @@ -185,11 +318,11 @@ class ThreePiecesOfTerminology(GraphScene): class WalkingRandolph(GraphScene): args_list = [ - (SAMPLE_GRAPH, [0, 1, 7, 8]), + (SampleGraph(), [0, 1, 7, 8]), ] @staticmethod def args_to_string(graph, path): - return graph["name"] + "".join(map(str, path)) + return str(graph) + "".join(map(str, path)) def __init__(self, graph, path, *args, **kwargs): self.path = path @@ -211,7 +344,7 @@ class WalkingRandolph(GraphScene): class PathExamples(GraphScene): - args_list = [(SAMPLE_GRAPH,)] + args_list = [(SampleGraph(),)] def construct(self): GraphScene.construct(self) paths = [ @@ -257,14 +390,14 @@ class PathExamples(GraphScene): class IntroduceCycle(WalkingRandolph): args_list = [ - (SAMPLE_GRAPH, [0, 1, 3, 2, 0]) + (SampleGraph(), [0, 1, 3, 2, 0]) ] def construct(self): WalkingRandolph.construct(self) self.remove(self.randy) encompassed_cycles = filter( lambda c : set(c).issubset(self.path), - self.graph["region_cycles"] + self.graph.region_cycles ) regions = [ self.region_from_cycle(cycle) @@ -339,7 +472,7 @@ class DefineSpanningTree(GraphScene): )) self.dither(2) - unneeded_edges = filter(out_of_spanning_set, self.graph["edges"]) + unneeded_edges = filter(out_of_spanning_set, self.graph.edges) for edge, limit in zip(unneeded_edges, range(5)): line = Line(self.points[edge[0]], self.points[edge[1]]) line.highlight("red") @@ -453,11 +586,15 @@ class FacebookGraphAsAbstractSet(Scene): (1, 3), (1, 2), ] - names_mob = text_mobject("\\\\".join(names)).shift(3*LEFT) - friends_mob = tex_mobject("\\\\".join([ + names_string = "\\\\".join(names + ["$\\vdots$"]) + friends_string = "\\\\".join([ "\\text{%s}&\\leftrightarrow\\text{%s}"%(names[i],names[j]) for i, j in friend_pairs - ]), size = "\\Large").shift(3*RIGHT) + ] + ["\\vdots"]) + names_mob = text_mobject(names_string).shift(3*LEFT) + friends_mob = tex_mobject( + friends_string, size = "\\Large" + ).shift(3*RIGHT) accounts = text_mobject("\\textbf{Accounts}") accounts.shift(3*LEFT).to_edge(UP) friendships = text_mobject("\\textbf{Friendships}") @@ -479,68 +616,147 @@ class FacebookGraphAsAbstractSet(Scene): class ExamplesOfGraphs(GraphScene): def construct(self): buff = 0.5 - self.graph["vertices"] = map( + self.graph.vertices = map( lambda v : v + DOWN + RIGHT, - self.graph["vertices"] + self.graph.vertices ) - GraphScene.construct(self) - objects, notion = CompoundMobject(*text_mobject( + GraphScene.construct(self) + self.generate_regions() + objects, notions = CompoundMobject(*text_mobject( ["Objects \\quad\\quad ", "Thing that connects objects"] )).to_corner().shift(0.5*RIGHT).split() horizontal_line = Line( (-SPACE_WIDTH, SPACE_HEIGHT-1, 0), - (max(notion.points[:,0]), SPACE_HEIGHT-1, 0) + (max(notions.points[:,0]), SPACE_HEIGHT-1, 0) ) - vert_line_x_val = min(notion.points[:,0]) - buff + vert_line_x_val = min(notions.points[:,0]) - buff vertical_line = Line( (vert_line_x_val, SPACE_HEIGHT, 0), (vert_line_x_val,-SPACE_HEIGHT, 0) ) - objects_and_notion_strings = [ + objects_and_notions = [ ("Facebook accounts", "Friendship"), - ("Cities", "Roads between them"), - ("Wikipedia pages", "Links"), + ("English Words", "Differ by One Letter"), + ("Mathematicians", "Coauthorship"), ("Neurons", "Synapses"), ( "Regions our graph \\\\ cuts the plane into", - "Shareed edges" + "Shared edges" ) ] - objects_and_notions = [ - ( - text_mobject(obj, size = "\\small").to_edge(LEFT), - text_mobject(no, size = "\\small").to_edge(LEFT).shift( - (vert_line_x_val + SPACE_WIDTH)*RIGHT - ) - ) - for obj, no in objects_and_notion_strings - ] + self.clear() - self.add(objects, notion, horizontal_line, vertical_line) + self.add(objects, notions, horizontal_line, vertical_line) for (obj, notion), height in zip(objects_and_notions, it.count(2, -1)): - if obj == objects_and_notions[-1][0]: - obj.highlight("yellow") - notion.highlight("yellow") - self.animate(*[ - ShowCreation(mob, run_time = 1.0) - for mob in self.edges + self.vertices - ]) - self.dither() - self.generate_regions() - for region in self.regions: - self.highlight_region(region) - self.add(obj.shift(height*UP)) - self.dither(1) - self.add(notion.shift(height*UP)) - if obj == objects_and_notions[-1][0]: - self.animate(*[ - ShowCreation(deepcopy(e).highlight(), run_time = 1) - for e in self.edges - ]) - self.dither() + obj_mob = text_mobject(obj, size = "\\small").to_edge(LEFT) + not_mob = text_mobject(notion, size = "\\small").to_edge(LEFT) + not_mob.shift((vert_line_x_val + SPACE_WIDTH)*RIGHT) + obj_mob.shift(height*UP) + not_mob.shift(height*UP) + + if obj.startswith("Regions"): + self.handle_dual_graph(obj_mob, not_mob) + elif obj.startswith("English"): + self.handle_english_words(obj_mob, not_mob) else: + self.add(obj_mob) + self.dither() + self.add(not_mob) + self.dither() + + def handle_english_words(self, words1, words2): + words = map(text_mobject, ["graph", "grape", "gape", "gripe"]) + words[0].shift(RIGHT) + words[1].shift(3*RIGHT) + words[2].shift(3*RIGHT + 2*UP) + words[3].shift(3*RIGHT + 2*DOWN) + lines = [ + Line(*pair) + for pair in [ + ( + words[0].get_center() + RIGHT*words[0].get_width()/2, + words[1].get_center() + LEFT*words[1].get_width()/2 + ),( + words[1].get_center() + UP*words[1].get_height()/2, + words[2].get_center() + DOWN*words[2].get_height()/2 + ),( + words[1].get_center() + DOWN*words[1].get_height()/2, + words[3].get_center() + UP*words[3].get_height()/2 + ) + ] + ] + + comp_words = CompoundMobject(*words) + comp_lines = CompoundMobject(*lines) + self.add(words1) + self.animate(ShowCreation(comp_words, run_time = 1.0)) + self.dither() + self.add(words2) + self.animate(ShowCreation(comp_lines, run_time = 1.0)) + self.dither() + self.remove(comp_words, comp_lines) + + + def handle_dual_graph(self, words1, words2): + words1.highlight("yellow") + words2.highlight("yellow") + connected = text_mobject("Connected") + connected.highlight("lightgreen") + not_connected = text_mobject("Not Connected") + not_connected.highlight("red") + for mob in connected, not_connected: + mob.shift(self.points[3] + UP) + + self.animate(*[ + ShowCreation(mob, run_time = 1.0) + for mob in self.edges + self.vertices + ]) + self.dither() + for region in self.regions: + self.highlight_region(region) + self.add(words1) + self.dither() + self.reset_background() + self.add(words2) + + region_pairs = it.combinations(self.graph.region_cycles, 2) + for x in range(6): + want_matching = (x%2 == 0) + found = False + while True: + try: + cycle1, cycle2 = region_pairs.next() + except: + return + shared = set(cycle1).intersection(cycle2) + if len(shared) == 2 and want_matching: + break + if len(shared) != 2 and not want_matching: + break + for cycle in cycle1, cycle2: + index = self.graph.region_cycles.index(cycle) + self.highlight_region(self.regions[index]) + if want_matching: + self.remove(not_connected) + self.add(connected) + tup = tuple(shared) + if tup not in self.graph.edges: + tup = tuple(reversed(tup)) + edge = deepcopy(self.edges[self.graph.edges.index(tup)]) + edge.highlight("red") + self.animate(ShowCreation(edge), run_time = 1.0) + self.dither() + self.remove(edge) + else: + self.remove(connected) + self.add(not_connected) self.dither(2) + self.reset_background() + + + + class DrawDualGraph(GraphScene): def construct(self): @@ -647,7 +863,7 @@ class ListOfCorrespondances(Scene): class CyclesCorrespondWithConnectedComponents(GraphScene): - args_list = [(SAMPLE_GRAPH,)] + args_list = [(SampleGraph(),)] def construct(self): GraphScene.construct(self) self.generate_regions() @@ -694,7 +910,7 @@ class CyclesCorrespondWithConnectedComponents(GraphScene): class IntroduceMortimer(GraphScene): - args_list = [(SAMPLE_GRAPH,)] + args_list = [(SampleGraph(),)] def construct(self): GraphScene.construct(self) self.generate_dual_graph() @@ -756,7 +972,7 @@ class IntroduceMortimer(GraphScene): self.dither() class RandolphMortimerSpanningTreeGame(GraphScene): - args_list = [(SAMPLE_GRAPH,)] + args_list = [(SampleGraph(),)] def construct(self): GraphScene.construct(self) self.generate_spanning_tree() @@ -781,23 +997,23 @@ class RandolphMortimerSpanningTreeGame(GraphScene): )) self.dither() for index in range(len(self.regions)): - if index > 0: - edge = self.edges[dual_edges[index-1]] - midpoint = edge.get_center() - self.animate(*[ - ShowCreation(Line( - midpoint, - tip - ).highlight("red")) - for tip in edge.start, edge.end - ], run_time = time_per_dual_edge) + # if index > 0: + # edge = self.edges[dual_edges[index-1]] + # midpoint = edge.get_center() + # self.animate(*[ + # ShowCreation(Line( + # midpoint, + # tip + # ).highlight("red")) + # for tip in edge.start, edge.end + # ], run_time = time_per_dual_edge) self.highlight_region(self.regions[region_ordering[index]]) self.dither(time_per_dual_edge) self.dither() cycle_index = region_ordering[-1] - cycle = self.graph["region_cycles"][cycle_index] + cycle = self.graph.region_cycles[cycle_index] self.highlight_region(self.regions[cycle_index], "black") self.animate(ShowCreation(CompoundMobject(*[ Line(self.points[last], self.points[next]).highlight("green") @@ -806,7 +1022,7 @@ class RandolphMortimerSpanningTreeGame(GraphScene): self.dither() class MortimerCannotTraverseCycle(GraphScene): - args_list = [(SAMPLE_GRAPH,)] + args_list = [(SampleGraph(),)] def construct(self): GraphScene.construct(self) self.generate_dual_graph() @@ -855,24 +1071,86 @@ class MortimerCannotTraverseCycle(GraphScene): ]) self.dither() +class TwoPropertiesOfSpanningTree(Scene): + def construct(self): + spanning, tree = text_mobject(["Spanning ", "Tree"], size = "\\Huge") + spanning_explanation = text_mobject(""" + Touches every vertex + """).shift(spanning.get_center() + 2*DOWN) + tree_explanation = text_mobject(""" + No Cycles + """).shift(tree.get_center() + 2*UP) + + self.add(spanning, tree) + self.dither() + for word, explanation, vect in [ + (spanning, spanning_explanation, 0.5*UP), + (tree, tree_explanation, 0.5*DOWN) + ]: + self.add(explanation) + self.add(Arrow( + explanation.get_center() + vect, + tail = word.get_center() - vect, + )) + self.animate(ApplyMethod(word.highlight, "yellow")) + self.dither() + + +class DualSpanningTree(GraphScene): + def construct(self): + GraphScene.construct(self) + self.generate_dual_graph() + self.generate_spanning_tree() + randy = Randolph() + randy.scale(RANDOLPH_SCALE_VAL) + randy.move_to(self.points[0]) + morty = Mortimer() + morty.scale(RANDOLPH_SCALE_VAL) + morty.move_to(self.dual_points[0]) + dual_edges = [1, 3, 4, 7, 11, 9, 13] + words = text_mobject(""" + The red edges form a spanning tree of the dual graph! + """).to_edge(UP) + + self.add(self.spanning_tree, randy, morty) + self.animate(ShowCreation(CompoundMobject( + *np.array(self.edges)[dual_edges] + ).highlight("red"))) + self.add(words) + self.dither() + class TreeCountFormula(Scene): def construct(self): time_per_branch = 0.5 - self.add(text_mobject(""" + text = text_mobject(""" In any tree: $$E + 1 = V$$ - """)) - gs = GraphScene(SAMPLE_GRAPH) + """) + gs = GraphScene(SampleGraph()) gs.generate_treeified_spanning_tree() branches = gs.treeified_spanning_tree.to_edge(LEFT).split() - self.add(Dot(branches[0].points[0])) + + all_dots = [Dot(branches[0].points[0])] + self.add(text, all_dots[0]) for branch in branches: self.animate( ShowCreation(branch), run_time = time_per_branch ) - self.add(Dot(branch.points[-1])) + dot = Dot(branch.points[-1]) + self.add(dot) + all_dots.append(dot) self.dither() + self.remove(*all_dots) + self.animate( + FadeOut(text), + FadeIn(CompoundMobject(*gs.edges + gs.vertices)), + *[ + Transform(*pair) + for pair in zip(branches,gs.spanning_tree.split()) + ] + ) + class FinalSum(Scene): def construct(self): diff --git a/scripts/moser_main.py b/scripts/moser_main.py index 7f2c2501..916f59d7 100644 --- a/scripts/moser_main.py +++ b/scripts/moser_main.py @@ -723,9 +723,9 @@ class DefiningGraph(GraphScene): #Move to new graph # new_graph = deepcopy(self.graph) - # new_graph["vertices"] = [ + # new_graph.vertices = [ # (v[0] + 3*random(), v[1] + 3*random(), 0) - # for v in new_graph["vertices"] + # for v in new_graph.vertices # ] # new_graph_scene = GraphScene(new_graph) # self.animate(*[ @@ -739,7 +739,7 @@ class IntersectCubeGraphEdges(GraphScene): def args_to_string(*args): return "" def __init__(self, *args, **kwargs): - GraphScene.__init__(self, CUBE_GRAPH, *args, **kwargs) + GraphScene.__init__(self, CubeGraph(), *args, **kwargs) self.remove(self.edges[0], self.edges[4]) self.animate(*[ Transform( @@ -803,7 +803,7 @@ class EulersFormula(GraphScene): ).highlight("red") for e in self.edges for start, end, midpoint in [ - (e.start, e.end, (e.start + e.end)/2) + (e.start, e.end, (e.start + e.end)/2.0) ] ] frame_time = 0.3 @@ -811,7 +811,7 @@ class EulersFormula(GraphScene): self.generate_regions() parameters = [ (colored_dots, "V", "mobject", "-", "show"), - (colored_edges, "E", "mobject", "+", "show_creation"), + (colored_edges, "E", "mobject", "+", "show"), (self.regions, "F", "region", "=2", "show_all") ] for items, letter, item_type, symbol, mode in parameters: @@ -877,7 +877,7 @@ class ShowMoserGraphLines(CircleScene): radians = list(set(map(lambda x : x%(2*np.pi), radians))) radians.sort() CircleScene.__init__(self, radians, *args, **kwargs) - n, plus_n_choose_4 = tex_mobjects(["n", r"+{n \choose 4}"]) + n, plus_n_choose_4 = tex_mobjects(["n", "+{n \\choose 4}"]) n_choose_2, plus_2_n_choose_4, plus_n = tex_mobjects([ r"{n \choose 2}",r"&+2{n \choose 4}\\",r"&+n" ])