diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index 97cff1bb835..021a50f262e 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -45,3 +45,4 @@ whereas others have multiple bases. sage/rings/polynomial/polynomial_fateman sage/rings/polynomial/integer_valued_polynomials + sage/rings/polynomial/q_integer_valued_polynomials diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 45f5fbc090f..7af6ae3bf7d 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3214,6 +3214,10 @@ REFERENCES: The Electronic Journal of Combinatorics 11 (2004), #R77. http://www.combinatorics.org/Volume_11/PDF/v11i1r77.pdf +.. [HaHo2017] Nate Harman and Sam Hopkins, + *Quantum integer-valued polynomials*, + J. Alg. Comb. 2017, :doi:`10.1007/s10801-016-0717-3` + .. [Hai1989] M.D. Haiman, *On mixed insertion, symmetry, and shifted Young tableaux*. Journal of Combinatorial Theory, Series A Volume 50, Number 2 (1989), pp. 196-225. @@ -3227,7 +3231,7 @@ REFERENCES: http://www-math.mit.edu/~hajiagha/pp11.ps .. [Han1960] Haim Hanani, - On quadruple systems, + *On quadruple systems*, pages 145--157, vol. 12, Canadian Journal of Mathematics, 1960 diff --git a/src/sage/rings/polynomial/all.py b/src/sage/rings/polynomial/all.py index 853f422bdc7..f2295443420 100644 --- a/src/sage/rings/polynomial/all.py +++ b/src/sage/rings/polynomial/all.py @@ -54,3 +54,5 @@ # Integer-valued Univariate Polynomial Ring lazy_import('sage.rings.polynomial.integer_valued_polynomials', 'IntegerValuedPolynomialRing') +lazy_import('sage.rings.polynomial.q_integer_valued_polynomials', + 'QuantumValuedPolynomialRing') diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index 94a0a0c8b9a..cbd48976335 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -52,6 +52,7 @@ py.install_sources( 'polynomial_singular_interface.py', 'polynomial_zmod_flint.pxd', 'polynomial_zz_pex.pxd', + 'q_integer_valued_polynomials.py', 'real_roots.pxd', 'skew_polynomial_element.pxd', 'skew_polynomial_finite_field.pxd', diff --git a/src/sage/rings/polynomial/q_integer_valued_polynomials.py b/src/sage/rings/polynomial/q_integer_valued_polynomials.py new file mode 100644 index 00000000000..fdcca6d5fff --- /dev/null +++ b/src/sage/rings/polynomial/q_integer_valued_polynomials.py @@ -0,0 +1,1263 @@ +r""" +Quantum-valued polynomial rings + +This provides a `q`-analogue of the :class:`~sage.rings.polynomials.integer_valued_polynomials.IntegerValuedPolynomialRing`. + +AUTHORS: + +- Frédéric Chapoton (2024-03): Initial version +""" +# **************************************************************************** +# Copyright (C) 2024 Frédéric Chapoton +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.arith.misc import binomial +from sage.categories.algebras import Algebras +from sage.categories.realizations import Category_realization_of_parent +from sage.categories.rings import Rings +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.q_analogues import q_binomial, q_int +from sage.data_structures.blas_dict import linear_combination +from sage.matrix.constructor import matrix +from sage.misc.bindable_class import BindableClass +from sage.misc.cachefunc import cached_method +from sage.modules.free_module_element import vector +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.sets.family import Family +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.structure.element import parent +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + + +def q_int_x(n, q=None): + """ + Return the interpolating polynomial of `q`-integers. + + INPUT: + + - ``n`` -- a positive integer + + - ``q`` -- optional variable + + EXAMPLES:: + + sage: from sage.rings.polynomial.q_integer_valued_polynomials import q_int_x + sage: q_int_x(3) + q^2*x + q + 1 + + TESTS:: + + sage: from sage.rings.polynomial.q_integer_valued_polynomials import q_int_x + sage: q_int_x(3, 1) + x + 2 + """ + if q is None: + ring_q = PolynomialRing(ZZ, 'q') + q = ring_q.gen() + else: + ring_q = q.parent() + x = polygen(ring_q, 'x') + return q_int(n - 1, q) + q**(n - 1) * x + + +def q_binomial_x(m, n, q=None): + r""" + Return a `q`-analogue of ``binomial(m + x, n)``. + + When evaluated at the `q`-integer `[k]_q`, this gives + the usual `q`-binomial coefficient `[m + k, n]_q`. + + INPUT: + + - ``m`` and ``n`` -- positive integers + + - ``q`` -- optional variable + + EXAMPLES:: + + sage: from sage.combinat.q_analogues import q_int + sage: from sage.rings.polynomial.q_integer_valued_polynomials import q_binomial_x, q_int_x + sage: q_binomial_x(4,2)(0) == q_binomial(4,2) + True + sage: q_binomial_x(3,2)(1) == q_binomial(4,2) + True + sage: q_binomial_x(3,1) == q_int_x(4) + True + sage: q_binomial_x(2,0).parent() + Univariate Polynomial Ring in x over Fraction Field of + Univariate Polynomial Ring in q over Integer Ring + + TESTS:: + + sage: from sage.rings.polynomial.q_integer_valued_polynomials import q_binomial_x + sage: q_binomial_x(4,2,1) + 1/2*x^2 + 7/2*x + 6 + """ + if q is None: + ring_q = PolynomialRing(ZZ, 'q') + else: + ring_q = q.parent() + ring = PolynomialRing(ring_q.fraction_field(), 'x') + if n == 0: + return ring.one() + return ring.prod(q_int_x(m + 2 - i, q) / q_int(i, q) + for i in range(1, n + 1)) + + +class QuantumValuedPolynomialRing(UniqueRepresentation, Parent): + r""" + The quantum-valued polynomial ring over a base ring. + + Quantum-valued polynomial rings are commutative and associative + algebras, with a basis indexed by nonnegative integers. + + The elements are polynomials in one variable `x` with coefficients in + the field of rational functions in `q`, such that the value at + every nonegative `q`-integer is a polynomial in `q`. + + This algebra is endowed with two bases, named ``B`` or ``Binomial`` + and ``S`` or ``Shifted``. + + INPUT: + + - ``R`` -- commutative ring + + - ``q`` -- optional variable + + There are two possible input formats: + + - If the argument ``q`` is not given, then the ring ``R`` is + taken as a base ring and the ring of Laurent polynomials in `q` + over ``R`` is built and used. + + - If the argument ``q`` is given, then it should belong to the ring ``R`` + and be invertible in this ring. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ).S(); F + Quantum-Valued Polynomial Ring over Rational Field + in the shifted basis + + sage: F.gen() + S[1] + + sage: S = QuantumValuedPolynomialRing(ZZ); S + Quantum-Valued Polynomial Ring over Integer Ring + sage: S.base_ring() + Univariate Laurent Polynomial Ring in q over Integer Ring + + Quantum-valued polynomial rings commute with their base ring:: + + sage: K = QuantumValuedPolynomialRing(QQ).S() + sage: a = K.gen() + sage: c = K.monomial(2) + + Quantum-valued polynomial rings are commutative:: + + sage: c^3 * a == c * a * c * c + True + + We can also manipulate elements in the basis and coerce elements from our + base field:: + + sage: F = QuantumValuedPolynomialRing(QQ).S() + sage: B = F.basis() + sage: B[2] * B[3] + (q^-5+q^-4+q^-3)*S[3] - (q^-6+2*q^-5+3*q^-4+3*q^-3+2*q^-2+q^-1)*S[4] + + (q^-6+q^-5+2*q^-4+2*q^-3+2*q^-2+q^-1+1)*S[5] + sage: 1 - B[2] * B[2] / 2 + S[0] - (1/2*q^-3)*S[2] + (1/2*q^-4+q^-3+q^-2+1/2*q^-1)*S[3] + - (1/2*q^-4+1/2*q^-3+q^-2+1/2*q^-1+1/2)*S[4] + """ + @staticmethod + def __classcall_private__(cls, R, q=None) -> None: + """ + Normalize the input. + + EXAMPLES:: + + sage: q = LaurentPolynomialRing(QQ, 'q').gen() + sage: F1 = QuantumValuedPolynomialRing(QQ) + sage: F2 = QuantumValuedPolynomialRing(q.parent(), q) + sage: F1 is F2 + True + """ + if R not in Rings().Commutative(): + msg = "argument R must be a commutative ring" + raise TypeError(msg) + + if q is None: + laurent = LaurentPolynomialRing(R, 'q') + q = laurent.gen() + else: + laurent = q.parent() + return super().__classcall__(cls, laurent, q) + + def __init__(self, R, q) -> None: + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ); F + Quantum-Valued Polynomial Ring over Rational Field + sage: TestSuite(F).run() + + sage: F = QuantumValuedPolynomialRing(QQ, 1); F + Quantum-Valued Polynomial Ring over Integer Ring + sage: TestSuite(F).run() + + TESTS:: + + sage: QuantumValuedPolynomialRing(24) + Traceback (most recent call last): + ... + TypeError: argument R must be a commutative ring + """ + self._ground_ring = R.base_ring() + self._q = q + cat = Algebras(R).Commutative().WithBasis() + Parent.__init__(self, base=R, category=cat.WithRealizations()) + + _shorthands = ["B", "S"] + + def _repr_(self) -> str: + r""" + Return the string representation. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ) + sage: F # indirect doctest + Quantum-Valued Polynomial Ring over Rational Field + + sage: QuantumValuedPolynomialRing(ZZ) + Quantum-Valued Polynomial Ring over Integer Ring + """ + base = self.base_ring().base_ring() + return f"Quantum-Valued Polynomial Ring over {base}" + + def a_realization(self): + """ + Return a default realization. + + The Shifted realization is chosen. + + EXAMPLES:: + + sage: QuantumValuedPolynomialRing(QQ).a_realization() + Quantum-Valued Polynomial Ring over Rational Field + in the shifted basis + """ + return self.Shifted() + + class Bases(Category_realization_of_parent): + def super_categories(self) -> list: + r""" + Return the super-categories of ``self``. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ); A + Quantum-Valued Polynomial Ring over Rational Field + sage: C = A.Bases(); C + Category of bases of Quantum-Valued Polynomial Ring + over Rational Field + sage: C.super_categories() + [Category of realizations of Quantum-Valued Polynomial Ring + over Rational Field, + Join of Category of algebras with basis + over Univariate Laurent Polynomial Ring in q over Rational Field and + Category of filtered algebras + over Univariate Laurent Polynomial Ring in q over Rational Field and + Category of commutative algebras + over Univariate Laurent Polynomial Ring in q over Rational Field and + Category of realizations of unital magmas] + """ + A = self.base() + category = Algebras(A.base_ring()).Commutative().Filtered() + return [A.Realizations(), + category.Realizations().WithBasis()] + + class ParentMethods: + def ground_ring(self): + """ + Return the ring of coefficients. + + This ring is not supposed to contain the variable `q`. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: A.ground_ring() + Rational Field + """ + return self.realization_of()._ground_ring + + def _repr_(self) -> str: + r""" + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ).S() + sage: F # indirect doctest + Quantum-Valued Polynomial Ring over Rational Field + in the shifted basis + """ + real = self.realization_of() + return f"{real} in the {self._realization_name()} basis" + + @cached_method + def one_basis(self): + r""" + Return the number 0, which index the unit of this algebra. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: A.one_basis() + 0 + sage: A.one() + S[0] + """ + return self.basis().keys()(0) + + def q(self): + """ + Return the variable `q`. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: A.q() + q + """ + return self.realization_of()._q + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: A.degree_on_basis(4) + 4 + """ + return ZZ(m) + + def from_polynomial(self, p): + r""" + Convert a polynomial into the ring of quantum-valued polynomials. + + This raises a :exc:`ValueError` if this is not possible. + + INPUT: + + - ``p`` -- a polynomial in ``x`` with coefficients in ``QQ(q)`` + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S() + sage: S = A.basis() + sage: A.from_polynomial((S[1]).polynomial()) + S[1] + sage: A.from_polynomial((S[2]+2*S[3]).polynomial()) + S[2] + 2*S[3] + + sage: A = QuantumValuedPolynomialRing(ZZ).B() + sage: B = A.basis() + sage: A.from_polynomial((B[1]).polynomial()) + B[1] + sage: A.from_polynomial((B[2]+2*B[3]).polynomial()) + B[2] + 2*B[3] + + TESTS:: + + sage: A = QuantumValuedPolynomialRing(QQ).B() + sage: q = polygen(QQ,'q') + sage: x = polygen(q.parent(),'x') + sage: A.from_polynomial(x**2/(q+1)+1) + Traceback (most recent call last): + ... + ValueError: not a polynomial with integer values : 1/(q + 1) is not a Laurent polynomial + """ + B = self.basis() + poly = self._poly + laurent_polys = self.base_ring() + remain = p.change_variable_name('x') + result = self.zero() + while remain: + N = remain.degree() + base_N = poly(N) + top_coeff = remain.leading_coefficient() / base_N.lc() + denom = top_coeff.denominator() + if denom.is_term(): + numer = top_coeff.numerator() + top_coeff_laurent = laurent_polys(numer) / laurent_polys(denom) + else: + msg = 'not a polynomial with integer values :' + msg += f' {top_coeff} is not a Laurent polynomial' + raise ValueError(msg) + remain += -top_coeff * base_N + result += top_coeff_laurent * B[N] + return result + + def gen(self, i=0): + r""" + Return the generator of the algebra. + + The optional argument is ignored. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: F.gen() + S[1] + """ + return self.basis()[1] + + @cached_method + def algebra_generators(self): + r""" + Return the generators of this algebra. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S(); A + Quantum-Valued Polynomial Ring over Integer Ring + in the shifted basis + sage: A.algebra_generators() + Family (S[1],) + """ + return Family([self.basis()[1]]) + + gens = algebra_generators + + class ElementMethods: + def __call__(self, v): + """ + Return the evaluation at some value ``v``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: f = B**2+4*B+6 + sage: f(1/3) + (q^2 + 18*q + 99)/9 + + sage: F = QuantumValuedPolynomialRing(ZZ).B() + sage: B = F.gen() + sage: f = F.monomial(2)+4*B+6 + sage: f(1/3) + (66*q^2 + 66*q - 2)/(9*q^2 + 9*q) + """ + return self.polynomial()(v) + + def polynomial(self): + """ + Convert to a polynomial in `x`. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: S = F.gen() + sage: (S+1).polynomial() + q*x + 2 + + sage: F = QuantumValuedPolynomialRing(ZZ).B() + sage: B = F.gen() + sage: (B+1).polynomial() + x + 1 + + TESTS:: + + sage: F.zero().polynomial().parent() + Univariate Polynomial Ring in x over Fraction Field + of Univariate Polynomial Ring in q over Integer Ring + """ + ring = self.parent().ground_ring() + fractions = PolynomialRing(ring, 'q').fraction_field() + R = PolynomialRing(fractions, 'x') + p = self.parent()._poly + return R.sum(c * p(i) for i, c in self) + + def shift(self, j=1): + """ + Shift all indices by `j`. + + INPUT: + + - `j` -- integer (default 1) + + In the binomial basis, the shift by 1 corresponds to + a summation operator from `0` to `x`. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).shift() + S[1] + S[2] + """ + A = self.parent() + return A._from_dict({A._indices(i + j): c for i, c in self}) + + def sum_of_coefficients(self): + """ + Return the sum of coefficients. + + In the shifted basis, this is the evaluation at `x=0`. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: B = F.basis() + sage: (B[2]*B[4]).sum_of_coefficients() + 1 + """ + R = self.parent().base_ring() + return R.sum(self._monomial_coefficients.values()) + + class Shifted(CombinatorialFreeModule, BindableClass): + r""" + The quantum-valued polynomial ring in the shifted basis. + + The basis used here is given by `S[i] = \genfrac{[}{]}{0pt}{}{i+x}{i}_q` for `i \in \NN`. + + Assuming `n_1 \leq n_2`, the product of two monomials `S[n_1] \cdot S[n_2]` + is given by the sum + + .. MATH:: + + \sum_{k=0}^{n_1} (-1)^k q^{\binom{k}{2} - n_1 * n_2} \genfrac{[}{]}{0pt}{}{n_1}{k}_q \genfrac{[}{]}{0pt}{}{n_1+n_2-k}{n_1}_q S[n_1 + n_2 - k]. + """ + def __init__(self, A): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ).S(); F + Quantum-Valued Polynomial Ring over Rational Field + in the shifted basis + sage: TestSuite(F).run() # not tested + """ + CombinatorialFreeModule.__init__(self, A.base_ring(), + NonNegativeIntegers(), + category=A.Bases(), + prefix="S", + latex_prefix=r"\mathbb{S}") + + def _an_element_(self): + """ + Return a small element of ``self``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ).S() + sage: F.an_element() + 2*S[0] + 4*S[2] + """ + NonNeg = self.basis().keys() + ring = self.base_ring() + return self.element_class(self, {NonNeg(0): ring(2), + NonNeg(2): ring(4)}) + + def _realization_name(self) -> str: + r""" + TESTS:: + + sage: F = QuantumValuedPolynomialRing(QQ).S() + sage: F._realization_name() + 'shifted' + """ + return "shifted" + + def product_on_basis(self, n1, n2): + r""" + Return the product of basis elements ``n1`` and ``n2``. + + INPUT: + + - ``n1``, ``n2`` -- integers + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: A.product_on_basis(0, 1) + S[1] + """ + i = ZZ(n1) + j = ZZ(n2) + if j < i: + j, i = i, j + q = self.q() + return self._from_dict({i + j - k: (-1)**k + * q_binomial(i, k) + * q_binomial(i + j - k, i) + * q**(binomial(k, 2) - i * j) + for k in range(i + 1)}) + + def _from_binomial_basis(self, i): + """ + Convert from the ``binomial(x,k)`` basis. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: S = QuantumValuedPolynomialRing(ZZ).S() + sage: B = QuantumValuedPolynomialRing(ZZ).B() + sage: b = B.basis() + sage: S(b[3]+1) # indirect doctest + -(q^-6-1)*S[0] + (q^-8+q^-7+q^-6)*S[1] + - (q^-9+q^-8+q^-7)*S[2] + (q^-9)*S[3] + sage: B(_) + B[0] + B[3] + """ + i = ZZ(i) + R = self.base_ring() + q = self.q() + return self._from_dict({k: R((-1)**(i - k) * q_binomial(i, k)) + * q**(-i**2 + binomial(i - k, 2)) + for k in range(i + 1)}) + + def from_h_vector(self, hv): + """ + Convert from some `h`-vector. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S() + sage: B = A.basis() + sage: ex = B[2] + B[3] + sage: A.from_h_vector(ex.h_vector()) + S[2] + S[3] + + sage: q = A.base_ring().gen() + sage: ex = B[2] + q*B[3] + sage: A.from_h_vector(ex.h_vector()) + S[2] + q*S[3] + """ + B = self.basis() + ring = self.base_ring() + q = self.q() + d = len(hv) - 1 + m = matrix(ring, d + 1, d + 1, + [(-1)**(d - j) * q_binomial(d - i, d - j, q) * + q**(-d * (d - i) + binomial(d - j, 2)) + for j in range(d + 1) + for i in range(d + 1)]) + v = vector(ring, [hv[i] for i in range(d + 1)]) + return sum(ring(c) * B[i] for i, c in enumerate(m * v)) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + INPUT: + + - ``x`` -- an element of the base ring or something convertible + + EXAMPLES:: + + sage: R = QuantumValuedPolynomialRing(QQ).S() + sage: x = R.gen() + sage: R(3) + 3*S[0] + sage: R(x) + S[1] + """ + P = parent(x) + if isinstance(P, QuantumValuedPolynomialRing.Shifted): + if P is self: + return x + if P is not self.base_ring(): + return self.element_class(self, x.monomial_coefficients()) + + # ok, not a quantum-valued polynomial ring element + R = self.base_ring() + # coercion via base ring + x = R(x) + if not x: + return self.element_class(self, {}) + return self.from_base_ring_from_one_basis(x) + + def _coerce_map_from_(self, R) -> bool: + r""" + Return whether there is a coercion from ``R`` into ``self``. + + INPUT: + + - ``R`` -- a commutative ring + + The things that coerce into ``self`` are + + - Quantum-Valued Polynomial Rings over a base + with a coercion map into ``self.base_ring()``. + + - Anything with a coercion into ``self.base_ring()``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(GF(7)).S(); F + Quantum-Valued Polynomial Ring over Finite Field of size 7 + in the shifted basis + + Elements of the quantum-valued polynomial ring canonically coerce in:: + + sage: x = F.gen() + sage: F.coerce(x*x) # indirect doctest + (6*q^-1)*S[1] + (q^-1+1)*S[2] + + Elements of the integers coerce in, since there is a coerce map + from `\ZZ` to GF(7):: + + sage: F.coerce(1) # indirect doctest + S[0] + + There is no coerce map from `\QQ` to `\GF{7}`:: + + sage: F.coerce(2/3) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Rational Field to + Quantum-Valued Polynomial Ring over Finite Field of size 7 + in the shifted basis + + Elements of the base ring coerce in:: + + sage: F.coerce(GF(7)(5)) + 5*S[0] + + The quantum-valued polynomial ring over `\ZZ` on `x` coerces in, since + `\ZZ` coerces to `\GF{7}`:: + + sage: G = QuantumValuedPolynomialRing(ZZ).S() + sage: Gx = G.gen() + sage: z = F.coerce(Gx**2); z + -(q^-1)*S[1] + (q^-1+1)*S[2] + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the shuffle + algebra over `\GF{7}` does not coerce to the one over `\ZZ`:: + + sage: G.coerce(x^3+x) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Quantum-Valued Polynomial + Ring over Finite Field of size 7 in the shifted basis + to Quantum-Valued Polynomial Ring over Integer Ring + in the shifted basis + + TESTS:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: G = QuantumValuedPolynomialRing(QQ).S() + sage: H = QuantumValuedPolynomialRing(ZZ).S() + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + sage: F._coerce_map_from_(H) + True + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + True + sage: F.has_coerce_map_from(PolynomialRing(ZZ, 'x')) + False + """ + # quantum-valued polynomial rings in the same variable + # over any base that coerces in: + if isinstance(R, QuantumValuedPolynomialRing.Shifted): + return self.base_ring().has_coerce_map_from(R.base_ring()) + if isinstance(R, QuantumValuedPolynomialRing.Binomial): + return R.module_morphism(self._from_binomial_basis, + codomain=self) + return self.base_ring().has_coerce_map_from(R) + + def _poly(self, i): + """ + Convert the basis element `S[i]` to a polynomial. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: F._poly(4).factor() + (1/(q^6 + 3*q^5 + 5*q^4 + 6*q^3 + 5*q^2 + 3*q + 1)) * + (q*x + 1) * (q^2*x + q + 1) * (q^3*x + q^2 + q + 1) * + (q^4*x + q^3 + q^2 + q + 1) + """ + return q_binomial_x(i, i) + + class Element(CombinatorialFreeModule.Element): + def umbra(self): + """ + Return the Bernoulli umbra. + + This is the derivative at `-1` of the shift by one. + + This is related to Carlitz's `q`-Bernoulli numbers. + + .. SEEALSO:: :meth:`derivative_at_minus_one` + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).umbra() + (q + 2)/(q + 1) + """ + return self.shift().derivative_at_minus_one() + + def variable_shift(self, k=1): + r""" + Return the image by the shift on variables. + + The shift is the substitution operator + + .. MATH:: + + x \mapsto q x + 1. + + INPUT: + + - `k` -- integer (default: 1) + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S() + sage: S = A.basis() + sage: S[5].variable_shift() + S[0] + q*S[1] + q^2*S[2] + q^3*S[3] + q^4*S[4] + q^5*S[5] + + sage: S[5].variable_shift(-1) + -(q^-5)*S[4] + (q^-5)*S[5] + + TESTS:: + + sage: S[5].variable_shift(0) + S[5] + sage: S[5].variable_shift().variable_shift(-1) + S[5] + sage: S[5].variable_shift(2).variable_shift(-2) + S[5] + sage: S[3].variable_shift(-2) + (q^-5)*S[1] - (q^-6+q^-5)*S[2] + (q^-6)*S[3] + """ + if k == 0: + return self + + A = self.parent() + q = A.q() + + def on_basis(n): + return {A._indices(j): q**(k * j) + * q_binomial(k + n - 1 - j, n - j) + for j in range(n + 1)} + + mc = self._monomial_coefficients + ret = linear_combination((on_basis(index), coeff) + for index, coeff in mc.items()) + return A.element_class(A, ret) + + def derivative_at_minus_one(self): + """ + Return the 'derivative' at -1. + + .. SEEALSO:: :meth:`umbra` + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).derivative_at_minus_one() + 1 + """ + ring = q_int(1).parent() + return ring.sum(c / q_int(i) for i, c in self if i > 0) + + def h_vector(self): + """ + Return the numerator of the generating series of values. + + If ``self`` is an Ehrhart polynomial, this is the h-vector. + + .. SEEALSO:: :meth:`h_polynomial`, :meth:`fraction` + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S() + sage: ex = A.basis()[4] + sage: ex.h_vector() + (0, 0, 0, 0, 1) + + sage: q = polygen(QQ,'q') + sage: x = polygen(q.parent(),'x') + sage: ex = A.from_polynomial((1+q*x)**3) + sage: ex.h_vector() + (0, q^3, 2*q + 2*q^2, 1) + """ + d = max(self.support()) + ring = self.parent().base_ring() + q = self.parent().q() + + def fn(j, i): + return ((-1)**(d - j) * + q**(binomial(d - j + i + 1, 2) - + binomial(i + 1, 2)) * + q_binomial(d - i, d - j)) + m = matrix(ring, d + 1, d + 1, fn) + v = vector(ring, [self.coefficient(i) for i in range(d + 1)]) + return m * v + + def h_polynomial(self): + """ + Return the `h`-vector as a polynomial. + + .. SEEALSO:: :meth:`h_vector`, :meth:`fraction` + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).S() + sage: q = polygen(ZZ,'q') + sage: x = polygen(q.parent(),'x') + sage: ex = A.from_polynomial((1+q*x)**3) + sage: ex.h_polynomial() + z^3 + (2*q + 2*q^2)*z^2 + q^3*z + """ + ring = PolynomialRing(self.parent().base_ring(), 'z') + return ring(list(self.h_vector())) + + def fraction(self): + """ + Return the generating series of values as a fraction. + + .. SEEALSO:: :meth:`h_vector`, :meth:`h_polynomial` + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).S() + sage: ex = A.basis()[4] + sage: ex.fraction().factor() + (-1) * (t - 1)^-1 * (q*t - 1)^-1 * (q^2*t - 1)^-1 * (q^3*t - 1)^-1 * (q^4*t - 1)^-1 + + sage: q = polygen(QQ,'q') + sage: x = polygen(q.parent(), 'x') + sage: ex = A.from_polynomial((1+q*x)**3) + sage: ex.fraction().factor() + (t - 1)^-1 * (q*t - 1)^-1 * (q^2*t - 1)^-1 * (q^3*t - 1)^-1 * (q^3*t^2 + 2*q^2*t + 2*q*t + 1) + sage: ex.fraction().numerator() + q^3*t^2 + 2*q^2*t + 2*q*t + 1 + """ + v = self.h_vector() + d = len(v) + R = PolynomialRing(QQ, 'q,t') + frac_R = R.fraction_field() + q, t = R.gens() + denom = R.prod(1 - q**i * t for i in range(d)) + numer = sum(frac_R(v[i]) * t**(d - 1 - i) for i in range(d)) + return numer / denom + + S = Shifted + + # ===== Another basis for the same algebra ===== + + class Binomial(CombinatorialFreeModule, BindableClass): + r""" + The quantum-valued polynomial ring in the binomial basis. + + The basis used here is given by `B[i] = \genfrac{[}{]}{0pt}{}{x}{i}_q` for `i \in \NN`. + + Assuming `n_1 \leq n_2`, the product of two monomials `B[n_1] \cdot B[n_2]` + is given by the sum + + .. MATH:: + + \sum_{k=0}^{n_1} q^{(k-n_1)(k-n_2)} \genfrac{[}{]}{0pt}{}{n_1}{k}_q \genfrac{[}{]}{0pt}{}{n_1+n_2-k}{n_1}_q B[n_1 + n_2 - k]. + """ + def __init__(self, A) -> None: + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(QQ).B(); F + Quantum-Valued Polynomial Ring over Rational Field + in the binomial basis + sage: TestSuite(F).run() # not tested + """ + CombinatorialFreeModule.__init__(self, A.base_ring(), + NonNegativeIntegers(), + category=A.Bases(), + prefix="B", + latex_prefix=r"\mathbb{B}") + + def _realization_name(self) -> str: + r""" + TESTS:: + + sage: F = QuantumValuedPolynomialRing(QQ).B() + sage: F._realization_name() + 'binomial' + """ + return "binomial" + + def product_on_basis(self, n1, n2): + r""" + Return the product of basis elements ``n1`` and ``n2``. + + INPUT: + + - ``n1``, ``n2`` -- integers + + The formula is taken from Theorem 3.4 in [HaHo2017]_. + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(QQ).B() + sage: A.product_on_basis(0, 1) + B[1] + """ + i = ZZ(n1) + j = ZZ(n2) + if j < i: + j, i = i, j + + q = self.q() + return self._from_dict({i + j - k: + q_binomial(i, k) + * q_binomial(i + j - k, i) + * q**((k - i) * (k - j)) + for k in range(i + 1)}) + + def _from_shifted_basis(self, i): + """ + Convert from the shifted binomial(x+k,k) basis. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: S = QuantumValuedPolynomialRing(ZZ).S() + sage: B = QuantumValuedPolynomialRing(ZZ).B() + sage: s = S.basis() + sage: B(s[3]+1) # indirect doctest + 2*B[0] + (q+q^2+q^3)*B[1] + (q^4+q^5+q^6)*B[2] + q^9*B[3] + sage: S(_) + S[0] + S[3] + """ + i = ZZ(i) + R = self.base_ring() + q = self.q() + return self._from_dict({k: R(q_binomial(i, k)) + * q**(k**2) + for k in range(i + 1)}) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + EXAMPLES:: + + sage: R = QuantumValuedPolynomialRing(QQ).B() + sage: x = R.gen() + sage: R(3) + 3*B[0] + sage: R(x) + B[1] + """ + P = x.parent() + if isinstance(P, QuantumValuedPolynomialRing.Binomial): + if P is self: + return x + if P is not self.base_ring(): + return self.element_class(self, x.monomial_coefficients()) + + # ok, not a quantum-valued polynomial ring element + R = self.base_ring() + # coercion via base ring + x = R(x) + if not x: + return self.element_class(self, {}) + return self.from_base_ring_from_one_basis(x) + + def _coerce_map_from_(self, R): + r""" + Return whether there is a coercion from ``R`` into ``self``. + + INPUT: + + - ``R`` -- a commutative ring + + The things that coerce into ``self`` are + + - Quantum-Valued Polynomial Rings over a base + with a coercion map into ``self.base_ring()``. + + - Anything with a coercion into ``self.base_ring()``. + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(GF(7)).B(); F + Quantum-Valued Polynomial Ring over Finite Field of size 7 + in the binomial basis + + Elements of the integer-valued polynomial ring canonically coerce + in:: + + sage: x = F.gen() + sage: F.coerce(x*x) # indirect doctest + B[1] + (q+q^2)*B[2] + + Elements of the integers coerce in, since there is a coerce map + from `\ZZ` to `\GF(7)`:: + + sage: F.coerce(1) # indirect doctest + B[0] + + There is no coerce map from `\QQ` to `\GF{7}`:: + + sage: F.coerce(2/3) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Rational Field to + Quantum-Valued Polynomial Ring over Finite Field of size 7 + in the binomial basis + + Elements of the base ring coerce in:: + + sage: F.coerce(GF(7)(5)) + 5*B[0] + + The integer-valued polynomial ring over `\ZZ` on `x` coerces in, + since `\ZZ` coerces to `\GF{7}`:: + + sage: G = QuantumValuedPolynomialRing(ZZ).B() + sage: Gx = G.gen() + sage: z = F.coerce(Gx**2); z + B[1] + (q+q^2)*B[2] + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the + integer-valued polynomial algebra over `\GF{7}` does not + coerce to the one over `\ZZ`:: + + sage: G.coerce(x^3+x) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from + Quantum-Valued Polynomial Ring over Finite Field of size 7 + in the binomial basis to Quantum-Valued Polynomial Ring + over Integer Ring in the binomial basis + + TESTS:: + + sage: F = QuantumValuedPolynomialRing(ZZ).B() + sage: G = QuantumValuedPolynomialRing(QQ).B() + sage: H = QuantumValuedPolynomialRing(ZZ).B() + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + sage: F._coerce_map_from_(H) + True + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + True + sage: F.has_coerce_map_from(PolynomialRing(ZZ,'x')) + False + """ + # quantum-valued polynomial rings over any base + # that coerces in: + if isinstance(R, QuantumValuedPolynomialRing.Binomial): + return self.base_ring().has_coerce_map_from(R.base_ring()) + if isinstance(R, QuantumValuedPolynomialRing.Shifted): + return R.module_morphism(self._from_shifted_basis, + codomain=self) + return self.base_ring().has_coerce_map_from(R) + + def _poly(self, i): + """ + Convert the basis element `B[i]` to a polynomial. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: F = QuantumValuedPolynomialRing(ZZ).B() + sage: F._poly(4).factor() + (1/(q^12 + 3*q^11 + 5*q^10 + 6*q^9 + 5*q^8 + 3*q^7 + q^6)) * + (x - 1) * x * (x - q - 1) * (x - q^2 - q - 1) + """ + return q_binomial_x(0, i) + + class Element(CombinatorialFreeModule.Element): + def variable_shift(self, k=1): + r""" + Return the image by the shift of variables. + + On polynomials, the action for `k=1` is the shift + on variables `x \mapsto 1 + qx`. + + This implementation follows formula (5.5) in [HaHo2017]_. + + INPUT: + + - `k` -- nonnegative integer (default: 1) + + EXAMPLES:: + + sage: A = QuantumValuedPolynomialRing(ZZ).B() + sage: B = A.basis() + sage: B[5].variable_shift() + B[4] + q^5*B[5] + + TESTS:: + + sage: B[5].variable_shift(0) + B[5] + """ + if k == 0: + return self + + A = self.parent() + q = A.q() + + def on_basis(n): + return {A._indices(j): q**((k + j - n) * j) + * q_binomial(k, n - j) + for j in range(n + 1)} + + mc = self._monomial_coefficients + ret = linear_combination((on_basis(index), coeff) + for index, coeff in mc.items()) + return A.element_class(A, ret) + + B = Binomial