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)
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)
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
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
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) ]]))
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()])
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)
def plot(self): """ EXAMPLES:: sage: UnitCubeTriangulation(3).plot() """ return sum( Polyhedron(simplex).plot() for simplex in self.simplex_iter())
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
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)
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)
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)
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)
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
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
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)
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)
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
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
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
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)
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)
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)]]))
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
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, )]))