예제 #1
0
def polygon_values(vertices):
    vertices = normalize_vertices(vertices)

    vertices_str = ",".join(repr(pt).replace(" ", "") for pt in vertices)

    polygon = Polyhedron(vertices)
    volume = float(polygon.volume())
    points = polygon.integral_points()
    num_points = len(points)
    num_interior = len([pt for pt in points if polygon.interior_contains(pt)])

    length = int(max(x for x, y in vertices))
    width = int(max(y for x, y in vertices))

    SymmQQ = polygon.restricted_automorphism_group(output="matrixlist")
    if len(SymmQQ) == 1:
        # Fast path for trivial symmetry group
        symm = 1
    else:
        # Restrict symmetries to symmetries in GL(3, ZZ)
        Symm = [s for s in SymmQQ if abs(s.determinant()) == 1 and all(x in ZZ for x in s.list())]
        if all(s.determinant() > 0 for s in Symm):
            symm = len(Symm)
        else:
            symm = -len(Symm)

    return (vertices_str, len(vertices), volume,
        num_points, num_interior, num_points - num_interior,
        width, length, symm)
예제 #2
0
def inner_open_normal_fan(N):
    """
    Construct the relatively open, inner normal fan of a lattice
    polytope.

    We approximate 'x > 0' via 'x >= 1', see the part on 'models' of half-open
    cones in the second paper.
    """
    if N.is_empty():
        raise ValueError('need a non-empty polytope')

    V = [vector(QQ, v) for v in N.vertices()]

    if len(V) == 1:
        # Confusingly, given an empty list of (in)equalities, Sage
        # returns the empty polyhedron instead of the whole space.
        yield Polyhedron(eqns=[(N.ambient_dim() + 1) * (0, )])
        return

    for face in N.face_lattice():
        if face.dim() == -1:
            continue
        W = [vector(QQ, w) for w in face.vertices()]

        eqns = [vector(QQ, [0] + list(W[0] - W[i])) for i in range(1, len(W))]
        idx = [i for i in range(len(V)) if V[i] not in W]
        ieqs = [vector(QQ, [-1] + list(V[i] - W[0])) for i in idx]
        yield Polyhedron(ieqs=ieqs, eqns=eqns)
예제 #3
0
    def padically_evaluate_regular(self, datum):
        # Our integral is
        # |y|^(s-1) * prod_v ||x^(v + weight) : weight in wt(v); y||^(-1).
        if not datum.is_regular():
            raise ValueError('need a regular datum')

        n = self.nvertices
        RS = RationalSet([datum.polyhedron]) * RationalSet(PositiveOrthant(1))

        polytopes = [Polyhedron(vertices=[vector(ZZ, n * [0] + [1])])]

        I = identity_matrix(ZZ, n)

        vertices = [[vector(ZZ, n * [0] + [1])]
                    for _ in range(n)]  # y appears in each factor

        for i, (u, v) in enumerate(datum.edges):
            assert u == v and u in range(n)
            vertices[u].append(vector(ZZ, list(I[u] + datum.weights[i]) + [0]))

        for v in vertices:
            polytopes.append(Polyhedron(vertices=v))

        for z in padically_evaluate_monomial_integral(RS, polytopes,
                                                      [(1, ) + n * (0, ),
                                                       (n + 1) * (1, )]):
            yield z
예제 #4
0
def relative_initial_forms(F, polyhedron=None, split=True):
    if not F:
        raise ValueError('need non-empty collection of polynomials')

    if split:
        G, d, A = split_off_torus(F)
        N = sum(g.newton_polytope() for g in G)
        n = A.nrows()
    else:
        N = sum(f.newton_polytope() for f in F)
        n = N.ambient_dim()

    if polyhedron is None:
        polyhedron = Polyhedron(ieqs=[(n + 1) * (0, )])

    for Q in inner_open_normal_fan(N):
        if split:
            Q = linear_image_of_polyhedron(
                DirectProductOfPolyhedra(
                    Q, Polyhedron(ieqs=[(n - d + 1) * (0, )])), A.transpose())
        Q &= polyhedron
        if Q.dim() == -1:
            continue
        y = get_point_in_polyhedron(Q)
        yield [initial_form_by_direction(f, y) for f in F], Q
예제 #5
0
def StrictlyPositiveOrthant(d):
    if d == 0:
        return Polyhedron(ambient_dim=0, vertices=[()], base_ring=QQ)
    else:
        return Polyhedron(ieqs=block_matrix([[
            matrix(QQ, d, 1, [-1 for i in range(d)]),
            identity_matrix(QQ, d)
        ]]))
예제 #6
0
def linear_image_of_polyhedron(P, A):
    if A.nrows() != P.ambient_dim():
        raise ValueError('matrix does not act on ambient space of polyhedra')
    return Polyhedron(ambient_dim=P.ambient_dim(),
                      vertices=[vector(v) * A for v in P.vertices()],
                      rays=[vector(r) * A for r in P.rays()],
                      lines=[vector(l) * A for l in P.lines()])
