Example #1
0
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)
Example #2
0
 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
Example #3
0
 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
Example #4
0
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
Example #5
0
 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
Example #6
0
    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))]
Example #7
0
    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)) ]