3b1b-manim/old_projects/eulers_characteristic_formula.py

1187 lines
38 KiB
Python
Raw Normal View History

2015-06-13 19:00:23 -07:00
#!/usr/bin/env python
2018-08-09 17:56:05 -07:00
2015-06-13 19:00:23 -07:00
import numpy as np
import itertools as it
from copy import deepcopy
import sys
from animation import *
from mobject import *
from constants import *
from mobject.region import *
2015-06-19 08:31:02 -07:00
import displayer as disp
from scene.scene import Scene, GraphScene
2015-06-19 08:31:02 -07:00
from scene.graphs import *
from .moser_main import EulersFormula
2015-06-13 19:00:23 -07:00
from script_wrapper import command_line_create_scene
MOVIE_PREFIX = "ecf_graph_scenes/"
2016-08-02 15:50:32 -07:00
RANDOLPH_SCALE_FACTOR = 0.3
EDGE_ANNOTATION_SCALE_FACTOR = 0.7
2015-06-19 08:31:02 -07:00
DUAL_CYCLE = [3, 4, 5, 6, 1, 0, 2, 3]
2015-06-13 19:00:23 -07:00
2015-06-22 10:14:53 -07:00
class EulersFormulaWords(Scene):
def construct(self):
self.add(TexMobject("V-E+F=2"))
2015-06-22 10:14:53 -07:00
class TheTheoremWords(Scene):
def construct(self):
self.add(TextMobject("The Theorem:"))
2015-06-22 10:14:53 -07:00
class ProofAtLastWords(Scene):
def construct(self):
self.add(TextMobject("The Proof At Last..."))
2015-06-22 10:14:53 -07:00
class DualSpanningTreeWords(Scene):
def construct(self):
self.add(TextMobject("Spanning trees have duals too!"))
2015-06-22 10:14:53 -07:00
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)
2018-01-15 19:15:05 -08:00
self.wait(2)
self.play(Transform(
2018-03-30 11:51:31 -07:00
Dot(student_bubble.tip).set_color("black"),
Mobject(student_bubble, student_bubble.text)
2015-06-22 10:14:53 -07:00
))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-22 10:14:53 -07:00
self.remove(teacher_bubble.text)
teacher_bubble.write("Does that make this \\\\ any less elegant?")
self.add(teacher_bubble.text)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-22 10:14:53 -07:00
class IllustrateDuality(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
self.add(TextMobject("Duality").to_edge(UP))
2015-06-22 10:14:53 -07:00
self.remove(*self.vertices)
def special_alpha(t):
if t > 0.5:
t = 1 - t
if t < 0.25:
return smooth(4*t)
2015-06-22 10:14:53 -07:00
else:
return 1
kwargs = {
"run_time" : 5.0,
"rate_func" : special_alpha
2015-06-22 10:14:53 -07:00
}
self.play(*[
2015-06-22 10:14:53 -07:00
Transform(*edge_pair, **kwargs)
for edge_pair in zip(self.edges, self.dual_edges)
] + [
Transform(
Mobject(*[
2015-06-22 10:14:53 -07:00
self.vertices[index]
for index in cycle
]),
dv,
**kwargs
)
for cycle, dv in zip(
self.graph.region_cycles,
self.dual_vertices
)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
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 = TextMobject([
2015-06-22 10:14:53 -07:00
"Connected ", "Planar ", "Graph"
2015-06-27 04:49:10 -07:00
]).to_edge(UP).split()
2018-03-30 11:51:31 -07:00
not_okay = TextMobject("Not Okay").set_color("red")
planar_explanation = TextMobject("""
2015-06-22 10:14:53 -07:00
(``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)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.add(graph)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
kwargs = {
"rate_func" : there_and_back,
2015-06-22 10:14:53 -07:00
"run_time" : 5.0
}
self.add(not_okay)
self.play(*[
2015-06-22 10:14:53 -07:00
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)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-22 10:14:53 -07:00
self.remove(planar_explanation)
self.add(not_okay)
self.remove(*edges_to_remove)
self.play(ShowCreation(
Mobject(*edges_to_remove),
rate_func = lambda t : 1 - t,
2015-06-22 10:14:53 -07:00
run_time = 1.0
))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-22 10:14:53 -07:00
self.remove(not_okay)
self.add(connected, *edges_to_remove)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
class OldIntroduceGraphs(GraphScene):
2015-06-13 19:00:23 -07:00
def construct(self):
GraphScene.construct(self)
self.draw_vertices()
self.draw_edges()
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-13 19:00:23 -07:00
self.clear()
self.add(*self.edges)
2015-06-22 10:14:53 -07:00
self.replace_vertices_with(Face().scale(0.4))
2016-08-02 15:50:32 -07:00
friends = TextMobject("Friends").scale(EDGE_ANNOTATION_SCALE_FACTOR)
2015-06-13 19:00:23 -07:00
self.annotate_edges(friends.shift((0, friends.get_height()/2, 0)))
self.play(*[
CounterclockwiseTransform(vertex, Dot(point))
2015-06-13 19:00:23 -07:00
for vertex, point in zip(self.vertices, self.points)
]+[
Transform(ann, line)
for ann, line in zip(
self.edge_annotations,
self.edges
)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-13 19:00:23 -07:00
class PlanarGraphDefinition(Scene):
def construct(self):
Not, quote, planar, end_quote = TextMobject([
2015-06-19 08:31:02 -07:00
"Not \\\\", "``", "Planar", "''",
# "no matter how \\\\ hard you try"
2015-06-27 04:49:10 -07:00
]).split()
shift_val = Mobject(Not, planar).to_corner().get_center()
2018-03-30 11:51:31 -07:00
Not.set_color("red").shift(shift_val)
2015-06-19 08:31:02 -07:00
graphs = [
Mobject(*GraphScene(g).mobjects)
2015-06-19 08:31:02 -07:00
for g in [
2015-06-22 10:14:53 -07:00
CubeGraph(),
CompleteGraph(5),
OctohedronGraph()
2015-06-19 08:31:02 -07:00
]
]
self.add(quote, planar, end_quote)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(
2015-06-19 08:31:02 -07:00
FadeOut(quote),
FadeOut(end_quote),
ApplyMethod(planar.shift, shift_val),
FadeIn(graphs[0]),
run_time = 1.5
)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.remove(graphs[0])
self.add(graphs[1])
2018-03-30 11:51:31 -07:00
planar.set_color("red")
2015-06-19 08:31:02 -07:00
self.add(Not)
2018-01-15 19:15:05 -08:00
self.wait(2)
2018-03-30 11:51:31 -07:00
planar.set_color("white")
2015-06-19 08:31:02 -07:00
self.remove(Not)
self.remove(graphs[1])
self.add(graphs[2])
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
class TerminologyFromPolyhedra(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(CubeGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
rot_kwargs = {
"radians" : np.pi / 3,
"run_time" : 5.0
}
vertices = [
point / 2 + OUT if abs(point[0]) == 2 else point + IN
for point in self.points
]
cube = Mobject(*[
2015-06-19 08:31:02 -07:00
Line(vertices[edge[0]], vertices[edge[1]])
2015-06-22 10:14:53 -07:00
for edge in self.graph.edges
2015-06-19 08:31:02 -07:00
])
cube.rotate(-np.pi/3, [0, 0, 1])
cube.rotate(-np.pi/3, [0, 1, 0])
dots_to_vertices = TextMobject("Dots $\\to$ Vertices").to_corner()
lines_to_edges = TextMobject("Lines $\\to$ Edges").to_corner()
regions_to_faces = TextMobject("Regions $\\to$ Faces").to_corner()
2015-06-13 19:00:23 -07:00
2015-06-19 08:31:02 -07:00
self.clear()
# self.play(TransformAnimations(
2015-06-22 10:14:53 -07:00
# Rotating(Dodecahedron(), **rot_kwargs),
# Rotating(cube, **rot_kwargs)
# ))
self.play(Rotating(cube, **rot_kwargs))
2015-06-19 08:31:02 -07:00
self.clear()
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(l1, l2)
for l1, l2 in zip(cube.split(), self.edges)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.add(dots_to_vertices)
self.play(*[
2015-06-19 08:31:02 -07:00
ShowCreation(dot, run_time = 1.0)
for dot in self.vertices
])
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.remove(dots_to_vertices, *self.vertices)
self.add(lines_to_edges)
self.play(ApplyMethod(
2018-03-30 11:51:31 -07:00
Mobject(*self.edges).set_color, "yellow"
2015-06-19 08:31:02 -07:00
))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.clear()
self.add(*self.edges)
self.add(regions_to_faces)
self.generate_regions()
for region in self.regions:
2018-03-30 11:51:31 -07:00
self.set_color_region(region)
2018-01-15 19:15:05 -08:00
self.wait(3.0)
2015-06-19 08:31:02 -07:00
class ThreePiecesOfTerminology(GraphScene):
def construct(self):
GraphScene.construct(self)
terms = cycles, spanning_trees, dual_graphs = [
TextMobject(phrase).shift(y*UP).to_edge()
2015-06-19 08:31:02 -07:00
for phrase, y in [
("Cycles", 3),
("Spanning Trees", 1),
("Dual Graphs", -1),
]
]
self.generate_spanning_tree()
2016-08-02 15:50:32 -07:00
scale_factor = 1.2
2015-06-19 08:31:02 -07:00
def accent(mobject, color = "yellow"):
2018-03-30 11:51:31 -07:00
return mobject.scale_in_place(scale_factor).set_color(color)
2015-06-19 08:31:02 -07:00
def tone_down(mobject):
2018-03-30 11:51:31 -07:00
return mobject.scale_in_place(1.0/scale_factor).set_color("white")
2015-06-19 08:31:02 -07:00
self.add(accent(cycles))
self.trace_cycle(run_time = 1.0)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
tone_down(cycles)
self.remove(self.traced_cycle)
self.add(accent(spanning_trees))
self.play(ShowCreation(self.spanning_tree), run_time = 1.0)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
tone_down(spanning_trees)
self.remove(self.spanning_tree)
self.add(accent(dual_graphs, "red"))
self.generate_dual_graph()
for mob in self.mobjects:
mob.fade
self.play(*[
2015-06-19 08:31:02 -07:00
ShowCreation(mob, run_time = 1.0)
for mob in self.dual_vertices + self.dual_edges
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.clear()
self.play(ApplyMethod(
Mobject(*terms).center
2015-06-19 08:31:02 -07:00
))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class WalkingRandolph(GraphScene):
args_list = [
2015-06-22 10:14:53 -07:00
(SampleGraph(), [0, 1, 7, 8]),
2015-06-19 08:31:02 -07:00
]
@staticmethod
def args_to_string(graph, path):
2015-06-22 10:14:53 -07:00
return str(graph) + "".join(map(str, path))
2015-06-19 08:31:02 -07:00
def __init__(self, graph, path, *args, **kwargs):
self.path = path
GraphScene.__init__(self, graph, *args, **kwargs)
def construct(self):
GraphScene.construct(self)
point_path = [self.points[i] for i in self.path]
randy = Randolph()
2016-08-02 15:50:32 -07:00
randy.scale(RANDOLPH_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
randy.move_to(point_path[0])
for next, last in zip(point_path[1:], point_path):
self.play(
2015-06-19 08:31:02 -07:00
WalkPiCreature(randy, next),
2018-03-30 11:51:31 -07:00
ShowCreation(Line(last, next).set_color("yellow")),
2015-06-19 08:31:02 -07:00
run_time = 2.0
)
self.randy = randy
class PathExamples(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(SampleGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
paths = [
(1, 2, 4, 5, 6),
(6, 7, 1, 3),
]
non_paths = [
[(0, 1), (7, 8), (5, 6),],
[(5, 0), (0, 2), (0, 1)],
]
2018-03-30 11:51:31 -07:00
valid_path = TextMobject("Valid \\\\ Path").set_color("green")
not_a_path = TextMobject("Not a \\\\ Path").set_color("red")
2015-06-19 08:31:02 -07:00
for mob in valid_path, not_a_path:
mob.to_edge(UP)
kwargs = {"run_time" : 1.0}
for path, non_path in zip(paths, non_paths):
path_lines = Mobject(*[
2015-06-19 08:31:02 -07:00
Line(
self.points[path[i]],
self.points[path[i+1]]
2018-03-30 11:51:31 -07:00
).set_color("yellow")
2015-06-19 08:31:02 -07:00
for i in range(len(path) - 1)
])
non_path_lines = Mobject(*[
2015-06-19 08:31:02 -07:00
Line(
self.points[pp[0]],
self.points[pp[1]],
2018-03-30 11:51:31 -07:00
).set_color("yellow")
2015-06-19 08:31:02 -07:00
for pp in non_path
])
self.remove(not_a_path)
self.add(valid_path)
self.play(ShowCreation(path_lines, **kwargs))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.remove(path_lines)
self.remove(valid_path)
self.add(not_a_path)
self.play(ShowCreation(non_path_lines, **kwargs))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.remove(non_path_lines)
class IntroduceCycle(WalkingRandolph):
args_list = [
2015-06-22 10:14:53 -07:00
(SampleGraph(), [0, 1, 3, 2, 0])
2015-06-19 08:31:02 -07:00
]
def construct(self):
WalkingRandolph.construct(self)
self.remove(self.randy)
2018-08-09 17:56:05 -07:00
encompassed_cycles = [c for c in self.graph.region_cycles if set(c).issubset(self.path)]
2015-06-19 08:31:02 -07:00
regions = [
self.region_from_cycle(cycle)
for cycle in encompassed_cycles
]
for region in regions:
2018-03-30 11:51:31 -07:00
self.set_color_region(region)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class IntroduceRandolph(GraphScene):
def construct(self):
GraphScene.construct(self)
randy = Randolph().move_to((-3, 0, 0))
name = TextMobject("Randolph")
self.play(Transform(
2015-06-19 08:31:02 -07:00
randy,
2016-08-02 15:50:32 -07:00
deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR).move_to(self.points[0]),
2015-06-19 08:31:02 -07:00
))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
name.shift((0, 1, 0))
self.add(name)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class DefineSpanningTree(GraphScene):
def construct(self):
GraphScene.construct(self)
randy = Randolph()
2016-08-02 15:50:32 -07:00
randy.scale(RANDOLPH_SCALE_FACTOR).move_to(self.points[0])
dollar_signs = TextMobject("\\$\\$")
2016-08-02 15:50:32 -07:00
dollar_signs.scale(EDGE_ANNOTATION_SCALE_FACTOR)
dollar_signs = Mobject(*[
2015-06-19 08:31:02 -07:00
deepcopy(dollar_signs).shift(edge.get_center())
for edge in self.edges
])
unneeded = TextMobject("unneeded!")
2016-08-02 15:50:32 -07:00
unneeded.scale(EDGE_ANNOTATION_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
self.generate_spanning_tree()
def green_dot_at_index(index):
return Dot(
self.points[index],
radius = 2*Dot.DEFAULT_RADIUS,
color = "lightgreen",
)
def out_of_spanning_set(point_pair):
stip = self.spanning_tree_index_pairs
return point_pair not in stip and \
tuple(reversed(point_pair)) not in stip
self.add(randy)
self.accent_vertices(run_time = 2.0)
self.add(dollar_signs)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.remove(dollar_signs)
run_time_per_branch = 0.5
self.play(
2015-06-19 08:31:02 -07:00
ShowCreation(green_dot_at_index(0)),
run_time = run_time_per_branch
)
for pair in self.spanning_tree_index_pairs:
self.play(ShowCreation(
2015-06-19 08:31:02 -07:00
Line(
self.points[pair[0]],
self.points[pair[1]]
2018-03-30 11:51:31 -07:00
).set_color("yellow"),
2015-06-19 08:31:02 -07:00
run_time = run_time_per_branch
))
self.play(ShowCreation(
2015-06-19 08:31:02 -07:00
green_dot_at_index(pair[1]),
run_time = run_time_per_branch
))
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
2018-08-09 17:56:05 -07:00
unneeded_edges = list(filter(out_of_spanning_set, self.graph.edges))
for edge, limit in zip(unneeded_edges, list(range(5))):
2015-06-19 08:31:02 -07:00
line = Line(self.points[edge[0]], self.points[edge[1]])
2018-03-30 11:51:31 -07:00
line.set_color("red")
self.play(ShowCreation(line, run_time = 1.0))
2015-06-19 08:31:02 -07:00
self.add(unneeded.center().shift(line.get_center() + 0.2*UP))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.remove(line, unneeded)
class NamingTree(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_spanning_tree()
self.generate_treeified_spanning_tree()
branches = self.spanning_tree.split()
branches_copy = deepcopy(branches)
treeified_branches = self.treeified_spanning_tree.split()
tree = TextMobject("``Tree''").to_edge(UP)
spanning_tree = TextMobject("``Spanning Tree''").to_edge(UP)
2015-06-19 08:31:02 -07:00
self.add(*branches)
self.play(
FadeOut(Mobject(*self.edges + self.vertices)),
Animation(Mobject(*branches)),
2015-06-19 08:31:02 -07:00
)
self.clear()
self.add(tree, *branches)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(b1, b2, run_time = 2)
for b1, b2 in zip(branches, treeified_branches)
])
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
2015-06-19 08:31:02 -07:00
FadeIn(mob)
for mob in self.edges + self.vertices
] + [
Transform(b1, b2, run_time = 2)
for b1, b2 in zip(branches, branches_copy)
])
self.accent_vertices(run_time = 2)
self.remove(tree)
self.add(spanning_tree)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
class DualGraph(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
self.add(TextMobject("Dual Graph").to_edge(UP).shift(2*LEFT))
self.play(*[
2015-06-19 08:31:02 -07:00
ShowCreation(mob)
for mob in self.dual_edges + self.dual_vertices
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class FacebookLogo(Scene):
def construct(self):
im = ImageMobject("facebook_full_logo", invert = False)
self.add(im.scale(0.7))
class FacebookGraph(GraphScene):
def construct(self):
GraphScene.construct(self)
account = ImageMobject("facebook_silhouette", invert = False)
account.scale(0.05)
logo = ImageMobject("facebook_logo", invert = False)
logo.scale(0.1)
logo.shift(0.2*LEFT + 0.1*UP)
account.add(logo).center()
account.shift(0.2*LEFT + 0.1*UP)
friends = TexMobject(
2015-06-19 08:31:02 -07:00
"\\leftarrow \\text{friends} \\rightarrow"
2016-08-02 15:50:32 -07:00
).scale(0.5*EDGE_ANNOTATION_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
self.clear()
accounts = [
deepcopy(account).shift(point)
for point in self.points
]
self.add(*accounts)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.annotate_edges(friends)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
CounterclockwiseTransform(account, vertex)
2015-06-19 08:31:02 -07:00
for account, vertex in zip(accounts, self.vertices)
])
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(ann, edge)
for ann, edge in zip(self.edge_annotations, self.edges)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class FacebookGraphAsAbstractSet(Scene):
def construct(self):
names = [
"Louis",
"Randolph",
"Mortimer",
"Billy Ray",
"Penelope",
]
friend_pairs = [
(0, 1),
(0, 2),
(1, 2),
(3, 0),
(4, 0),
(1, 3),
(1, 2),
]
2015-06-22 10:14:53 -07:00
names_string = "\\\\".join(names + ["$\\vdots$"])
friends_string = "\\\\".join([
2015-06-19 08:31:02 -07:00
"\\text{%s}&\\leftrightarrow\\text{%s}"%(names[i],names[j])
for i, j in friend_pairs
2015-06-22 10:14:53 -07:00
] + ["\\vdots"])
names_mob = TextMobject(names_string).shift(3*LEFT)
friends_mob = TexMobject(
2015-06-22 10:14:53 -07:00
friends_string, size = "\\Large"
).shift(3*RIGHT)
accounts = TextMobject("\\textbf{Accounts}")
2015-06-19 08:31:02 -07:00
accounts.shift(3*LEFT).to_edge(UP)
friendships = TextMobject("\\textbf{Friendships}")
2015-06-19 08:31:02 -07:00
friendships.shift(3*RIGHT).to_edge(UP)
lines = Mobject(
Line(UP*FRAME_Y_RADIUS, DOWN*FRAME_Y_RADIUS),
Line(LEFT*FRAME_X_RADIUS + 3*UP, RIGHT*FRAME_X_RADIUS + 3*UP)
2018-03-30 11:51:31 -07:00
).set_color("white")
2015-06-19 08:31:02 -07:00
self.add(accounts, friendships, lines)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
for mob in names_mob, friends_mob:
self.play(ShowCreation(
2015-06-19 08:31:02 -07:00
mob, run_time = 1.0
))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class ExamplesOfGraphs(GraphScene):
def construct(self):
buff = 0.5
2018-08-09 17:56:05 -07:00
self.graph.vertices = [v + DOWN + RIGHT for v in self.graph.vertices]
2015-06-22 10:14:53 -07:00
GraphScene.construct(self)
self.generate_regions()
objects, notions = Mobject(*TextMobject(
2015-06-19 08:31:02 -07:00
["Objects \\quad\\quad ", "Thing that connects objects"]
)).to_corner().shift(0.5*RIGHT).split()
horizontal_line = Line(
(-FRAME_X_RADIUS, FRAME_Y_RADIUS-1, 0),
(max(notions.points[:,0]), FRAME_Y_RADIUS-1, 0)
2015-06-19 08:31:02 -07:00
)
2015-06-22 10:14:53 -07:00
vert_line_x_val = min(notions.points[:,0]) - buff
2015-06-19 08:31:02 -07:00
vertical_line = Line(
(vert_line_x_val, FRAME_Y_RADIUS, 0),
(vert_line_x_val,-FRAME_Y_RADIUS, 0)
2015-06-19 08:31:02 -07:00
)
2015-06-22 10:14:53 -07:00
objects_and_notions = [
2015-06-19 08:31:02 -07:00
("Facebook accounts", "Friendship"),
2015-06-22 10:14:53 -07:00
("English Words", "Differ by One Letter"),
("Mathematicians", "Coauthorship"),
2015-06-19 08:31:02 -07:00
("Neurons", "Synapses"),
(
"Regions our graph \\\\ cuts the plane into",
2015-06-22 10:14:53 -07:00
"Shared edges"
2015-06-19 08:31:02 -07:00
)
]
2015-06-22 10:14:53 -07:00
2015-06-19 08:31:02 -07:00
self.clear()
2015-06-22 10:14:53 -07:00
self.add(objects, notions, horizontal_line, vertical_line)
2015-06-19 08:31:02 -07:00
for (obj, notion), height in zip(objects_and_notions, it.count(2, -1)):
obj_mob = TextMobject(obj, size = "\\small").to_edge(LEFT)
not_mob = TextMobject(notion, size = "\\small").to_edge(LEFT)
not_mob.shift((vert_line_x_val + FRAME_X_RADIUS)*RIGHT)
2015-06-22 10:14:53 -07:00
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)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.add(not_mob)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
def handle_english_words(self, words1, words2):
2018-08-09 17:56:05 -07:00
words = list(map(TextMobject, ["graph", "grape", "gape", "gripe"]))
2015-06-22 10:14:53 -07:00
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 = Mobject(*words)
comp_lines = Mobject(*lines)
2015-06-22 10:14:53 -07:00
self.add(words1)
self.play(ShowCreation(comp_words, run_time = 1.0))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.add(words2)
self.play(ShowCreation(comp_lines, run_time = 1.0))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.remove(comp_words, comp_lines)
def handle_dual_graph(self, words1, words2):
2018-03-30 11:51:31 -07:00
words1.set_color("yellow")
words2.set_color("yellow")
connected = TextMobject("Connected")
2018-03-30 11:51:31 -07:00
connected.set_color("lightgreen")
not_connected = TextMobject("Not Connected")
2018-03-30 11:51:31 -07:00
not_connected.set_color("red")
2015-06-22 10:14:53 -07:00
for mob in connected, not_connected:
mob.shift(self.points[3] + UP)
self.play(*[
2015-06-22 10:14:53 -07:00
ShowCreation(mob, run_time = 1.0)
for mob in self.edges + self.vertices
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
for region in self.regions:
2018-03-30 11:51:31 -07:00
self.set_color_region(region)
2015-06-22 10:14:53 -07:00
self.add(words1)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
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 = next(region_pairs)
2015-06-22 10:14:53 -07:00
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)
2018-03-30 11:51:31 -07:00
self.set_color_region(self.regions[index])
2015-06-22 10:14:53 -07:00
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)])
2018-03-30 11:51:31 -07:00
edge.set_color("red")
self.play(ShowCreation(edge), run_time = 1.0)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.remove(edge)
2015-06-19 08:31:02 -07:00
else:
2015-06-22 10:14:53 -07:00
self.remove(connected)
self.add(not_connected)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-22 10:14:53 -07:00
self.reset_background()
2015-06-19 08:31:02 -07:00
class DrawDualGraph(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_regions()
self.generate_dual_graph()
region_mobs = [
ImageMobject(disp.paint_region(reg, self.background), invert = False)
for reg in self.regions
]
for region, mob in zip(self.regions, region_mobs):
2018-03-30 11:51:31 -07:00
self.set_color_region(region, mob.get_color())
2015-06-19 08:31:02 -07:00
outer_region = self.regions.pop()
outer_region_mob = region_mobs.pop()
outer_dual_vertex = self.dual_vertices.pop()
2018-08-09 17:56:05 -07:00
internal_edges = [e for e in self.dual_edges if abs(e.start[0]) < FRAME_X_RADIUS and \
abs(e.end[0]) < FRAME_X_RADIUS and \
abs(e.start[1]) < FRAME_Y_RADIUS and \
2018-08-09 17:56:05 -07:00
abs(e.end[1]) < FRAME_Y_RADIUS]
external_edges = [e for e in self.dual_edges if e not in internal_edges]
2015-06-19 08:31:02 -07:00
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.reset_background()
2018-03-30 11:51:31 -07:00
self.set_color_region(outer_region, outer_region_mob.get_color())
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(reg_mob, dot)
for reg_mob, dot in zip(region_mobs, self.dual_vertices)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.reset_background()
self.play(ApplyFunction(
lambda p : (FRAME_X_RADIUS + FRAME_Y_RADIUS)*p/np.linalg.norm(p),
2015-06-19 08:31:02 -07:00
outer_region_mob
))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
for edges in internal_edges, external_edges:
self.play(*[
2015-06-19 08:31:02 -07:00
ShowCreation(edge, run_time = 2.0)
for edge in edges
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class EdgesAreTheSame(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
self.remove(*self.vertices)
self.add(*self.dual_edges)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(*pair, run_time = 2.0)
for pair in zip(self.dual_edges, self.edges)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.add(
TextMobject("""
2015-06-19 08:31:02 -07:00
(Or at least I would argue they should \\\\
be thought of as the same thing.)
""", size = "\\small").to_edge(UP)
)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class ListOfCorrespondances(Scene):
def construct(self):
buff = 0.5
correspondances = [
["Regions cut out by", "Vertices of"],
["Edges of", "Edges of"],
["Cycles of", "Connected components of"],
["Connected components of", "Cycles of"],
["Spanning tree in", "Complement of spanning tree in"],
["", "Dual of"],
]
for corr in correspondances:
corr[0] += " original graph"
corr[1] += " dual graph"
arrow = TexMobject("\\leftrightarrow", size = "\\large")
2015-06-19 08:31:02 -07:00
lines = []
for corr, height in zip(correspondances, it.count(3, -1)):
left = TextMobject(corr[0], size = "\\small")
right = TextMobject(corr[1], size = "\\small")
2015-06-19 08:31:02 -07:00
this_arrow = deepcopy(arrow)
for mob in left, right, this_arrow:
mob.shift(height*UP)
arrow_xs = this_arrow.points[:,0]
left.to_edge(RIGHT)
left.shift((min(arrow_xs) - FRAME_X_RADIUS, 0, 0))
2015-06-19 08:31:02 -07:00
right.to_edge(LEFT)
right.shift((max(arrow_xs) + FRAME_X_RADIUS, 0, 0))
lines.append(Mobject(left, right, this_arrow))
2015-06-19 08:31:02 -07:00
last = None
for line in lines:
2018-03-30 11:51:31 -07:00
self.add(line.set_color("yellow"))
2015-06-19 08:31:02 -07:00
if last:
2018-03-30 11:51:31 -07:00
last.set_color("white")
2015-06-19 08:31:02 -07:00
last = line
2018-01-15 19:15:05 -08:00
self.wait(1)
2015-06-19 08:31:02 -07:00
class CyclesCorrespondWithConnectedComponents(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(SampleGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
self.generate_regions()
self.generate_dual_graph()
cycle = [4, 2, 1, 5, 4]
enclosed_regions = [0, 2, 3, 4]
dual_cycle = DUAL_CYCLE
enclosed_vertices = [0, 1]
randy = Randolph()
2016-08-02 15:50:32 -07:00
randy.scale(RANDOLPH_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
randy.move_to(self.points[cycle[0]])
lines_to_remove = []
for last, next in zip(cycle, cycle[1:]):
line = Line(self.points[last], self.points[next])
2018-03-30 11:51:31 -07:00
line.set_color("yellow")
self.play(
2015-06-19 08:31:02 -07:00
ShowCreation(line),
WalkPiCreature(randy, self.points[next]),
run_time = 1.0
)
lines_to_remove.append(line)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.remove(randy, *lines_to_remove)
for region in np.array(self.regions)[enclosed_regions]:
2018-03-30 11:51:31 -07:00
self.set_color_region(region)
2018-01-15 19:15:05 -08:00
self.wait(2)
2015-06-19 08:31:02 -07:00
self.reset_background()
lines = Mobject(*[
2015-06-19 08:31:02 -07:00
Line(self.dual_points[last], self.dual_points[next])
for last, next in zip(dual_cycle, dual_cycle[1:])
2018-03-30 11:51:31 -07:00
]).set_color("red")
self.play(ShowCreation(lines))
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(v, Dot(
v.get_center(),
radius = 3*Dot.DEFAULT_RADIUS
2018-03-30 11:51:31 -07:00
).set_color("green"))
2015-06-19 08:31:02 -07:00
for v in np.array(self.vertices)[enclosed_vertices]
] + [
2018-03-30 11:51:31 -07:00
ApplyMethod(self.edges[0].set_color, "green")
2015-06-19 08:31:02 -07:00
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class IntroduceMortimer(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(SampleGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
self.generate_regions()
randy = Randolph().shift(LEFT)
morty = Mortimer().shift(RIGHT)
name = TextMobject("Mortimer")
2015-06-19 08:31:02 -07:00
name.shift(morty.get_center() + 1.2*UP)
randy_path = (0, 1, 3)
morty_path = (-2, -3, -4)
morty_crossed_lines = [
2018-03-30 11:51:31 -07:00
Line(self.points[i], self.points[j]).set_color("red")
2015-06-19 08:31:02 -07:00
for i, j in [(7, 1), (1, 5)]
]
kwargs = {"run_time" : 1.0}
self.clear()
self.add(randy)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.add(morty, name)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
self.remove(name)
2016-08-02 15:50:32 -07:00
small_randy = deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR)
small_morty = deepcopy(morty).scale(RANDOLPH_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
small_randy.move_to(self.points[randy_path[0]])
small_morty.move_to(self.dual_points[morty_path[0]])
self.play(*[
2015-06-19 08:31:02 -07:00
FadeIn(mob)
for mob in self.vertices + self.edges
] + [
Transform(randy, small_randy),
Transform(morty, small_morty),
], **kwargs)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
2018-03-30 11:51:31 -07:00
self.set_color_region(self.regions[morty_path[0]])
2015-06-19 08:31:02 -07:00
for last, next in zip(morty_path, morty_path[1:]):
self.play(WalkPiCreature(morty, self.dual_points[next]),**kwargs)
2018-03-30 11:51:31 -07:00
self.set_color_region(self.regions[next])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
for last, next in zip(randy_path, randy_path[1:]):
line = Line(self.points[last], self.points[next])
2018-03-30 11:51:31 -07:00
line.set_color("yellow")
self.play(
2015-06-19 08:31:02 -07:00
WalkPiCreature(randy, self.points[next]),
ShowCreation(line),
**kwargs
)
2018-01-15 19:15:05 -08:00
self.wait()
self.play(*[
2015-06-19 08:31:02 -07:00
ApplyMethod(
line.rotate_in_place,
np.pi/10,
rate_func = wiggle)
2015-06-19 08:31:02 -07:00
for line in morty_crossed_lines
], **kwargs)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class RandolphMortimerSpanningTreeGame(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(SampleGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
self.generate_spanning_tree()
self.generate_dual_graph()
self.generate_regions()
2016-08-02 15:50:32 -07:00
randy = Randolph().scale(RANDOLPH_SCALE_FACTOR)
morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
randy.move_to(self.points[0])
morty.move_to(self.dual_points[0])
attempted_dual_point_index = 2
region_ordering = [0, 1, 7, 2, 3, 5, 4, 6]
dual_edges = [1, 3, 4, 7, 11, 9, 13]
time_per_dual_edge = 0.5
self.add(randy, morty)
self.play(ShowCreation(self.spanning_tree))
2018-01-15 19:15:05 -08:00
self.wait()
self.play(WalkPiCreature(
2015-06-19 08:31:02 -07:00
morty, self.dual_points[attempted_dual_point_index],
rate_func = lambda t : 0.3 * there_and_back(t),
2015-06-19 08:31:02 -07:00
run_time = 2.0,
))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
for index in range(len(self.regions)):
2015-06-22 10:14:53 -07:00
# if index > 0:
# edge = self.edges[dual_edges[index-1]]
# midpoint = edge.get_center()
# self.play(*[
2015-06-22 10:14:53 -07:00
# ShowCreation(Line(
# midpoint,
# tip
2018-03-30 11:51:31 -07:00
# ).set_color("red"))
2015-06-22 10:14:53 -07:00
# for tip in edge.start, edge.end
# ], run_time = time_per_dual_edge)
2018-03-30 11:51:31 -07:00
self.set_color_region(self.regions[region_ordering[index]])
2018-01-15 19:15:05 -08:00
self.wait(time_per_dual_edge)
self.wait()
2015-06-19 08:31:02 -07:00
cycle_index = region_ordering[-1]
2015-06-22 10:14:53 -07:00
cycle = self.graph.region_cycles[cycle_index]
2018-03-30 11:51:31 -07:00
self.set_color_region(self.regions[cycle_index], "black")
self.play(ShowCreation(Mobject(*[
2018-03-30 11:51:31 -07:00
Line(self.points[last], self.points[next]).set_color("green")
2015-06-19 08:31:02 -07:00
for last, next in zip(cycle, list(cycle)[1:] + [cycle[0]])
])))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
class MortimerCannotTraverseCycle(GraphScene):
2015-06-22 10:14:53 -07:00
args_list = [(SampleGraph(),)]
2015-06-19 08:31:02 -07:00
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
dual_cycle = DUAL_CYCLE
trapped_points = [0, 1]
2016-08-02 15:50:32 -07:00
morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR)
2015-06-19 08:31:02 -07:00
morty.move_to(self.dual_points[dual_cycle[0]])
time_per_edge = 0.5
text = TextMobject("""
2015-06-19 08:31:02 -07:00
One of these lines must be included
in the spanning tree if those two inner
vertices are to be reached.
""").scale(0.7).to_edge(UP)
all_lines = []
matching_edges = []
kwargs = {"run_time" : time_per_edge, "rate_func" : None}
2015-06-19 08:31:02 -07:00
for last, next in zip(dual_cycle, dual_cycle[1:]):
line = Line(self.dual_points[last], self.dual_points[next])
2018-03-30 11:51:31 -07:00
line.set_color("red")
self.play(
2015-06-19 08:31:02 -07:00
WalkPiCreature(morty, self.dual_points[next], **kwargs),
ShowCreation(line, **kwargs),
)
all_lines.append(line)
center = line.get_center()
2018-08-09 17:56:05 -07:00
distances = [np.linalg.norm(center - e.get_center()) for e in self.edges]
2015-06-19 08:31:02 -07:00
matching_edges.append(
self.edges[distances.index(min(distances))]
)
self.play(*[
2015-06-19 08:31:02 -07:00
Transform(v, Dot(
v.get_center(),
radius = 3*Dot.DEFAULT_RADIUS,
color = "green"
))
for v in np.array(self.vertices)[trapped_points]
])
self.add(text)
self.play(*[
2018-03-30 11:51:31 -07:00
Transform(line, deepcopy(edge).set_color(line.get_color()))
2015-06-19 08:31:02 -07:00
for line, edge in zip(all_lines, matching_edges)
])
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
2015-06-22 10:14:53 -07:00
class TwoPropertiesOfSpanningTree(Scene):
def construct(self):
spanning, tree = TextMobject(
2015-06-27 04:49:10 -07:00
["Spanning ", "Tree"],
size = "\\Huge"
).split()
spanning_explanation = TextMobject("""
2015-06-22 10:14:53 -07:00
Touches every vertex
""").shift(spanning.get_center() + 2*DOWN)
tree_explanation = TextMobject("""
2015-06-22 10:14:53 -07:00
No Cycles
""").shift(tree.get_center() + 2*UP)
self.add(spanning, tree)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
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,
))
2018-03-30 11:51:31 -07:00
self.play(ApplyMethod(word.set_color, "yellow"))
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
class DualSpanningTree(GraphScene):
def construct(self):
GraphScene.construct(self)
self.generate_dual_graph()
self.generate_spanning_tree()
randy = Randolph()
2016-08-02 15:50:32 -07:00
randy.scale(RANDOLPH_SCALE_FACTOR)
2015-06-22 10:14:53 -07:00
randy.move_to(self.points[0])
morty = Mortimer()
2016-08-02 15:50:32 -07:00
morty.scale(RANDOLPH_SCALE_FACTOR)
2015-06-22 10:14:53 -07:00
morty.move_to(self.dual_points[0])
dual_edges = [1, 3, 4, 7, 11, 9, 13]
words = TextMobject("""
2015-06-22 10:14:53 -07:00
The red edges form a spanning tree of the dual graph!
""").to_edge(UP)
self.add(self.spanning_tree, randy, morty)
self.play(ShowCreation(Mobject(
2015-06-22 10:14:53 -07:00
*np.array(self.edges)[dual_edges]
2018-03-30 11:51:31 -07:00
).set_color("red")))
2015-06-22 10:14:53 -07:00
self.add(words)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
2015-06-19 08:31:02 -07:00
class TreeCountFormula(Scene):
def construct(self):
time_per_branch = 0.5
text = TextMobject("""
2015-06-19 08:31:02 -07:00
In any tree:
$$E + 1 = V$$
2015-06-22 10:14:53 -07:00
""")
gs = GraphScene(SampleGraph())
2015-06-19 08:31:02 -07:00
gs.generate_treeified_spanning_tree()
branches = gs.treeified_spanning_tree.to_edge(LEFT).split()
2015-06-22 10:14:53 -07:00
all_dots = [Dot(branches[0].points[0])]
self.add(text, all_dots[0])
2015-06-19 08:31:02 -07:00
for branch in branches:
self.play(
2015-06-19 08:31:02 -07:00
ShowCreation(branch),
run_time = time_per_branch
)
2015-06-22 10:14:53 -07:00
dot = Dot(branch.points[-1])
self.add(dot)
all_dots.append(dot)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-22 10:14:53 -07:00
self.remove(*all_dots)
self.play(
2015-06-22 10:14:53 -07:00
FadeOut(text),
FadeIn(Mobject(*gs.edges + gs.vertices)),
2015-06-22 10:14:53 -07:00
*[
Transform(*pair)
for pair in zip(branches,gs.spanning_tree.split())
]
)
2015-06-19 08:31:02 -07:00
class FinalSum(Scene):
def construct(self):
lines = TexMobject([
2015-06-19 08:31:02 -07:00
"(\\text{Number of Randolph's Edges}) + 1 &= V \\\\ \n",
"(\\text{Number of Mortimer's Edges}) + 1 &= F \\\\ \n",
" \\Downarrow \\\\", "E","+","2","&=","V","+","F",
2015-06-27 04:49:10 -07:00
], size = "\\large").split()
for line in lines[:2] + [Mobject(*lines[2:])]:
2015-06-19 08:31:02 -07:00
self.add(line)
2018-01-15 19:15:05 -08:00
self.wait()
self.wait()
2015-06-19 08:31:02 -07:00
symbols = V, minus, E, plus, F, equals, two = TexMobject(
2015-06-19 08:31:02 -07:00
"V - E + F = 2".split(" ")
)
plus = TexMobject("+")
2015-06-19 08:31:02 -07:00
anims = []
for mob, index in zip(symbols, [-3, -2, -7, -6, -1, -4, -5]):
copy = plus if index == -2 else deepcopy(mob)
copy.center().shift(lines[index].get_center())
copy.scale_in_place(lines[index].get_width()/mob.get_width())
anims.append(CounterclockwiseTransform(copy, mob))
2015-06-19 08:31:02 -07:00
self.clear()
self.play(*anims, run_time = 2.0)
2018-01-15 19:15:05 -08:00
self.wait()
2015-06-19 08:31:02 -07:00
2015-06-13 19:00:23 -07:00
if __name__ == "__main__":
2015-06-19 08:31:02 -07:00
command_line_create_scene(MOVIE_PREFIX)