예제 #7
0
def DirectProductOfPolyhedra(P, Q):
    m = P.ambient_dim()
    n = Q.ambient_dim()

    ieqs = []
    eqns = [(n + m + 1) * (0, )]  # ensures correctness when P == RR^m, Q==RR^n

    # [b | a] --> [b | a | 0]
    def f(v):
        return v + n * [0]

    # [b | a] --> [b | 0 | a]
    def g(v):
        return [v[0]] + m * [0] + v[1:]

    for i in P.inequalities():
        ieqs.append(f(list(i)))
    for i in Q.inequalities():
        ieqs.append(g(list(i)))

    for e in P.equations():
        eqns.append(f(list(e)))
    for e in Q.equations():
        eqns.append(g(list(e)))
    return Polyhedron(ieqs=ieqs, eqns=eqns, base_ring=QQ, ambient_dim=m + n)
예제 #8
0
    def plot(self):
        """
        EXAMPLES::

            sage: UnitCubeTriangulation(3).plot()
        """
        return sum(
            Polyhedron(simplex).plot() for simplex in self.simplex_iter())
예제 #9
0
    def padically_evaluate_regular(self, datum):
        T = datum.toric_datum
        if not T.is_regular():
            raise ValueError('Can only processed regular toric data')

        M = Set(range(T.length()))
        q = SR.var('q')

        alpha = {}

        for I in Subsets(M):
            F = [T.initials[i] for i in I]
            V = SubvarietyOfTorus(F, torus_dim=T.ambient_dim)
            alpha[I] = V.count()

        def cat(u, v):
            return vector(list(u) + list(v))

        for I in Subsets(M):
            cnt = sum((-1)**len(J) * alpha[I + J] for J in Subsets(M - I))
            if not cnt:
                continue

            P = DirectProductOfPolyhedra(T.polyhedron,
                                         StrictlyPositiveOrthant(len(I)))

            it = iter(identity_matrix(ZZ, len(I)).rows())
            ieqs = []
            for i in M:
                ieqs.append(
                    cat(
                        vector(ZZ, (0, )),
                        cat(
                            vector(ZZ, T.initials[i].exponents()[0]) -
                            vector(ZZ, T.lhs[i].exponents()[0]),
                            next(it) if i in I else zero_vector(ZZ, len(I)))))

            if not ieqs:
                ieqs = [vector(ZZ, (T.ambient_dim + len(I) + 1) * [0])]

            Q = Polyhedron(ieqs=ieqs,
                           base_ring=QQ,
                           ambient_dim=T.ambient_dim + len(I))

            foo, ring = symbolic_to_ratfun(
                cnt * (q - 1)**len(I) / q**(T.ambient_dim),
                [var('t'), var('q')])
            corr_cnt = CyclotomicRationalFunction.from_laurent_polynomial(
                foo, ring)

            Phi = matrix([
                cat(T.integrand[0], zero_vector(ZZ, len(I))),
                cat(T.integrand[1], vector(ZZ,
                                           len(I) * [-1]))
            ]).transpose()
            sm = RationalSet([P.intersection(Q)]).generating_function()
            for z in sm.monomial_substitution(QQ['t', 'q'], Phi):
                yield corr_cnt * z
예제 #10
0
def dual_cone_as_polyhedron(rays, strict=False):
    """
    Compute the polyhedron consisting of all y such that
    r * y >= 0 (or r * y > 0 if strict=True) for r in rays.
    """
    if not rays:
        raise TypeError('Need at least one ray')
    c = Integer(-1) if strict else ZZ.zero()
    return Polyhedron(ieqs=[[c] + list(v) for v in rays], base_ring=QQ)
예제 #11
0
    def solve(self):
        """
        Builds a classic convex polyhedron over vts and
        returns a set of constraints (i.e. the facets of the polyhedron)
        """

        ts = [t for t in self.terms if t != 1]

        logger.debug('Create vertices from {} traces'.format(len(self.tcs)))

        vts = [[QQ(t.subs(tc)) for t in ts] for tc in self.tcs]
        vts = map(list, set(map(tuple, vts)))  # make these vts unique

        logger.debug('Build (gen CL) poly from {} vts in {} dims: {}'.format(
            len(vts), len(vts[0]), ts))

        poly = Polyhedron(vertices=vts, base_ring=QQ)
        try:
            rs = [list(p.vector()) for p in poly.inequality_generator()]

            logger.debug('Sage: found {} inequalities'.format(len(rs)))

            # remove rs of form [const_c, 0 , ..., 0]
            # because those translate to the trivial form 'const_c >= 0'
            rs = [s for s in rs if any(x != 0 for x in s[1:])]

        except AttributeError:
            logger.warn('Sage: cannot construct polyhedron')
            rs = []

        if rs:
            # parse the result
            def _f(s):
                return s[0] + sum(map(prod, zip(s[1:], ts)))

            rs = [_f(s) >= 0 for s in rs]

            # remove trivial (tautology) str(x) <=> str(x)
            rs = [
                s for s in rs
                if not (s.is_relational() and str(s.lhs()) == str(s.rhs()))
            ]

        self.sols = map(InvIeq, rs)
