def glue(g,h,n): """ Glues two ribbon graphs together. INPUT: - A ribbon graph - A ribbon graph - A non-negative integer. OUTPUT: A ribbon graph >>> f = RibbonGraph.vertex(4) >>> g = RibbonGraph.vertex(3) >>> glue(f,g,0) #doctest: +ELLIPSIS <__main__.RibbonGraph object at 0...> """ if n < 0: raise ValueError("Need a non-negative integer.") if n > len(g.bd) or n > len(h.bd): raise ValueError("Cannot glue this many points.") a = g.copy() b = h.copy() he = a.jg.he.union(b.jg.he) jg = ribbon.justgraph(he) for i in xrange(n): x = a.bd.pop() y = b.bd.popleft() jg.stitch(x,y) return RibbonGraph(jg,list(a.bd)+list(b.bd))
def compose(self,other): """Composition of morphisms. INPUT: Two morphisms OUTPUT: A morphism EXAMPLES: >>> s1 = Artin_generator(3,1) >>> s2 = Artin_generator(3,2) >>> s1.compose(s2) # doctest:+ELLIPSIS <__main__.Morphism instance at 0x...> >>> s1 = Artin_generator(3,1) >>> t1 = Artin_generator(3,-1) >>> s1.compose(t1) # doctest:+ELLIPSIS <__main__.Morphism instance at 0x...> """ if len(self.codomain) != len(other.domain): raise ValueError sm = self.copy() om = other.copy() jg = ribbon.justgraph(sm.graph.he.union(om.graph.he)) for u, v in zip(sm.codomain,om.domain): jg.stitch(u, v) jg.normal() return Morphism(jg,sm.domain,om.codomain)
def vertex(x): """ Constructs a single vertex of valency n. INPUT: A positive integer n, at least 1 or a list of features. OUTPUT: A closed ribbon graph. EXAMPLES: >>> RibbonGraph.vertex(3) # doctest:+ELLIPSIS <__main__.RibbonGraph object at 0x...> """ if isinstance(x, Iterable): n = len(x) else: n = x if n<1: raise ValueError("Not enough points.") a = [ ribbon.halfedge() for i in xrange(n) ] for i in xrange(n-1): a[i].c = a[i+1] a[n-1].c = a[0] if isinstance(x, Iterable): for i, r in zip(xrange(n),x): if not isinstance(r, ribbon.features): raise ValueError a[i].decorations = r h = ribbon.justgraph(a) return RibbonGraph(h,a)
def polygon(n): """ This constructs a polygon with n sides. INPUT: A positive integer n, at least 1. OUTPUT: A closed ribbon graph. EXAMPLE: >>> RibbonGraph.polygon(4) # doctest:+ELLIPSIS <__main__.RibbonGraph object at 0x...> """ if n<1: raise ValueError a = [ ribbon.halfedge() for i in xrange(n) ] b1 = [ ribbon.halfedge() for i in xrange(n) ] b2 = [ ribbon.halfedge() for i in xrange(n) ] for i in xrange(n-1): b1[i].e = b2[i+1] b2[i+1].e = b1[i] b1[n-1].e = b2[0] b2[0].e = b1[n-1] for i in xrange(n): a[i].c = b1[i] b1[i].c = b2[i] b2[i].c = a[i] h = ribbon.justgraph(a+b1+b2) return RibbonGraph(h,a)
def Artin_generator(n,k): """Constructs the Artin generator s_k of n string braif group. INPUT: n a positive integer, k an integer with 0 < |k| < n OUTPUT: A Morphism >>> s1 = Artin_generator(3,1) >>> s2 = Artin_generator(3,2) """ if not 0 < abs(k) < n: raise ValueError # These are also defined in knots.py in_over = ribbon.Features('head','blue',True) in_under = ribbon.Features('head','blue',False) out_over = ribbon.Features('tail','blue',True) out_under = ribbon.Features('tail','blue',False) r = range(n) do = [ ribbon.halfedge() for i in r ] co = [ ribbon.halfedge() for i in r ] for a in do: a.decorations = in_over a.IsI = True for a in co: a.decorations = out_over a.IsI = True for i in r: do[i].c = co[i] co[i].c = do[i] p =abs(k) do[p-1].IsI = False do[p].IsI = False co[p-1].IsI = False co[p].IsI = False do[p-1].c = do[p] do[p].c = co[p] co[p].c = co[p-1] co[p-1].c = do[p-1] if k > 0: do[k-1].decorations = in_over do[k].decorations = in_under co[k-1].decorations = out_under co[k].decorations = out_over elif k < 0: do[p-1].decorations = in_under do[p].decorations = in_over co[p-1].decorations = out_over co[p].decorations = out_under else: raise RuntimeError g = ribbon.justgraph( set(do+co) ) return Morphism(g, do, co)
def from_PlanarDiagram(PD): """Produces a LinkDiagram from the planar diagram notation. INPUT: A list or set of 4-tuples OUTPUT: A closed justgraph EXAMPLES: >>> LinkDiagram.from_PlanarDiagram([[1,2,3,4],[4,3,6,5],[2,1,5,6]]) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> g = LinkDiagram.from_PlanarDiagram([[1,2,3,4],[4,3,6,5],[2,1,5,6]]) >>> [ len(v) for v in g.vertices ] [4, 4, 4] >>> [ len(v) for v in g.edges ] [2, 2, 2, 2, 2, 2] >>> x = [ len(v) for v in g.faces ] >>> x.sort() >>> x [2, 2, 2, 3, 3] >>> x = [ len(v) for v in g.graph.get_orbits( lambda a: a.e.c ) ] >>> x.sort() >>> x [2, 2, 2, 3, 3] """ if not all([ len(x) == 4 for x in PD ]): raise ValueError("Not a valid planar diagram code") for i in range(2*len(PD)): p = [ x for x in PD if i+1 in x ] if len(p) != 2: raise ValueError("Not a valid planar diagram code") D = dict() he = set() for x in PD: a = [ ribbon.halfedge() for i in range(4) ] for i in range(4): a[i-1].c = a[i] D[tuple(x)] = a he = he.union(set(a)) for i in range(2*len(PD)): p = [ x for x in PD if i+1 in x ] a0 = D[tuple(p[0])][p[0].index(i+1)] a1 = D[tuple(p[1])][p[1].index(i+1)] a0.e = a1 a1.e = a0 g = ribbon.justgraph(he) outside = g.get_orbits( lambda a: a.e.c )[0] return LinkDiagram(g,outside)
def vogel_move(f,u,v): # This goes into an infinite loop phi = f.graph.copy() new = phi.codomain a = phi.map[u] b = a.e c = phi.map[v] d = c.e x = [ ribbon.halfedge() for i in range(4) ] for i in range(4): x[i-1].c = x[i] y = [ ribbon.halfedge() for i in range(4) ] for i in range(4): y[i-1].c = y[i] a.e = x[0]; x[0].e = a d.e = x[3]; x[3].e = d b.e = y[1]; y[1].e = b c.e = y[2]; y[2].e = c x[1].e = y[0]; y[0].e = x[1] x[2].e = y[3]; y[3].e = x[2] switch = {'head':'tail','neither':'neither','tail':'head'} ut = a.decorations.directed ot = switch[ut] if False: x[0].decorations._replace(a.decorations.colour) x[1].decorations._replace(c.decorations.colour) x[2].decorations._replace(a.decorations.colour) x[3].decorations._replace(c.decorations.colour) y[0].decorations._replace(d.decorations.colour) y[1].decorations._replace(b.decorations.colour) y[2].decorations._replace(d.decorations.colour) y[3].decorations._replace(b.decorations.colour) for a in x: a.decorations._replace(colour='red') for a in y: a.decorations._replace(colour='red') g = ribbon.justgraph( new.he.union( set(x+y) ) ) u = phi.map[f.outside[0]] outside = [u] s = u.e.c while s != u: outside.append(s) s = s.e.c return LinkDiagram(g,outside)
def map(self): labels = ('VE','VF','EV','EF','FV','FE',) flags = dict([]) for l in labels: for i in range(self.degree): flags[(i,l,)] = ribbon.halfedge() for i in range(self.degree): flags[(i,'VE',)].e = flags[(i,'EV',)] flags[(i,'EV',)].e = flags[(i,'VE',)] flags[(i,'VF',)].e = flags[(i,'FV',)] flags[(i,'FV',)].e = flags[(i,'VF',)] flags[(i,'FE',)].e = flags[(i,'EF',)] flags[(i,'EF',)].e = flags[(i,'FE',)] #flags[(i,'VE',)]).c = flags[(self.sigma[i],'VF',)] flags[(self.sigma[i],'VE',)].c = flags[(i,'VF',)] flags[(i,'EV',)].c = flags[(i,'EF',)] flags[(i,'VF',)].c = flags[(i,'VE',)] flags[(self.phi[i],'FV',)].c = flags[(i,'FE',)] flags[(i,'FE',)].c = flags[(i,'FV',)] #flags[(i,'EF',)].c = flags[(self.alpha[i],'EV',)] flags[(self.alpha[i],'EF',)].c = flags[(i,'EV',)] for i in range(self.degree): flags[(i,'EV',)].decorations = ribbon.Features('neither','red',True) flags[(i,'VE',)].decorations = ribbon.Features('neither','red',True) flags[(i,'FV',)].decorations = ribbon.Features('neither','blue',True) flags[(i,'VF',)].decorations = ribbon.Features('neither','blue',True) flags[(i,'FE',)].decorations = ribbon.Features('neither','green',True) flags[(i,'EF',)].decorations = ribbon.Features('neither','green',True) g = ribbon.justgraph(set(flags.values())) a = self.faces[0] x = flags[(a[0],'FE',)] out = [x] y = x.c while y != x: out.append(y) y = y.c out.reverse() sut = [ flags[(i,'FE',)] for i in a ] + [ flags[(i,'FV',)] for i in a ] if set(sut) != set(out): raise RuntimeError return g, out
def ribbon(self): g1, out1 = self.map() #g1.inspect() g2, inc1 = g1.subdivision() #g2.inspect() out2 = [ inc1[x] for x in out1 ] g3, inc2 = g2.subdivision() out3 = [ inc2[x] for x in out2 ] #g3.inspect() #g, out = g3, out3 g, out = g2, out2 bd = [ x.e for x in out ] he = g.he for x in out: x.e.e = None he.remove(x) gj = ribbon.justgraph(he) return spider.RibbonGraph(gj,bd)
def connected_sum(self,other): """Constructs the connected sum of two link diagrams. EXAMPLE: >>> c = DT([4,6,2]) >>> g = LinkDiagram.from_DT(c) >>> g.connected_sum(g) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> g.connected_sum(g).genus 2 """ phi = self.graph.copy() psi = other.graph.copy() he = phi.codomain.he.union(psi.codomain.he) u = phi.map[self.outside[0]] v = psi.map[other.outside[0]] x = u.e; y = v.e if u.decorations.over == v.decorations.over: u.e = y; y.e = u v.e = x; x.e = v else: u.e = x; x.e = u v.e = y; y.e = v outside = [u] s = u.e.c while s != u: outside.append(s) s = s.e.c return LinkDiagram( ribbon.justgraph(he), outside )
def closure(self, bv=None): """Construct the closure of a web. INPUT: A web and a boundary vector. OUTPUT: A closed web. EXAMPLES: >>> RibbonGraph.polygon(5).closure() #doctest: +ELLIPSIS <closedgraph.ClosedGraph object at 0x...> >>> RibbonGraph.polygon(5).closure().graph.count_vertices() [0, 0, 5, 10] """ if bv == None: bv = [1] * len(self.bd) if sum(bv) != len(self.bd): raise ValueError("Boundary vector is not consistent with web.") if len(bv) < 3: raise ValueError("Not enough corners.") C = len(bv) B = len(self.bd) phi = self.jg.copy() he = phi.codomain.he rim = ribbon.Features('neither','black',True) switch = {'head':'tail','neither':'neither','tail':'head'} ci = [ ribbon.halfedge() for i in xrange(C) ] for a in ci: a.decorations = rim co = [ ribbon.halfedge() for i in xrange(C) ] for a in co: a.decorations = rim he = he.union(ci+co) bi = [ ribbon.halfedge() for i in xrange(B) ] for a in bi: a.decorations = rim bo = [ ribbon.halfedge() for i in xrange(B) ] for a in bo: a.decorations = rim bc = [ ribbon.halfedge() for i in xrange(B) ] he = he.union(bi+bo+bc) for i in xrange(C): ci[i].c = co[i] co[i].c = ci[i] nb = [ phi.map[a] for a in self.bd ] for i in xrange(B): bi[i].c = bo[i] bo[i].c = bc[i] bc[i].c = bi[i] bc[i].e = nb[i] nb[i].e = bc[i] for a in bc: f = a.e.decorations a.decorations = ribbon.Features(switch[f.directed],f.colour,True) p = 0 for i, a in enumerate(bv): r = co[i-1] for j in xrange(a): bi[p].e = r r.e = bi[p] r = bo[p] p += 1 r.e = ci[i] ci[i].e = r ng = ribbon.justgraph(he) u = co[0] outside = [u] s = u.e.c while s != u: outside.append(s) s = s.e.c return closedgraph.ClosedGraph(ng, outside)
def from_DT(DT): """Construct a knot diagram from the Dowker-Thistlewaite code. The theory is given in the original paper: Classification of knot projections C. H. Dowker & Morwen B. Thistlewaite Topology and its Applications 16 (1983), 19--31 INPUT: A list of positive even integers OUTPUT: A closed justgraph EXAMPLES: >>> LinkDiagram.from_DT(DT([4,6,2])) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> LinkDiagram.from_DT(DT([4,6,8,2])) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> LinkDiagram.from_DT(DT([4,8,10,2,6])) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> LinkDiagram.from_DT(DT([6,8,10,2,4])) #doctest: +ELLIPSIS <__main__.LinkDiagram object at 0x...> >>> g = LinkDiagram.from_DT(DT([4,6,2])) >>> [ len(v) for v in g.vertices ] [4, 4, 4] >>> [ len(v) for v in g.edges ] [2, 2, 2, 2, 2, 2] >>> x = [ len(v) for v in g.faces ] >>> x.sort() >>> x [2, 2, 2, 3, 3] >>> g = LinkDiagram.from_DT(DT([4,6,8,2])) >>> [ len(v) for v in g.vertices ] [4, 4, 4, 4] >>> [ len(v) for v in g.edges ] [2, 2, 2, 2, 2, 2, 2, 2] >>> x = [ len(v) for v in g.faces ] >>> x.sort() >>> x [2, 2, 3, 3, 3, 3] >>> x = [ len(v) for v in g.graph.get_orbits( lambda a: a.c.e ) ] >>> x.sort() >>> x [2, 2, 3, 3, 3, 3] >>> g = LinkDiagram.from_DT(DT([4,8,10,2,6])) >>> [ len(v) for v in g.vertices ] [4, 4, 4, 4, 4] >>> [ len(v) for v in g.edges ] [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] >>> x = [ len(v) for v in g.faces ] >>> x.sort() >>> x [2, 2, 2, 3, 3, 4, 4] >>> x = [ len(v) for v in g.graph.get_orbits( lambda a: a.c.e ) ] >>> x.sort() >>> x [2, 2, 2, 3, 3, 4, 4] >>> g = LinkDiagram.from_DT(DT([8,10,2,12,4,6])) >>> x = [ len(v) for v in g.vertices ] >>> x.sort() >>> x [4, 4, 4, 4, 4, 4] >>> [ len(v) for v in g.edges ] [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] >>> x = [ len(v) for v in g.faces ] >>> x.sort() >>> x [2, 2, 2, 3, 3, 3, 4, 5] >>> x = [ len(v) for v in g.graph.get_orbits( lambda a: a.c.e ) ] >>> x.sort() >>> x [2, 2, 2, 3, 3, 3, 4, 5] REFS: http://katlas.org/wiki/DT_%28Dowker-Thistlethwaite%29_Codes """ emb = DT.orientation m = 2 * len(emb) left = [ ribbon.halfedge() for i in range(m) ] right = [ ribbon.halfedge() for i in range(m) ] for i in range(m): left[i].e = right[i-1] right[i-1].e = left[i] for i, k in enumerate(DT.code): a = abs(k)-1 x = [ left[2*i], left[a], right[2*i], right[a] ] if k > 0: x[0].decorations = in_over x[1].decorations = in_under x[2].decorations = out_over x[3].decorations = out_under elif k < 0: x[0].decorations = in_under x[1].decorations = in_over x[2].decorations = out_under x[3].decorations = out_over else: raise ValueError("Not a valid Dowker-Thistlewaite code") if emb[i] == -1: x.reverse() elif emb[i] != 1: raise RuntimeError for i in xrange(4): x[i-1].c = x[i] g = ribbon.justgraph( set(left).union(set(right)) ) outside = g.get_orbits( lambda a: a.e.c )[0] return LinkDiagram(g,outside)
def braid(self): """Given a link diagram find a braid whose closure is the link. The existence of the braid is known as Alexander's theorem. This is an implementation of Vogel's algorithm. This does not change the number of Seifert circles but does increase the number of crossings. EXAMPLES: >>> c = DT([4,6,2]) >>> g = LinkDiagram.from_DT(c) >>> g.braid #doctest: +ELLIPSIS <pivotal.Morphism instance at 0x...> >>> c = DT([4,6,8,2]) >>> g = LinkDiagram.from_DT(c) >>> g.braid #doctest: +ELLIPSIS <pivotal.Morphism instance at 0x...> c = DT([4,8,10,2,6]) g = LinkDiagram.from_DT(c) g.braid #doctest: +ELLIPSIS <pivotal.Morphism instance at 0x...> >>> c = DT([8,10,2,12,4,6]) >>> g = LinkDiagram.from_DT(c) >>> g.braid #doctest: +ELLIPSIS <pivotal.Morphism instance at 0x...> """ def get_arc(f): """Finds an arc.""" sf = f.seifert DS = dict() for x in sf: for a in x: DS[a] = x # Could use product and ifilter from itertools for fc in f.faces: for j, v in enumerate(fc): for i in range(j-1): u = fc[i] if DS[u] != DS[v] and u.decorations.directed == v.decorations.directed: return u,v return None def vogel_move(f,u,v): # This goes into an infinite loop phi = f.graph.copy() new = phi.codomain a = phi.map[u] b = a.e c = phi.map[v] d = c.e x = [ ribbon.halfedge() for i in range(4) ] for i in range(4): x[i-1].c = x[i] y = [ ribbon.halfedge() for i in range(4) ] for i in range(4): y[i-1].c = y[i] a.e = x[0]; x[0].e = a d.e = x[3]; x[3].e = d b.e = y[1]; y[1].e = b c.e = y[2]; y[2].e = c x[1].e = y[0]; y[0].e = x[1] x[2].e = y[3]; y[3].e = x[2] switch = {'head':'tail','neither':'neither','tail':'head'} ut = a.decorations.directed ot = switch[ut] if False: x[0].decorations._replace(a.decorations.colour) x[1].decorations._replace(c.decorations.colour) x[2].decorations._replace(a.decorations.colour) x[3].decorations._replace(c.decorations.colour) y[0].decorations._replace(d.decorations.colour) y[1].decorations._replace(b.decorations.colour) y[2].decorations._replace(d.decorations.colour) y[3].decorations._replace(b.decorations.colour) for a in x: a.decorations._replace(colour='red') for a in y: a.decorations._replace(colour='red') g = ribbon.justgraph( new.he.union( set(x+y) ) ) u = phi.map[f.outside[0]] outside = [u] s = u.e.c while s != u: outside.append(s) s = s.e.c return LinkDiagram(g,outside) f = self while 1: a = get_arc(f) if a == None: break f = vogel_move(f,a[0],a[1]) print f.genus #f.show() # The code below appears to be correct faces = f.faces fc = [ frozenset(x) for x in faces ] def start(): for x in f.seifert: if frozenset(x) in fc: if x[0].decorations.directed == 'head': return x raise RuntimeError index = dict() for a in f.graph.he: index[a] = None for a in start(): index[a] = 0 index[a.e] = 0 def v_find(i): for x in f.vertices: u = [ a for a in x if index[a] == None ] v = [ a for a in x if index[a] == i ] if len(u) == 2 and len(v) == 2: return u,v raise RuntimeError sf = f.seifert DS = dict() for x in sf: for a in x: DS[a] = x n = len(sf)/2 # This is #braid strings = #Seifert circles for i in range(n-1): c = v_find(i) for x in c[0]: for a in DS[x]: index[a] = i+1 index[a.e] = i+1 if any([ index[a] == None for a in f.graph.he ]): raise RuntimeError for a in start(): if a.decorations.directed == 'head': cut_path = [ a ] break DF = dict() for x in faces: for a in x: DF[a] = x for i in range(n-1): x = DF[ cut_path[i].e ] for a in x: if index[a] == i+1 and a.decorations.directed == 'head': cut_path.append(a) break if len(cut_path) != n: raise RuntimeError phi = f.graph.copy() do = [ phi.map[a] for a in cut_path ] co = [ phi.map[a.e] for a in cut_path ] for a in cut_path: phi.map[a].e = None phi.map[a.e].e = None g = ribbon.justgraph(phi.codomain.he) return pivotal.Morphism(g,do,co)