def root(self): if self.mode == 'K': ell = self.ell self.RS = RationalSet([PositiveOrthant(ell)], ambient_dim=ell) actual_ring = self.ring F = FractionField(actual_ring) r = matrix(F, self.R).rank() self.r = r if not self.d: return F = [ LaurentIdeal( gens=[LaurentPolynomial(f) for f in self.R.minors(j)], RS=self.RS, normalise=True) for j in range(r + 1) ] elif self.mode == 'O': self.RS = RationalSet([PositiveOrthant(self.d)], ambient_dim=self.d) actual_ring = PolynomialRing(QQ, 'x', self.d) xx = vector(actual_ring, actual_ring.gens()) self.C = matrix(actual_ring, [xx * matrix(actual_ring, A) for A in self.basis]) r = matrix(FractionField(actual_ring), self.C).rank() self.r = r if not self.d: return F = [ LaurentIdeal( gens=[LaurentPolynomial(f) for f in self.C.minors(j)], RS=self.RS, normalise=True) for j in range(r + 1) ] else: raise ValueError('invalid mode') oo = r + 1 # 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}. self.pairs = ([(oo, 0)] + [(i, oo) for i in range(1, r)] + [(i, i - 1) for i in range(1, r + 1)]) # Total number of pairs: 2 * r self.integrand = ((1, ) + (2 * r - 1) * (0, ), (self.d - r + 1, ) + (r - 1) * (-1, ) + r * (+1, )) self.datum = IgusaDatum(F + [ LaurentIdeal(gens=[], RS=self.RS, ring=FractionField(actual_ring)) ]).simplify() return self.datum
def __init__(self, *polynomials): if not polynomials: raise ValueError('need at least one polynomial') polynomials = [f if isinstance(f, LaurentPolynomial) else LaurentPolynomial(FractionField(f.parent())(f)) for f in polynomials] self.nvars = polynomials[0].nvars self.ideal = LaurentIdeal(gens=polynomials, RS=RationalSet(PositiveOrthant(self.nvars)), normalise=True)
def split_off_torus(F): # Given a list F of polynomials, return G,d,T where len(F) == len(G) # and G consists of polynomials in d variables; T = change of basis. # There exists A in GL(n,ZZ) s.t. F[i]^A == G[i] * (Laurent term). # In particular, F is non-degenerate relative to {0} iff this the case # for G; cf Remark 4.3(ii) and Lemma 6.1(ii) in arXiv:1405.5711. # # The number of variables in G is the dimension of Newton(prod(F)). # if not F: return [], 0, None R = F[0].parent() v = {f: vector(ZZ, f.exponents()[0]) for f in F} submodule = (ZZ**R.ngens()).submodule( chain.from_iterable( (vector(ZZ, e) - v[f] for e in f.exponents()) for f in F)) _, _, T = matrix(ZZ, submodule.basis_matrix()).smith_form() d = submodule.rank() Rd = PolynomialRing(QQ, d, R.gens()[:d]) K = FractionField(R) return [ Rd( normalise_laurent_polynomial( K(f([monomial_exp(K, e) for e in T.rows()])))) for f in F ], d, T
def _purge_multiples(self): if self.is_empty(): return self D = conify_polyhedron(self.polyhedron) F = FractionField(self.ring) def lte(i, j): if i == j: return True rat = F(self.rhs[j] * self.lhs[i] / self.rhs[i] / self.lhs[j]) try: alpha = monomial_log(rat.numerator()) - monomial_log( rat.denominator()) except ValueError: return False return is_contained_in_dual(Cone([alpha]), D) mins = minimal_elements(range(len(self.rhs)), lte) if len(mins) == len(self.rhs): return self return ToricDatum(self.ring, self.integrand, [(self.lhs[i], self.rhs[i]) for i in mins], [self.initials[i] for i in mins], self.polyhedron, depth=self._depth)
def normalise_laurent_polynomial(f): """ Rescale a Laurent polynomial by Laurent monomials. Details TBA. """ # Since Sage's Laurent polynomials are useless, we just use rational # functions instead. # First make sure, 'f' really is one. R = f.parent() K = FractionField(R) f = K(f) if K.ngens() == 0: return R(0) if not f else R(1) f = f.numerator() return R(f / gcd(f.monomials()))
def common_overring(R, S): if R.has_coerce_map_from(S): return R elif S.has_coerce_map_from(R): return S else: combined_vars = list( set(R.base_ring().gens()) | set(S.base_ring().gens())) K = FractionField(PolynomialRing(QQ, len(combined_vars), combined_vars)) return PolynomialRing(K, R.gens())
def make_map_latex(map_str): # FIXME: Get rid of nu when map is defined over QQ if "nu" not in map_str: R0 = QQ else: R0 = PolynomialRing(QQ, 'nu') R = PolynomialRing(R0, 2, 'x,y') F = FractionField(R) phi = F(map_str) num = phi.numerator() den = phi.denominator() c_num = num.denominator() c_den = den.denominator() lc = c_den / c_num # rescale coeffs to make them integral. then try to factor out gcds # numerator num_new = c_num * num num_cs = num_new.coefficients() if R0 == QQ: num_cs_ZZ = num_cs else: num_cs_ZZ = [] for el in num_cs: num_cs_ZZ = num_cs_ZZ + el.coefficients() num_gcd = gcd(num_cs_ZZ) # denominator den_new = c_den * den den_cs = den_new.coefficients() if R0 == QQ: den_cs_ZZ = den_cs else: den_cs_ZZ = [] for el in den_cs: den_cs_ZZ = den_cs_ZZ + el.coefficients() den_gcd = gcd(den_cs_ZZ) lc = lc * (num_gcd / den_gcd) num_new = num_new / num_gcd den_new = den_new / den_gcd # make strings for lc, num, and den num_str = latex(num_new) den_str = latex(den_new) if lc == 1: lc_str = "" else: lc_str = latex(lc) if den_new == 1: if lc == 1: phi_str = num_str else: phi_str = lc_str + "(" + num_str + ")" else: phi_str = lc_str + "\\frac{" + num_str + "}" + "{" + den_str + "}" return phi_str
def make_curve_latex(crv_str): # FIXME: Get rid of nu when map is defined over QQ if "nu" not in crv_str: R0 = QQ else: R0 = PolynomialRing(QQ, 'nu') R = PolynomialRing(R0, 2, 'x,y') F = FractionField(R) sides = crv_str.split("=") lhs = latex(F(sides[0])).replace('(', '\\left(').replace(')', '\\right)') rhs = latex(F(sides[1])).replace('(', '\\left(').replace(')', '\\right)') eqn_str = lhs + '=' + rhs return eqn_str
def make_curve_latex(crv_str): # FIXME: Get rid of nu when map is defined over QQ if "nu" not in crv_str: R0 = QQ else: R0 = PolynomialRing(QQ, "nu") R = PolynomialRing(R0, 2, "x,y") F = FractionField(R) sides = crv_str.split("=") lhs = latex(F(sides[0])) rhs = latex(F(sides[1])) eqn_str = lhs + "=" + rhs return eqn_str
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 make_map_latex(map_str, nu=None): if "nu" not in map_str: R0 = QQ else: R0 = PolynomialRing(QQ, "nu") R = PolynomialRing(R0, 2, "x,y") F = FractionField(R) phi = F(map_str) num = phi.numerator() den = phi.denominator() c_num = num.denominator() c_den = den.denominator() lc = c_den / c_num # rescale coeffs to make them integral. then try to factor out gcds # numerator num_new = c_num * num num_cs = num_new.coefficients() if R0 == QQ: num_cs_ZZ = num_cs else: num_cs_ZZ = [] for el in num_cs: num_cs_ZZ = num_cs_ZZ + el.coefficients() num_gcd = gcd(num_cs_ZZ) # denominator den_new = c_den * den den_cs = den_new.coefficients() if R0 == QQ: den_cs_ZZ = den_cs else: den_cs_ZZ = [] for el in den_cs: den_cs_ZZ = den_cs_ZZ + el.coefficients() den_gcd = gcd(den_cs_ZZ) lc = lc * (num_gcd / den_gcd) num_new = num_new / num_gcd den_new = den_new / den_gcd # evaluate at nu, if given if nu and ("nu" in map_str): S = PolynomialRing(CC, 2, 'x,y') lc = lc.subs(nu=nu) num_dict = dict() den_dict = dict() for m, c in num_new.dict().items(): num_dict[m] = c.subs(nu=nu) for m, c in den_new.dict().items(): den_dict[m] = c.subs(nu=nu) num_new = S(num_dict) den_new = S(den_dict) # make strings for lc, num, and den num_str = latex(num_new) den_str = latex(den_new) if lc == 1: lc_str = "" else: lc_str = latex(lc) if den_new == 1: if lc == 1: phi_str = num_str else: phi_str = lc_str + "(" + num_str + ")" else: phi_str = lc_str + "\\frac{%s}{%s}" % (num_str, den_str) return phi_str
def from_polyhedron(cls, P, ring, base_list=None): """ Use LattE to compute the generating function of a rational polyhedron as a sum of small rational functions. """ if P.is_empty(): if ring.ngens() != P.ambient_dim(): raise TypeError('Dimension mismatch') return cls(ring, base_list=base_list) elif P.is_zero(): return cls([CyclotomicRationalFunction(ring.one())], base_list=base_list) elif len(P.vertices()) == 1 and (not P.rays()) and (not P.lines()): # For some reason, LattE doesn't produce .rat files for points. return cls([ CyclotomicRationalFunction( ring.one(), exponents=[vector(ZZ, P.vertices()[0])]) ]) hrep = 'polyhedron.hrep' ratfun = hrep + '.rat' with TemporaryDirectory() as tmpdir, cd(tmpdir): with open(hrep, 'w') as f: f.write(latteify_polyhedron(P)) with open('/dev/null', 'w') as DEVNULL: retcode = subprocess.call([ common.count, '--compute-vertex-cones=4ti2', '--triangulation=cddlib', '--multivariate-generating-function', hrep ], stdout=DEVNULL, stderr=DEVNULL, env=augmented_env(common.count)) if retcode != 0: raise RuntimeError( 'LattE failed. Make sure it has been patched in order to be compatible with Zeta.' ) K = FractionField(ring) variables = [K.coerce(x) for x in ring.gens()] def exp(a): return K.prod(x**e for x, e in zip(variables, a)) def vectorise_string(s): return vector(ZZ, s.strip().split()) with TemporaryList() as summands, open(ratfun, 'r') as f: while True: line = f.readline() if not line: break line = line.strip() if not line: continue # ignore empty lines # The modified version of 'count' produces files in the following format: # { # scalar # nterms # a[1] ... a[n] \ # ... | nterms many # c[1] ... c[n] / # nrays # u[1] ... u[n] \ # ... | nrays many # w[1] ... w[n] / # } # ... # This corresponds to scalar * sum(X^a + ... + X^c) / (1 - X^u) / ... / (1-X^w). if line != "{": raise RuntimeError( 'Invalid LattE output (BEGIN) [line=%s]' % line) scalar = ring(f.readline()) nterms = int(f.readline()) numerator = K(scalar) * K.sum( exp(vectorise_string(f.readline())) for _ in range(nterms)) nrays = int(f.readline()) exponents = [vector(ZZ, len(variables))] + [ vectorise_string(f.readline()) for _ in range(nrays) ] line = f.readline().strip() if line != '}': raise RuntimeError('Invalid Latte output (END)') summands.append( CyclotomicRationalFunction.from_laurent_polynomial( numerator, ring, exponents)) return cls(summands, base_list=base_list)