def __init__(self, underlying, numbering): Fatgraph.__init__(self, underlying) self.underlying = underlying assert len(numbering) == self.num_boundary_cycles self.numbering = dict(numbering) if __debug__: count = [0 for x in xrange(self.num_boundary_cycles)] for (bcy, n) in self.numbering.iteritems(): assert type(n) is types.IntType, \ "NumberedFatgraph.__init__: 2nd argument has wrong type:" \ " expecting (BoundaryCycle, Int) pair, got `(%s, %s)`." \ " Reversed-order arguments?" \ % (bcy, n) assert isinstance(bcy, BoundaryCycle), \ "NumberedFatgraph.__init__: 1st argument has wrong type:" \ " expecting (BoundaryCycle, Int) pair, got `(%s, %s)`." \ " Reversed-order arguments?" \ % (bcy, n) assert bcy in self.boundary_cycles, \ "NumberedFatgraph.__init__():" \ " Cycle `%s` is no boundary cycle of graph `%s` " \ % (bcy, self.underlying) count[n] += 1 if count[n] > 1: raise AssertionError("NumberedFatgraph.__init__():" \ " Duplicate key %d" % n) assert sum(count) != self.num_boundary_cycles - 1, \ "NumberedFatgraph.__init__():" \ " Initializer does not exhaust range `0..%d`: %s" \ % (self.num_boundary_cycles - 1, numbering)
def __init__(self, underlying, numbering): Fatgraph.__init__(self, underlying) self.underlying = underlying assert len(numbering) == self.num_boundary_cycles self.numbering = dict(numbering) if __debug__: count = [ 0 for x in xrange(self.num_boundary_cycles) ] for (bcy,n) in self.numbering.iteritems(): assert type(n) is types.IntType, \ "NumberedFatgraph.__init__: 2nd argument has wrong type:" \ " expecting (BoundaryCycle, Int) pair, got `(%s, %s)`." \ " Reversed-order arguments?" \ % (bcy, n) assert isinstance(bcy, BoundaryCycle), \ "NumberedFatgraph.__init__: 1st argument has wrong type:" \ " expecting (BoundaryCycle, Int) pair, got `(%s, %s)`." \ " Reversed-order arguments?" \ % (bcy, n) assert bcy in self.boundary_cycles, \ "NumberedFatgraph.__init__():" \ " Cycle `%s` is no boundary cycle of graph `%s` " \ % (bcy, self.underlying) count[n] += 1 if count[n] > 1: raise AssertionError("NumberedFatgraph.__init__():" \ " Duplicate key %d" % n) assert sum(count) != self.num_boundary_cycles - 1, \ "NumberedFatgraph.__init__():" \ " Initializer does not exhaust range `0..%d`: %s" \ % (self.num_boundary_cycles - 1, numbering)
def isomorphisms(G1, G2): """Iterate over isomorphisms from `G1` to `G2`. See `Fatgraph.isomrphisms` for a discussion of the representation of isomorphisms and example usage. A concrete example taken from `M_{1,4}`:latex: :: >>> g1 = NumberedFatgraph( ... Fatgraph([Vertex([1, 0, 2]), Vertex([2, 1, 5]), Vertex([0, 4, 3]), Vertex([8, 5, 6]), Vertex([3, 6, 7, 7]), Vertex([8, 4, 9, 9])]), ... numbering={ ... BoundaryCycle([(0, 0, 1), (0, 1, 2), (0, 2, 0), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 1), (2, 2, 0), (3, 0, 1), (3, 1, 2), (4, 1, 2), (4, 3, 0), (5, 1, 2), (5, 3, 0)]):0, ... BoundaryCycle([(2, 1, 2), (3, 2, 0), (4, 0, 1), (5, 0, 1)]):1, ... BoundaryCycle([(4, 2, 3)]):2, ... BoundaryCycle([(5, 2, 3)]):3, ... }) >>> g2 = NumberedFatgraph( ... Fatgraph([Vertex([1, 0, 5, 6]), Vertex([1, 0, 2]), Vertex([5, 2, 3]), Vertex([8, 4, 3]), Vertex([7, 7, 6]), Vertex([4, 8, 9, 9])]), ... numbering={ ... BoundaryCycle([(0, 0, 1), (0, 1, 2), (0, 2, 3), (0, 3, 0), (1, 0, 1), (1, 1, 2), (1, 2, 0), (2, 0, 1), (2, 1, 2), (2, 2, 0), (3, 1, 2), (3, 2, 0), (4, 1, 2), (4, 2, 0), (5, 1, 2), (5, 3, 0)]):0, ... BoundaryCycle([(3, 0, 1), (5, 0, 1)]):1, ... BoundaryCycle([(4, 0, 1)]):2, ... BoundaryCycle([(5, 2, 3)]):3, ... }) >>> len(list(NumberedFatgraph.isomorphisms(g1, g2))) 0 """ for iso in Fatgraph.isomorphisms(G1.underlying, G2.underlying): pe_does_not_preserve_bc = False for bc1 in G1.underlying.boundary_cycles: bc2 = iso.transform_boundary_cycle(bc1) # there are cases (see examples in the # `Fatgraph.__eq__` docstring, in which the above # algorithm may find a valid mapping, changing from # `g1` to an *alternate* representation of `g2` - # these should fail as they don't preserve the # boundary cycles, so we catch them here. if (bc2 not in G2.numbering) \ or (G1.numbering[bc1] != G2.numbering[bc2]): pe_does_not_preserve_bc = True # if bc2 not in G2.numbering: # print ("DEBUG: Rejecting isomorphism %r between marked fatgraphs %r and %r:" # " %r not in destination boundary cycles" # % (iso, G1, G2, bc2)) # else: # print ("DEBUG: Rejecting isomorphism %r between marked fatgraphs %r and %r:" # " boundary cycle %r has number %d in G1 and %d in G2" # % (iso, G1, G2, bc2, G1.numbering[bc1], G2.numbering[bc2])) break if pe_does_not_preserve_bc: continue # to next underlying graph isomorphism yield iso
def facets(self, edge, other): """Iterate over facets obtained by contracting `edge` and projecting onto `other`. Each returned item is a triple `(j, k, s)`, where: - `j` is the index of a `NumberedFatgraph` in `self`; - `k` is the index of a `NumberedFatgraph` in `other`; - `s` is the sign by which `self[j].contract(edge)` projects onto `other[k]`. Only triples for which `s != 0` are returned. Examples:: >>> p0 = NumberedFatgraphPool(Fatgraph([Vertex([1, 2, 0, 1, 0]), Vertex([3, 3, 2])])) >>> p1 = NumberedFatgraphPool(Fatgraph([Vertex([0, 1, 0, 1, 2, 2])])) >>> list(NumberedFatgraphPool.facets(p0, 2, p1)) [(0, 0, 1), (1, 1, 1)] """ assert not self.graph.is_loop(edge) assert self.is_orientable assert other.is_orientable g0 = self.graph g1 = g0.contract(edge) g2 = other.graph assert len(g1.boundary_cycles) == len(g2.boundary_cycles) # compute isomorphism map `f1` from `g1` to `g2`: if there is # no such isomorphisms, then stop iteration (do this first so # then we do not waste time on computing if we need to abort # anyway) f1 = Fatgraph.isomorphisms(g1, g2).next() ## 1. compute map `phi0` induced on `g0.boundary_cycles` from the ## graph map `f0` which contracts `edge`. ## (e1, e2) = g0.endpoints(edge) assert set(g1.boundary_cycles) == set([g0.contract_boundary_cycle(bcy, e1, e2) for bcy in g0.boundary_cycles]), \ "NumberedFatgraphPool.facets():" \ " Boundary cycles of contracted graph are not the same" \ " as contracted boundary cycles of parent graph:" \ " `%s` vs `%s`" % (g1.boundary_cycles, [g0.contract_boundary_cycle(bcy, e1, e2) for bcy in g0.boundary_cycles]) phi0_inv = Permutation((i1, i0) for (i0, i1) in enumerate( g1.boundary_cycles.index(g0.contract_boundary_cycle(bc0, e1, e2)) for bc0 in g0.boundary_cycles)) ## 2. compute map `phi1` induced by isomorphism map `f1` on ## the boundary cycles of `g1` and `g2`. ## phi1_inv = Permutation((i1, i0) for (i0, i1) in enumerate( g2.boundary_cycles.index(f1.transform_boundary_cycle(bc1)) for bc1 in g1.boundary_cycles)) assert len(phi1_inv) == len(g1.boundary_cycles) assert len(phi1_inv) == len(g2.boundary_cycles) ## 3. Compute the composite map `f1^(-1) * f0`. ## ## For every numbering `nb` on `g0`, compute the (index of) ## corresponding numbering on `g2` (under the composition map ## `f1^(-1) * f0`) and return a triple `(index of nb, index of ## push-forward, sign)`. ## ## In the following: ## ## - `j` is the index of a numbering `nb` in `self.numberings`; ## - `k` is the index of the corresponding numbering in `other.numberings`, ## under the composition map `f1^(-1) * f0`; ## - `a` is the the unique automorphism `a` of `other.graph` such that:: ## ## self.numberings[j] = pull_back(<permutation induced by `a` applied to> other.numberings[k]) ## ## - `s` is the pull-back sign (see below). ## ## The pair `k`,`a` is computed using the ## `NumberedFatgraphPool._index` (which see), applied to each ## of `self.numberings`, rearranged according to the ## permutation of boundary cycles induced by `f1^(-1) * f0`. ## for (j, (k, a)) in enumerate( other._index(phi1_inv.rearranged(phi0_inv.rearranged(nb))) for nb in self.numberings): ## there are three components to the sign `s`: ## - the sign given by the ismorphism `f1` ## - the sign of the automorphism of `g2` that transforms the ## push-forward numbering into the chosen representative in the same orbit ## - the alternating sign from the homology differential s = f1.compare_orientations() \ * a.compare_orientations() \ * minus_one_exp(g0.edge_numbering[edge]) yield (j, k, s)
def facets(self, edge, other): """Iterate over facets obtained by contracting `edge` and projecting onto `other`. Each returned item is a triple `(j, k, s)`, where: - `j` is the index of a `NumberedFatgraph` in `self`; - `k` is the index of a `NumberedFatgraph` in `other`; - `s` is the sign by which `self[j].contract(edge)` projects onto `other[k]`. Only triples for which `s != 0` are returned. Examples:: >>> p0 = NumberedFatgraphPool(Fatgraph([Vertex([1, 2, 0, 1, 0]), Vertex([3, 3, 2])])) >>> p1 = NumberedFatgraphPool(Fatgraph([Vertex([0, 1, 0, 1, 2, 2])])) >>> list(NumberedFatgraphPool.facets(p0, 2, p1)) [(0, 0, 1), (1, 1, 1)] """ assert not self.graph.is_loop(edge) assert self.is_orientable assert other.is_orientable g0 = self.graph g1 = g0.contract(edge) g2 = other.graph assert len(g1.boundary_cycles) == len(g2.boundary_cycles) # compute isomorphism map `f1` from `g1` to `g2`: if there is # no such isomorphisms, then stop iteration (do this first so # then we do not waste time on computing if we need to abort # anyway) f1 = Fatgraph.isomorphisms(g1,g2).next() ## 1. compute map `phi0` induced on `g0.boundary_cycles` from the ## graph map `f0` which contracts `edge`. ## (e1, e2) = g0.endpoints(edge) assert set(g1.boundary_cycles) == set([ g0.contract_boundary_cycle(bcy, e1, e2) for bcy in g0.boundary_cycles ]), \ "NumberedFatgraphPool.facets():" \ " Boundary cycles of contracted graph are not the same" \ " as contracted boundary cycles of parent graph:" \ " `%s` vs `%s`" % (g1.boundary_cycles, [ g0.contract_boundary_cycle(bcy, e1, e2) for bcy in g0.boundary_cycles ]) phi0_inv = Permutation((i1,i0) for (i0,i1) in enumerate( g1.boundary_cycles.index(g0.contract_boundary_cycle(bc0, e1, e2)) for bc0 in g0.boundary_cycles )) ## 2. compute map `phi1` induced by isomorphism map `f1` on ## the boundary cycles of `g1` and `g2`. ## phi1_inv = Permutation((i1,i0) for (i0,i1) in enumerate( g2.boundary_cycles.index(f1.transform_boundary_cycle(bc1)) for bc1 in g1.boundary_cycles )) assert len(phi1_inv) == len(g1.boundary_cycles) assert len(phi1_inv) == len(g2.boundary_cycles) ## 3. Compute the composite map `f1^(-1) * f0`. ## ## For every numbering `nb` on `g0`, compute the (index of) ## corresponding numbering on `g2` (under the composition map ## `f1^(-1) * f0`) and return a triple `(index of nb, index of ## push-forward, sign)`. ## ## In the following: ## ## - `j` is the index of a numbering `nb` in `self.numberings`; ## - `k` is the index of the corresponding numbering in `other.numberings`, ## under the composition map `f1^(-1) * f0`; ## - `a` is the the unique automorphism `a` of `other.graph` such that:: ## ## self.numberings[j] = pull_back(<permutation induced by `a` applied to> other.numberings[k]) ## ## - `s` is the pull-back sign (see below). ## ## The pair `k`,`a` is computed using the ## `NumberedFatgraphPool._index` (which see), applied to each ## of `self.numberings`, rearranged according to the ## permutation of boundary cycles induced by `f1^(-1) * f0`. ## for (j, (k, a)) in enumerate(other._index(phi1_inv.rearranged(phi0_inv.rearranged(nb))) for nb in self.numberings): ## there are three components to the sign `s`: ## - the sign given by the ismorphism `f1` ## - the sign of the automorphism of `g2` that transforms the ## push-forward numbering into the chosen representative in the same orbit ## - the alternating sign from the homology differential s = f1.compare_orientations() \ * a.compare_orientations() \ * minus_one_exp(g0.edge_numbering[edge]) yield (j, k, s)