Example #1
0
def orbifold_euler_characteristics(g,n):
    """
    Return the orbifold/virtual Euler characteristics of `M_{g,n}`,
    computed according to Harer-Zagier.
    """
    if g==0:
        return factorial(n-3) * minus_one_exp(n-3)
    elif g==1:
        return Fraction(minus_one_exp(n), 12) * factorial(n-1)
    else: # g > 1
        return bernoulli(2*g) * factorial(2*g+n-3) / (factorial(2*g-2) * 2*g) * minus_one_exp(n)
Example #2
0
def euler_characteristics(g, n):
    """
    Return Euler characteristics of `M_{g,n}`.

    The Euler characteristics is computed according to formulas and
    tables found in:
    * Bini-Gaiffi-Polito, arXiv:math/9806048, p.3
    * Bini-Harer, arXiv:math/0506083, p. 10
    """
    if g == 0:
        # according to Bini-Gaiffi-Polito arXiv:math/9806048, p.3
        return factorial(n - 3) * minus_one_exp(n - 3)
    elif g == 1:
        # according to Bini-Gaiffi-Polito, p. 15
        if n > 4:
            return factorial(n - 1) * Fraction(minus_one_exp(n - 1), 12)
        else:
            es = [1, 1, 0, 0]
            return es[n - 1]  # no n==0 computed in [BGP]
    elif g == 2:
        # according to Bini-Gaiffi-Polito, p. 14
        if n > 6:
            return factorial(n + 1) * Fraction(minus_one_exp(n + 1), 240)
        else:
            es = [1, 2, 2, 0, -4, 0, -24]
            return es[n]
    elif g > 2:
        # according to Bini-Harer arXiv:math/0506083, p. 10
        es = [  # n==1  n==2     n==3     n==4        n==5        n==6          n==7          n==8
            [8, 6, 4, -10, 30, -660, 6540, 79200],  # g==3
            [-2, -10, -24, -24, -360, 2352, -37296, 501984],  # g==4
            [12, 26, 92, 182, 1674, -16716, 238980, -3961440],  # g==5
            [0, -46, -206, 188, -7512, 124296, -2068392, 37108656],  # g==6
            [38, 120, 676, -1862, 71866, -1058676, 21391644,
             -422727360],  # g==7
            [
                -166, -630, -5362, 16108, -680616, 12234600, -259464240,
                5719946400
            ],  # g==8
            [
                748, 2132, 29632, -323546, 7462326, -164522628, 3771668220,
                -90553767840
            ],  # g==9
            [
                -1994, 6078, -213066, 4673496, -106844744, 2559934440,
                -64133209320, 1.664e+12
            ],  # g==10
        ]
        # g=0,1,2 already done above
        return es[g - 3][n]
    else:
        raise ValueError("No Euler characteristics known for M_{g,n},"
                         " where g=%s and n=%s" % (g, n))
Example #3
0
def orbifold_euler_characteristics(g, n):
    """
    Return the orbifold/virtual Euler characteristics of `M_{g,n}`,
    computed according to Harer-Zagier.
    """
    if g == 0:
        return factorial(n - 3) * minus_one_exp(n - 3)
    elif g == 1:
        return Fraction(minus_one_exp(n), 12) * factorial(n - 1)
    else:  # g > 1
        return bernoulli(2 * g) * factorial(2 * g + n - 3) / (
            factorial(2 * g - 2) * 2 * g) * minus_one_exp(n)
Example #4
0
def euler_characteristics(g,n):
    """
    Return Euler characteristics of `M_{g,n}`.

    The Euler characteristics is computed according to formulas and
    tables found in:
    * Bini-Gaiffi-Polito, arXiv:math/9806048, p.3
    * Bini-Harer, arXiv:math/0506083, p. 10
    """
    if g==0:
        # according to Bini-Gaiffi-Polito arXiv:math/9806048, p.3
        return factorial(n-3)*minus_one_exp(n-3)
    elif g==1:
        # according to Bini-Gaiffi-Polito, p. 15
        if n>4:
            return factorial(n-1)*Fraction(minus_one_exp(n-1),12)
        else:
            es = [1,1,0,0]
            return es[n-1] # no n==0 computed in [BGP]
    elif g==2:
        # according to Bini-Gaiffi-Polito, p. 14
        if n>6:
            return factorial(n+1)*Fraction(minus_one_exp(n+1),240)
        else:
            es = [1,2,2,0,-4,0,-24]
            return es[n]
    elif g>2:
        # according to Bini-Harer arXiv:math/0506083, p. 10
        es = [# n==1  n==2     n==3     n==4        n==5        n==6          n==7          n==8
              [    8,    6,       4,     -10,         30,       -660,         6540,        79200], # g==3
              [   -2,  -10,     -24,     -24,       -360,       2352,       -37296,       501984], # g==4
              [   12,   26,      92,     182,       1674,     -16716,       238980,     -3961440], # g==5
              [    0,  -46,    -206,     188,      -7512,     124296,     -2068392,     37108656], # g==6
              [   38,  120,     676,   -1862,      71866,   -1058676,     21391644,   -422727360], # g==7
              [ -166, -630,   -5362,   16108,    -680616,   12234600,   -259464240,   5719946400], # g==8
              [  748, 2132,   29632, -323546,    7462326, -164522628,   3771668220, -90553767840], # g==9
              [-1994, 6078, -213066, 4673496, -106844744, 2559934440, -64133209320,    1.664e+12], # g==10
              ]
        # g=0,1,2 already done above
        return es[g-3][n]
    else:
        raise ValueError("No Euler characteristics known for M_{g,n},"
                         " where g=%s and n=%s" % (g,n))
