Repository: sagemath/sage

Issue:
adding the ring of integer-valued polynomials
<div id="comment:0"></div>

in two different basis

CC:  @tscrim

Component: **commutative algebra**

Author: **Frédéric Chapoton**

_Issue created by migration from https://trac.sagemath.org/ticket/34814_




Discussion:

Resolving patch:
diff --git a/src/sage/rings/polynomial/all.py b/src/sage/rings/polynomial/all.py
--- a/src/sage/rings/polynomial/all.py
+++ b/src/sage/rings/polynomial/all.py
@@ -1,8 +1,7 @@
 """
 Polynomials
 """
-
-#*****************************************************************************
+# ****************************************************************************
 #       Copyright (C) 2005 William Stein <wstein@gmail.com>
 #
 #  Distributed under the terms of the GNU General Public License (GPL)
@@ -14,8 +13,8 @@
 #
 #  The full text of the GPL is available at:
 #
-#                  http://www.gnu.org/licenses/
-#*****************************************************************************
+#                  https://www.gnu.org/licenses/
+# ****************************************************************************
 
 from sage.misc.lazy_import import lazy_import
 
@@ -51,3 +50,7 @@
 
 # Evaluation of cyclotomic polynomials
 from sage.rings.polynomial.cyclotomic import cyclotomic_value