예제 #12
0
    def topologically_evaluate_regular(self, datum):
        if not datum.is_regular():
            raise ValueError('need a regular datum')

        euler_cap = {}
        torus_factor_dim = {}

        N = Set(range(len(datum.polynomials)))

        _, d, _ = split_off_torus([datum.initials[i].num for i in N])
        min_dim = datum.ambient_dim - d

        # NOTE: triangulation/"topologisation" of RationalSet instances only
        # considers cones of maximal dimension.
        if datum.RS.dim() <= min_dim - 2:
            return
            yield

        if datum.RS.dim() >= min_dim:
            raise RuntimeError('this should be impossible')

        for I in Subsets(N):
            F = [datum.initials[i].num for i in I]
            V = SubvarietyOfTorus(F, torus_dim=datum.ambient_dim)
            U,W = V.split_off_torus()
            torus_factor_dim[I] = W.torus_dim

            assert torus_factor_dim[I] >= min_dim

            if W.torus_dim > min_dim:
                continue
            euler_cap[I] = U.khovanskii_characteristic() if U.is_nondegenerate() else U.euler_characteristic()

        for I in Subsets(N):
            chi = sum((-1)**len(J) * euler_cap[I + J] for J in Subsets(N - I)
                      if torus_factor_dim[I + J] == min_dim)
            if not chi:
                continue

            I = list(I)
            id = identity_matrix(ZZ, len(I))
            def vectorise(first, k, vec):
                w = id[I.index(k)] if k in I else vector(ZZ,len(I))
                return vector(ZZ, [first] + list(vec) + list(w))

            polytopes = []
            for (i, j) in self.pairs:
                vertices = [vectorise(0, k, datum.ideals[i].initials[m].exponents()[0]) for m, k in enumerate(datum._ideal2poly[i])] +\
                           [vectorise(1, k, datum.ideals[j].initials[m].exponents()[0]) for m, k in enumerate(datum._ideal2poly[j])]
                polytopes.append(Polyhedron(vertices=vertices, ambient_dim=1+datum.ambient_dim + len(I)))
            extended_RS = (RationalSet(StrictlyPositiveOrthant(1)) * datum.RS *
                           RationalSet(StrictlyPositiveOrthant(len(I))))

            for surf in topologically_evaluate_monomial_integral(extended_RS, polytopes,
                                                                 self.integrand, dims=[min_dim+len(I)]):
                yield SURF(scalar=chi*surf.scalar, rays=surf.rays)
예제 #13
0
    def solve(self):
        """
        Builds a classic convex polyhedron over vts and
        returns a set of constraints (i.e. the facets of the polyhedron)
        """

        ts = [t for t in self.terms if t != 1]
        
        logger.debug('Create vertices from {} traces'
                     .format(len(self.tcs)))
                     
        vts = [[QQ(t.subs(tc)) for t in ts] for tc in self.tcs]
        vts = map(list,set(map(tuple,vts))) #make these vts unique

        logger.debug('Build (gen CL) poly from {} vts in {} dims: {}'
                     .format(len(vts),len(vts[0]),ts))

        poly = Polyhedron(vertices=vts,base_ring=QQ)
        try:
            rs = [list(p.vector()) for p in poly.inequality_generator()]

            logger.debug('Sage: found {} inequalities'.format(len(rs)))

            #remove rs of form [const_c, 0 , ..., 0]
            #because those translate to the trivial form 'const_c >= 0'
            rs = [s for s in rs if any(x != 0 for x in s[1:])]

        except AttributeError:
            logger.warn('Sage: cannot construct polyhedron')
            rs = []


        if rs:
            #parse the result
            _f = lambda s: s[0] + sum(map(prod,zip(s[1:],ts)))
            rs = [_f(s) >= 0 for s in rs]

            #remove trivial (tautology) str(x) <=> str(x)
            rs = [s for s in rs
                  if not (s.is_relational()
                          and str(s.lhs()) == str(s.rhs()))]

        self.sols = map(InvIeq,rs)
예제 #14
0
def is_weak_fano(Delta):
    """
    Check whether Delta is a (weak) Fano polytope.

    OUTPUT:

    - 2 if Fano

    - 1 if weak Fano

    - 0 if not weak Fano
    """
    N = normal_vertices(Delta)
    Normal = Polyhedron(vertices=N)
    if len(interior_points(Normal)) == 1:
        # Weak Fano => Fano iff all elements of N
        # are vertices
        if Normal.n_vertices() == len(N):
            return 2
        else:
            return 1
    return 0
예제 #15
0
    def padically_evaluate_regular(self, datum):
        A = self.A
        n = self.nvertices
        m = self.nedges

        RS = RationalSet(PositiveOrthant(n + 1))

        polytopes = [Polyhedron(vertices=[vector(ZZ, n * [0] + [1])])]

        I = list(identity_matrix(ZZ, n + 1))

        for j in range(m):
            vertices = [vector(ZZ, n * [0] + [1])]
            for i, a in enumerate(A.column(j)):
                if a == 1:
                    vertices.append(I[i])
            polytopes.append(Polyhedron(vertices=vertices))

        for z in padically_evaluate_monomial_integral(RS, polytopes,
                                                      [(1, ) + m * (0, ),
                                                       (m + 1) * (1, )]):
            yield z
예제 #16
0
    def topologically_evaluate_regular(self, datum):
        if not datum.is_regular():
            raise ValueError('need a regular datum')

        euler_cap = {}
        torus_factor_dim = {}

        N = Set(range(len(datum.polynomials)))
        _, d, _ = split_off_torus([datum.initials[i].num for i in N])
        min_dim = datum.ambient_dim - d

        if datum.RS.dim() < min_dim:
            logger.debug('Totally irrelevant datum')
            return
            yield

        for I in Subsets(N):
            F = [datum.initials[i].num for i in I]
            V = SubvarietyOfTorus(F, torus_dim=datum.ambient_dim)
            U,W = V.split_off_torus()
            torus_factor_dim[I] = W.torus_dim
            euler_cap[I] = U.khovanskii_characteristic() if U.is_nondegenerate() else U.euler_characteristic()
            assert torus_factor_dim[I] >= min_dim

        for I in Subsets(N):
            chi = sum((-1)**len(J) * euler_cap[I+J] for J in Subsets(N-I) if torus_factor_dim[I+J] == min_dim)
            if not chi:
                logger.debug('Vanishing Euler characteristic: I = %s' % I)
                continue

            I = list(I)
            polytopes = []

            id = identity_matrix(ZZ, len(I))
            def vectorise(k, vec):
                w = id[I.index(k)] if k in I else vector(ZZ,len(I))
                return vector(ZZ, list(vec) + list(w))

            assert len(datum._ideal2poly[0]) == len(datum.ideals[0].gens)
            polytopes = [Polyhedron(vertices=[vectorise(k, datum.ideals[0].initials[m].exponents()[0]) for m,k in enumerate(datum._ideal2poly[0])],
                                    ambient_dim=datum.ambient_dim+len(I))]

            extended_RS = datum.RS * RationalSet(StrictlyPositiveOrthant(len(I)))

            assert all(extended_RS.ambient_dim == P.ambient_dim() for P in polytopes)
            for surf in topologically_evaluate_monomial_integral(extended_RS, polytopes,
                                                                 [ (1,), (0,) ],
                                                                 dims=[min_dim + len(I)],
                                                                 ):
                yield SURF(scalar=chi*surf.scalar, rays=surf.rays)
예제 #17
0
def dominating_cones(vectors):
    assert vectors
    for j, v in enumerate(vectors):
        # 1st cone: v[0] * x < v[1] * x, ..., v[n] * x
        # 2nd cone: v[1] * x < v[2] * x, ..., v[n] * n; v[1] <= v[0]
        # ...
        # In particular, in the i-th cone returned, vectors[i] dominates
        # all others.
        ieqs = []
        for i, w in enumerate(vectors):
            if i == j:
                continue
            ieqs.append([0 if i < j else -1] + list(w - v))
        yield Polyhedron(eqns=[], ieqs=ieqs, base_ring=QQ)
예제 #18
0
    def padically_evaluate_regular(self, datum, extra_RS=None):
        if not datum.is_regular():
            raise ValueError('need a regular datum')

        # The extra variable's valuation is in extra_RS.
        if extra_RS is None:
            extra_RS = RationalSet(StrictlyPositiveOrthant(1))

        q = var('q')
        count_cap = {}
        N = Set(range(len(datum.polynomials)))

        for I in Subsets(N):
            F = [datum.initials[i].num for i in I]
            V = SubvarietyOfTorus(F, torus_dim=datum.ambient_dim)
            count_cap[I] = V.count()

            # BEGIN SANITY CHECK
            # q = var('q')
            # u, w = V.split_off_torus()
            # assert ((count_cap[I]/(q-1)**w.torus_dim).simplify_full())(q=1) == u.euler_characteristic()
            # END SANITY CHECK

        for I in Subsets(N):
            cnt = sum((-1)**len(J) * count_cap[I + J] for J in Subsets(N - I))
            if not cnt:
                continue

            I = list(I)
            id = identity_matrix(ZZ, len(I))

            def vectorise(first, k, vec):
                w = id[I.index(k)] if k in I else vector(ZZ, len(I))
                return vector(ZZ, [first] + list(vec) + list(w))

            polytopes = []
            for (i, j) in self.pairs:
                vertices = [vectorise(0, k, datum.ideals[i].initials[m].exponents()[0]) for m, k in enumerate(datum._ideal2poly[i])] +\
                           [vectorise(1, k, datum.ideals[j].initials[m].exponents()[0]) for m, k in enumerate(datum._ideal2poly[j])]
                polytopes.append(Polyhedron(vertices=vertices, ambient_dim=1 + datum.ambient_dim + len(I)))

            extended_RS = extra_RS * datum.RS * RationalSet(StrictlyPositiveOrthant(len(I)))

            foo, ring = symbolic_to_ratfun(cnt * (q - 1)**(1 + len(I)) / q**(1 + datum.ambient_dim), [var('t'), var('q')])
            corr_cnt = CyclotomicRationalFunction.from_laurent_polynomial(foo, ring)

            for z in padically_evaluate_monomial_integral(extended_RS, polytopes, self.integrand):
                yield corr_cnt * z
예제 #19
0
    def padically_evaluate_regular(self, datum):
        if not datum.is_regular():
            raise ValueError('need a regular datum')

        q = var('q')
        count_cap = {}
        N = Set(range(len(datum.polynomials)))

        for I in Subsets(N):
            F = [datum.initials[i].num for i in I]
            V = SubvarietyOfTorus(F, torus_dim=datum.ambient_dim)
            count_cap[I] = V.count()

        for I in Subsets(N):
            cnt = sum((-1)**len(J) * count_cap[I+J] for J in Subsets(N-I))
            if not cnt:
                continue

            I = list(I)
            polytopes = []

            id = identity_matrix(ZZ, len(I))
            def vectorise(k, vec):
                w = id[I.index(k)] if k in I else vector(ZZ,len(I))
                return vector(ZZ, list(vec) + list(w))

            assert len(datum._ideal2poly[0]) == len(datum.ideals[0].gens)
            polytopes = [Polyhedron(vertices=[ vectorise(k, datum.ideals[0].initials[m].exponents()[0]) for m,k in enumerate(datum._ideal2poly[0]) ], ambient_dim=datum.ambient_dim+len(I))]

            extended_RS = datum.RS * RationalSet(StrictlyPositiveOrthant(len(I)))

            foo, ring = symbolic_to_ratfun(cnt * (q - 1)**len(I) / q**datum.ambient_dim, [var('t'), var('q')])
            corr_cnt = CyclotomicRationalFunction.from_laurent_polynomial(foo, ring)

            assert all(extended_RS.ambient_dim == P.ambient_dim() for P in polytopes)

            for z in padically_evaluate_monomial_integral(extended_RS, polytopes, [ (1,), (0,) ]):
                yield corr_cnt * z
예제 #20
0
    def root(self):
        d = self.ring.ngens()
        self.d = d

        polyhedra = []
        for i in range(d):
            eqns = [
                (i+1) * [0] + [1] + (d-i-1) * [0]
            ]
            ieqs = [
                [-1] + j * [0] + [1] + (d-j-1) * [0]
                for j in range(i)
            ]
            ieqs += [
                (j+1) * [0] + [1] + (d-j-1) * [0]
                for j in range(i+1,d)
            ]
            polyhedra.append(Polyhedron(eqns=eqns, ieqs=ieqs))
        self.RS = RationalSet(polyhedra, ambient_dim=d)

        # NOTE:
        # A bug in Sage prevents rank computations over (fields of fractions of)
        # polynomial rings in zero variables over fields.
        if d > 0:
            F = FractionField(self.ring)
        else:
            F = FractionField(self.ring.base_ring())

        two_u = matrix(F, self.R).rank() # self.R.rank()
        if two_u % 2:
            raise RuntimeError('this is odd')
        self.u = two_u // 2
        self.v = matrix(F, self.S).rank() # self.S.rank()

        if not d:
            return

        F = [
            LaurentIdeal(
                gens = [LaurentPolynomial(_sqrt(f)) for f in principal_minors(self.R, 2*j)],
                RS = self.RS,
                normalise = True)
            for j in range(0, self.u+1)
        ]

        G = [LaurentIdeal(gens=[LaurentPolynomial(g) for g in self.S.minors(j)],
                          RS=self.RS,
                          normalise=True)
             for j in range(self.v + 1)]

        oo = self.u + self.v + 2

        # On pairs:
        # The first component is used as is, the second is multiplied by the extra
        # variable. Note that index 0 corresponds to {1} and index oo to {0}.
        # We skip the |F_1|/|F_0 cap xF_1| factor which is generically trivial.
        self.pairs = (
            [(oo, 0)] +
            [(i, oo) for i in range(2, self.u)] + [(i + 1, i) for i in range(1, self.u)] +
            [(i, oo) for i in range(self.u + 2, self.u + self.v + 1)] +
            [(i + 1, i) for i in range(self.u + 1, self.u + self.v + 1)])
        # Note: q^b t^a really corresponds to a*s - b, in contrast to subobjects, where
        # the (-1)-shift coming from Jacobians is included.
        # This also means we don't have to manually add (-1)s for extra variables.
        self.integrand = (
            (self.u,) + (self.u - 2) * (1,) + (self.u - 1) * (-1,) + (2 * self.v - 1) * (0,),
            (self.d + 1 - self.v,) + (2 * self.u - 3) * (0,) + (self.v - 1) * (-1,) + self.v * (1,))

        self.datum = IgusaDatum(F + G + [LaurentIdeal(gens=[], RS=self.RS, ring=FractionField(self.ring))])
        self.datum = self.datum.simplify()

        return self.datum