Example #5
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 #6
0
def FatgraphComplex(g, n):
    """Return the fatgraph complex for given genus `g` and number of
    boundary components `n`.

    This is a factory method returning a `homology.ChainComplex`
    instance, populated with the correct vector spaces and
    differentials to compute the graph homology of the space
    `M_{g,n}`.
    """
    ## Minimum number of edges is attained when there's only one
    ## vertex; so, by Euler's formula `V - E + n = 2 - 2*g`, we get:
    ## `E = 2*g + n - 1`.
    min_edges = 2 * g + n - 1
    logging.debug("  Minimum number of edges: %d", min_edges)

    ## Maximum number of edges is reached in graphs with all vertices
    ## tri-valent, so, combining Euler's formula with `3*V = 2*E`, we
    ## get: `E = 6*g + 3*n - 6`.  These are also graphs corresponding
    ## to top-dimensional cells.
    top_dimension = 6 * g + 3 * n - 6
    logging.debug("  Maximum number of edges: %d", top_dimension)

    #: list of primitive graphs, graded by number of edges
    # generators = [ AggregateList() for dummy in xrange(top_dimension) ]
    C = MgnChainComplex(top_dimension)

    # gather graphs
    chi = Fraction(0)
    for graph in MgnGraphsIterator(g, n):
        grade = graph.num_edges - 1
        pool = NumberedFatgraphPool(graph)
        # compute orbifold Euler characteristics (needs to include *all* graphs)
        chi += Fraction(
            minus_one_exp(grade - min_edges) * len(pool),
            pool.num_automorphisms)
        # discard non-orientable graphs
        if not pool.is_orientable:
            continue
        C.module[grade].aggregate(pool)
    C.orbifold_euler_characteristics = chi

    for i in xrange(top_dimension):
        logging.debug("  Initialized grade %d chain module (dimension %d)", i,
                      len(C.module[i]))

    return C
Example #7
0
def FatgraphComplex(g, n):
    """Return the fatgraph complex for given genus `g` and number of
    boundary components `n`.

    This is a factory method returning a `homology.ChainComplex`
    instance, populated with the correct vector spaces and
    differentials to compute the graph homology of the space
    `M_{g,n}`.
    """
    ## Minimum number of edges is attained when there's only one
    ## vertex; so, by Euler's formula `V - E + n = 2 - 2*g`, we get:
    ## `E = 2*g + n - 1`.
    min_edges = 2*g + n - 1
    logging.debug("  Minimum number of edges: %d", min_edges)
    
    ## Maximum number of edges is reached in graphs with all vertices
    ## tri-valent, so, combining Euler's formula with `3*V = 2*E`, we
    ## get: `E = 6*g + 3*n - 6`.  These are also graphs corresponding
    ## to top-dimensional cells.
    top_dimension = 6*g + 3*n - 6
    logging.debug("  Maximum number of edges: %d", top_dimension)

    #: list of primitive graphs, graded by number of edges
    #generators = [ AggregateList() for dummy in xrange(top_dimension) ]
    C = MgnChainComplex(top_dimension)

    # gather graphs
    chi = Fraction(0)
    for graph in MgnGraphsIterator(g,n):
        grade = graph.num_edges - 1
        pool = NumberedFatgraphPool(graph)
        # compute orbifold Euler characteristics (needs to include *all* graphs)
        chi += Fraction(minus_one_exp(grade-min_edges)*len(pool), pool.num_automorphisms)
        # discard non-orientable graphs
        if not pool.is_orientable:
            continue
        C.module[grade].aggregate(pool)
    C.orbifold_euler_characteristics = chi
        
    for i in xrange(top_dimension):
        logging.debug("  Initialized grade %d chain module (dimension %d)",
                      i, len(C.module[i]))
        
    return C
Example #8
0
    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)
Example #9
0
    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)