class TestGradedModule(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ,"x",3); self.x = self.poly_ring.gens()[0]; self.y = self.poly_ring.gens()[1]; self.z = self.poly_ring.gens()[2]; def test_monomial_basis_zero(self): one = self.poly_ring.one() zero = self.poly_ring.zero() gm = GradedModule([[one,one,one,one]],[0,1,2,3],[1,2,3]) self.assertEqual(gm.monomial_basis(0),[(one,zero,zero,zero)]) def test_monomial_basis(self): x = self.x y = self.y one = self.poly_ring.one() zero = self.poly_ring.zero() gm = GradedModule([[one,one]],[0,1],[1,2,3]) true_basis = [(x**2,zero),(y,zero),(zero,x)] self.assertEqual(Set(gm.monomial_basis(2)),Set(true_basis)) def test_homogeneous_parts_A(self): one = self.poly_ring.one() zero = self.poly_ring.zero() gm = GradedModule([[one,one]],[0,1],[1,2,3]) parts = gm.get_homogeneous_parts([one,one]) parts_true = { 0:[one,zero] , 1:[zero,one] } self.assertEqual(parts,parts_true) def test_homogeneous_parts_B(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() zero = self.poly_ring.zero() gm = GradedModule([[one,one]],[0,1],[1,2,3]) parts = gm.get_homogeneous_parts([x*y,x**3+z*y]) self.assertEqual(parts,{3:[x*y,zero],4:[zero,x**3],6:[zero,z*y]}) def test_homogeneous_part_basisA(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() gm = GradedModule([[z,one,x**2 + y]],[0,1,2],[1,2,3]) basis = gm.homogeneous_part_basis(6); self.assertEqual(len(basis),10) def test_homogeneous_part_basisB(self): #From bug found with ncd x = self.x y = self.y z = self.z zero = self.poly_ring.zero() gm = GradedModule([[zero, x*z, x*y], [zero, -x*z, zero], [y*z, zero, zero]],[1, 1, 1],[1, 1, 1]) basis = gm.homogeneous_part_basis(3); self.assertEqual(len(basis),3)
def has_rep(self, n, restrict=None, force=False): """ Returns `True` if there exists an `n`-dimensional representation of `self`, and `False` otherwise. The optional argument `restrict` may be used to restrict the possible images of the generators. To do so, `restrict` must be a tuple with entries of `None`, `'diagonal'`, `'lower'`, or `'upper'`. Its length must match the number of generators of `self`. Use `force=True` if the function does not recognize the base field as computable, but the field is computable. """ if (not force and self.base_field() not in NumberFields and self.base_field() not in FiniteFields): raise TypeError( 'Base field must be computable. If %s is computable' % self.base_field() + ' then use force=True to bypass this.') if n not in ZZ or n < 1: raise ValueError('Dimension must be a positive integer.') from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing import math B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1), 'x', order='deglex') M = MatrixSpace(B, n, sparse=True) gen_matrix = list() if not isinstance(restrict, (tuple, list)): restrict = [None for i in range(self.ngens())] if len(restrict) != self.ngens(): raise ValueError( 'Length of restrict does not match number of generators.') for i in range(self.ngens()): ith_gen_matrix = [] for j in range(n): for k in range(n): if restrict[i] == 'upper' and j > k: ith_gen_matrix.append(B.zero()) elif restrict[i] == 'lower' and j < k: ith_gen_matrix.append(B.zero()) elif restrict[i] == 'diagonal' and j != k: ith_gen_matrix.append(B.zero()) else: ith_gen_matrix.append(B.gen(j + (j + 1) * k + i * n**2)) gen_matrix.append(M(ith_gen_matrix)) relB_list = list() for i in range(self.nrels()): relB_list += self._to_matrix(self.rel(i), M, gen_matrix).list() relB = B.ideal(relB_list) if relB.dimension() == -1: return False else: return True
def rand_w_hom_divisor(n, degs=None, mon_num=None, var="z"): if degs == None: degs = [randrange(2, 6) for _ in range(n)] deg = sum(degs) if mon_num == None: mon_num = randrange(2, 8) poly_ring = PolynomialRing(QQ, n, var) div = poly_ring.zero() min_w = min(degs) for i in range(mon_num): expo = [0] * n cur_deg = 0 while cur_deg != deg: if cur_deg > deg: expo = [0] * n cur_deg = 0 if deg - cur_deg < min_w: expo = [0] * n cur_deg = 0 next_g = randrange(0, n) expo[next_g] += 1 cur_deg += degs[next_g] coeff = randrange(-n, n) / n mon = poly_ring.one() for i, e in enumerate(expo): mon *= poly_ring.gens()[i]**e div += coeff * mon return div
class TestConvertSymToPoly(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ,"x",3); self.x = self.poly_ring.gens()[0]; self.y = self.poly_ring.gens()[1]; self.z = self.poly_ring.gens()[2]; self.vars = var('x,y,z') def test_zero(self): zero = 0*self.vars[0] poly = convert_symbolic_to_polynomial(zero,self.poly_ring,self.vars) self.assertEqual(poly,self.poly_ring.zero()) def test_convert(self): x = self.x y = self.y z = self.z sym_poly = 4*self.vars[0]**4 + self.vars[1]*self.vars[0]**12*self.vars[1]-self.vars[2] poly = convert_symbolic_to_polynomial(sym_poly,self.poly_ring,self.vars) self.assertEqual(poly,4*x**4+y*x**12*y-z) def test_convert_univarient(self): y = self.y sym_poly = 4*self.vars[1]**4 + self.vars[1]*self.vars[1]**12*self.vars[1]-self.vars[1] poly = convert_symbolic_to_polynomial(sym_poly,self.poly_ring,self.vars) self.assertEqual(poly,4*y**4+y*y**12*y-y) def test_convert_partial(self): y = self.y z = self.z sym_poly = 4*self.vars[2]**4 + self.vars[1]*self.vars[1]**12*self.vars[1]-self.vars[1] poly = convert_symbolic_to_polynomial(sym_poly,self.poly_ring,self.vars) self.assertEqual(poly,4*z**4+y*y**12*y-y)
def rand_w_hom_divisor(n,degs=None,mon_num=None,var="z"): if degs==None: degs = [randrange(2,6) for _ in range(n)] deg = sum(degs) if mon_num==None: mon_num = randrange(2,8) poly_ring = PolynomialRing(QQ,n,var) div = poly_ring.zero() min_w = min(degs) for i in range(mon_num): expo = [0]*n cur_deg = 0 while cur_deg!=deg: if cur_deg>deg: expo = [0]*n cur_deg = 0 if deg-cur_deg<min_w: expo = [0]*n cur_deg = 0 next_g = randrange(0,n) expo[next_g] += 1 cur_deg += degs[next_g] coeff = randrange(-n,n)/n mon = poly_ring.one() for i,e in enumerate(expo): mon *= poly_ring.gens()[i]**e div += coeff*mon return div
class TestWeightedMinDegree(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ,"x",3); self.x = self.poly_ring.gens()[0]; self.y = self.poly_ring.gens()[1]; self.z = self.poly_ring.gens()[2]; def test_zero(self): self.assertEqual(wieghted_min_degree(self.poly_ring.zero(),[1,1,1]),0); def test_homogeneous(self): x = self.x; y = self.y; z = self.z; f = x**4 + 4*y*z**3 - z**2; self.assertEqual(wieghted_min_degree(f,[1,1,1]),2); def test_non_homogeneous(self): x = self.x; y = self.y; z = self.z; f = x**4 + 4*y*z**3 - z**2; self.assertEqual(wieghted_min_degree(f,[2,1,2]),4); def test_negative_wieghts(self): x = self.x; y = self.y; z = self.z; f = x**4 + 4*y*z**3 - z**2; self.assertEqual(wieghted_min_degree(f,[-1,-1,1]),-4);
class TestLogaritmicDerivations(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ, "x", 3) self.x = self.poly_ring.gens()[0] self.y = self.poly_ring.gens()[1] self.z = self.poly_ring.gens()[2] def test_normal_crossing(self): zero = self.poly_ring.zero() log_der = LogarithmicDerivations(self.x * self.y * self.z) free = SingularModule([[self.x, zero, zero], [zero, self.y, zero], [zero, zero, self.z]]) self.assertTrue(log_der.equals(free))
def polynomial(self, g, n, ring=None): if ring is None: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing if n == 1: # Otherwise, the b is not numbered! ring = PolynomialRing(self._base_ring, 'b0', n) else: ring = PolynomialRing(self._base_ring, 'b', n) b = ring.gens() p = ring.zero() p += sum( self.F(g, n, I) * prod(b[i]**j for i, j in enumerate(I)) for I in compositions(g, n)) return p
def Tokuyama_coefficient(self, name='t'): r""" Return the Tokuyama coefficient attached to ``self``. Following the exposition of [BBF]_, Tokuyama's formula asserts .. MATH:: \sum_{G} (t+1)^{s(G)} t^{l(G)} z_1^{d_{n+1}} z_2^{d_{n}-d_{n+1}} \cdots z_{n+1}^{d_1-d_2} = s_{\lambda}(z_1,\dots,z_{n+1}) \prod_{i<j} (z_j+tz_i), where the sum is over all strict Gelfand-Tsetlin patterns with fixed top row `\lambda + \rho`, with `\lambda` a partition with at most `n+1` parts and `\rho = (n, n-1, \ldots, 1, 0)`, and `s_\lambda` is a Schur function. INPUT: - ``name`` -- (Default: ``'t'``) An alternative name for the variable `t`. EXAMPLES:: sage: P = GelfandTsetlinPattern([[3,2,1],[2,2],[2]]) sage: P.Tokuyama_coefficient() 0 sage: G = GelfandTsetlinPattern([[3,2,1],[3,1],[2]]) sage: G.Tokuyama_coefficient() t^2 + t sage: G = GelfandTsetlinPattern([[2,1,0],[1,1],[1]]) sage: G.Tokuyama_coefficient() 0 sage: G = GelfandTsetlinPattern([[5,3,2,1,0],[4,3,2,0],[4,2,1],[3,2],[3]]) sage: G.Tokuyama_coefficient() t^8 + 3*t^7 + 3*t^6 + t^5 """ R = PolynomialRing(ZZ, name) t = R.gen(0) if not self.is_strict(): return R.zero() return (t + 1)**(self.number_of_special_entries()) * t**( self.number_of_boxes())
def Tokuyama_coefficient(self, name='t'): r""" Return the Tokuyama coefficient attached to ``self``. Following the exposition of [BBF]_, Tokuyama's formula asserts .. MATH:: \sum_{G} (t+1)^{s(G)} t^{l(G)} z_1^{d_{n+1}} z_2^{d_{n}-d_{n+1}} \cdots z_{n+1}^{d_1-d_2} = s_{\lambda}(z_1,\dots,z_{n+1}) \prod_{i<j} (z_j+tz_i), where the sum is over all strict Gelfand-Tsetlin patterns with fixed top row `\lambda + \rho`, with `\lambda` a partition with at most `n+1` parts and `\rho = (n, n-1, \ldots, 1, 0)`, and `s_\lambda` is a Schur function. INPUT: - ``name`` -- (Default: ``'t'``) An alternative name for the variable `t`. EXAMPLES:: sage: P = GelfandTsetlinPattern([[3,2,1],[2,2],[2]]) sage: P.Tokuyama_coefficient() 0 sage: G = GelfandTsetlinPattern([[3,2,1],[3,1],[2]]) sage: G.Tokuyama_coefficient() t^2 + t sage: G = GelfandTsetlinPattern([[2,1,0],[1,1],[1]]) sage: G.Tokuyama_coefficient() 0 sage: G = GelfandTsetlinPattern([[5,3,2,1,0],[4,3,2,0],[4,2,1],[3,2],[3]]) sage: G.Tokuyama_coefficient() t^8 + 3*t^7 + 3*t^6 + t^5 """ R = PolynomialRing(ZZ, name) t = R.gen(0) if not self.is_strict(): return R.zero() return (t+1)**(self.number_of_special_entries()) * t**(self.number_of_boxes())
class TestConvertPolyToSym(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ,"x",3); self.x = self.poly_ring.gens()[0]; self.y = self.poly_ring.gens()[1]; self.z = self.poly_ring.gens()[2]; self.vars = var('x,y,z') def test_zero(self): zero = self.poly_ring.zero() poly = convert_polynomial_to_symbolic(zero,self.vars) self.assertEqual(poly,0) def test_convert(self): x = self.x y = self.y z = self.z poly = 4*x**4+y*x**12*y-z sym_poly = 4*self.vars[0]**4 + self.vars[1]*self.vars[0]**12*self.vars[1]-self.vars[2] con_poly = convert_polynomial_to_symbolic(poly,self.vars) self.assertEqual(sym_poly,con_poly)
class TestConvertPolyToSym(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ, "x", 3) self.x = self.poly_ring.gens()[0] self.y = self.poly_ring.gens()[1] self.z = self.poly_ring.gens()[2] self.vars = var('x,y,z') def test_zero(self): zero = self.poly_ring.zero() poly = convert_polynomial_to_symbolic(zero, self.vars) self.assertEqual(poly, 0) def test_convert(self): x = self.x y = self.y z = self.z poly = 4 * x**4 + y * x**12 * y - z sym_poly = 4 * self.vars[0]**4 + self.vars[1] * self.vars[ 0]**12 * self.vars[1] - self.vars[2] con_poly = convert_polynomial_to_symbolic(poly, self.vars) self.assertEqual(sym_poly, con_poly)
class TestConvertSymToPoly(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ, "x", 3) self.x = self.poly_ring.gens()[0] self.y = self.poly_ring.gens()[1] self.z = self.poly_ring.gens()[2] self.vars = var('x,y,z') def test_zero(self): zero = 0 * self.vars[0] poly = convert_symbolic_to_polynomial(zero, self.poly_ring, self.vars) self.assertEqual(poly, self.poly_ring.zero()) def test_convert(self): x = self.x y = self.y z = self.z sym_poly = 4 * self.vars[0]**4 + self.vars[1] * self.vars[ 0]**12 * self.vars[1] - self.vars[2] poly = convert_symbolic_to_polynomial(sym_poly, self.poly_ring, self.vars) self.assertEqual(poly, 4 * x**4 + y * x**12 * y - z) def test_convert_univarient(self): y = self.y sym_poly = 4 * self.vars[1]**4 + self.vars[1] * self.vars[ 1]**12 * self.vars[1] - self.vars[1] poly = convert_symbolic_to_polynomial(sym_poly, self.poly_ring, self.vars) self.assertEqual(poly, 4 * y**4 + y * y**12 * y - y) def test_convert_partial(self): y = self.y z = self.z sym_poly = 4 * self.vars[2]**4 + self.vars[1] * self.vars[ 1]**12 * self.vars[1] - self.vars[1] poly = convert_symbolic_to_polynomial(sym_poly, self.poly_ring, self.vars) self.assertEqual(poly, 4 * z**4 + y * y**12 * y - y)
def permanental_minor_polynomial(A, permanent_only=False, var='t', prec=None): r""" Return the polynomial of the sums of permanental minors of ``A``. INPUT: - `A` -- a matrix - `permanent_only` -- if True, return only the permanent of `A` - `var` -- name of the polynomial variable - `prec` -- if prec is not None, truncate the polynomial at precision `prec` The polynomial of the sums of permanental minors is .. MATH:: \sum_{i=0}^{min(nrows, ncols)} p_i(A) x^i where `p_i(A)` is the `i`-th permanental minor of `A` (that can also be obtained through the method :meth:`~sage.matrix.matrix2.Matrix.permanental_minor` via ``A.permanental_minor(i)``). The algorithm implemented by that function has been developed by P. Butera and M. Pernici, see [ButPer]. Its complexity is `O(2^n m^2 n)` where `m` and `n` are the number of rows and columns of `A`. Moreover, if `A` is a banded matrix with width `w`, that is `A_{ij}=0` for `|i - j| > w` and `w < n/2`, then the complexity of the algorithm is `O(4^w (w+1) n^2)`. INPUT: - ``A`` -- matrix - ``permanent_only`` -- optional boolean. If ``True``, only the permanent is computed (might be faster). - ``var`` -- a variable name EXAMPLES:: sage: from sage.matrix.matrix_misc import permanental_minor_polynomial sage: m = matrix([[1,1],[1,2]]) sage: permanental_minor_polynomial(m) 3*t^2 + 5*t + 1 sage: permanental_minor_polynomial(m, permanent_only=True) 3 sage: permanental_minor_polynomial(m, prec=2) 5*t + 1 :: sage: M = MatrixSpace(ZZ,4,4) sage: A = M([1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1]) sage: permanental_minor_polynomial(A) 84*t^3 + 114*t^2 + 28*t + 1 sage: [A.permanental_minor(i) for i in range(5)] [1, 28, 114, 84, 0] An example over `\QQ`:: sage: M = MatrixSpace(QQ,2,2) sage: A = M([1/5,2/7,3/2,4/5]) sage: permanental_minor_polynomial(A, True) 103/175 An example with polynomial coefficients:: sage: R.<a> = PolynomialRing(ZZ) sage: A = MatrixSpace(R,2)([[a,1], [a,a+1]]) sage: permanental_minor_polynomial(A, True) a^2 + 2*a A usage of the ``var`` argument:: sage: m = matrix(ZZ,4,[0,1,2,3,1,2,3,0,2,3,0,1,3,0,1,2]) sage: permanental_minor_polynomial(m, var='x') 164*x^4 + 384*x^3 + 172*x^2 + 24*x + 1 ALGORITHM: The permanent `perm(A)` of a `n \times n` matrix `A` is the coefficient of the `x_1 x_2 \ldots x_n` monomial in .. MATH:: \prod_{i=1}^n \left( \sum_{j=1}^n A_{ij} x_j \right) Evaluating this product one can neglect `x_i^2`, that is `x_i` can be considered to be nilpotent of order `2`. To formalize this procedure, consider the algebra `R = K[\eta_1, \eta_2, \ldots, \eta_n]` where the `\eta_i` are commuting, nilpotent of order `2` (i.e. `\eta_i^2 = 0`). Formally it is the quotient ring of the polynomial ring in `\eta_1, \eta_2, \ldots, \eta_n` quotiented by the ideal generated by the `\eta_i^2`. We will mostly consider the ring `R[t]` of polynomials over `R`. We denote a generic element of `R[t]` by `p(\eta_1, \ldots, \eta_n)` or `p(\eta_{i_1}, \ldots, \eta_{i_k})` if we want to emphasize that some monomials in the `\eta_i` are missing. Introduce an "integration" operation `\langle p \rangle` over `R` and `R[t]` consisting in the sum of the coefficients of the non-vanishing monomials in `\eta_i` (i.e. the result of setting all variables `\eta_i` to `1`). Let us emphasize that this is *not* a morphism of algebras as `\langle \eta_1 \rangle^2 = 1` while `\langle \eta_1^2 \rangle = 0`! Let us consider an example of computation. Let `p_1 = 1 + t \eta_1 + t \eta_2` and `p_2 = 1 + t \eta_1 + t \eta_3`. Then .. MATH:: p_1 p_2 = 1 + 2t \eta_1 + t (\eta_2 + \eta_3) + t^2 (\eta_1 \eta_2 + \eta_1 \eta_3 + \eta_2 \eta_3) and .. MATH:: \langle p_1 p_2 \rangle = 1 + 4t + 3t^2 In this formalism, the permanent is just .. MATH:: perm(A) = \langle \prod_{i=1}^n \sum_{j=1}^n A_{ij} \eta_j \rangle A useful property of `\langle . \rangle` which makes this algorithm efficient for band matrices is the following: let `p_1(\eta_1, \ldots, \eta_n)` and `p_2(\eta_j, \ldots, \eta_n)` be polynomials in `R[t]` where `j \ge 1`. Then one has .. MATH:: \langle p_1(\eta_1, \ldots, \eta_n) p_2 \rangle = \langle p_1(1, \ldots, 1, \eta_j, \ldots, \eta_n) p_2 \rangle where `\eta_1,..,\eta_{j-1}` are replaced by `1` in `p_1`. Informally, we can "integrate" these variables *before* performing the product. More generally, if a monomial `\eta_i` is missing in one of the terms of a product of two terms, then it can be integrated in the other term. Now let us consider an `m \times n` matrix with `m \leq n`. The *sum of permanental `k`-minors of `A`* is .. MATH:: perm(A, k) = \sum_{r,c} perm(A_{r,c}) where the sum is over the `k`-subsets `r` of rows and `k`-subsets `c` of columns and `A_{r,c}` is the submatrix obtained from `A` by keeping only the rows `r` and columns `c`. Of course `perm(A, \min(m,n)) = perm(A)` and note that `perm(A,1)` is just the sum of all entries of the matrix. The generating function of these sums of permanental minors is .. MATH:: g(t) = \left\langle \prod_{i=1}^m \left(1 + t \sum_{j=1}^n A_{ij} \eta_j\right) \right\rangle In fact the `t^k` coefficient of `g(t)` corresponds to choosing `k` rows of `A`; `\eta_i` is associated to the i-th column; nilpotency avoids having twice the same column in a product of `A`'s. For more details, see the article [ButPer]. From a technical point of view, the product in `K[\eta_1, \ldots, \eta_n][t]` is implemented as a subroutine in :func:`prm_mul`. The indices of the rows and columns actually start at `0`, so the variables are `\eta_0, \ldots, \eta_{n-1}`. Polynomials are represented in dictionary form: to a variable `\eta_i` is associated the key `2^i` (or in Python ``1 << i``). The keys associated to products are obtained by considering the development in base `2`: to the monomial `\eta_{i_1} \ldots \eta_{i_k}` is associated the key `2^{i_1} + \ldots + 2^{i_k}`. So the product `\eta_1 \eta_2` corresponds to the key `6 = (110)_2` while `\eta_0 \eta_3` has key `9 = (1001)_2`. In particular all operations on monomials are implemented via bitwise operations on the keys. REFERENCES: .. [ButPer] P. Butera and M. Pernici "Sums of permanental minors using Grassmann algebra", :arxiv:`1406.5337` """ if permanent_only: prec = None elif prec is not None: prec = int(prec) if prec == 0: raise ValueError('the argument `prec` must be a positive integer') K = PolynomialRing(A.base_ring(), var) nrows = A.nrows() ncols = A.ncols() A = A.rows() p = {0: K.one()} t = K.gen() vars_to_do = range(ncols) for i in range(nrows): # build the polynomial p1 = 1 + t sum A_{ij} eta_j if permanent_only: p1 = {} else: p1 = {0: K.one()} a = A[i] # the i-th row of A for j in range(len(a)): if a[j]: p1[1<<j] = a[j] * t # make the product with the preceding polynomials, taking care of # variables that can be integrated mask_free = 0 j = 0 while j < len(vars_to_do): jj = vars_to_do[j] if all(A[k][jj] == 0 for k in range(i+1, nrows)): mask_free += 1 << jj vars_to_do.remove(jj) else: j += 1 p = prm_mul(p, p1, mask_free, prec) if not p: return K.zero() if len(p) != 1 or 0 not in p: raise RuntimeError("Something is wrong! Certainly a problem in the" " algorithm... please contact [email protected]") p = p[0] return p[min(nrows,ncols)] if permanent_only else p
def ehrhart_polynomial(self, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. INPUT: - ``verbose`` - (boolean, default to ``False``) if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details you should consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: - ``dual`` - (boolean) triangulate and signed-decompose in the dual space - ``irrational_primal`` - (boolean) triangulate in the dual space, signed-decompose in the primal space using irrationalization. - ``irrational_all_primal`` - (boolean) Triangulate and signed-decompose in the primal space using irrationalization. - ``maxdet`` -- (integer) decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). - ``no_decomposition`` -- (boolean) do not signed-decompose simplicial cones. - ``compute_vertex_cones`` -- (string) either 'cdd' or 'lrs' or '4ti2' - ``smith_form`` -- (string) either 'ilio' or 'lidia' - ``dualization`` -- (string) either 'cdd' or '4ti2' - ``triangulation`` - (string) 'cddlib', '4ti2' or 'topcom' - ``triangulation_max_height`` - (integer) use a uniform distribution of height from 1 to this number .. NOTE:: Any additional argument is forwarded to LattE's executable ``count``. All occurrences of '_' will be replaced with a '-'. ALGORITHM: This method calls the program ``count`` from LattE integrale, a program for lattice point enumeration (see https://www.math.ucdavis.edu/~latte/). .. SEEALSO:: :mod:`~sage.interfaces.latte` the interface to LattE integrale EXAMPLES:: sage: P = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: p = P.ehrhart_polynomial() # optional - latte_int sage: p # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: p(1) # optional - latte_int 6 sage: len(P.integral_points()) 6 sage: p(2) # optional - latte_int 36 sage: len((2*P).integral_points()) 36 The unit hypercubes:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: P = Polyhedron(ambient_dim=3, vertices=[]) sage: P.ehrhart_polynomial() # optional - latte_int 0 sage: parent(_) # optional - latte_int Univariate Polynomial Ring in t over Rational Field TESTS: Test options:: sage: P = Polyhedron(ieqs=[[1,-1,1,0], [-1,2,-1,0], [1,1,-2,0]], eqns=[[-1,2,-1,-3]], base_ring=ZZ) sage: p = P.ehrhart_polynomial(maxdet=5, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd '--maxdet=5' /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(dual=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --dual /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --irrational-primal /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_all_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --irrational-all-primal /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 Test bad options:: sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale program failed (exit code 1): ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd '--bim-bam-boum=19' /dev/stdin Unknown command/option --bim-bam-boum=19 """ if self.is_empty(): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, 't') return R.zero() # note: the options below are explicitely written in the function # declaration in order to keep tab completion (see #18211). kwds.update({ 'dual': dual, 'irrational_primal': irrational_primal, 'irrational_all_primal': irrational_all_primal, 'maxdet': maxdet, 'no_decomposition': no_decomposition, 'compute_vertex_cones': compute_vertex_cones, 'smith_form': smith_form, 'dualization': dualization, 'triangulation': triangulation, 'triangulation_max_height': triangulation_max_height }) from sage.interfaces.latte import count ine = self.cdd_Hrepresentation() return count(ine, cdd=True, ehrhart_polynomial=True, verbose=verbose, **kwds)
def ehrhart_polynomial(self, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. INPUT: - ``verbose`` - (boolean, default to ``False``) if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details you should consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: - ``dual`` - (boolean) triangulate and signed-decompose in the dual space - ``irrational_primal`` - (boolean) triangulate in the dual space, signed-decompose in the primal space using irrationalization. - ``irrational_all_primal`` - (boolean) Triangulate and signed-decompose in the primal space using irrationalization. - ``maxdet`` -- (integer) decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). - ``no_decomposition`` -- (boolean) do not signed-decompose simplicial cones. - ``compute_vertex_cones`` -- (string) either 'cdd' or 'lrs' or '4ti2' - ``smith_form`` -- (string) either 'ilio' or 'lidia' - ``dualization`` -- (string) either 'cdd' or '4ti2' - ``triangulation`` - (string) 'cddlib', '4ti2' or 'topcom' - ``triangulation_max_height`` - (integer) use a uniform distribution of height from 1 to this number .. NOTE:: Any additional argument is forwarded to LattE's executable ``count``. All occurrences of '_' will be replaced with a '-'. ALGORITHM: This method calls the program ``count`` from LattE integrale, a program for lattice point enumeration (see https://www.math.ucdavis.edu/~latte/). EXAMPLES:: sage: P = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: p = P.ehrhart_polynomial() # optional - latte_int sage: p # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: p(1) # optional - latte_int 6 sage: len(P.integral_points()) 6 sage: p(2) # optional - latte_int 36 sage: len((2*P).integral_points()) 36 The unit hypercubes:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: P = Polyhedron(ambient_dim=3, vertices=[]) sage: P.ehrhart_polynomial() # optional - latte_int 0 sage: parent(_) # optional - latte_int Univariate Polynomial Ring in t over Rational Field TESTS: Test options:: sage: P = Polyhedron(ieqs=[[1,-1,1,0], [-1,2,-1,0], [1,1,-2,0]], eqns=[[-1,2,-1,-3]], base_ring=ZZ) sage: p = P.ehrhart_polynomial(maxdet=5, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' '--maxdet=5' --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(dual=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --dual --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-primal --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_all_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-all-primal --cdd ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 Test bad options:: sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale failed with exit code 1 to execute... """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(QQ, 't') if self.is_empty(): return R.zero() from sage.misc.misc import SAGE_TMP from subprocess import Popen, PIPE ine = self.cdd_Hrepresentation() args = ['count', '--ehrhart-polynomial'] if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') # note: the options below are explicitely written in the function # declaration in order to keep tab completion (see #18211). kwds.update({ 'dual': dual, 'irrational_primal': irrational_primal, 'irrational_all_primal': irrational_all_primal, 'maxdet': maxdet, 'no_decomposition': no_decomposition, 'compute_vertex_cones': compute_vertex_cones, 'smith_form': smith_form, 'dualization': dualization, 'triangulation': triangulation, 'triangulation_max_height': triangulation_max_height }) 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 += ['--cdd', '/dev/stdin'] try: # The cwd argument is needed because latte # always produces diagnostic output files. latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: from sage.misc.package import PackageNotFoundError raise PackageNotFoundError('latte_int') ans, err = latte_proc.communicate(ine) ret_code = latte_proc.poll() if ret_code: if err is None: err = ", see error message above" else: err = ":\n" + err raise RuntimeError( "LattE integrale failed with exit code {} to execute {}". format(ret_code, ' '.join(args)) + err.strip()) p = ans.splitlines()[-2] return R(p)
def ehrhart_polynomial(self, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. INPUT: - ``verbose`` - (boolean, default to ``False``) if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details you should consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: - ``dual`` - (boolean) triangulate and signed-decompose in the dual space - ``irrational_primal`` - (boolean) triangulate in the dual space, signed-decompose in the primal space using irrationalization. - ``irrational_all_primal`` - (boolean) Triangulate and signed-decompose in the primal space using irrationalization. - ``maxdet`` -- (integer) decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). - ``no_decomposition`` -- (boolean) do not signed-decompose simplicial cones. - ``compute_vertex_cones`` -- (string) either 'cdd' or 'lrs' or '4ti2' - ``smith_form`` -- (string) either 'ilio' or 'lidia' - ``dualization`` -- (string) either 'cdd' or '4ti2' - ``triangulation`` - (string) 'cddlib', '4ti2' or 'topcom' - ``triangulation_max_height`` - (integer) use a uniform distribution of height from 1 to this number .. NOTE:: Any additional argument is forwarded to LattE's executable ``count``. All occurrences of '_' will be replaced with a '-'. ALGORITHM: This method calls the program ``count`` from LattE integrale, a program for lattice point enumeration (see https://www.math.ucdavis.edu/~latte/). EXAMPLES:: sage: P = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: p = P.ehrhart_polynomial() # optional - latte_int sage: p # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: p(1) # optional - latte_int 6 sage: len(P.integral_points()) 6 sage: p(2) # optional - latte_int 36 sage: len((2*P).integral_points()) 36 The unit hypercubes:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: P = Polyhedron(ambient_dim=3, vertices=[]) sage: P.ehrhart_polynomial() # optional - latte_int 0 sage: parent(_) # optional - latte_int Univariate Polynomial Ring in t over Rational Field TESTS: Test options:: sage: P = Polyhedron(ieqs=[[1,-1,1,0], [-1,2,-1,0], [1,1,-2,0]], eqns=[[-1,2,-1,-3]], base_ring=ZZ) sage: p = P.ehrhart_polynomial(maxdet=5, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' '--maxdet=5' --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(dual=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --dual --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-primal --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_all_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-all-primal --cdd ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 Test bad options:: sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale failed with exit code 1 to execute... """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(QQ, 't') if self.is_empty(): return R.zero() from sage.misc.misc import SAGE_TMP from subprocess import Popen, PIPE ine = self.cdd_Hrepresentation() args = ['count', '--ehrhart-polynomial'] if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') # note: the options below are explicitely written in the function # declaration in order to keep tab completion (see #18211). kwds.update({ 'dual' : dual, 'irrational_primal' : irrational_primal, 'irrational_all_primal' : irrational_all_primal, 'maxdet' : maxdet, 'no_decomposition' : no_decomposition, 'compute_vertex_cones' : compute_vertex_cones, 'smith_form' : smith_form, 'dualization' : dualization, 'triangulation' : triangulation, 'triangulation_max_height': triangulation_max_height}) 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 += ['--cdd', '/dev/stdin'] try: # The cwd argument is needed because latte # always produces diagnostic output files. latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: from sage.misc.package import PackageNotFoundError raise PackageNotFoundError('latte_int') ans, err = latte_proc.communicate(ine) ret_code = latte_proc.poll() if ret_code: if err is None: err = ", see error message above" else: err = ":\n" + err raise RuntimeError("LattE integrale failed with exit code {} to execute {}".format(ret_code, ' '.join(args)) + err.strip()) p = ans.splitlines()[-2] return R(p)
def ehrhart_quasipolynomial(self, variable='t', engine=None, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Compute the Ehrhart quasipolynomial of this polyhedron with rational vertices. If the polyhedron is a lattice polytope, returns the Ehrhart polynomial, a univariate polynomial in ``variable`` over a rational field. If the polyhedron has rational, nonintegral vertices, returns a tuple of polynomials in ``variable`` over a rational field. The Ehrhart counting function of a polytope `P` with rational vertices is given by a *quasipolynomial*. That is, there exists a positive integer `l` and `l` polynomials `ehr_{P,i} \text{ for } i \in \{1,\dots,l \}` such that if `t` is equivalent to `i` mod `l` then `tP \cap \mathbb Z^d = ehr_{P,i}(t)`. INPUT: - ``variable`` -- string (default: 't'); The variable in which the Ehrhart polynomial should be expressed. - ``engine`` -- string; The backend to use. Allowed values are: * ``None`` (default); When no input is given the Ehrhart polynomial is computed using Normaliz (optional) * ``'latte'``; use LattE Integrale program (requires optional package 'latte_int') * ``'normaliz'``; use the Normaliz program (requires optional package 'pynormaliz'). The backend of ``self`` must be set to 'normaliz'. - When the ``engine`` is 'latte', the additional input values are: * ``verbose`` - boolean (default: ``False``); If ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: * ``dual`` - boolean; triangulate and signed-decompose in the dual space * ``irrational_primal`` - boolean; triangulate in the dual space, signed-decompose in the primal space using irrationalization. * ``irrational_all_primal`` - boolean; triangulate and signed-decompose in the primal space using irrationalization. * ``maxdet`` -- integer; decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). * ``no_decomposition`` -- boolean; do not signed-decompose simplicial cones. * ``compute_vertex_cones`` -- string; either 'cdd' or 'lrs' or '4ti2' * ``smith_form`` -- string; either 'ilio' or 'lidia' * ``dualization`` -- string; either 'cdd' or '4ti2' * ``triangulation`` - string; 'cddlib', '4ti2' or 'topcom' * ``triangulation_max_height`` - integer; use a uniform distribution of height from 1 to this number OUTPUT: A univariate polynomial over a rational field or a tuple of such polynomials. .. SEEALSO:: :mod:`~sage.interfaces.latte` the interface to LattE Integrale `PyNormaliz <https://pypi.org/project/PyNormaliz>`_ .. WARNING:: If the polytope has rational, non integral vertices, it must have ``backend='normaliz'``. EXAMPLES: As a first example, consider the line segment [0,1/2]. If we dilate this line segment by an even integral factor `k`, then the dilated line segment will contain `k/2 +1` lattice points. If `k` is odd then there will be `k/2+1/2` lattice points in the dilated line segment. Note that it is necessary to set the backend of the polytope to 'normaliz':: sage: line_seg = Polyhedron(vertices=[[0],[1/2]],backend='normaliz') # optional - pynormaliz sage: line_seg # optional - pynormaliz A 1-dimensional polyhedron in QQ^1 defined as the convex hull of 2 vertices sage: line_seg.ehrhart_quasipolynomial() # optional - pynormaliz (1/2*t + 1, 1/2*t + 1/2) For a more exciting example, let us look at the subpolytope of the 3 dimensional permutahedron fixed by the reflection across the hyperplane `x_1 = x_4`:: sage: verts = [[3/2, 3, 4, 3/2], ....: [3/2, 4, 3, 3/2], ....: [5/2, 1, 4, 5/2], ....: [5/2, 4, 1, 5/2], ....: [7/2, 1, 2, 7/2], ....: [7/2, 2, 1, 7/2]] sage: subpoly = Polyhedron(vertices=verts, backend='normaliz') # optional - pynormaliz sage: eq = subpoly.ehrhart_quasipolynomial() # optional - pynormaliz sage: eq # optional - pynormaliz (4*t^2 + 3*t + 1, 4*t^2 + 2*t) sage: eq = subpoly.ehrhart_quasipolynomial() # optional - pynormaliz sage: eq # optional - pynormaliz (4*t^2 + 3*t + 1, 4*t^2 + 2*t) sage: even_ep = eq[0] # optional - pynormaliz sage: odd_ep = eq[1] # optional - pynormaliz sage: even_ep(2) # optional - pynormaliz 23 sage: ts = 2*subpoly # optional - pynormaliz sage: ts.integral_points_count() # optional - pynormaliz latte_int 23 sage: odd_ep(1) # optional - pynormaliz 6 sage: subpoly.integral_points_count() # optional - pynormaliz latte_int 6 A polytope with rational nonintegral vertices must have ``backend='normaliz'``:: sage: line_seg = Polyhedron(vertices=[[0],[1/2]]) sage: line_seg.ehrhart_quasipolynomial() Traceback (most recent call last): ... TypeError: The backend of the polyhedron should be 'normaliz' The polyhedron should be compact:: sage: C = Polyhedron(backend='normaliz',rays=[[1/2,2],[2,1]]) # optional - pynormaliz sage: C.ehrhart_quasipolynomial() # optional - pynormaliz Traceback (most recent call last): ... ValueError: Ehrhart quasipolynomial only defined for compact polyhedra If the polytope happens to be a lattice polytope, the Ehrhart polynomial is returned:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)], backend='normaliz') # optional - pynormaliz sage: simplex = simplex.change_ring(QQ) # optional - pynormaliz sage: poly = simplex.ehrhart_quasipolynomial(engine='normaliz') # optional - pynormaliz sage: poly # optional - pynormaliz 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: simplex.ehrhart_polynomial() # optional - pynormaliz latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 TESTS: The cache of the Ehrhart quasipolynomial is being pickled:: sage: P = polytopes.cuboctahedron(backend='normaliz')/2 # optional - pynormaliz sage: P.ehrhart_quasipolynomial() # optional - pynormaliz (5/6*t^3 + 2*t^2 + 5/3*t + 1, 5/6*t^3 + 1/2*t^2 + 1/6*t - 1/2) sage: Q = loads(dumps(P)) # optional - pynormaliz sage: Q.ehrhart_quasipolynomial.is_in_cache() # optional - pynormaliz True sage: P = polytopes.cuboctahedron().change_ring(QQ) # optional - latte_int sage: P.ehrhart_quasipolynomial(engine='latte') # optional - latte_int 20/3*t^3 + 8*t^2 + 10/3*t + 1 sage: Q = loads(dumps(P)) # optional - latte_int sage: Q.ehrhart_quasipolynomial.is_in_cache(engine='latte') # optional - latte_int True """ if self.is_empty(): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, 't') return R.zero() if not self.is_compact(): raise ValueError("Ehrhart quasipolynomial only defined for compact polyhedra") if engine is None: # setting the default to 'normaliz' engine = 'normaliz' if engine == 'normaliz': return self._ehrhart_quasipolynomial_normaliz(variable) if engine == 'latte': if any(not v.is_integral() for v in self.vertex_generator()): raise TypeError("the polytope has nonintegral vertices, the engine and backend of self should be 'normaliz'") poly = self._ehrhart_polynomial_latte(verbose, dual, irrational_primal, irrational_all_primal, maxdet, no_decomposition, compute_vertex_cones, smith_form, dualization, triangulation, triangulation_max_height, **kwds) return poly.change_variable_name(variable) # TO DO: replace this change of variable by creating the appropriate # polynomial ring in the latte interface. else: raise TypeError("the engine should be 'latte' or 'normaliz'")
def rand_pham_divisor(n, var="z"): poly_ring = PolynomialRing(QQ, n, var) div = poly_ring.zero() for g in poly_ring.gens(): div += g**(randrange(2, 9)) return div
class DifferentialPolynomialRing: element_class = DifferentialPolynomial def __init__(self, base_ring, fibre_names, base_names, max_differential_orders): self._fibre_names = tuple(fibre_names) self._base_names = tuple(base_names) self._max_differential_orders = tuple(max_differential_orders) base_dim = len(self._base_names) fibre_dim = len(self._fibre_names) jet_names = [] idx_to_name = {} for fibre_idx in range(fibre_dim): u = self._fibre_names[fibre_idx] idx_to_name[(fibre_idx, ) + tuple([0] * base_dim)] = u for d in range(1, max_differential_orders[fibre_idx] + 1): for multi_index in IntegerVectors(d, base_dim): v = '{}_{}'.format( u, ''.join(self._base_names[i] * multi_index[i] for i in range(base_dim))) jet_names.append(v) idx_to_name[(fibre_idx, ) + tuple(multi_index)] = v self._polynomial_ring = PolynomialRing( base_ring, base_names + fibre_names + tuple(jet_names)) self._idx_to_var = { idx: self._polynomial_ring(idx_to_name[idx]) for idx in idx_to_name } self._var_to_idx = { jet: idx for (idx, jet) in self._idx_to_var.items() } # for conversion: base_vars = [var(b) for b in self._base_names] symbolic_functions = [ function(f)(*base_vars) for f in self._fibre_names ] self._subs_jet_vars = SubstituteJetVariables(symbolic_functions) self._subs_tot_ders = SubstituteTotalDerivatives(symbolic_functions) def __repr__(self): return 'Differential Polynomial Ring in {} over {}'.format( ', '.join(map(repr, self._polynomial_ring.gens())), self._polynomial_ring.base_ring()) def _latex_(self): return self._polynomial_ring._latex_() def base_ring(self): return self._polynomial_ring.base_ring() def _first_ngens(self, n): return tuple( self.element_class(self, self._polynomial_ring.gen(i)) for i in range(n)) def gens(self): return self._first_ngens(self._polynomial_ring.ngens()) def gen(self, i): return self.element_class(self, self._polynomial_ring.gen(i)) def base_variables(self): return self._first_ngens(len(self._base_names)) def base_dim(self): return len(self._base_names) def fibre_variable(self, i): return self.element_class( self, self._polynomial_ring.gen(len(self._base_names) + i)) def fibre_variables(self): base_dim = len(self._base_names) fibre_dim = len(self._fibre_names) return tuple( self.element_class(self, self._polynomial_ring.gen(base_dim + i)) for i in range(fibre_dim)) def fibre_dim(self): return len(self._fibre_names) def jet_variables(self): base_dim = len(self._base_names) fibre_dim = len(self._fibre_names) whole_dim = self._polynomial_ring.ngens() return tuple( self.element_class(self, self._polynomial_ring.gen(i)) for i in range(base_dim + fibre_dim, whole_dim)) def max_differential_orders(self): return self._max_differential_orders def _single_var_weights(self, u): return self._var_to_idx[u][1:] def _diff_single_var(self, u, x): x_idx = self._polynomial_ring.gens().index(x) u_idx = self._var_to_idx[u] du_idx = list(u_idx) du_idx[1 + x_idx] += 1 du_idx = tuple(du_idx) if du_idx in self._idx_to_var: return self._idx_to_var[du_idx] else: raise ValueError( "can't differentiate {} any further with respect to {}".format( u, x)) def _integrate_single_var(self, u, x): x_idx = self._polynomial_ring.gens().index(x) u_idx = self._var_to_idx[u] if u_idx[1 + x_idx] == 0: raise ValueError( "can't integrate {} any further with respect to {}".format( u, x)) iu_idx = list(u_idx) iu_idx[1 + x_idx] -= 1 iu_idx = tuple(iu_idx) return self._idx_to_var[iu_idx] def __contains__(self, arg): if isinstance(arg, self.element_class) and arg.parent() is self: return True if arg in self._polynomial_ring.base_ring(): return True return False def __call__(self, arg): if isinstance(arg, self.element_class) and arg.parent() is self: return arg if is_Expression(arg): arg = self._subs_jet_vars(arg) return self.element_class(self, self._polynomial_ring(arg)) def zero(self): return self.element_class(self, self._polynomial_ring.zero()) def one(self): return self.element_class(self, self._polynomial_ring.one()) def homogeneous_monomials(self, fibre_degrees, weights, max_differential_orders=None): fibre_vars = self.fibre_variables() if not len(fibre_degrees) == len(fibre_vars): raise ValueError( 'length of fibre_degrees vector must match number of fibre variables' ) base_vars = self.base_variables() if not len(weights) == len(base_vars): raise ValueError( 'length of weights vector must match number of base variables') monomials = [] fibre_degree = sum(fibre_degrees) fibre_indexes = {} fibre_idx = 0 for i in range(len(fibre_degrees)): for j in range(fibre_degrees[i]): fibre_indexes[fibre_idx] = i fibre_idx += 1 proto = sum([[fibre_vars[i]] * fibre_degrees[i] for i in range(len(fibre_degrees))], []) for V in product(*[IntegerVectors(w, fibre_degree) for w in weights]): total_differential_order = [0 for i in range(fibre_degree)] term = [p for p in proto] skip = False for j in range(fibre_degree): fibre_idx = fibre_indexes[j] for i in range(len(base_vars)): if V[i][j] > 0: total_differential_order[j] += V[i][j] if max_differential_orders is not None and total_differential_order[ j] > max_differential_orders[fibre_idx]: skip = True break term[j] = term[j].total_derivative(*([base_vars[i]] * V[i][j])) if skip: break if not skip: monomials.append(prod(term)) return monomials
class TestSingularModule(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ, "x", 3) self.x = self.poly_ring.gens()[0] self.y = self.poly_ring.gens()[1] self.z = self.poly_ring.gens()[2] def test_creation(self): x = self.x y = self.y z = self.z SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) #Check this did cause an error self.assertTrue(True) def test_create_ring_str(self): x = self.x y = self.y z = self.z sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) self.assertEqual(sm.create_ring_str(), "ring r=0,x(1..3),dp;\n") def test_create_module_str(self): x = self.x y = self.y z = self.z sm = SingularModule([[x, 2 * y**2, 3 * z**3]]) self.assertEqual( sm.create_module_str("MT"), "module MT=[1*x(1)^1,2*x(2)^2,3*x(3)^3];\n groebner(MT);\n") def test_contains_zero(self): x = self.x y = self.y z = self.z sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) zero = self.poly_ring.zero() #Assert we always contain zero self.assertTrue(sm.contains([zero, zero, zero])) def test_contains_gen(self): x = self.x y = self.y z = self.z sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) #Assert we contain our generators self.assertTrue(sm.contains([x, y + z, z**3 - 2 * y])) self.assertTrue(sm.contains([x, y, z])) def test_contains_combination(self): x = self.x y = self.y z = self.z sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) #Assert we contain a combination of the generorators self.assertTrue(sm.contains([2 * x, 2 * y + z, z**3 - 2 * y + z])) def test_contains_fail(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) #Detect that certain vectors are not contained self.assertFalse(sm.contains([one, x, z])) self.assertFalse(sm.contains([y, y, y])) self.assertFalse(sm.contains([z, y, x])) def test_contains_fail_multiple(self): x = self.x y = self.y z = self.z sm = SingularModule([[x**2, x * y, x * z]]) #Detect that this is not contianed even though a multiple is self.assertFalse(sm.contains([x, y, z])) def test_contains_module(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() smA = SingularModule([[x**2, x * y, y * z], [x, y, z]]) smB = SingularModule([[x**2 + x, x * y + y, y * z + z], [zero, zero, y * z - x * z]]) self.assertTrue(smA.contains(smB)) def test_contained_in_ambient(self): x = self.x y = self.y z = self.z smA = SingularModule([[x, y**3, z + x], [z, x, x**2]]) am = smA.ambient_free_module() self.assertTrue(am.contains(smA)) def test_intersect(self): x = self.x y = self.y z = self.z smA = SingularModule([[x, y**3, z + x], [z, x, x**2]]) smB = SingularModule([[z, y, x], [-z, x**2, 4 * y + z]]) smI = smA.intersection(smB) gen_1_1 = x**5 * z + x * y**3 * z**2 + 4 * y**4 * z**2 + y**3 * z**3 + x**3 * y * z - x**3 * z**2 - x**2 * z**3 - x**3 * z - 4 * x**2 * y * z - x**2 * z**2 - x * y * z**2 - y * z**3 gen_2_1 = x**4 * y**3 * z - x**3 * y**3 * z + x**2 * y**4 * z + 4 * y**5 * z + y**4 * z**2 + x**5 - x**4 * z - x**3 * z**2 - 4 * x**2 * y**2 - 2 * x**2 * y * z - x * y * z**2 gen_3_1 = x**3 * y**3 * z - x**3 * y * z + 4 * x**2 * y**4 * z + x**2 * y**3 * z**2 + x**6 - 4 * x**3 * y**2 - x**4 * z - x**3 * z**2 - x**3 * z - 4 * x**2 * y * z + 4 * x * y**2 * z - 2 * x**2 * z**2 - 3 * x * y * z**2 + 4 * y**2 * z**2 - x * z**3 + y * z**3 gens = [[gen_1_1, gen_2_1, gen_3_1]] #Test this example self.assertEqual(gens, smI.gens) def test_intersect_contains(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2 = SingularModule([[x, y**2, z**3]]) #Assert the intersection is contained in both modules gens = (sm1.intersection(sm2)).gens for gen in gens: self.assertTrue(sm1.contains(gen)) for gen in gens: self.assertTrue(sm2.contains(gen)) def test_intersection_symetry(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2 = SingularModule([[x, y**2, z**3]]) sm_1_2 = sm1.intersection(sm2) sm_2_1 = sm2.intersection(sm1) #Assert that equals is symetric self.assertTrue(sm_1_2.equals(sm_2_1)) def test_reduce_lossless(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2.reduce_generators() self.assertTrue(sm1.equals(sm2)) def test_standard_basis_irredundent(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) std_gens = sm1.standard_basis() std_mod = SingularModule(std_gens) self.assertTrue(sm1.equals(std_mod)) def test_equals_A(self): x = self.x y = self.y z = self.z #Assert these are equal sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2 = SingularModule([[x, y, z], [x, y + z, z**3 - 2 * y], [x, y, z**2]]) self.assertTrue(sm1.equals(sm2)) def test_equals_B(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() #Assert these are equal - from crossing divisor sm1 = SingularModule([[x, zero, zero], [zero, y, zero], [zero, zero, z]]) sm2 = SingularModule([[zero, y, z], [zero, zero, z], [x, zero, zero]]) self.assertTrue(sm1.equals(sm2)) def test_equals_not(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) sm2 = SingularModule([[x, y, z]]) #Assert these are not equal self.assertFalse(sm1.equals(sm2)) def test_ambient_free_module(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() zero = self.poly_ring.zero() sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2]]) self.assertEqual( sm1.ambient_free_module().gens, [[one, zero, zero], [zero, one, zero], [zero, zero, one]]) def test_is_free_trivial(self): free = SingularModule.create_free_module(3, self.poly_ring) self.assertTrue(free.is_free()) def test_is_free(self): x = self.x y = self.y zero = self.poly_ring.zero() free = SingularModule([[x, zero, zero], [zero, y, zero], [zero, zero, x**2]]) self.assertTrue(free.is_free()) def test_is_free_not(self): x = self.x y = self.y sm1 = SingularModule([[x, x, x], [y, y, y]]) self.assertFalse(sm1.is_free()) def test_create_relationA(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() zero = self.poly_ring.zero() relation = [x**2, one + z**2, y] ideal = Ideal(self.poly_ring, [x**2 * y - z**2]) mod = SingularModule.create_from_relation(relation, ideal) true_mod = SingularModule([[one, -x**2, x**4], [zero, y, -z**2 - 1], [zero, z**2, -x**2 * z**2 - x**2], [zero, zero, x**2 * y - z**2]]) self.assertTrue(mod.equals(true_mod)) def test_create_relationB(self): #From an error uncovered in log derivations x = self.x y = self.y z = self.z zero = self.poly_ring.zero() relation = [y * z, x * z, x * y] ideal = Ideal(self.poly_ring, [x * y * z]) mod = SingularModule.create_from_relation(relation, ideal) true_mod = SingularModule([[x, zero, zero], [zero, y, zero], [zero, zero, z]]) self.assertTrue(mod.equals(true_mod)) def test_create_relation_satisfy_A(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() relation = [x + x**4 - y, (y + z)**3, -2 * y + self.poly_ring.one()] ideal = Ideal(self.poly_ring, [x**2 * y - z**2]) mod = SingularModule.create_from_relation(relation, ideal) for gen in mod.gens: sum = zero for g, rel in zip(gen, relation): sum = sum + g * rel self.assertTrue(sum in ideal) def test_create_relation_satisfy_B(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() zero = self.poly_ring.zero() relation = [x**2, one + z**2, y] ideal = Ideal(self.poly_ring, [x**2 * y - z**2]) mod = SingularModule.create_from_relation(relation, ideal) for gen in mod.gens: sum = zero for g, rel in zip(gen, relation): sum = sum + g * rel self.assertTrue(sum in ideal) def test_create_relations_satisfy(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() relations = [[x**2, z * y], [y**2, -x]] ideals = [ Ideal(self.poly_ring, [x**2 * y - z**2]), Ideal(self.poly_ring, [x * y - z]) ] mod = SingularModule.create_from_relations(relations, ideals) for relation, ideal in zip(relations, ideals): for gen in mod.gens: sum = zero for g, rel in zip(gen, relation): sum = sum + g * rel self.assertTrue(sum in ideal) def test_create_singular_free(self): free_matrix = "MM[1,1]=1\nMM[1,2]=0\nMM[1,3]=0\n" free_matrix = free_matrix + "MM[2,1]=0\nMM[2,2]=1\nMM[2,3]=0\n" free_matrix = free_matrix + "MM[3,1]=0\nMM[3,2]=0\nMM[3,3]=1\n" free_c = SingularModule.create_from_singular_matrix( self.poly_ring, free_matrix) free = SingularModule.create_free_module(3, self.poly_ring) self.assertTrue(free.equals(free_c)) def test_create_singular(self): out = "mat_inter[1,1]=0\n" out = out + "mat_inter[1,2]=0\n" out = out + "mat_inter[1,3]=x(1)*x(2)*x(3)\n" out = out + "mat_inter[2,1]=x(2)\n" out = out + "mat_inter[2,2]=0\n" out = out + "mat_inter[2,3]=0\n" out = out + "mat_inter[3,1]=-x(3)\n" out = out + "mat_inter[3,2]=x(3)\n" out = out + "mat_inter[3,3]=0\n" mod = SingularModule.create_from_singular_matrix( self.poly_ring, out, "mat_inter") x = self.x y = self.y z = self.z zero = self.poly_ring.zero() mod_true = [[zero, y, -z], [zero, zero, z], [x * y * z, zero, zero]] self.assertEqual(mod.gens, mod_true) def test_lift_zero(self): x = self.x y = self.y z = self.z one = self.poly_ring.one() zero = self.poly_ring.zero() sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z], [x, y, z**2], [one, x, y]]) vec = sm1.lift([zero, zero, zero]) self.assertEqual(vec, [zero for _ in range(4)]) def test_lift_satisfy(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) vec = sm1.lift([ x**2 + x * z + x, x * y + x * z + y * z + y, z**3 * x - 2 * y * x + z**2 + z ]) self.assertEqual(vec[0] * x + vec[1] * x, x**2 + x * z + x) self.assertEqual(vec[0] * (y + z) + vec[1] * y, x * y + x * z + y * z + y) self.assertEqual(vec[0] * (z**3 - 2 * y) + vec[1] * z, z**3 * x - 2 * y * x + z**2 + z) def test_lift_linear(self): x = self.x y = self.y z = self.z sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]]) vec = sm1.lift([3 * x, 3 * y + z, z**3 - 2 * y + 2 * z], True) self.assertEqual(vec, [1, 2])
def demazure_character(self, w, f=None): r""" Returns the Demazure character associated to ``w``. INPUT: - ``w`` -- an element of the ambient weight lattice realization of the crystal, or a reduced word, or an element in the associated Weyl group OPTIONAL: - ``f`` -- a function from the crystal to a module This is currently only supported for crystals whose underlying weight space is the ambient space. The Demazure character is obtained by applying the Demazure operator `D_w` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ParentMethods.demazure_operator`) to the highest weight element of the classical crystal. The simple Demazure operators `D_i` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ElementMethods.demazure_operator_simple`) do not braid on the level of crystals, but on the level of characters they do. That is why it makes sense to input ``w`` either as a weight, a reduced word, or as an element of the underlying Weyl group. EXAMPLES:: sage: T = crystals.Tableaux(['A',2], shape = [2,1]) sage: e = T.weight_lattice_realization().basis() sage: weight = e[0] + 2*e[2] sage: weight.reduced_word() [2, 1] sage: T.demazure_character(weight) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x1*x3^2 sage: T = crystals.Tableaux(['A',3],shape=[2,1]) sage: T.demazure_character([1,2,3]) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3 sage: W = WeylGroup(['A',3]) sage: w = W.from_reduced_word([1,2,3]) sage: T.demazure_character(w) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3 sage: T = crystals.Tableaux(['B',2], shape = [2]) sage: e = T.weight_lattice_realization().basis() sage: weight = -2*e[1] sage: T.demazure_character(weight) x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1 sage: T = crystals.Tableaux("B2",shape=[1/2,1/2]) sage: b2=WeylCharacterRing("B2",base_ring=QQ).ambient() sage: T.demazure_character([1,2],f=lambda x:b2(x.weight())) b2(-1/2,1/2) + b2(1/2,-1/2) + b2(1/2,1/2) REFERENCES: .. [D1974] \M. Demazure, Desingularisation des varietes de Schubert, Ann. E. N. S., Vol. 6, (1974), p. 163-172 .. [M2009] Sarah Mason, An Explicit Construction of Type A Demazure Atoms, Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313. :arXiv:`0707.4267` """ from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing if hasattr(w, 'reduced_word'): word = w.reduced_word() else: word = w n = self.weight_lattice_realization().n u = self.algebra(ZZ).sum_of_monomials(self.module_generators) u = self.demazure_operator(u, word) if f is None: x = ['x%s' % i for i in range(1, n + 1)] P = PolynomialRing(ZZ, x) # TODO: use P.linear_combination when PolynomialRing will be a ModulesWithBasis return sum((coeff * prod((x[i]**(c.weight()[i]) for i in range(n)), P.one()) for c, coeff in u), P.zero()) else: return sum((coeff * f(c)) for c, coeff in u)
def demazure_character(self, weight, reduced_word = False): r""" Returns the Demazure character associated to the specified weight in the ambient weight lattice. INPUT: - ``weight`` -- an element of the weight lattice realization of the crystal, or a reduced word - ``reduced_word`` -- a boolean (default: ``False``) whether ``weight`` is given as a reduced word This is currently only supported for crystals whose underlying weight space is the ambient space. EXAMPLES:: sage: T = CrystalOfTableaux(['A',2], shape = [2,1]) sage: e = T.weight_lattice_realization().basis() sage: weight = e[0] + 2*e[2] sage: weight.reduced_word() [2, 1] sage: T.demazure_character(weight) x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x1*x3^2 sage: T = CrystalOfTableaux(['A',3],shape=[2,1]) sage: T.demazure_character([1,2,3], reduced_word = True) x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x2^2*x3 sage: T = CrystalOfTableaux(['B',2], shape = [2]) sage: e = T.weight_lattice_realization().basis() sage: weight = -2*e[1] sage: T.demazure_character(weight) x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1 TODO: detect automatically if weight is a reduced word, and remove the (untested!) ``reduced_word`` option. REFERENCES:: .. [D1974] M. Demazure, Desingularisation des varietes de Schubert, Ann. E. N. S., Vol. 6, (1974), p. 163-172 .. [M2009] Sarah Mason, An Explicit Construction of Type A Demazure Atoms, Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313 (arXiv:0707.4267) """ from sage.misc.misc_c import prod from sage.rings.rational_field import QQ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing if reduced_word: word = weight else: word = weight.reduced_word() n = self.weight_lattice_realization().n u = list( self.module_generators ) for i in reversed(word): u = u + sum((x.demazure_operator(i, truncated = True) for x in u), []) x = ['x%s'%i for i in range(1,n+1)] P = PolynomialRing(QQ, x) u = [b.weight() for b in u] return sum((prod((x[i]**(la[i]) for i in range(n)), P.one()) for la in u), P.zero())
def covariant_z0(F, z0_cov=False, prec=53, emb=None, error_limit=0.000001): r""" Return the covariant and Julia invariant from Cremona-Stoll [CS2003]_. In [CS2003]_ and [HS2018]_ the Julia invariant is denoted as `\Theta(F)` or `R(F, z(F))`. Note that you may get faster convergence if you first move `z_0(F)` to the fundamental domain before computing the true covariant INPUT: - ``F`` -- binary form of degree at least 3 with no multiple roots - ``z0_cov`` -- boolean, compute only the `z_0` invariant. Otherwise, solve the minimization problem - ``prec``-- positive integer. precision to use in CC - ``emb`` -- embedding into CC - ``error_limit`` -- sets the error tolerance (default:0.000001) OUTPUT: a complex number, a real number EXAMPLES:: sage: from sage.rings.polynomial.binary_form_reduce import covariant_z0 sage: R.<x,y> = QQ[] sage: F = 19*x^8 - 262*x^7*y + 1507*x^6*y^2 - 4784*x^5*y^3 + 9202*x^4*y^4\ ....: - 10962*x^3*y^5 + 7844*x^2*y^6 - 3040*x*y^7 + 475*y^8 sage: covariant_z0(F, prec=80, z0_cov=True) (1.3832330115323681438175 + 0.31233552177413614978744*I, 3358.4074848663492819259) sage: F = -x^8 + 6*x^7*y - 7*x^6*y^2 - 12*x^5*y^3 + 27*x^4*y^4\ ....: - 4*x^3*y^5 - 19*x^2*y^6 + 10*x*y^7 - 5*y^8 sage: covariant_z0(F, prec=80) (0.64189877107807122203366 + 1.1852516565091601348355*I, 3134.5148284344627168276) :: sage: R.<x,y> = QQ[] sage: covariant_z0(x^3 + 2*x^2*y - 3*x*y^2, z0_cov=True)[0] 0.230769230769231 + 0.799408065031789*I sage: -1/covariant_z0(-y^3 + 2*y^2*x + 3*y*x^2, z0_cov=True)[0] 0.230769230769231 + 0.799408065031789*I :: sage: R.<x,y> = QQ[] sage: covariant_z0(2*x^2*y - 3*x*y^2, z0_cov=True)[0] 0.750000000000000 + 1.29903810567666*I sage: -1/covariant_z0(-x^3 - x^2*y + 2*x*y^2, z0_cov=True)[0] + 1 0.750000000000000 + 1.29903810567666*I :: sage: R.<x,y> = QQ[] sage: covariant_z0(x^2*y - x*y^2, prec=100) # tol 1e-28 (0.50000000000000000000000000003 + 0.86602540378443864676372317076*I, 1.5396007178390020386910634147) TESTS:: sage: R.<x,y>=QQ[] sage: covariant_z0(x^2 + 24*x*y + y^2) Traceback (most recent call last): ... ValueError: must be at least degree 3 sage: covariant_z0((x+y)^3, z0_cov=True) Traceback (most recent call last): ... ValueError: cannot have multiple roots for z0 invariant sage: covariant_z0(x^3 + 3*x*y + y) Traceback (most recent call last): ... TypeError: must be a binary form sage: covariant_z0(-2*x^2*y^3 + 3*x*y^4 + 127*y^5) Traceback (most recent call last): ... ValueError: cannot have a root with multiplicity >= 5/2 sage: covariant_z0((x^2+2*y^2)^2) Traceback (most recent call last): ... ValueError: must have at least 3 distinct roots """ R = F.parent() d = ZZ(F.degree()) if R.ngens() != 2 or any(sum(t) != d for t in F.exponents()): raise TypeError('must be a binary form') if d < 3: raise ValueError('must be at least degree 3') f = F.subs({R.gen(1): 1}).univariate_polynomial() if f.degree() < d: # we have a root at infinity if f.constant_coefficient() != 0: # invert so we find all roots! mat = matrix(ZZ, 2, 2, [0, -1, 1, 0]) else: t = 0 while f(t) == 0: t += 1 mat = matrix(ZZ, 2, 2, [t, -1, 1, 0]) else: mat = matrix(ZZ, 2, 2, [1, 0, 0, 1]) f = F(list(mat * vector(R.gens()))).subs({R.gen(1): 1}).univariate_polynomial() # now we have a single variable polynomial with all the roots of F K = ComplexField(prec=prec) if f.base_ring() != K: if emb is None: f = f.change_ring(K) else: f = f.change_ring(emb) roots = f.roots() if max(ex for _, ex in roots) > 1 or f.degree() < d - 1: if z0_cov: raise ValueError('cannot have multiple roots for z0 invariant') else: # just need a starting point for Newton's method f = f.lc() * prod(p for p, ex in f.factor()) # removes multiple roots if f.degree() < 3: raise ValueError('must have at least 3 distinct roots') roots = f.roots() roots = [p for p, _ in roots] # finding quadratic Q_0, gives us our covariant, z_0 dF = f.derivative() n = ZZ(f.degree()) PR = PolynomialRing(K, 'x,y') x, y = PR.gens() # finds Stoll and Cremona's Q_0 q = sum([(1/(dF(r).abs()**(2/(n-2)))) * ((x-(r*y)) * (x-(r.conjugate()*y))) for r in roots]) # this is Q_0 , always positive def as long as F has distinct roots A = q.monomial_coefficient(x**2) B = q.monomial_coefficient(x * y) C = q.monomial_coefficient(y**2) # need positive root try: z = ((-B + ((B**2)-(4*A*C)).sqrt()) / (2 * A)) except ValueError: raise ValueError("not enough precision") if z.imag() < 0: z = (-B - ((B**2)-(4*A*C)).sqrt()) / (2 * A) if z0_cov: FM = f # for Julia's invariant else: # solve the minimization problem for 'true' covariant CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error z = CF(z) FM = F(list(mat * vector(R.gens()))).subs({R.gen(1): 1}).univariate_polynomial() from sage.rings.polynomial.complex_roots import complex_roots L1 = complex_roots(FM, min_prec=prec) L = [] # making sure multiplicity isn't too large using convergence conditions in paper for p, e in L1: if e >= d / 2: raise ValueError('cannot have a root with multiplicity >= %s/2' % d) for _ in range(e): L.append(p) RCF = PolynomialRing(CF, 'u,t') a = RCF.zero() c = RCF.zero() u, t = RCF.gens() for l in L: denom = ((t - l) * (t - l.conjugate()) + u**2) a += u**2 / denom c += (t - l.real()) / denom # Newton's Method, to find solutions. Error bound is less than diameter of our z err = z.diameter() zz = z.diameter() g1 = a.numerator() - d / 2 * a.denominator() g2 = c.numerator() G = vector([g1, g2]) J = jacobian(G, [u, t]) v0 = vector([z.imag(), z.real()]) # z0 as starting point # finds our correct z while err <= zz: NJ = J.subs({u: v0[0], t: v0[1]}) NJinv = NJ.inverse() # inverse for CIF matrix seems to return fractions not CIF elements, fix them if NJinv.base_ring() != CF: NJinv = matrix(CF, 2, 2, [CF(zw.numerator() / zw.denominator()) for zw in NJinv.list()]) w = z v0 = v0 - NJinv*G.subs({u: v0[0], t: v0[1]}) z = v0[1].constant_coefficient() + v0[0].constant_coefficient()*CF.gen(0) err = z.diameter() # precision zz = (w - z).abs().lower() # difference in w and z else: # despite there is no break, this happens if err > error_limit or err.is_NaN(): raise ValueError("accuracy of Newton's root not within tolerance(%s > %s), increase precision" % (err, error_limit)) if z.imag().upper() <= z.diameter(): raise ArithmeticError("Newton's method converged to z not in the upper half plane") z = z.center() # Julia's invariant if FM.base_ring() != ComplexField(prec=prec): FM = FM.change_ring(ComplexField(prec=prec)) tF = z.real() uF = z.imag() th = FM.lc().abs()**2 for r, ex in FM.roots(): for _ in range(ex): th = th * ((((r-tF).abs())**2 + uF**2)/uF) # undo shift and invert (if needed) # since F \cdot m ~ m^(-1)\cdot z # we apply m to z to undo m acting on F l = mat * vector([z, 1]) return l[0] / l[1], th
def rand_pham_divisor(n,var="z"): poly_ring = PolynomialRing(QQ,n,var) div = poly_ring.zero() for g in poly_ring.gens(): div += g**(randrange(2,9)) return div
def ehrhart_polynomial(self, engine=None, variable='t', verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. The polyhedron must be a lattice polytope. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP\cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. The Ehrhart polynomial may be computed using either LattE Integrale or Normaliz by setting ``engine`` to 'latte' or 'normaliz' respectively. INPUT: - ``engine`` -- string; The backend to use. Allowed values are: * ``None`` (default); When no input is given the Ehrhart polynomial is computed using LattE Integrale (optional) * ``'latte'``; use LattE integrale program (optional) * ``'normaliz'``; use Normaliz program (optional package pynormaliz). The backend of ``self`` must be set to 'normaliz'. - ``variable`` -- string (default: 't'); The variable in which the Ehrhart polynomial should be expressed. - When the ``engine`` is 'latte', the additional input values are: * ``verbose`` - boolean (default: ``False``); If ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: * ``dual`` - boolean; triangulate and signed-decompose in the dual space * ``irrational_primal`` - boolean; triangulate in the dual space, signed-decompose in the primal space using irrationalization. * ``irrational_all_primal`` - boolean; triangulate and signed-decompose in the primal space using irrationalization. * ``maxdet`` -- integer; decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). * ``no_decomposition`` -- boolean; do not signed-decompose simplicial cones. * ``compute_vertex_cones`` -- string; either 'cdd' or 'lrs' or '4ti2' * ``smith_form`` -- string; either 'ilio' or 'lidia' * ``dualization`` -- string; either 'cdd' or '4ti2' * ``triangulation`` - string; 'cddlib', '4ti2' or 'topcom' * ``triangulation_max_height`` - integer; use a uniform distribution of height from 1 to this number OUTPUT: A univariate polynomial in ``variable`` over a rational field. .. SEEALSO:: :mod:`~sage.interfaces.latte` the interface to LattE Integrale `PyNormaliz <https://pypi.org/project/PyNormaliz>`_ EXAMPLES: To start, we find the Ehrhart polynomial of a three-dimensional ``simplex``, first using ``engine='latte'``. Leaving the engine unspecified sets the ``engine`` to 'latte' by default:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: simplex = simplex.change_ring(QQ) sage: poly = simplex.ehrhart_polynomial(engine='latte') # optional - latte_int sage: poly # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: poly(1) # optional - latte_int 6 sage: len(simplex.integral_points()) # optional - latte_int 6 sage: poly(2) # optional - latte_int 36 sage: len((2*simplex).integral_points()) # optional - latte_int 36 Now we find the same Ehrhart polynomial, this time using ``engine='normaliz'``. To use the Normaliz engine, the ``simplex`` must be defined with ``backend='normaliz'``:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)], backend='normaliz') # optional - pynormaliz sage: simplex = simplex.change_ring(QQ) # optional - pynormaliz sage: poly = simplex.ehrhart_polynomial(engine = 'normaliz') # optional - pynormaliz sage: poly # optional - pynormaliz 7/2*t^3 + 2*t^2 - 1/2*t + 1 If the ``engine='normaliz'``, the backend should be ``'normaliz'``, otherwise it returns an error:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: simplex = simplex.change_ring(QQ) sage: simplex.ehrhart_polynomial(engine='normaliz') # optional - pynormaliz Traceback (most recent call last): ... TypeError: The backend of the polyhedron should be 'normaliz' The polyhedron should be compact:: sage: C = Polyhedron(backend='normaliz',rays=[[1,2],[2,1]]) # optional - pynormaliz sage: C = C.change_ring(QQ) # optional - pynormaliz sage: C.ehrhart_polynomial() # optional - pynormaliz Traceback (most recent call last): ... ValueError: Ehrhart polynomial only defined for compact polyhedra The polyhedron should have integral vertices:: sage: L = Polyhedron(vertices = [[0],[1/2]]) sage: L.ehrhart_polynomial() Traceback (most recent call last): ... TypeError: the polytope has nonintegral vertices, use ehrhart_quasipolynomial with backend 'normaliz' TESTS: The cache of the Ehrhart polynomial is being pickled:: sage: P = polytopes.cube().change_ring(QQ) # optional - latte_int sage: P.ehrhart_polynomial() # optional - latte_int 8*t^3 + 12*t^2 + 6*t + 1 sage: Q = loads(dumps(P)) # optional - latte_int sage: Q.ehrhart_polynomial.is_in_cache() # optional - latte_int True """ # check if ``self`` is compact and has vertices in ZZ if self.is_empty(): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, variable) return R.zero() if not self.is_compact(): raise ValueError("Ehrhart polynomial only defined for compact polyhedra") if any(not v.is_integral() for v in self.vertex_generator()): raise TypeError("the polytope has nonintegral vertices, use ehrhart_quasipolynomial with backend 'normaliz'") # Passes to specific latte or normaliz subfunction depending on engine if engine is None: # set default engine to latte engine = 'latte' if engine == 'latte': poly = self._ehrhart_polynomial_latte(verbose, dual, irrational_primal, irrational_all_primal, maxdet, no_decomposition, compute_vertex_cones, smith_form, dualization, triangulation, triangulation_max_height, **kwds) return poly.change_variable_name(variable) # TO DO: replace this change of variable by creating the appropriate # polynomial ring in the latte interface. elif engine == 'normaliz': return self._ehrhart_polynomial_normaliz(variable) else: raise ValueError("engine must be 'latte' or 'normaliz'")
def demazure_character(self, w, f = None): r""" Returns the Demazure character associated to ``w``. INPUT: - ``w`` -- an element of the ambient weight lattice realization of the crystal, or a reduced word, or an element in the associated Weyl group OPTIONAL: - ``f`` -- a function from the crystal to a module This is currently only supported for crystals whose underlying weight space is the ambient space. The Demazure character is obtained by applying the Demazure operator `D_w` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ParentMethods.demazure_operator`) to the highest weight element of the classical crystal. The simple Demazure operators `D_i` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ElementMethods.demazure_operator_simple`) do not braid on the level of crystals, but on the level of characters they do. That is why it makes sense to input ``w`` either as a weight, a reduced word, or as an element of the underlying Weyl group. EXAMPLES:: sage: T = crystals.Tableaux(['A',2], shape = [2,1]) sage: e = T.weight_lattice_realization().basis() sage: weight = e[0] + 2*e[2] sage: weight.reduced_word() [2, 1] sage: T.demazure_character(weight) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x1*x3^2 sage: T = crystals.Tableaux(['A',3],shape=[2,1]) sage: T.demazure_character([1,2,3]) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3 sage: W = WeylGroup(['A',3]) sage: w = W.from_reduced_word([1,2,3]) sage: T.demazure_character(w) x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3 sage: T = crystals.Tableaux(['B',2], shape = [2]) sage: e = T.weight_lattice_realization().basis() sage: weight = -2*e[1] sage: T.demazure_character(weight) x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1 sage: T = crystals.Tableaux("B2",shape=[1/2,1/2]) sage: b2=WeylCharacterRing("B2",base_ring=QQ).ambient() sage: T.demazure_character([1,2],f=lambda x:b2(x.weight())) b2(-1/2,1/2) + b2(1/2,-1/2) + b2(1/2,1/2) REFERENCES: - [De1974]_ - [Ma2009]_ """ from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing if hasattr(w, 'reduced_word'): word = w.reduced_word() else: word = w n = self.weight_lattice_realization().n u = self.algebra(ZZ).sum_of_monomials(self.module_generators) u = self.demazure_operator(u, word) if f is None: x = ['x%s'%i for i in range(1,n+1)] P = PolynomialRing(ZZ, x) # TODO: use P.linear_combination when PolynomialRing will be a ModulesWithBasis return sum((coeff*prod((x[i]**(c.weight()[i]) for i in range(n)), P.one()) for c, coeff in u), P.zero()) else: return sum((coeff*f(c)) for c, coeff in u)
def has_irred_rep(self, n, gen_set=None, restrict=None, force=False): """ Returns `True` if there exists an `n`-dimensional irreducible representation of `self`, and `False` otherwise. Of course, this function runs `has_rep(n, restrict)` to verify there is a representation in the first place, and returns `False` if not. The argument `restrict` may be used equivalenty to its use in `has_rep()`. The argument `gen_set` may be set to `'PBW'` or `'pbw'`, if `self` has an algebra basis similar to that of a Poincaré-Birkhoff-Witt basis. Alternatively, an explicit generating set for the algorithm implemented by this function can be given, as a tuple or array of `FreeAlgebraElements`. This is only useful if the package cannot reduce the elements of `self`, but they can be reduced in theory. Use `force=True` if the function does not recognize the base field as computable, but the field is computable. """ if (not force and self.base_field() not in NumberFields and self.base_field() not in FiniteFields): raise TypeError( 'Base field must be computable. If %s is computable' % self.base_field() + ' then use force=True to bypass this.') if n not in ZZ or n < 1: raise ValueError('Dimension must be a positive integer.') if not self.has_rep(n, restrict): return False from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.groups.all import SymmetricGroup import math B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1), 'x', order='deglex') M = MatrixSpace(B, n, sparse=True) gen_matrix = list() if not isinstance(restrict, (tuple, list)): restrict = [None for i in range(self.ngens())] if len(restrict) != self.ngens(): raise ValueError( 'Length of restrict does not match number of generators.') for i in range(self.ngens()): ith_gen_matrix = [] for j in range(n): for k in range(n): if restrict[i] == 'upper' and j > k: ith_gen_matrix.append(B.zero()) elif restrict[i] == 'lower' and j < k: ith_gen_matrix.append(B.zero()) elif restrict[i] == 'diagonal' and j != k: ith_gen_matrix.append(B.zero()) else: ith_gen_matrix.append(B.gen(j + (j + 1) * k + i * n**2)) gen_matrix.append(M(ith_gen_matrix)) relB = list() for i in range(self.nrels()): relB += self._to_matrix(self.rel(i), M, gen_matrix).list() Z = FreeAlgebra(ZZ, 2 * n - 2, 'Y') standard_poly = Z(0) for s in SymmetricGroup(2 * n - 2).list(): standard_poly += s.sign() * reduce( lambda x, y: x * y, [Z('Y%s' % (i - 1)) for i in s.tuple()]) if n <= 6 and is_NumberField(self.base_field()): p = 2 * n else: p = int( math.floor(n * math.sqrt(2 * n**2 / float(n - 1) + 1 / float(4)) + n / float(2) - 3)) if isinstance(gen_set, (tuple, list)): try: gen_set = [ self._to_matrix(elt, M, gen_matrix) for elt in gen_set ] except (NameError, TypeError) as error: print(error) if gen_set == None: word_gen_set = list(self._create_rep_gen_set(n, p)) gen_set = [ self._to_matrix(_to_element(self, [[word, self.one()]]), M, gen_matrix) for word in word_gen_set ] elif gen_set == 'pbw' or gen_set == 'PBW': word_gen_set = list(self._create_pbw_rep_gen_set(n, p)) gen_set = [ self._to_matrix(_to_element(self, [[word, self.one()]]), M, gen_matrix) for word in word_gen_set ] else: raise TypeError('Invalid generating set.') ordering = [i for i in range(2 * n - 2)] max_ordering = [ len(gen_set) - (2 * n - 2) + i for i in range(2 * n - 2) ] ordering.insert(0, 0) max_ordering.insert(0, len(gen_set)) rep_exists = False z = B.gen(B.ngens() - 1) while ordering[0] != max_ordering[0]: y = gen_set[ordering[0]].trace_of_product( standard_poly.subs({ Z('Y%s' % (j - 1)): gen_set[ordering[j]] for j in range(1, 2 * n - 1) })) radB_test = relB + [B(1) - z * y] if B.one() not in B.ideal(radB_test): rep_exists = True break for i in range(2 * n - 2, -1, -1): if i != 0 and ordering[i] != max_ordering[i]: ordering[i] += 1 break elif i == 0: ordering[i] += 1 if ordering[i] != max_ordering[i]: for j in range(1, 2 * n - 1): ordering[j] = j - 1 return rep_exists
def ehrhart_polynomial(self, engine=None, variable='t', verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. The Ehrhart polynomial may be computed using either LattE Integrale or Normaliz by setting ``engine`` to 'latte' or 'normaliz' respectively. INPUT: - ``engine`` -- string; The backend to use. Allowed values are: * ``None`` (default); When no input is given the Ehrhart polynomial is computed using LattE Integrale (optional) * ``'latte'``; use LattE integrale program (optional) * ``'normaliz'``; use Normaliz program (optional). The backend of ``self`` must be set to 'normaliz'. - ``variable`` -- string (default: 't'); The variable in which the Ehrhart polynomial should be expressed. - When the ``engine`` is 'latte' or None, the additional input values are: * ``verbose`` - boolean (default: ``False``); if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: * ``dual`` - boolean; triangulate and signed-decompose in the dual space * ``irrational_primal`` - boolean; triangulate in the dual space, signed-decompose in the primal space using irrationalization. * ``irrational_all_primal`` - boolean; Triangulate and signed-decompose in the primal space using irrationalization. * ``maxdet`` -- integer; decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). * ``no_decomposition`` -- boolean; do not signed-decompose simplicial cones. * ``compute_vertex_cones`` -- string; either 'cdd' or 'lrs' or '4ti2' * ``smith_form`` -- string; either 'ilio' or 'lidia' * ``dualization`` -- string; either 'cdd' or '4ti2' * ``triangulation`` - string; 'cddlib', '4ti2' or 'topcom' * ``triangulation_max_height`` - integer; use a uniform distribution of height from 1 to this number OUTPUT: The Ehrhart polynomial as a a univariate polynomial in ``variable`` over a rational field. .. SEEALSO:: :mod:`~sage.interfaces.latte` the interface to LattE Integrale `PyNormaliz <https://pypi.python.org/pypi/PyNormaliz/1.5>`_ EXAMPLES: To start, we find the Ehrhart polynomial of a three-dimensional ``simplex``, first using ``engine='latte'``. Leaving the engine unspecified sets the ``engine`` to 'latte' by default:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: poly = simplex.ehrhart_polynomial(engine = 'latte') # optional - latte_int sage: poly # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: poly(1) # optional - latte_int 6 sage: len(simplex.integral_points()) # optional - latte_int 6 sage: poly(2) # optional - latte_int 36 sage: len((2*simplex).integral_points()) # optional - latte_int 36 Now we find the same Ehrhart polynomial, this time using ``engine='normaliz'``. To use the Normaliz engine, the ``simplex`` must be defined with ``backend='normaliz'``:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)], backend='normaliz') # optional - pynormaliz sage: poly = simplex.ehrhart_polynomial(engine='normaliz') # optional - pynormaliz sage: poly # optional - pynormaliz 7/2*t^3 + 2*t^2 - 1/2*t + 1 If the ``engine='normaliz'``, the backend should be ``'normaliz'``, otherwise it returns an error:: sage: simplex = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: simplex.ehrhart_polynomial(engine='normaliz') # optional - pynormaliz Traceback (most recent call last): ... TypeError: The polyhedron's backend should be 'normaliz' Now we find the Ehrhart polynomials of the unit hypercubes of dimensions three through six. They are computed first with ``engine='latte'`` and then with ``engine='normaliz'``. The degree of the Ehrhart polynomial matches the dimension of the hypercube, and the coefficient of the leading monomial equals the volume of the unit hypercube:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d)),backend='normaliz') # optional - pynormaliz sage: hypercube(3).ehrhart_polynomial(engine='normaliz') # optional - pynormaliz t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial(engine='normaliz') # optional - pynormaliz t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial(engine='normaliz') # optional - pynormaliz t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial(engine='normaliz') # optional - pynormaliz t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: p = Polyhedron(ambient_dim=3, vertices=[]) sage: p.ehrhart_polynomial() 0 sage: parent(_) Univariate Polynomial Ring in t over Rational Field The polyhedron should be compact:: sage: C = Polyhedron(rays=[[1,2],[2,1]]) sage: C.ehrhart_polynomial() Traceback (most recent call last): ... ValueError: Ehrhart polynomial only defined for compact polyhedra """ if self.is_empty(): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, variable) return R.zero() if not self.is_compact(): raise ValueError( "Ehrhart polynomial only defined for compact polyhedra") if engine is None: # setting the default to 'latte' engine = 'latte' if engine == 'latte': poly = self._ehrhart_polynomial_latte( verbose, dual, irrational_primal, irrational_all_primal, maxdet, no_decomposition, compute_vertex_cones, smith_form, dualization, triangulation, triangulation_max_height, **kwds) return poly.change_variable_name(variable) # TO DO: replace this change of variable by creating the appropriate # polynomial ring in the latte interface. elif engine == 'normaliz': return self._ehrhart_polynomial_normaliz(variable) else: raise ValueError("engine must be 'latte' or 'normaliz'")
def ehrhart_polynomial(self, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. INPUT: - ``verbose`` - (boolean, default to ``False``) if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details you should consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: - ``dual`` - (boolean) triangulate and signed-decompose in the dual space - ``irrational_primal`` - (boolean) triangulate in the dual space, signed-decompose in the primal space using irrationalization. - ``irrational_all_primal`` - (boolean) Triangulate and signed-decompose in the primal space using irrationalization. - ``maxdet`` -- (integer) decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). - ``no_decomposition`` -- (boolean) do not signed-decompose simplicial cones. - ``compute_vertex_cones`` -- (string) either 'cdd' or 'lrs' or '4ti2' - ``smith_form`` -- (string) either 'ilio' or 'lidia' - ``dualization`` -- (string) either 'cdd' or '4ti2' - ``triangulation`` - (string) 'cddlib', '4ti2' or 'topcom' - ``triangulation_max_height`` - (integer) use a uniform distribution of height from 1 to this number .. NOTE:: Any additional argument is forwarded to LattE's executable ``count``. All occurrences of '_' will be replaced with a '-'. ALGORITHM: This method calls the program ``count`` from LattE integrale, a program for lattice point enumeration (see https://www.math.ucdavis.edu/~latte/). .. SEEALSO:: :mod:`~sage.interfaces.latte` the interface to LattE integrale EXAMPLES:: sage: P = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: p = P.ehrhart_polynomial() # optional - latte_int sage: p # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: p(1) # optional - latte_int 6 sage: len(P.integral_points()) 6 sage: p(2) # optional - latte_int 36 sage: len((2*P).integral_points()) 36 The unit hypercubes:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: P = Polyhedron(ambient_dim=3, vertices=[]) sage: P.ehrhart_polynomial() # optional - latte_int 0 sage: parent(_) # optional - latte_int Univariate Polynomial Ring in t over Rational Field TESTS: Test options:: sage: P = Polyhedron(ieqs=[[1,-1,1,0], [-1,2,-1,0], [1,1,-2,0]], eqns=[[-1,2,-1,-3]], base_ring=ZZ) sage: p = P.ehrhart_polynomial(maxdet=5, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd '--maxdet=5' /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(dual=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --dual /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --irrational-primal /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_all_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd --irrational-all-primal /dev/stdin ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 Test bad options:: sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale program failed (exit code 1): ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --cdd '--bim-bam-boum=19' /dev/stdin Unknown command/option --bim-bam-boum=19 """ if self.is_empty(): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, 't') return R.zero() # note: the options below are explicitely written in the function # declaration in order to keep tab completion (see #18211). kwds.update({ 'dual' : dual, 'irrational_primal' : irrational_primal, 'irrational_all_primal' : irrational_all_primal, 'maxdet' : maxdet, 'no_decomposition' : no_decomposition, 'compute_vertex_cones' : compute_vertex_cones, 'smith_form' : smith_form, 'dualization' : dualization, 'triangulation' : triangulation, 'triangulation_max_height': triangulation_max_height}) from sage.interfaces.latte import count ine = self.cdd_Hrepresentation() return count(ine, cdd=True, ehrhart_polynomial=True, verbose=verbose, **kwds)
def permanental_minor_polynomial(A, permanent_only=False, var='t', prec=None): r""" Return the polynomial of the sums of permanental minors of ``A``. INPUT: - `A` -- a matrix - `permanent_only` -- if True, return only the permanent of `A` - `var` -- name of the polynomial variable - `prec` -- if prec is not None, truncate the polynomial at precision `prec` The polynomial of the sums of permanental minors is .. MATH:: \sum_{i=0}^{min(nrows, ncols)} p_i(A) x^i where `p_i(A)` is the `i`-th permanental minor of `A` (that can also be obtained through the method :meth:`~sage.matrix.matrix2.Matrix.permanental_minor` via ``A.permanental_minor(i)``). The algorithm implemented by that function has been developed by P. Butera and M. Pernici, see [ButPer]. Its complexity is `O(2^n m^2 n)` where `m` and `n` are the number of rows and columns of `A`. Moreover, if `A` is a banded matrix with width `w`, that is `A_{ij}=0` for `|i - j| > w` and `w < n/2`, then the complexity of the algorithm is `O(4^w (w+1) n^2)`. INPUT: - ``A`` -- matrix - ``permanent_only`` -- optional boolean. If ``True``, only the permanent is computed (might be faster). - ``var`` -- a variable name EXAMPLES:: sage: from sage.matrix.matrix_misc import permanental_minor_polynomial sage: m = matrix([[1,1],[1,2]]) sage: permanental_minor_polynomial(m) 3*t^2 + 5*t + 1 sage: permanental_minor_polynomial(m, permanent_only=True) 3 sage: permanental_minor_polynomial(m, prec=2) 5*t + 1 :: sage: M = MatrixSpace(ZZ,4,4) sage: A = M([1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1]) sage: permanental_minor_polynomial(A) 84*t^3 + 114*t^2 + 28*t + 1 sage: [A.permanental_minor(i) for i in range(5)] [1, 28, 114, 84, 0] An example over `\QQ`:: sage: M = MatrixSpace(QQ,2,2) sage: A = M([1/5,2/7,3/2,4/5]) sage: permanental_minor_polynomial(A, True) 103/175 An example with polynomial coefficients:: sage: R.<a> = PolynomialRing(ZZ) sage: A = MatrixSpace(R,2)([[a,1], [a,a+1]]) sage: permanental_minor_polynomial(A, True) a^2 + 2*a A usage of the ``var`` argument:: sage: m = matrix(ZZ,4,[0,1,2,3,1,2,3,0,2,3,0,1,3,0,1,2]) sage: permanental_minor_polynomial(m, var='x') 164*x^4 + 384*x^3 + 172*x^2 + 24*x + 1 ALGORITHM: The permanent `perm(A)` of a `n \times n` matrix `A` is the coefficient of the `x_1 x_2 \ldots x_n` monomial in .. MATH:: \prod_{i=1}^n \left( \sum_{j=1}^n A_{ij} x_j \right) Evaluating this product one can neglect `x_i^2`, that is `x_i` can be considered to be nilpotent of order `2`. To formalize this procedure, consider the algebra `R = K[\eta_1, \eta_2, \ldots, \eta_n]` where the `\eta_i` are commuting, nilpotent of order `2` (i.e. `\eta_i^2 = 0`). Formally it is the quotient ring of the polynomial ring in `\eta_1, \eta_2, \ldots, \eta_n` quotiented by the ideal generated by the `\eta_i^2`. We will mostly consider the ring `R[t]` of polynomials over `R`. We denote a generic element of `R[t]` by `p(\eta_1, \ldots, \eta_n)` or `p(\eta_{i_1}, \ldots, \eta_{i_k})` if we want to emphasize that some monomials in the `\eta_i` are missing. Introduce an "integration" operation `\langle p \rangle` over `R` and `R[t]` consisting in the sum of the coefficients of the non-vanishing monomials in `\eta_i` (i.e. the result of setting all variables `\eta_i` to `1`). Let us emphasize that this is *not* a morphism of algebras as `\langle \eta_1 \rangle^2 = 1` while `\langle \eta_1^2 \rangle = 0`! Let us consider an example of computation. Let `p_1 = 1 + t \eta_1 + t \eta_2` and `p_2 = 1 + t \eta_1 + t \eta_3`. Then .. MATH:: p_1 p_2 = 1 + 2t \eta_1 + t (\eta_2 + \eta_3) + t^2 (\eta_1 \eta_2 + \eta_1 \eta_3 + \eta_2 \eta_3) and .. MATH:: \langle p_1 p_2 \rangle = 1 + 4t + 3t^2 In this formalism, the permanent is just .. MATH:: perm(A) = \langle \prod_{i=1}^n \sum_{j=1}^n A_{ij} \eta_j \rangle A useful property of `\langle . \rangle` which makes this algorithm efficient for band matrices is the following: let `p_1(\eta_1, \ldots, \eta_n)` and `p_2(\eta_j, \ldots, \eta_n)` be polynomials in `R[t]` where `j \ge 1`. Then one has .. MATH:: \langle p_1(\eta_1, \ldots, \eta_n) p_2 \rangle = \langle p_1(1, \ldots, 1, \eta_j, \ldots, \eta_n) p_2 \rangle where `\eta_1,..,\eta_{j-1}` are replaced by `1` in `p_1`. Informally, we can "integrate" these variables *before* performing the product. More generally, if a monomial `\eta_i` is missing in one of the terms of a product of two terms, then it can be integrated in the other term. Now let us consider an `m \times n` matrix with `m \leq n`. The *sum of permanental `k`-minors of `A`* is .. MATH:: perm(A, k) = \sum_{r,c} perm(A_{r,c}) where the sum is over the `k`-subsets `r` of rows and `k`-subsets `c` of columns and `A_{r,c}` is the submatrix obtained from `A` by keeping only the rows `r` and columns `c`. Of course `perm(A, \min(m,n)) = perm(A)` and note that `perm(A,1)` is just the sum of all entries of the matrix. The generating function of these sums of permanental minors is .. MATH:: g(t) = \left\langle \prod_{i=1}^m \left(1 + t \sum_{j=1}^n A_{ij} \eta_j\right) \right\rangle In fact the `t^k` coefficient of `g(t)` corresponds to choosing `k` rows of `A`; `\eta_i` is associated to the i-th column; nilpotency avoids having twice the same column in a product of `A`'s. For more details, see the article [ButPer]. From a technical point of view, the product in `K[\eta_1, \ldots, \eta_n][t]` is implemented as a subroutine in :func:`prm_mul`. The indices of the rows and columns actually start at `0`, so the variables are `\eta_0, \ldots, \eta_{n-1}`. Polynomials are represented in dictionary form: to a variable `\eta_i` is associated the key `2^i` (or in Python ``1 << i``). The keys associated to products are obtained by considering the development in base `2`: to the monomial `\eta_{i_1} \ldots \eta_{i_k}` is associated the key `2^{i_1} + \ldots + 2^{i_k}`. So the product `\eta_1 \eta_2` corresponds to the key `6 = (110)_2` while `\eta_0 \eta_3` has key `9 = (1001)_2`. In particular all operations on monomials are implemented via bitwise operations on the keys. REFERENCES: .. [ButPer] \P. Butera and M. Pernici "Sums of permanental minors using Grassmann algebra", :arxiv:`1406.5337` """ if permanent_only: prec = None elif prec is not None: prec = int(prec) if prec == 0: raise ValueError('the argument `prec` must be a positive integer') K = PolynomialRing(A.base_ring(), var) nrows = A.nrows() ncols = A.ncols() A = A.rows() p = {0: K.one()} t = K.gen() vars_to_do = range(ncols) for i in range(nrows): # build the polynomial p1 = 1 + t sum A_{ij} eta_j if permanent_only: p1 = {} else: p1 = {0: K.one()} a = A[i] # the i-th row of A for j in range(len(a)): if a[j]: p1[1 << j] = a[j] * t # make the product with the preceding polynomials, taking care of # variables that can be integrated mask_free = 0 j = 0 while j < len(vars_to_do): jj = vars_to_do[j] if all(A[k][jj] == 0 for k in range(i + 1, nrows)): mask_free += 1 << jj vars_to_do.remove(jj) else: j += 1 p = prm_mul(p, p1, mask_free, prec) if not p: return K.zero() if len(p) != 1 or 0 not in p: raise RuntimeError( "Something is wrong! Certainly a problem in the" " algorithm... please contact [email protected]") p = p[0] return p[min(nrows, ncols)] if permanent_only else p
def CyclicSievingPolynomial(L, cyc_act=None, order=None, get_order=False): """ Return the unique polynomial ``p`` of degree smaller than ``order`` such that the triple ``(L, cyc_act, p)`` exhibits the Cyclic Sieving Phenomenon. If ``cyc_act`` is None, ``L`` is expected to contain the orbit lengths. INPUT: - ``L`` -- if ``cyc_act`` is ``None``: list of orbit sizes, otherwise list of objects - ``cyc_act`` -- (default:``None``) bijective function from ``L`` to ``L`` - ``order`` -- (default:``None``) if set to an integer, this cyclic order of ``cyc_act`` is used (must be an integer multiple of the order of ``cyc_act``) otherwise, the order of ``cyc_action`` is used - ``get_order`` -- (default:``False``) if ``True``, a tuple ``[p,n]`` is returned where ``p`` is as above, and ``n`` is the order EXAMPLES:: sage: from sage.combinat.cyclic_sieving_phenomenon import CyclicSievingPolynomial sage: S42 = Subsets([1,2,3,4], 2) sage: def cyc_act(S): return Set(i.mod(4) + 1 for i in S) sage: cyc_act([1,3]) {2, 4} sage: cyc_act([1,4]) {1, 2} sage: CyclicSievingPolynomial(S42, cyc_act) q^3 + 2*q^2 + q + 2 sage: CyclicSievingPolynomial(S42, cyc_act, get_order=True) [q^3 + 2*q^2 + q + 2, 4] sage: CyclicSievingPolynomial(S42, cyc_act, order=8) q^6 + 2*q^4 + q^2 + 2 sage: CyclicSievingPolynomial([4,2]) q^3 + 2*q^2 + q + 2 TESTS: We check that :trac:`13997` is handled:: sage: CyclicSievingPolynomial(S42, cyc_act, order=8, get_order=True) [q^6 + 2*q^4 + q^2 + 2, 8] sage: CyclicSievingPolynomial(S42, cyc_act, order=11) Traceback (most recent call last): ... ValueError: order is not a multiple of the order of the cyclic action """ if cyc_act: orbits = orbit_decomposition(L, cyc_act) else: orbits = [list(range(k)) for k in L] R = PolynomialRing(ZZ, 'q') q = R.gen() p = R.zero() orbit_sizes = {} for orbit in orbits: length = len(orbit) if length in orbit_sizes: orbit_sizes[length] += 1 else: orbit_sizes[length] = 1 n = lcm(list(orbit_sizes)) if order: if order.mod(n): raise ValueError("order is not a multiple of the order" " of the cyclic action") else: order = n for i in range(n): if i == 0: j = sum(orbit_sizes.values()) else: j = sum(orbit_sizes[l] for l in orbit_sizes if ZZ(i).mod(n / l) == 0) p += j * q**i p = p(q**(order // n)) if get_order: return [p, order] else: return p
class TestLogartihmicDifferentialForms(unittest.TestCase): def setUp(self): self.poly_ring = PolynomialRing(QQ,"x",3); self.x = self.poly_ring.gens()[0]; self.y = self.poly_ring.gens()[1]; self.z = self.poly_ring.gens()[2]; self.vars = var('x,y,z') def test_p_forms_crossing_ngens(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) self.assertEqual(len(logdf.p_form_generators(0)),1) self.assertEqual(len(logdf.p_form_generators(1)),3) self.assertEqual(len(logdf.p_form_generators(2)),3) self.assertEqual(len(logdf.p_form_generators(3)),1) def test_0_modules_crossing_ngens(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) crossing_0_module = SingularModule([[crossing]]) self.assertTrue(crossing_0_module.equals(logdf.p_module(0))) def test_1_modules_crossing_ngens(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() crossing = x*y*z logdf = LogarithmicDifferentialForms(crossing) crossing_1_module = SingularModule([[y*z,zero,zero],[zero,x*z,zero],[zero,zero,x*y]]) self.assertTrue(crossing_1_module.equals(logdf.p_module(1))) def test_2_modules_crossing_ngens(self): x = self.x y = self.y z = self.z zero = self.poly_ring.zero() crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) crossing_2_module = SingularModule([[z,zero,zero],[zero,y,zero],[zero,zero,x]]) self.assertTrue(crossing_2_module.equals(logdf.p_module(2))) def test_3_modules_crossing_ngens(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) crossing_3_module = SingularModule([[self.poly_ring.one()]]) self.assertTrue(crossing_3_module.equals(logdf.p_module(3))) def test_p_module_n_crossing(self): #Make sure this doesnt throw an error - fix bug for i in range(4,5): p_ring = PolynomialRing(QQ,i,"z") crossing = p_ring.one() for g in p_ring.gens(): crossing *= g logdf = LogarithmicDifferentialForms(crossing) logdf.p_module(i-1) def test_complement_complex_crossing(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) complex = logdf.chain_complex("complement") complex_size = {} for i,c in complex.iteritems(): complex_size[i] = len(c) self.assertEqual(complex_size,{0:1,1:3,2:3,3:1}) def test_complement_complex_whitney(self): whitney = self.x**2*self.y - self.z**2 logdf = LogarithmicDifferentialForms(whitney) complex = logdf.chain_complex("complement") complex_size = {} for i,c in complex.iteritems(): complex_size[i] = len(c) self.assertEqual(complex_size,{0:1,1:1,2:0,3:0}) def test_equi_complex_crossing(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) complex = logdf.chain_complex("equivarient") complex_size = {} for i,c in complex.iteritems(): complex_size[i] = len(c) self.assertEqual(complex_size,{0:1,1:3,2:4,3:4}) def test_equi_complex_whitney(self): whitney = self.x**2*self.y - self.z**2 logdf = LogarithmicDifferentialForms(whitney) complex = logdf.chain_complex("equivarient") complex_size = {} for i,c in complex.iteritems(): complex_size[i] = len(c) self.assertEqual(complex_size,{0:1,1:1,2:1,3:1}) def test_complement_homology_crossing(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) homology = logdf.homology("complement") homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:3,2:3,3:1}) def test_equi_homology_whitney(self): whitney = self.x**2*self.y-self.z**2 logdf = LogarithmicDifferentialForms(whitney) homology = logdf.homology("equivarient") homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:0,2:0,3:0}) def test_relative_complex_0_crossing(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) homology = logdf.chain_complex("relative",None,0) homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:2,2:1,3:0}) def test_relative_complex_0_whitney(self): whitney = self.x**2*self.y-self.z**2 logdf = LogarithmicDifferentialForms(whitney) homology = logdf.chain_complex("relative",None,0) homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:0,2:0,3:0}) def test_relative_homology_0_crossing(self): crossing = self.x*self.y*self.z logdf = LogarithmicDifferentialForms(crossing) homology = logdf.homology("relative",0) homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:2,2:1,3:0}) def test_relative_homology_0_whitney(self): whitney = self.x**2*self.y-self.z**2 logdf = LogarithmicDifferentialForms(whitney) homology = logdf.homology("relative",0) homology_size = {} for i,c in homology.iteritems(): homology_size[i] = len(c) self.assertEqual(homology_size,{0:1,1:0,2:0,3:0})