예제 #21
0
 def __init__(self, eqns, ieqs):
     self.eqns = eqns
     self.ieqs = ieqs
     self.ambient_dimension = len((self.eqns + self.ieqs)[0]) - 1
     self.sage_polyhedron = SagePolyhedron(eqns=self.eqns, ieqs=self.ieqs)
예제 #22
0
    def topologically_evaluate_regular(self, datum):
        T = datum.toric_datum
        if not T.is_regular():
            raise ValueError('Can only processed regular toric data')

        # All our polyhedra all really half-open cones (with a - 1 >=0
        # being an imitation of a >= 0).

        C = conify_polyhedron(T.polyhedron)

        M = Set(range(T.length()))

        logger.debug('Dimension of polyhedron: %d' % T.polyhedron.dim())

        # STEP 1:
        # Compute the Euler characteristcs of the subvarieties of
        # Torus^sth defined by some subsets of T.initials.
        # Afterwards, we'll combine those into Denef-style Euler characteristics
        # via inclusion-exclusion.

        logger.debug('STEP 1')

        alpha = {}
        tdim = {}

        for I in Subsets(M):
            logger.debug('Processing I = %s' % I)
            F = [T.initials[i] for i in I]

            V = SubvarietyOfTorus(F, torus_dim=T.ambient_dim)
            U, W = V.split_off_torus()

            # Keep track of the dimension of the torus factor for F == 0.
            tdim[I] = W.torus_dim

            if tdim[I] > C.dim():
                # In this case, we will never need alpha[I].
                logger.debug('Totally irrelevant intersection.')
                # alpha[I] = ZZ(0)
            else:
                # To ensure that the computation of Euler characteristics succeeds in case
                # of global non-degeneracy, we test this first.
                # The 'euler_characteristic' method may change generating sets,
                # possibly introducing degeneracies.
                alpha[I] = U.khovanskii_characteristic() if U.is_nondegenerate(
                ) else U.euler_characteristic()
                logger.debug(
                    'Essential Euler characteristic alpha[%s] = %d; dimension of torus factor = %d'
                    % (I, alpha[I], tdim[I]))

        logger.debug(
            'Done computing essential Euler characteristics of intersections: %s'
            % alpha)

        # STEP 2:
        # Compute the topological zeta functions of the extended cones.
        # That is, add extra variables, add variable constraints (here: just >= 0),
        # and add newly monomialised conditions.

        def cat(u, v):
            return vector(list(u) + list(v))

        logger.debug('STEP 2')
        for I in Subsets(M):
            logger.debug('Current set: I = %s' % I)

            # P = C_0 x R_(>0)^I in the paper
            P = DirectProductOfPolyhedra(T.polyhedron,
                                         StrictlyPositiveOrthant(len(I)))

            it = iter(identity_matrix(ZZ, len(I)).rows())
            ieqs = []
            for i in M:
                # Turn lhs[i] | monomial of initials[i] * y[i] if in I,
                #      lhs[i] | monomial of initials[i] otherwise
                # into honest cone conditions.
                ieqs.append(
                    cat(
                        vector(ZZ, (0, )),
                        cat(
                            vector(ZZ, T.initials[i].exponents()[0]) -
                            vector(ZZ, T.lhs[i].exponents()[0]),
                            next(it) if i in I else zero_vector(ZZ, len(I)))))

            if not ieqs:
                # For some reason, not providing any constraints yields the empty
                # polyhedron in Sage; it should be all of RR^whatever, IMO.
                ieqs = [vector(ZZ, (T.ambient_dim + len(I) + 1) * [0])]

            Q = Polyhedron(ieqs=ieqs,
                           base_ring=QQ,
                           ambient_dim=T.ambient_dim + len(I))

            sigma = conify_polyhedron(P.intersection(Q))
            logger.debug('Dimension of Hensel cone: %d' % sigma.dim())

            # Obtain the desired Euler characteristic via inclusion-exclusion,
            # restricted to those terms contributing to the constant term mod q-1.
            chi = sum((-1)**len(J) * alpha[I + J] for J in Subsets(M - I)
                      if tdim[I + J] + len(I) == sigma.dim())

            if not chi:
                continue

            # NOTE: dim(P) = dim(sigma): choose any point omega in P
            # then a large point lambda in Pos^I will give (omega,lambda) in sigma.
            # Moreover, small perturbations of (omega,lambda) don't change that
            # so (omega,lambda) is an interior point of sigma inside P.

            surfs = (topologise_cone(
                sigma,
                matrix([
                    cat(T.integrand[0], zero_vector(ZZ, len(I))),
                    cat(T.integrand[1], vector(ZZ,
                                               len(I) * [-1]))
                ]).transpose()))

            for S in surfs:
                yield SURF(scalar=chi * S.scalar, rays=S.rays)