+
+# Integer-valued Univariate Polynomial Ring
+lazy_import('sage.rings.polynomial.integer_valued_polynomials',
+            'IntegerValuedPolynomialRing')
diff --git a/src/sage/rings/polynomial/integer_valued_polynomials.py b/src/sage/rings/polynomial/integer_valued_polynomials.py
new file mode 100644
--- /dev/null
+++ b/src/sage/rings/polynomial/integer_valued_polynomials.py
@@ -0,0 +1,1133 @@
+# -*- coding: utf-8 -*-
+r"""
+Integer-valued polynomial rings
+
+AUTHORS:
+
+- Frédéric Chapoton (2023-03): Initial version
+"""
+# ***************************************************************************
+#  Copyright (C) 2013 Frédéric Chapoton
+#
+#  Distributed under the terms of the GNU General Public License (GPL)
+#                  https://www.gnu.org/licenses/
+# ***************************************************************************
+from sage.arith.misc import (binomial, factorial)
+from sage.categories.rings import Rings
+from sage.categories.all import Algebras
+from sage.categories.realizations import Category_realization_of_parent
+from sage.combinat.free_module import CombinatorialFreeModule
+from sage.matrix.constructor import matrix
+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.polynomial_ring import polygen
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.rational_field import QQ
+from sage.sets.non_negative_integers import NonNegativeIntegers
+from sage.sets.family import Family
+from sage.misc.bindable_class import BindableClass
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.structure.parent import Parent
+
+
+class IntegerValuedPolynomialRing(UniqueRepresentation, Parent):
+    r"""
+    The integer-valued polynomial ring over a base ring `R`.
+
+    Integer-valued polynomial rings are commutative and associative
+    algebras, with a basis indexed by non-negative integers.
+
+    There are two natural bases, made of the sequence
+    `\binom{x}{n}` for `n \geq 0` (the *binomial basis*) and of
+    the other sequence `\binom{x+n}{n}` for `n \geq 0` (the *shifted basis*).
+
+    These two bases are available as follows::
+
+        sage: B = IntegerValuedPolynomialRing(QQ).Binomial()
+        sage: S = IntegerValuedPolynomialRing(QQ).Shifted()
+
+    or by using the shortcuts::
+
+        sage: B = IntegerValuedPolynomialRing(QQ).B()
+        sage: S = IntegerValuedPolynomialRing(QQ).S()
+
+    There is a conversion formula between the two bases:
+
+    .. MATH::
+
+        \binom{x}{i} = \sum_{k=0}^{i} (-1)^{i-k} \binom{i}{k} \binom{x+k}{k}
+
+    with inverse:
+
+    .. MATH::
+
+        \binom{x+i}{i} = \sum_{k=0}^{i} \binom{i}{k} \binom{x}{k}.
+
+    REFERENCES:
+
+    - :wikipedia:`Integer-valued polynomial`
+
+    TESTS::
+
+        sage: IntegerValuedPolynomialRing(24)
+        Traceback (most recent call last):
+        ...
+        TypeError: argument R must be a commutative ring
+    """
+    def __init__(self, R):
+        """
+        TESTS::
+
+            sage: IV = IntegerValuedPolynomialRing(ZZ)
+            sage: TestSuite(IV).run()
+        """
+        if R not in Rings().Commutative():
+            raise TypeError("argument R must be a commutative ring")
+        self._base = R
+        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: IntegerValuedPolynomialRing(QQ)
+            Integer-Valued Polynomial Ring over Rational Field
+        """
+        br = self.base_ring()
+        return f"Integer-Valued Polynomial Ring over {br}"
+
+    def a_realization(self):
+        """
+        Return a default realization.
+
+        The Binomial realization is chosen.
+
+        EXAMPLES::
+
+            sage: IntegerValuedPolynomialRing(QQ).a_realization()
+            Integer-Valued Polynomial Ring over Rational Field
+            in the binomial basis
+        """
+        return self.Binomial()
+
+    class Bases(Category_realization_of_parent):
+        def super_categories(self) -> list:
+            r"""
+            Return the super-categories of ``self``.
+
+            EXAMPLES::
+
+                sage: A = IntegerValuedPolynomialRing(QQ); A
+                Integer-Valued Polynomial Ring over Rational Field
+                sage: C = A.Bases(); C
+                Category of bases of Integer-Valued Polynomial Ring
+                over Rational Field
+                sage: C.super_categories()
+                [Category of realizations of Integer-Valued Polynomial Ring
+                 over Rational Field,
+                 Join of Category of algebras with basis over Rational Field and
+                 Category of filtered algebras over Rational Field and
+                 Category of commutative algebras 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 _repr_(self) -> str:
+                r"""
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(QQ).S()
+                    sage: F  # indirect doctest
+                    Integer-Valued Polynomial Ring over Rational Field
+                    in the shifted basis
+                    sage: F = IntegerValuedPolynomialRing(QQ).B()
+                    sage: F  # indirect doctest
+                    Integer-Valued Polynomial Ring over Rational Field
+                    in the binomial 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 = IntegerValuedPolynomialRing(QQ).S()
+                    sage: A.one_basis()
+                    0
+                    sage: A.one()
+                    S[0]
+                """
+                return self.basis().keys()(0)
+
+            def degree_on_basis(self, m):
+                r"""
+                Return the degree of the basis element indexed by ``m``.
+
+                EXAMPLES::
+
+                    sage: A = IntegerValuedPolynomialRing(QQ).S()
+                    sage: A.degree_on_basis(4)
+                    4
+                """
+                return ZZ(m)
+
+            def from_polynomial(self, p):
+                """
+                Convert a polynomial into the ring of integer-valued polynomials.
+
+                This raises a ``ValueError`` if this is not possible.
+
+                INPUT:
+
+                - ``p`` -- a polynomial in one variable
+
+                EXAMPLES::
+
+                    sage: A = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: S = A.basis()
+                    sage: S[5].polynomial()
+                    1/120*x^5 + 1/8*x^4 + 17/24*x^3 + 15/8*x^2 + 137/60*x + 1
+                    sage: A.from_polynomial(_)
+                    S[5]
+                    sage: x = polygen(QQ, 'x')
+                    sage: A.from_polynomial(x)
+                    -S[0] + S[1]
+
+                    sage: A = IntegerValuedPolynomialRing(ZZ).B()
+                    sage: B = A.basis()
+                    sage: B[5].polynomial()
+                    1/120*x^5 - 1/12*x^4 + 7/24*x^3 - 5/12*x^2 + 1/5*x
+                    sage: A.from_polynomial(_)
+                    B[5]
+                    sage: x = polygen(QQ, 'x')
+                    sage: A.from_polynomial(x)
+                    B[1]
+
+                TESTS::
+
+                    sage: x = polygen(QQ,'x')
+                    sage: A.from_polynomial(x+1/3)
+                    Traceback (most recent call last):
+                    ...
+                    ValueError: not a polynomial with integer values: 1/3
+                """
+                B = self.basis()
+                poly = self._poly
+                remain = p
+                result = self.zero()
+                while remain:
+                    N = remain.degree()
+                    top_coeff = remain.leading_coefficient() * factorial(N)
+                    try:
+                        top_coeff = self.base_ring()(top_coeff)
+                    except TypeError as exc:
+                        msg = 'not a polynomial with integer'
+                        msg += f' values: {top_coeff}'
+                        raise ValueError(msg) from exc
+                    remain += -top_coeff * poly(N)
+                    result += top_coeff * B[N]
+                return result
+
+            def gen(self, i=0):
+                r"""
+                Return the generator of this algebra.
+
+                The optional argument is ignored.
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).B()
+                    sage: F.gen()
+                    B[1]
+                """
+                return self.algebra_generators()[0]
+
+            @cached_method
+            def algebra_generators(self):
+                r"""
+                Return the generators of this algebra.
+
+                EXAMPLES::
+
+                    sage: A = IntegerValuedPolynomialRing(ZZ).S(); A
+                    Integer-Valued Polynomial Ring over Integer Ring
+                    in the shifted basis
+                    sage: A.algebra_generators()
+                    Family (S[1],)
+                """
+                NonNeg = self.basis().keys()
+                return Family([self.monomial(NonNeg(1))])
+
+            gens = algebra_generators
+
+        class ElementMethods:
+            def __call__(self, v):
+                """
+                Evaluation at some value ``v``
+
+                EXAMPLES::
+
+                     sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                     sage: B = F.gen()
+                     sage: f = B**2+4*B+6
+                     sage: f(1/3)
+                     118/9
+
+                     sage: F = IntegerValuedPolynomialRing(ZZ).B()
+                     sage: B = F.gen()
+                     sage: f = B**2+4*B+6
+                     sage: f(1/3)
+                     67/9
+                """
+                return self.polynomial()(v)
+
+            def polynomial(self):
+                """
+                Convert to a polynomial in `x`.
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: B = F.gen()
+                    sage: (B+1).polynomial()
+                    x + 2
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).B()
+                    sage: B = F.gen()
+                    sage: (B+1).polynomial()
+                    x + 1
+
+                TESTS::
+
+                    sage: F.zero().polynomial().parent()
+                    Univariate Polynomial Ring in x over Rational Field
+                """
+                R = PolynomialRing(QQ, '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 = IntegerValuedPolynomialRing(ZZ).B()
+                    sage: B = F.gen()
+                    sage: (B+1).shift()
+                    B[1] + B[2]
+                    sage: (B+1).shift(3)
+                    B[3] + B[4]
+                """
+                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 = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: B = F.basis()
+                    sage: (B[2]*B[4]).sum_of_coefficients()
+                    1
+
+                TESTS::
+
+                    sage: (0*B[2]).sum_of_coefficients().parent()
+                    Integer Ring
+                """
+                R = self.parent().base_ring()
+                return R.sum(self._monomial_coefficients.values())
+
+            def content(self):
+                """
+                Return the content of ``self``.
+
+                This is the gcd of the coefficients.
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: B = F.basis()
+                    sage: (3*B[4]+6*B[7]).content()
+                    3
+
+                TESTS::
+
+                    sage: (0*B[2]).content()
+                    0
+                """
+                from sage.arith.misc import gcd
+                return gcd(self._monomial_coefficients.values())
+
+    class Shifted(CombinatorialFreeModule, BindableClass):
+        r"""
+        The integer-valued polynomial ring in the shifted basis.
+
+        The basis used here is given by `S[i] = \binom{i+x}{i}` 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 \binom{n_1}{k}\binom{n_1+n_2-k}{n_1} S[n_1 + n_2 - k].
+
+        EXAMPLES::
+
+            sage: F = IntegerValuedPolynomialRing(QQ).S(); F
+            Integer-Valued Polynomial Ring over Rational Field
+            in the shifted basis
+
+            sage: F.gen()
+            S[1]
+
+            sage: S = IntegerValuedPolynomialRing(ZZ).S(); S
+            Integer-Valued Polynomial Ring over Integer Ring
+            in the shifted basis
+            sage: S.base_ring()
+            Integer Ring
+
+            sage: G = IntegerValuedPolynomialRing(S).S(); G
+            Integer-Valued Polynomial Ring over Integer-Valued Polynomial
+            Ring over Integer Ring in the shifted basis in the shifted basis
+            sage: G.base_ring()
+            Integer-Valued Polynomial Ring over Integer Ring
+            in the shifted basis
+
+        Integer-valued polynomial rings commute with their base ring::
+
+            sage: K = IntegerValuedPolynomialRing(QQ).S()
+            sage: a = K.gen()
+            sage: K.is_commutative()
+            True
+            sage: L = IntegerValuedPolynomialRing(K).S()
+            sage: c = L.gen()
+            sage: L.is_commutative()
+            True
+            sage: s = a * c^3; s
+            S[1]*S[1] + (-6*S[1])*S[2] + 6*S[1]*S[3]
+            sage: parent(s)
+            Integer-Valued Polynomial Ring over Integer-Valued Polynomial
+            Ring over Rational Field in the shifted basis in the shifted basis
+
+        Integer-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 = IntegerValuedPolynomialRing(QQ).S()
+            sage: S = F.basis()
+            sage: S[2] * S[3]
+            3*S[3] - 12*S[4] + 10*S[5]
+            sage: 1 - S[2] * S[2] / 2
+            S[0] - 1/2*S[2] + 3*S[3] - 3*S[4]
+        """
+        def __init__(self, A):
+            r"""
+            Initialize ``self``.
+
+            EXAMPLES::
+
+                sage: F = IntegerValuedPolynomialRing(QQ).S(); F
+                Integer-Valued Polynomial Ring over Rational Field
+                in the shifted basis
+                sage: TestSuite(F).run()
+            """
+            CombinatorialFreeModule.__init__(self, A.base_ring(),
+                                             NonNegativeIntegers(),
+                                             category=A.Bases(),
+                                             prefix="S",
+                                             latex_prefix=r"\mathbb{S}")
+
+        def _realization_name(self) -> str:
+            r"""
+            TESTS::
+
+                sage: F = IntegerValuedPolynomialRing(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 = IntegerValuedPolynomialRing(QQ).S()
+                sage: A.product_on_basis(0, 1)
+                S[1]
+                sage: A.product_on_basis(1, 2)
+                -2*S[2] + 3*S[3]
+            """
+            i = ZZ(n1)
+            j = ZZ(n2)
+            if j < i:
+                j, i = i, j
+
+            R = self.base_ring()
+            return self._from_dict({i + j - k: R((-1)**k * i.binomial(k) * (i + j - k).binomial(i))
+                                    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 = IntegerValuedPolynomialRing(ZZ).S()
+                sage: B = IntegerValuedPolynomialRing(ZZ).B()
+                sage: b = B.basis()
+                sage: S(b[3]+1)  # indirect doctest
+                3*S[1] - 3*S[2] + S[3]
+                sage: B(_)
+                B[0] + B[3]
+            """
+            i = ZZ(i)
+            R = self.base_ring()
+            return self._from_dict({k: R((-1)**(i - k) * i.binomial(k))
+                                    for k in range(i + 1)})
+
+        def from_h_vector(self, h):
+            """
+            Convert from some `h`-vector.
+
+            INPUT:
+
+            - ``h`` -- a tuple or vector
+
+            .. SEEALSO:: :meth:`Element.h_vector`
+
+            EXAMPLES::
+
+                sage: A = IntegerValuedPolynomialRing(ZZ).S()
+                sage: S = A.basis()
+                sage: ex = S[2]+S[4]
+                sage: A.from_h_vector(ex.h_vector())
+                S[2] + S[4]
+            """
+            d = len(h) - 1
+            m = matrix(QQ, d + 1, d + 1,
+                       lambda j, i: (-1)**(d - j) * binomial(d - i, d - j))
+            v = vector(QQ, [h[i] for i in range(d + 1)])
+            R = self.base_ring()
+            return self._from_dict({i: R(c)
+                                    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 = IntegerValuedPolynomialRing(QQ).S()
+                sage: x = R.gen()
+                sage: R(3)
+                3*S[0]
+                sage: R(x)
+                S[1]
+            """
+            P = x.parent()
+            if isinstance(P, IntegerValuedPolynomialRing.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 integer-valued polynomial ring element
+            R = self.base_ring()
+            # coercion via base ring
+            x = R(x)
+            if x == 0:
+                return self.element_class(self, {})
+            return self.from_base_ring_from_one_basis(x)
+
+        def _coerce_map_from_(self, R):
+            r"""
+            Return ``True`` if there is a coercion from ``R`` into ``self``
+            and ``False`` otherwise.
+
+            INPUT:
+
+            - ``R`` -- a commutative ring
+
+            The things that coerce into ``self`` are
+
+            - Integer-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 = IntegerValuedPolynomialRing(GF(7)).S(); F
+                Integer-Valued Polynomial Ring over Finite Field of size 7
+                in the shifted basis
+
+            Elements of the integer-valued polynomial ring canonically
+            coerce in::
+
+                sage: x = F.gen()
+                sage: F.coerce(x*x) # indirect doctest
+                6*S[1] + 2*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
+                Integer-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 integer-valued polynomial ring over `\ZZ` on `x` coerces in,
+            since `\ZZ` coerces to `\GF{7}`::
+
+                sage: G = IntegerValuedPolynomialRing(ZZ).S()
+                sage: Gx = G.gen()
+                sage: z = F.coerce(Gx**2); z
+                -S[1] + 2*S[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 Integer-Valued Polynomial
+                Ring over Finite Field of size 7  in the shifted basis
+                to Integer-Valued Polynomial
+                Ring over Integer Ring in the shifted basis
+
+            TESTS::
+
+                sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                sage: G = IntegerValuedPolynomialRing(QQ).S()
+                sage: H = IntegerValuedPolynomialRing(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
+            """
+            # integer-valued polynomial rings over any base
+            # that coerces in:
+            if isinstance(R, IntegerValuedPolynomialRing.Shifted):
+                return self.base_ring().has_coerce_map_from(R.base_ring())
+            if isinstance(R, IntegerValuedPolynomialRing.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.
+
+            EXAMPLES::
+
+                sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                sage: F._poly(4)
+                1/24*x^4 + 5/12*x^3 + 35/24*x^2 + 25/12*x + 1
+            """
+            x = polygen(QQ, 'x')
+            return 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.
+
+                .. SEEALSO:: :meth:`derivative_at_minus_one`
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: B = F.gen()
+                    sage: (B+1).umbra()
+                    3/2
+
+                TESTS::
+
+                    sage: [(B**n).umbra() for n in range(1, 11)]
+                    [1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66]
+                """
+                return self.shift().derivative_at_minus_one()
+
+            def delta(self):
+                r"""
+                Return the image by the difference operator `\Delta`.
+
+                The operator `\Delta` is defined on polynomials by
+
+                .. MATH::
+
+                    f \mapsto f(x+1)-f(x).
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: S = F.basis()
+                    sage: S[5].delta()
+                    S[0] + S[1] + S[2] + S[3] + S[4]
+                """
+                return self.variable_shift() - self
+
+            def variable_shift(self, k=1):
+                r"""
+                Return the image by the shift of variables.
+
+                On polynomials, the action is the shift
+                on variables `x \mapsto x + k`.
+
+                INPUT:
+
+                - `k` -- integer (default: 1)
+
+                EXAMPLES::
+
+                    sage: A = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: S = A.basis()
+                    sage: S[5].variable_shift()
+                    S[0] + S[1] + S[2] + S[3] + S[4] + S[5]
+
+                    sage: S[5].variable_shift(-1)
+                    -S[4] + S[5]
+
+                TESTS::
+
+                    sage: S[5].variable_shift(0)
+                    S[5]
+                    sage: S[5].variable_shift().variable_shift(-1)
+                    S[5]
+                """
+                if k == 0:
+                    return self
+
+                A = self.parent()
+
+                if k > 0:
+                    B = A.basis()
+                    resu = A.linear_combination((B[j], c) for i, c in self
+                                                for j in range(i + 1))
+                    if k == 1:
+                        return resu
+                    return resu.variable_shift(k - 1)
+
+                resu = self - A._from_dict({i - 1: c for i, c in self if i})
+                if k == -1:
+                    return resu
+                return resu.variable_shift(k + 1)
+
+            def derivative_at_minus_one(self):
+                """
+                Return the derivative at `-1`.
+
+                This is sometimes useful when `-1` is a root.
+
+                .. SEEALSO:: :meth:`umbra`
+
+                EXAMPLES::
+
+                    sage: F = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: B = F.gen()
+                    sage: (B+1).derivative_at_minus_one()
+                    1
+                """
+                return QQ.sum(c / QQ(i) for i, c in self if i)
+
+            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`
+
+                EXAMPLES::
+
+                    sage: x = polygen(QQ,'x')
+                    sage: A = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: ex = A.from_polynomial((1+x)**3)
+                    sage: ex.h_vector()
+                    (0, 1, 4, 1)
+                """
+                d = max(self.support(), default=-1)
+                m = matrix(QQ, d + 1, d + 1,
+                           lambda j, i: (-1)**(d - j) * (d - i).binomial(d - j))
+                v = vector(QQ, [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`
+
+                EXAMPLES::
+
+                    sage: x = polygen(QQ,'x')
+                    sage: A = IntegerValuedPolynomialRing(ZZ).S()
+                    sage: ex = A.from_polynomial((1+x)**3)
+                    sage: ex.h_polynomial()
+                    z^3 + 4*z^2 + z
+                """
+                anneau = PolynomialRing(self.parent().base_ring(), 'z')
+                return anneau(list(self.h_vector()))
+
+    S = Shifted
+
+    # =====     Another basis for the same algebra     =====
+
+    class Binomial(CombinatorialFreeModule, BindableClass):
+        r"""
+        The integer-valued polynomial ring in the binomial basis.
+
+        The basis used here is given by `B[i] = \binom{x}{i}` 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} \binom{n_1}{k}\binom{n_1+n_2-k}{n_1} B[n_1 + n_2 - k].
+
+        The product of two monomials is therefore a positive linear combination
+        of monomials.
+
+        EXAMPLES::
+
+            sage: F = IntegerValuedPolynomialRing(QQ).B(); F
+            Integer-Valued Polynomial Ring over Rational Field
+            in the binomial basis
+
+            sage: F.gen()
+            B[1]
+
+            sage: S = IntegerValuedPolynomialRing(ZZ).B(); S
+            Integer-Valued Polynomial Ring over Integer Ring
+            in the binomial basis
+            sage: S.base_ring()
+            Integer Ring
+
+            sage: G = IntegerValuedPolynomialRing(S).B(); G
+            Integer-Valued Polynomial Ring over Integer-Valued Polynomial Ring
+            over Integer Ring in the binomial basis in the binomial basis
+            sage: G.base_ring()
+            Integer-Valued Polynomial Ring over Integer Ring
+            in the binomial basis
+
+        Integer-valued polynomial rings commute with their base ring::
+
+            sage: K = IntegerValuedPolynomialRing(QQ).B()
+            sage: a = K.gen()
+            sage: K.is_commutative()
+            True
+            sage: L = IntegerValuedPolynomialRing(K).B()
+            sage: c = L.gen()
+            sage: L.is_commutative()
+            True
+            sage: s = a * c^3; s
+            B[1]*B[1] + 6*B[1]*B[2] + 6*B[1]*B[3]
+            sage: parent(s)
+            Integer-Valued Polynomial Ring over Integer-Valued Polynomial
+            Ring over Rational Field in the binomial basis in the binomial basis
+
+        Integer-valued polynomial rings are commutative::
+
+            sage: c^3 * a == c * a * c * c
+            True
+
+        We can also manipulate elements in the basis::
+
+            sage: F = IntegerValuedPolynomialRing(QQ).B()
+            sage: B = F.basis()
+            sage: B[2] * B[3]
+            3*B[3] + 12*B[4] + 10*B[5]
+            sage: 1 - B[2] * B[2] / 2
+            B[0] - 1/2*B[2] - 3*B[3] - 3*B[4]
+
+        and coerce elements from our base field::
+
+            sage: F(4/3)
+            4/3*B[0]
+        """
+        def __init__(self, A):
+            r"""
+            Initialize ``self``.
+
+            EXAMPLES::
+
+                sage: F = IntegerValuedPolynomialRing(QQ).B(); F
+                Integer-Valued Polynomial Ring over Rational Field
+                in the binomial basis
+                sage: TestSuite(F).run()
+            """
+            CombinatorialFreeModule.__init__(self, A.base_ring(),
+                                             NonNegativeIntegers(),
+                                             latex_prefix="",
+                                             category=A.Bases())
+
+        def _realization_name(self) -> str:
+            r"""
+            TESTS::
+
+                sage: F = IntegerValuedPolynomialRing(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
+
+            EXAMPLES::
+
+                sage: A = IntegerValuedPolynomialRing(QQ).B()
+                sage: A.product_on_basis(0, 1)
+                B[1]
+                sage: A.product_on_basis(1, 2)
+                2*B[2] + 3*B[3]
+            """
+            i = ZZ(n1)
+            j = ZZ(n2)
+            if j < i:
+                j, i = i, j
+
+            R = self.base_ring()
+            return self._from_dict({i + j - k:
+                                    R(binomial(i, k) * binomial(i + j - k, i))
+                                    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 = IntegerValuedPolynomialRing(ZZ).S()
+                sage: B = IntegerValuedPolynomialRing(ZZ).B()
+                sage: s = S.basis()
+                sage: B(s[3]+1)  # indirect doctest
+                2*B[0] + 3*B[1] + 3*B[2] + B[3]
+                sage: S(_)
+                S[0] + S[3]
+            """
+            i = ZZ(i)
+            R = self.base_ring()
+            return self._from_dict({k: R(i.binomial(k))
+                                    for k in range(i + 1)})
+
+        def _element_constructor_(self, x):
+            r"""
+            Convert ``x`` into ``self``.
+
+            EXAMPLES::
+
+                sage: R = IntegerValuedPolynomialRing(QQ).B()
+                sage: x = R.gen()
+                sage: R(3)
+                3*B[0]
+                sage: R(x)
+                B[1]
+            """
+            P = x.parent()
+            if isinstance(P, IntegerValuedPolynomialRing.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 integer-valued polynomial ring element
+            R = self.base_ring()
+            # coercion via base ring
+            x = R(x)
+            if x == 0:
+                return self.element_class(self, {})
+            return self.from_base_ring_from_one_basis(x)
+
+        def _coerce_map_from_(self, R):
+            r"""
+            Return ``True`` if there is a coercion from ``R`` into ``self``
+            and ``False`` otherwise.
+
+            The things that coerce into ``self`` are
+
+            - Integer-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 = IntegerValuedPolynomialRing(GF(7)).B(); F
+                Integer-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] + 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
+                Integer-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 = IntegerValuedPolynomialRing(ZZ).B()
+                sage: Gx = G.gen()
+                sage: z = F.coerce(Gx**2); z
+                B[1] + 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 Integer-Valued Polynomial
+                Ring over Finite Field of size 7 in the binomial basis to
+                Integer-Valued Polynomial Ring over Integer Ring
+                in the binomial basis
+
+            TESTS::
+
+                sage: F = IntegerValuedPolynomialRing(ZZ).B()
+                sage: G = IntegerValuedPolynomialRing(QQ).B()
+                sage: H = IntegerValuedPolynomialRing(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
+            """
+            # integer-valued polynomial rings over any base
+            # that coerces in:
+            if isinstance(R, IntegerValuedPolynomialRing.Binomial):
+                return self.base_ring().has_coerce_map_from(R.base_ring())
+            if isinstance(R, IntegerValuedPolynomialRing.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.
+
+            EXAMPLES::
+
+                sage: F = IntegerValuedPolynomialRing(ZZ).B()
+                sage: F._poly(4)
+                1/24*x^4 - 1/4*x^3 + 11/24*x^2 - 1/4*x
+            """
+            x = polygen(QQ, 'x')
+            return binomial(x, i)
+
+        class Element(CombinatorialFreeModule.Element):
+            pass
+
+    B = Binomial


Test patch: