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_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)) ]
# valences -- show vertex valences for given g,n elif 'valences' == cmdline.action: logging.debug("Computing vertex valences occurring in g=%d,n=%d fatgraphs ...", g, n) vvs = compute_valences(g,n) for vv in vvs: outfile.write("%s\n" % str(vv)) # graphs -- create graphs from given g,n but do not compute homology elif "graphs" == cmdline.action: logging.info("Will save graph list files into directory '%s'.", runtime.options.checkpoint_dir) graphs, D = compute_graphs(g,n) logging.info("Graph family computation took %.3fs.", timing.get("compute_graphs(%d,%d)" % (g,n))) # homology -- compute homology ranks elif 'homology' == cmdline.action: # compute graph complex and its homology ranks hs = compute_homology(g, n) logging.info("Homology computation took %.3fs.", timing.get("compute_homology(%d,%d)" % (g,n))) # print results for (i, h) in enumerate(hs): outfile.write("h_%d(M_{%d,%d}) = %d\n" % (i, g, n, h)) if cmdline.outfile is not None: logging.info("Results written to file '%s'" % cmdline.outfile)