예제 #23
0
def PositiveOrthant(d):
    if d == 0:
        return Polyhedron(ambient_dim=0, eqns=[], ieqs=[(0, )], base_ring=QQ)
    else:
        return Polyhedron(ieqs=block_matrix(
            [[matrix(QQ, d, 1), identity_matrix(QQ, d)]]))
예제 #24
0
class Polyhedron:
    def __init__(self, eqns, ieqs):
        self.eqns = eqns
        self.ieqs = ieqs
        self.ambient_dimension = len((self.eqns + self.ieqs)[0]) - 1
        self.sage_polyhedron = SagePolyhedron(eqns=self.eqns, ieqs=self.ieqs)

    @classmethod
    def from_triangulation(cls, T, max_weight, zeroed=None, zeros=None):
        if zeros is not None:
            zeroed = [(zeros >> i) & 1 for i in range(2 * T.zeta)][::-1]
        # Build the polytope.
        eqns, ieqs = [], []
        # Edge equations.
        for i in range(T.zeta):
            eqn = [0] * 2 * T.zeta
            if T.is_flippable(i):
                A, B = T.corner_lookup[i], T.corner_lookup[~i]
                x, y = A.labels[1], A.labels[2]
                z, w = B.labels[1], B.labels[2]
                eqn[x], eqn[y], eqn[z], eqn[w] = +1, +1, -1, -1
            else:
                A = T.corner_lookup[i]
                x = A.labels[1] if A[1] == ~A[0] else A.labels[2]
                z = A.labels[0]
                eqn[x], eqn[z] = +1, -1
            eqns.append([0] + eqn)  # V_x + X_y == V_z + V_w.
        # Zeroed (in)equalities
        for i in range(2 * T.zeta):
            if not zeroed[i]:
                ieq = [0] * 2 * T.zeta
                ieq[i] = +1
                ieqs.append([-1] + ieq)  # V_i >= 1.
            else:  # Zeroed equation.
                eqn = [0] * 2 * T.zeta
                eqn[i] = +1
                eqns.append([0] + eqn)  # V_i == 0.
        # Max weight inequality.
        ieqs.append([max_weight] + [-1] * 2 * T.zeta)  # sum V_i <= max_weight.
        return cls(eqns=eqns, ieqs=ieqs)

    def __str__(self):
        return 'EQN:[\n{}\n]\nIEQS:[\n{}\n]'.format(
            ',\n'.join(str(eqn) for eqn in self.eqns),
            ',\n'.join(str(ieq) for ieq in self.ieqs))

    def split(self, inequality):
        return Polyhedron(self.eqns, self.ieqs + [inequality])

    def restrict(self, equality):
        return Polyhedron(self.eqns + [equality], self.ieqs)

    def __contains__(self, coordinate):
        assert len(coordinate) == self.ambient_dimension
        homogenised = [1] + list(coordinate)

        def dot(X, Y):
            return sum(x * y for x, y in zip(X, Y))

        # print([dot(homogenised, ieq) >= 0 for ieq in self.ieqs])
        return all(dot(homogenised, eqn) == 0 for eqn in self.eqns) and all(
            dot(homogenised, ieq) >= 0 for ieq in self.ieqs)

    def get_index(self, coordinate, **kwds):
        assert coordinate in self
        D = self.ambient_dimension
        P = self
        index = 0
        for i in range(D):
            neg_axis = [0] * i + [-1] + [0] * (D - i - 1)
            P_lt_comp = P.split([coordinate[i] - 1] + neg_axis)
            index += P_lt_comp.integral_points_count(**kwds)
            P = P.restrict([coordinate[i]] + neg_axis)

        return index

    def get_integral_point(self, index, **kwds):
        if self.sage_polyhedron is not None:
            return self.sage_polyhedron.get_integral_point(**kwds)

        D = self.ambient_dimension
        axes = [[0] * i + [1] + [0] * (D - i - 1) for i in range(D)]
        coordinate = []
        P = self
        P_count = P.integral_points_count(
            **kwds
        )  # Record the number of integral points in P_{lower <= x_i < upper}.
        bounding_box = zip(*self.bounding_box())

        for axis, bounds in zip(
                axes, bounding_box
        ):  # Now compute x_i, the ith component of coordinate.
            neg_axis = [-x for x in axis]
            lower, upper = int(
                bounds[0]), int(bounds[1]) + 1  # So lower <= x_i < upper.
            while lower < upper - 1:
                # print(coordinate, lower, upper, P_count)
                guess = (lower + upper) // 2  # > lower.
                # Build new polyhedron by intersecting P with the halfspace {x_i < guess}.
                P_lt_guess = P.split([-lower] + axis).split([guess - 1] +
                                                            neg_axis)
                P_lt_guess_count = P_lt_guess.integral_points_count(**kwds)

                if P_lt_guess_count > index:  # Move upper down to guess.
                    upper = guess
                    index -= 0
                    P_count = P_lt_guess_count
                else:  # P_lt_guess_count <= index:  # Move lower up to guess.
                    lower = guess
                    index -= P_lt_guess_count
                    P_count -= P_lt_guess_count
                if P_count == 1:
                    Q = P.split([-lower] + axis).split([upper - 1] + neg_axis)
                    vertices = Q.vertices()
                    if len(vertices) == 1:  # Polytope is 0-dimensional.
                        return [int(x)
                                for x in vertices[0]]  # Remove any Fractions.
            coordinate.append(
                lower)  # Record the new component that we have found.
            P = P.restrict([lower] + neg_axis)
        # assert self.get_index(coordinate) == orig_index
        return coordinate

    def integral_points_count(self, **kwds):
        if self.sage_polyhedron is not None:
            return self.sage_polyhedron.integral_points_count(**kwds)

        latte_input = '{} {}\n{}\nlinearity {} {}'.format(
            len(self.eqns) + len(self.ieqs),
            self.ambient_dimension + 1,
            '\n'.join(' '.join(str(x) for x in X)
                      for X in self.eqns + self.ieqs),
            len(self.eqns),
            ' '.join(str(i + 1) for i in range(len(self.eqns))),
        )

        args = [os.path.abspath(os.path.join('bin', 'count'))]

        for key, value in kwds.items():
            if value is None or value is False:
                continue

            key = key.replace('_', '-')
            if value is True:
                args.append('--{}'.format(key))
            else:
                args.append('--{}={}'.format(key, value))
        # args += ['/dev/stdin']

        # The cwd argument is needed because latte
        # always produces diagnostic output files.
        with TemporaryDirectory() as tmpdir:
            # with TemporaryDirectory() as tmpdir:
            with open(os.path.join(tmpdir, 'test.pol'), 'w') as x:
                x.write(latte_input)
            args += [os.path.join(tmpdir, 'test.pol')]
            # X = subprocess.run(args, capture_output=True)
            # ans, err = X.stdout, X.stderr
            # ret_code = X.returncode
            latte_proc = Popen(args,
                               stdin=PIPE,
                               stdout=PIPE,
                               stderr=PIPE,
                               cwd=str(tmpdir))
            ans, err = latte_proc.communicate()
            ret_code = latte_proc.poll()

            if ans:  # Sometimes (when LattE's preproc does the work), no output appears on stdout.
                ans = ans.splitlines()[-1].decode()
            else:
                # opening a file is slow (30e-6s), so we read the file
                # numOfLatticePoints only in case of a IndexError above
                with open(os.path.join(tmpdir, 'numOfLatticePoints'),
                          'r') as f:
                    ans = f.read()

        try:
            return int(ans)
        except ValueError:
            return 0

    @memoize
    def vertices(self):
        if self.sage_polyhedron is not None:
            return self.sage_polyhedron.vertices()

        cddlib_input = 'H-representation\nlinearity {} {}\nbegin\n{} {} rational\n{}\nend'.format(
            len(self.eqns),
            ' '.join(str(i + 1) for i in range(len(self.eqns))),
            len(self.eqns) + len(self.ieqs),
            self.ambient_dimension + 1,
            '\n'.join(' '.join(str(x) for x in X)
                      for X in self.eqns + self.ieqs),
        )

        args = [os.path.abspath(os.path.join('bin', 'cddexec_gmp')), '--rep']
        cddlib_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        ans, err = cddlib_proc.communicate(input=cddlib_input.encode())
        ret_code = cddlib_proc.poll()

        def parse(x):
            n, _, d = x.partition('/')
            return Fraction(int(n), int(d) if d else 1)

        return [[parse(item) for item in line.split()[1:]]
                for line in ans.decode().splitlines()[4:-1]]

    @memoize
    def bounding_box(self):
        return list(
            zip(*[(min(coords), max(coords))
                  for coords in zip(*self.vertices())]))

    def random_point(self):
        while True:
            p = [
                randrange(int(lower),
                          int(upper) + 1)
                for lower, upper in zip(*self.bounding_box())
            ]
            if p in self:
                return p
예제 #25
0
 def dual_cone(self):
     return reduce(lambda C, D: C.intersection(D),
                   (Cone(C.rays()).dual()
                    for C in self.cones)) if self.cones else Cone(
                        Polyhedron(ieqs=[(self.ambient_dim + 1) * (0, )]))