def compute_graphs(g,n): """ Compute Fatgraphs occurring in `M_{g,n}`. Return a pair `(graphs, D)`, where `graphs` is the list of `(g,n)`-graphs, and `D` is a list, the `k`-th element of which is the list of differentials of graphs with `k` edges. """ timing.start("compute_graphs(%d,%d)" % (g,n)) runtime.g = g runtime.n = n logging.info("Stage I:" " Computing fat graphs for g=%d, n=%d ...", g, n) G = FatgraphComplex(g,n) logging.info("Stage II:" " Computing matrix form of boundary operators D[1],...,D[%d] ...", G.length-1) D = G.compute_boundary_operators() timing.stop("compute_graphs(%d,%d)" % (g,n)) return (G, D)
def run_homology_selftest(output=sys.stdout): ok = True # second, try known cases and inspect results for (g, n, ok) in [ (0,3, [1,0,0]), (0,4, [1,2,0,0,0,0]), (0,5, [1,5,6,0,0,0,0,0,0]), (1,1, [1,0,0]), (1,2, [1,0,0,0,0,0]), (2,1, [1,0,1,0,0,0,0,0,0]), ]: output.write(" Computation of M_{%d,%d} homology: " % (g,n)) # compute homology of M_{g,n} timing.start("homology M%d,%d" % (g,n)) hs = compute_homology(g,n) timing.stop("homology M%d,%d" % (g,n)) # check result if hs == ok: output.write("OK (elapsed: %0.3fs)\n" % timing.get("homology M%d,%d" % (g,n))) else: logging.error("Computation of M_{%d,%d} homology: FAILED, got %s expected %s" % (g,n,hs,ok)) output.write("FAILED, got %s expected %s\n" % (hs,ok)) ok = False return ok
def compute_boundary_operators(self): #: Matrix form of boundary operators; the `i`-th differential #: `D[i]` is `dim C[i-1]` rows (range) by `dim C[i]` columns #: (domain). m = self.module # micro-optimization D = DifferentialComplex() D.append(NullMatrix, 0, len(m[0])) for i in xrange(1, len(self)): timing.start("D[%d]" % i) p = len(m[i - 1]) # == dim C[i-1] q = len(m[i]) # == dim C[i] try: checkpoint = os.path.join(runtime.options.checkpoint_dir, ('M%d,%d-D%d.sms' % (runtime.g, runtime.n, i))) except AttributeError: checkpoint = None # maybe load `D[i]` from persistent storage if checkpoint and p > 0 and q > 0 and runtime.options.restart: d = SimpleMatrix(p, q) if d.load(checkpoint): D.append(d, p, q) logging.info(" Loaded %dx%d matrix D[%d] from file '%s'", p, q, i, checkpoint) continue # with next `i` # compute `D[i]` d = SimpleMatrix(p, q) j0 = 0 for pool1 in m[i].iterblocks(): k0 = 0 for pool2 in m[i - 1].iterblocks(): for edgeno in xrange(pool1.graph.num_edges): if pool1.graph.is_loop(edgeno): continue # with next `edgeno` for (j, k, s) in NumberedFatgraphPool.facets( pool1, edgeno, pool2): assert k < len(pool2) assert j < len(pool1) assert k + k0 < p assert j + j0 < q d.addToEntry(k + k0, j + j0, s) k0 += len(pool2) # # `pool2` will never be used again, so clear it from the cache. # # XXX: using implementation detail! # pool2.graph._cache_isomorphisms.clear() j0 += len(pool1) # `pool1` will never be used again, so clear it from the cache. # XXX: using implementation detail! pool1.graph._cache_isomorphisms.clear() timing.stop("D[%d]" % i) if checkpoint: d.save(checkpoint) D.append(d, p, q) logging.info(" Computed %dx%d matrix D[%d] (elapsed: %.3fs)", p, q, i, timing.get("D[%d]" % i)) return D
def compute_homology(g, n): """ Compute homology ranks of the graph complex of `M_{g,n}`. Return array of homology ranks. """ timing.start("compute_homology(%d,%d)" % (g,n)) runtime.g = g runtime.n = n (G, D) = compute_graphs(g, n) logging.info("Stage III: Computing rank of homology modules ...") hs = list(reversed(D.compute_homology_ranks())) timing.stop("compute_homology(%d,%d)" % (g,n)) # compare orbifold Euler characteristics computed_chi = G.orbifold_euler_characteristics logging.info("Computed orbifold Euler characteristics: %s", computed_chi) expected_chi = orbifold_euler_characteristics(g,n) logging.info(" Expected orbifold Euler characteristics (according to Harer): %s", expected_chi) if computed_chi != expected_chi: logging.error("Expected and computed orbifold Euler characteristics do not match!" " (computed: %s, expected: %s)" % (computed_chi, expected_chi)) # compare Euler characteristics computed_e = 0 for i in xrange(len(hs)): computed_e += minus_one_exp(i)*hs[i] expected_e = euler_characteristics(g,n) logging.info("Computed Euler characteristics: %s" % computed_e) logging.info(" Expected Euler characteristics: %s" % expected_e) if computed_e != expected_e: logging.error("Computed and expected Euler characteristics do not match:" " %s vs %s" % (computed_e, expected_e)) # verify result against other known theorems if g>0: # from Harer's SLN1337, Theorem 7.1 if hs[1] != 0: logging.error("Harer's Theorem 7.1 requires h_1=0 when g>0") ## DISABLED 2009-03-27: Harer's statement seems to be incorrect, ## at least for low genus... ## # From Harer's SLN1337, Theorem 7.2 ## if g==1 or g==2: ## if hs[2] != n: ## logging.error("Harer's Theorem 7.2 requires h_2=%d when g=1 or g=2" % n) ## elif g>2: ## if hs[2] != n+1: ## logging.error("Harer's Theorem 7.2 requires h_2=%d when g>2" % n+1) return hs
def compute_boundary_operators(self): #: Matrix form of boundary operators; the `i`-th differential #: `D[i]` is `dim C[i-1]` rows (range) by `dim C[i]` columns #: (domain). m = self.module # micro-optimization D = DifferentialComplex() D.append(NullMatrix, 0, len(m[0])) for i in xrange(1, len(self)): timing.start("D[%d]" % i) p = len(m[i-1]) # == dim C[i-1] q = len(m[i]) # == dim C[i] try: checkpoint = os.path.join(runtime.options.checkpoint_dir, ('M%d,%d-D%d.sms' % (runtime.g, runtime.n, i))) except AttributeError: checkpoint = None # maybe load `D[i]` from persistent storage if checkpoint and p>0 and q>0 and runtime.options.restart: d = SimpleMatrix(p, q) if d.load(checkpoint): D.append(d, p, q) logging.info(" Loaded %dx%d matrix D[%d] from file '%s'", p, q, i, checkpoint) continue # with next `i` # compute `D[i]` d = SimpleMatrix(p, q) j0 = 0 for pool1 in m[i].iterblocks(): k0 = 0 for pool2 in m[i-1].iterblocks(): for edgeno in xrange(pool1.graph.num_edges): if pool1.graph.is_loop(edgeno): continue # with next `edgeno` for (j, k, s) in NumberedFatgraphPool.facets(pool1, edgeno, pool2): assert k < len(pool2) assert j < len(pool1) assert k+k0 < p assert j+j0 < q d.addToEntry(k+k0, j+j0, s) k0 += len(pool2) # # `pool2` will never be used again, so clear it from the cache. # # XXX: using implementation detail! # pool2.graph._cache_isomorphisms.clear() j0 += len(pool1) # `pool1` will never be used again, so clear it from the cache. # XXX: using implementation detail! pool1.graph._cache_isomorphisms.clear() timing.stop("D[%d]" % i) if checkpoint: d.save(checkpoint) D.append(d, p, q) logging.info(" Computed %dx%d matrix D[%d] (elapsed: %.3fs)", p, q, i, timing.get("D[%d]" % i)) return D
def compute_homology_ranks(self): """Compute and return (list of) homology group ranks. Returns a list of integers: item at index `n` is the rank of the `n`-th homology group of this differential complex. Since the differential complex has finite length, homology group indices can only run from 0 to the length of the complex (all other groups being, trivially, null). """ # check that the differentials form a complex # if __debug__: # for i in xrange(1, len(self)-1): # assert is_null_product(self[i-1][0], self[i][0]), \ # "DifferentialComplex.compute_homology_ranks:" \ # " Product of boundary operator matrices D[%d] and D[%d]" \ # " is not null!" \ # % (i-1, i) #: ranks of `D[n]` matrices, for 0 <= n < len(self); the differential #: `D[0]` is the null map. ranks = list() # only compute those ranks that were not saved for (i, (A, ddim, cdim)) in enumerate(self): try: checkpoint = (os.path.join( runtime.options.checkpoint_dir, "M%d,%d-rkD%d.txt" % (runtime.g, runtime.n, i))) except AttributeError: # running tests, so no `runtime.options` checkpoint = None # XXX: LinBox segfaults if asked to compute the rank of a 0xL matrix if A.num_rows > 0 and A.num_columns > 0: rs = None if checkpoint is not None and runtime.options.restart: rs = load(checkpoint) if rs: r = rs[0] logging.info(" rank D[%d]=%d (loaded from file '%s')", i, r, checkpoint) if rs is None: # `rs` was not loaded from checkpoint file timing.start("rank D[%d]" % i) r = A.rank() timing.stop("rank D[%d]" % i) # checkpoint the computation so far if checkpoint is not None: save([r], checkpoint) logging.info(" rank D[%d]=%d (computed in %.3fs)", i, r, timing.get("rank D[%d]" % i)) else: # A is a 0xL matrix r = 0 logging.info(" rank D[%d]=%d (immediate)", i, r) ranks.append(r) ## compute homology group ranks from rank and nullity ## of boundary operators. ## ## By the rank-nullity theorem, if A:V-->W is a linear map, ## then null(A) = dim(V) - rk(A), hence: ## dim(Z_i) = null(D_i) = dim(C_i) - rk(D_i) ## dim(B_i) = rk(D_{i+1}) ## Therefore: ## h_i = dim(H_i) = dim(Z_i / B_i) = dim(Z_i) - dim(B_i) ## = (dim(C_i) - rk(D_i)) - rk(D_{i+1}) ## = dim(C_i) - (rk(D_i) + rk(D_{i+1})) ## where D_i:C_i-->C_{i+1} ## domain_dim = [ddim for (A, ddim, cdim) in self] domain_dim.append(self[-1][2]) # add dimension of last vector space ranks.append(0) # augment complex with the null map. # note: `domain_dim` indices are offset by 1 w.r.t. to `ranks` indices return [(domain_dim[i + 1] - ranks[i] - ranks[i + 1]) for i in xrange(len(self))]
def compute_homology_ranks(self): """Compute and return (list of) homology group ranks. Returns a list of integers: item at index `n` is the rank of the `n`-th homology group of this differential complex. Since the differential complex has finite length, homology group indices can only run from 0 to the length of the complex (all other groups being, trivially, null). """ # check that the differentials form a complex # if __debug__: # for i in xrange(1, len(self)-1): # assert is_null_product(self[i-1][0], self[i][0]), \ # "DifferentialComplex.compute_homology_ranks:" \ # " Product of boundary operator matrices D[%d] and D[%d]" \ # " is not null!" \ # % (i-1, i) #: ranks of `D[n]` matrices, for 0 <= n < len(self); the differential #: `D[0]` is the null map. ranks = list() # only compute those ranks that were not saved for (i, (A, ddim, cdim)) in enumerate(self): try: checkpoint = (os.path.join(runtime.options.checkpoint_dir, "M%d,%d-rkD%d.txt" % (runtime.g, runtime.n, i))) except AttributeError: # running tests, so no `runtime.options` checkpoint = None # XXX: LinBox segfaults if asked to compute the rank of a 0xL matrix if A.num_rows > 0 and A.num_columns > 0: rs = None if checkpoint is not None and runtime.options.restart: rs = load(checkpoint) if rs: r = rs[0] logging.info(" rank D[%d]=%d (loaded from file '%s')", i, r, checkpoint) if rs is None: # `rs` was not loaded from checkpoint file timing.start("rank D[%d]" % i) r = A.rank() timing.stop("rank D[%d]" % i) # checkpoint the computation so far if checkpoint is not None: save([r], checkpoint) logging.info(" rank D[%d]=%d (computed in %.3fs)", i, r, timing.get("rank D[%d]" % i)) else: # A is a 0xL matrix r = 0 logging.info(" rank D[%d]=%d (immediate)", i, r) ranks.append(r) ## compute homology group ranks from rank and nullity ## of boundary operators. ## ## By the rank-nullity theorem, if A:V-->W is a linear map, ## then null(A) = dim(V) - rk(A), hence: ## dim(Z_i) = null(D_i) = dim(C_i) - rk(D_i) ## dim(B_i) = rk(D_{i+1}) ## Therefore: ## h_i = dim(H_i) = dim(Z_i / B_i) = dim(Z_i) - dim(B_i) ## = (dim(C_i) - rk(D_i)) - rk(D_{i+1}) ## = dim(C_i) - (rk(D_i) + rk(D_{i+1})) ## where D_i:C_i-->C_{i+1} ## domain_dim = [ ddim for (A, ddim, cdim) in self ] domain_dim.append(self[-1][2]) # add dimension of last vector space ranks.append(0) # augment complex with the null map. # note: `domain_dim` indices are offset by 1 w.r.t. to `ranks` indices return [ (domain_dim[i+1] - ranks[i] - ranks[i+1]) for i in xrange(len(self)) ]