Repository: sagemath/sage
Pull request #39355: p-typical Witt vectors
Author: login: fchapoton
Labels: name: t: enhancement
name: c: padics
name: sd128
description: tickets of Sage Days 128 Le Teich
Created: 2025-01-20T21:04:32Z | Merged: 2025-05-11T17:36:36Z

This PR implements p-typical Witt vectors over a general commutative ring.
Several algorithms are implemented and SageMath automatically chooses the best algorithm depending on the base ring.

This PR is based on a  former work by Jacob Dennerlein (see #11457).

### :memo: Checklist

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [ ] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation preview.





Diff:
diff --git a/src/doc/en/reference/padics/index.rst b/src/doc/en/reference/padics/index.rst
index 6efcee751a6..5a0cf0fd8b4 100644
--- a/src/doc/en/reference/padics/index.rst
+++ b/src/doc/en/reference/padics/index.rst
@@ -42,4 +42,7 @@
    sage/rings/padics/common_conversion
    sage/rings/padics/morphism
 
+   sage/rings/padics/witt_vector_ring
+   sage/rings/padics/witt_vector
+
 .. include:: ../footer.txt
diff --git a/src/sage/rings/padics/all.py b/src/sage/rings/padics/all.py
index 7ece4e00493..0fde68dd314 100644
--- a/src/sage/rings/padics/all.py
+++ b/src/sage/rings/padics/all.py
@@ -4,3 +4,4 @@
 from sage.rings.padics.padic_generic import local_print_mode
 from sage.rings.padics.pow_computer import PowComputer
 from sage.rings.padics.pow_computer_ext import PowComputer_ext_maker
+from sage.rings.padics.witt_vector_ring import WittVectorRing
diff --git a/src/sage/rings/padics/meson.build b/src/sage/rings/padics/meson.build
index f589881042e..b0681f03885 100644
--- a/src/sage/rings/padics/meson.build
+++ b/src/sage/rings/padics/meson.build
@@ -47,6 +47,8 @@ py.install_sources(
   'tests.py',
   'tutorial.py',
   'unramified_extension_generic.py',
+  'witt_vector_ring.py',
+  'witt_vector.py',
   subdir: 'sage/rings/padics',
 )
 
diff --git a/src/sage/rings/padics/witt_vector.py b/src/sage/rings/padics/witt_vector.py
new file mode 100644
index 00000000000..f5f32d35774
--- /dev/null
+++ b/src/sage/rings/padics/witt_vector.py
@@ -0,0 +1,911 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._coordinates = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._coordinates = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th coordinate of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._coordinates[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._coordinates)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._coordinates) * W(inv_vec)).coordinates()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            if k == 0:
+                self._coordinates = tuple(R.zero() for i in range(self._prec))
+            else:
+                Z = Zp(p, prec=self._prec, type='fixed-mod')
+                self._coordinates = tuple(R(
+                     Z(k).teichmuller_expansion(i).residue().polynomial())
+                     ** (p**i)
+                     for i in range(self._prec))
+            return
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).coordinates()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._coordinates = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._coordinates)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._coordinates)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._coordinates == other.coordinates()
+        if op == op_NE:
+            return self._coordinates != other.coordinates()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._coordinates)
+        return vector(R, self._coordinates)
+
+    def coordinates(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.coordinates()
+            (1, 2, 3)
+        """
+        return self._coordinates
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field), is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        (1, 1, 1, 1, 1)
+        sage: u = 7*t
+        sage: u.phantom(lift=True)
+        (7, 7, 7, 7, 7)
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._coordinates = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._coordinates = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._coordinates = vec.coordinates()
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._coordinates = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._coordinates]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th coordinate of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._coordinates[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom(lift=True)
+            (6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2)
+        """
+        phantom = [self._phantom[i] + other._phantom[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        INPUT:
+
+        - ``prec`` -- the precision (default: ``None``) up to which the vector
+          is computed. When no integer is given, the whole truncated Witt
+          vector is computed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._coordinates), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._coordinates += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom(lift=True)
+            (0, 7, 7 + 3*7^2 + 5*7^3)
+        """
+        phantom = [self._phantom[i] * other._phantom[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom(lift=True)
+            (22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3)
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom(lift=True)
+            (0, 2*3, 2*3 + 2*3^2)
+        """
+        phantom = [self._phantom[i] - other._phantom[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def coordinates(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.coordinates()
+            (1, 2, 3)
+        """
+        self._compute_vector()
+
+        return self._coordinates
+
+    def phantom(self, lift=False):
+        """
+        Return the phantom components of the lift of ``self``.
+
+        INPUT:
+
+        - ``lift`` -- a Boolean (default: ``False``). When ``True``, return
+          the phantom components in the lift of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5,'t'), prec=3)
+            sage: t = W([1,1,3])
+            sage: t.phantom()
+            (1, 1, 1)
+            sage: t.phantom(lift=True)
+            (1, 1 + 5, 1 + 5 + 3*5^2)
+        """
+        if lift:
+            return tuple(self._phantom)
+        return tuple(self.parent().coefficient_ring()(x)
+                     for x in self._phantom)
+
+
+class WittVector_finotti(WittVector):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(7), prec=4, algorithm='finotti')
+        sage: 49*W.one()
+        (0, 0, 1, 0)
+    """
+    def _add_(self, other):
+        """
+        Return the sum of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(7), 'x'), prec=2, algorithm='finotti')
+            sage: t = W([x+3,x+2])
+            sage: u = W([6,x])
+            sage: t + u
+            (x + 2, x^6 + x^5 + 4*x^4 + 3*x^3 + 3*x^2 + 2*x + 2)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero ahead of time.
+        if other == P.zero():
+            return self
+        elif self == P.zero():
+            return other
+
+        G = []
+        for n in range(self._prec):
+            G_n = [self[n], other[n]]
+            for i in range(n):
+                G_n.append(P._eta_bar(G[i], n - i))
+            G.append(G_n)
+        sum_vec = tuple(sum(G[i]) for i in range(self._prec))
+
+        return P(sum_vec)
+
+    def _mul_(self, other):
+        """
+        Return the product of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(5), 'x'), prec=3, algorithm='finotti')
+            sage: t = W([1,2,3])
+            sage: u = W([x,x^2,x^3])
+            sage: t * u
+            (x, 2*x^5 + x^2, 3*x^25 + 4*x^22 + 4*x^19 + 2*x^16 + 3*x^13 + 2*x^10 + x^3)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero or one ahead of time.
+        if self == P.zero() or other == P.zero():
+            return P.zero()
+        if other == P.one():
+            return self
+        if self == P.one():
+            return other
+
+        from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        p = P.prime()
+        G = [[self[0] * other[0]]]
+        for n in range(1, self._prec):
+            G_n = [fast_char_p_power(self[0], p**n) * other[n],
+                   fast_char_p_power(other[0], p**n) * self[n]]
+            G_n.extend(fast_char_p_power(self[i], p**(n - i))
+                       * fast_char_p_power(other[n - i], p**i)
+                       for i in range(1, n))
+            for i in range(n):
+                G_n.append(P._eta_bar(G[i], n - i))
+            G.append(G_n)
+        prod_vec = tuple(sum(G[i]) for i in range(self._prec))
+
+        return P(prod_vec)
+
+
+class WittVector_pinvertible(WittVector):
+    """
+    Child class for truncated Witt vectors using the ``p_invertible``
+    algorithm.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(QQ, p=3, prec=3)
+        sage: t = W.random_element()
+        sage: t-t
+        (0, 0, 0)
+    """
+    def _add_(self, other):
+        """
+        Return the sum of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=2)
+            sage: t = W([1/2,3/4])
+            sage: u = W([5/6,7/8])
+            sage: t + u
+            (4/3, -7787621/15116544)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero ahead of time.
+        if other == P.zero():
+            return self
+        elif self == P.zero():
+            return other
+
+        p = P.prime()  # we know p is a unit in this case!
+        sum_vec = [self[0] + other[0]]
+        for n in range(1, self._prec):
+            next_sum = self[n] + other[n] + \
+                sum((self[i]**(p**(n - i)) + other[i]**(p**(n - i))
+                     - sum_vec[i]**(p**(n - i)))
+                    / p**(n - i)
+                    for i in range(n))
+            sum_vec.append(next_sum)
+
+        return P(sum_vec)
+
+    def _mul_(self, other):
+        """
+        Return the product of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: t = W([1/2,3/4,5/6])
+            sage: u = W([7/8,9/10,11/12])
+            sage: t * u
+            (7/16, 27033/10240, 5808213977/1342177280)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero or one ahead of time.
+        if self == P.zero() or other == P.zero():
+            return P.zero()
+        if other == P.one():
+            return self
+        if self == P.one():
+            return other
+
+        p = P.prime()  # we know p is a unit in this case!
+        prod_vec = [self[0] * other[0]]
+        for n in range(1, self._prec):
+            next_prod = (
+                sum(p**i * self[i]**(p**(n - i)) for i in range(n + 1)) *
+                sum(p**i * other[i]**(p**(n - i)) for i in range(n + 1)) -
+                sum(p**i * prod_vec[i]**(p**(n - i)) for i in range(n))
+            ) / p**n
+            prod_vec.append(next_prod)
+
+        return P(prod_vec)
+
+
+class WittVector_standard(WittVector):
+    """
+    Child class for truncated Witt vectors using the ``standard`` algorithm.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(5), prec=3, algorithm='standard')
+        sage: 5*W.one()
+        (0, 1, 0)
+    """
+    def _add_(self, other):
+        """
+        Return the sum of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Integers(25), p=5, prec=3)
+            sage: t = W([1,2,3])
+            sage: u = W([4,5,6])
+            sage: t + u
+            (5, 12, 4)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero ahead of time.
+        if other == P.zero():
+            return self
+        elif self == P.zero():
+            return other
+
+        s = P.sum_polynomials()
+        # note here this is tuple addition, i.e. concatenation
+        sum_vec = tuple(s[i](*(self._coordinates + other.coordinates()))
+                        for i in range(self._prec))
+
+        return P(sum_vec)
+
+    def _mul_(self, other):
+        """
+        Return the product of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Integers(13), p=2, prec=3)
+            sage: t = W([1,2,3])
+            sage: u = W([4,5,6])
+            sage: t * u
+            (4, 5, 5)
+        """
+        P = self.parent()
+
+        # As a slight optimization, we'll check for zero or one ahead of time.
+        if self == P.zero() or other == P.zero():
+            return P.zero()
+        if other == P.one():
+            return self
+        if self == P.one():
+            return other
+
+        p = P.prod_polynomials()
+        # note here this is tuple addition, i.e. concatenation
+        prod_vec = tuple(p[i](*(self._coordinates + other.coordinates()))
+                         for i in range(self._prec))
+
+        return P(prod_vec)
diff --git a/src/sage/rings/padics/witt_vector_ring.py b/src/sage/rings/padics/witt_vector_ring.py
new file mode 100644
index 00000000000..e0970ed1433
--- /dev/null
+++ b/src/sage/rings/padics/witt_vector_ring.py
@@ -0,0 +1,924 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that field;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(5), 't'), prec=4)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 4 over Univariate Polynomial Ring in t over Finite Field of size 5
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+
+        CommutativeRing.__init__(self, self)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, coefficient_ring, prec, p):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        S = PolynomialRing(coefficient_ring, x_y_vars)
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}\left(\Bold{F}_{3}[t]\right)
+        """
+        return "W_{%s}\\left(%s\\right)" % (latex(self._prec),
+                                            latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 - 2*X0^10*X1^3*Y0^15*Y1^2 - 2*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if not hasattr(self, '_prod_polynomials'):
+            self._generate_sum_and_product_polynomials(self._coefficient_ring,
+                                                       self._prec, self._prime)
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables)
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return a random truncated Witt vector. Extra arguments are passed to
+        the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 - 2*T0^3*U0^2 - 2*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if not hasattr(self, '_sum_polynomials'):
+            self._generate_sum_and_product_polynomials(self._coefficient_ring,
+                                                       self._prec, self._prime)
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables)
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+
+        sage: W = WittVectorRing(ZZ, p=11, prec=3, algorithm='finotti')
+        Traceback (most recent call last):
+        ...
+        ValueError: the 'finotti' algorithm only works for coefficients rings of characteristic p
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        if isinstance(coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        R = Zp(prime, prec=prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(prime)
+        table = [[0]]
+        for k in range(1, prec+1):
+            pk = prime**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // prime**(k-val))
+                for _ in range(val):
+                    temp = series % prime
+                    series = (series - R.teichmuller(temp)) // prime
+                row[i] = ZZ(series % prime)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        super().__init__(coefficient_ring, prec, prime)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in Finotti's "Computations with Witt
+        # vectors and the Greenberg transform", doi:10.1142/S1793042114500377
+        k = eta_index
+        p = self._prime
+        # if vec = (x,y), we know what to do: Theorem 8.6
+        if len(vec) == 2:
+            # Here we have to check if we've pre-computed already
+            x, y = vec
+            scriptN = [[None] for _ in range(k+1)]  # each list starts with
+            # None, so that indexing matches paper
+
+            # calculate first N_t scriptN's
+            for t in range(1, k+1):
+                for i in range(1, p**t):
+                    scriptN[t].append(self._binomial_table[t][i]
+                                      * fast_char_p_power(x, i)
+                                      * fast_char_p_power(y, p**t - i))
+            indexN = [p**i - 1 for i in range(k+1)]
+            for t in range(2, k+1):
+                for i in range(1, t):
+                    # append scriptN_{t, N_t+l}
+                    next_scriptN = self._eta_bar(
+                        scriptN[t-i][1:indexN[t-i]+t-i], i
+                    )
+                    scriptN[t].append(next_scriptN)
+            return sum(scriptN[k][1:])
+
+        # if vec is longer, we split and recurse: Proposition 5.4
+        # This is where we need to using multiprocessing.
+        else:
+            m = len(vec) // 2
+            v_1 = vec[:m]
+            v_2 = vec[m:]
+            s_1 = sum(v_1)
+            s_2 = sum(v_2)
+            scriptM = [[] for _ in range(k+1)]
+            for t in range(1, k+1):
+                scriptM[t].append(self._eta_bar(v_1, t))
+                scriptM[t].append(self._eta_bar(v_2, t))
+                scriptM[t].append(self._eta_bar((s_1, s_2), t))
+            for t in range(2, k+1):
+                for s in range(1, t):
+                    result = self._eta_bar(scriptM[t-s], s)
+                    scriptM[t].append(result)
+            return sum(scriptM[k])
+
+
+class WittVectorRing_phantom(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``phantom`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(19), prec=20)
+        sage: W
+        Ring of truncated 19-typical Witt vectors of length 20 over Finite Field of size 19
+
+        sage: W = WittVectorRing(QQ, p=23, prec=3, algorithm='phantom')
+        Traceback (most recent call last):
+        ...
+        ValueError: the 'phantom' algorithm only works when the coefficient ring is a finite field of p, or a polynomial ring on that field
+    """
+    Element = WittVector_phantom
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), p=23, prec=2)
+            sage: W
+            Ring of truncated 23-typical Witt vectors of length 2 over Finite Field of size 23
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not (coefficient_ring.characteristic() == prime
+                and (coefficient_ring in Fields().Finite()
+                     or ((isinstance(coefficient_ring, PolynomialRing_generic)
+                          or isinstance(coefficient_ring,
+                                        MPolynomialRing_base))
+                         and coefficient_ring.base() in Fields().Finite()))):
+            raise ValueError("the 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field")
+
+        if (coefficient_ring in Fields().Finite()
+            or isinstance(coefficient_ring,
+                          PolynomialRing_generic)):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_finotti]
+
+        super().__init__(coefficient_ring, prec, prime)
+
+
+class WittVectorRing_pinvertible(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``p_invertible`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(QQ, p=31, prec=20)
+        sage: W
+        Ring of truncated 31-typical Witt vectors of length 20 over Rational Field
+
+        sage: W = WittVectorRing(GF(3), prec=3, algorithm='p_invertible')
+        Traceback (most recent call last):
+        ...
+        ValueError: the 'p_invertible' algorithm only works when p is a unit in the ring of coefficients
+    """
+    Element = WittVector_pinvertible
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=3)
+            sage: W
+            Ring of truncated 11-typical Witt vectors of length 3 over Rational Field
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_pinvertible_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not coefficient_ring(prime).is_unit():
+            raise ValueError("the 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients")
+
+        self._always_coerce = [WittVectorRing_pinvertible,
+                               WittVectorRing_standard]
+        self._coerce_when_different = []
+
+        super().__init__(coefficient_ring, prec, prime)
+
+
+class WittVectorRing_standard(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``standard`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(3), prec=3, algorithm='standard')
+        sage: W
+        Ring of truncated 3-typical Witt vectors of length 3 over Finite Field of size 3
+    """
+    Element = WittVector_standard
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 2 over Integer Ring
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        self._always_coerce = []
+        self._coerce_when_different = [WittVectorRing]
+
+        self._generate_sum_and_product_polynomials(coefficient_ring, prec,
+                                                   prime)
+
+        super().__init__(coefficient_ring, prec, prime)


Reviews:
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T15:36:23Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:17:40Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:18:16Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:19:05Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:19:22Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:19:37Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:20:21Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:04:26Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:12:42Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:14:45Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:16:31Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:17:23Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:20:54Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:22:39Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T08:28:55Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T17:33:03Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T17:33:42Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T17:33:55Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T17:34:17Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T02:40:07Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T20:11:04Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T08:14:42Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:32:42Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:33:33Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:33:36Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:33:48Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:40:26Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:41:24Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:50:30Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:52:27Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:54:21Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:57:05Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:58:23Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:45:56Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T05:25:36Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T08:20:27Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T08:32:08Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T09:11:19Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T10:12:30Z
user: login: r-mb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T12:28:11Z
user: login: xcaruso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:06:44Z

Review comments:
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: You should add an error message
created_at: 2025-03-30T14:59:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Cannot we really imagine something better (e.g. Newton iteration)?
At least, one can compute the coordinates one by one and, hence, only work with univariate polynomials.
created_at: 2025-03-30T15:00:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: ```suggestion
        return str(self._vec)
```
Is it something I'm missing?
created_at: 2025-03-30T15:02:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: You should add here an `INPUT` section to say what is `R`.
created_at: 2025-03-30T15:03:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: It works on more general rings that `Fq`.
created_at: 2025-03-30T15:04:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: You should add spaces before and after the `=` sign.
The same comment also applies for the next lines.
created_at: 2025-03-30T15:04:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: There are other cases where this method of computation is relevant, I think. Examples include quotients, fraction fields (or, more generally, localizations), etc.
But this can be for another PR, if you prefer.
created_at: 2025-03-30T15:07:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Why don't you use `_int_to_vector` here?
created_at: 2025-03-30T15:08:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: You should add an error message, here.
created_at: 2025-03-30T15:09:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: I'm not sure calling `self.__class__` is the best thing to do.
It's probably better to write `self(phantom=phantom)`; I think it would be better handled in case of inheritance.
created_at: 2025-03-30T15:10:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().base()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Would be better to compare the phantom components (at the appropriate precision) if `other` is also an instance of `WittVector_phantom`, no?
created_at: 2025-03-30T15:13:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: I don't see the interest to have a separated function. Why don't you inline it in the `__classcall_private__` of `WittVectorRing`?

Or maybe, even better: you can let the `__init__` function of the chlid class decide if the base ring is appropriate to it. This will allow to have appropriate error messages when the user passes an nonappropriate algorithm.
created_at: 2025-03-30T15:19:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Should we have coercion maps between different implementations (i.e. different algorithms) of the same Witt vector ring? If yes, in which direction?
(Currently, coercion maps are defined in both directions which, I think, is definitely a bad idea.)
created_at: 2025-03-30T15:28:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: ```suggestion
        return "W_{%s}(%s)" % (latex(self._prec), latex(self.base()))
```

created_at: 2025-03-30T15:31:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self.base()}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self.base().cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self.base()(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self.base().characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self.base().characteristic()
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self.base().is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self):
+        """
+        Return the Witt product polynomials.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials()
+            [X0*Y0, X1*Y0^2 + X0^2*Y1 + 2*X1*Y1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials(self.base())
+        return self._prod_polynomials
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Don't you need to return a copy?
created_at: 2025-03-30T15:34:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self.base()}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self.base().cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self.base()(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self.base().characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self.base().characteristic()
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self.base().is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self):
+        """
+        Return the Witt product polynomials.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials()
+            [X0*Y0, X1*Y0^2 + X0^2*Y1 + 2*X1*Y1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials(self.base())
+        return self._prod_polynomials
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the base ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self.base().random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self):
+        """
+        Return the Witt sum polynomials.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials()
+            [X0 + Y0, -X0^4*Y0 + 3*X0^3*Y0^2 + 3*X0^2*Y0^3 - X0*Y0^4 + X1 + Y1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials(self.base())
+        return self._sum_polynomials
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self.base():
+            raise TypeError(f'{x} not in {self.base()}')
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, base_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        self._prec = prec
+        self._prime = prime
+        self._sum_polynomials = None
+        self._prod_polynomials = None
+
+        self._generate_binomial_table()
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Maybe, we could inline this function?
created_at: 2025-03-30T15:35:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: I am not sure Newton iteration can be used with any base ring. I went for the one-by-one computation. I am not totally sure it is faster though, in might depend on the base ring.
created_at: 2025-04-11T22:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Yes, this adds an unwanted extra comma at the end of the truncated Witt vector representation.
created_at: 2025-04-11T22:18:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Yes, I would like this to be in another PR.
created_at: 2025-04-11T22:19:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: This throws `TypeError: 'WittVectorRing_phantom_with_category.element_class' object is not callable`. Also, using `self.__class__` is recommended in the documentation here: https://doc.sagemath.org/html/en/thematic_tutorials/coercion_and_categories.html
created_at: 2025-04-11T22:19:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().base()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: The problem is indeed the appropriate precision, that precision is actually different for each phantom component. So I had the choice between computing the vector, or changing the precision for each phantom component everywhere in the code. As you can see, I went for the easy solution.
If you strongly disagree with this choice of mine, I can see if changing the precision for each phantom component works.
created_at: 2025-04-11T22:19:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: Fixed this in another commit due to the change of `base()` to `coefficient_ring()` in another commit.
created_at: 2025-04-11T22:20:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: The error message should not start by a capital letter and should not end by a point...
(This is a general comment for all error messages.)
created_at: 2025-04-12T08:04:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: I think that Newton iteration always works.
Suppose that $a = (a_0, \ldots, a_{n-1}) \in W_n(R)$ is the vector you want to inverse.
We start by definition $x_0 = (a_0^{-1}, 0, \ldots, 0)$ and then define the next $x_i$ by the recurrence:
$$x_{i+1} = 2 x_i - a x_i^2$$
Then a simple computation shows that:
$$1 - a x_{i+1} = (1 - a x_i)^2$$
for all $i$. Since the first coordinate of $a x_0 - 1$ vanishes, we deduce that the $2^i$ first coordinates of $a x_i - 1$ vanish as well. So $x_i$ is the inverse of $a$ as soon as $i \geq \log_2 n$.

Of course, in practice, it is enough to compute $x_i$ in $W_{2^i}(R)$.
created_at: 2025-04-12T08:12:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: OK.
created_at: 2025-04-12T08:14:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Same remark as above.
created_at: 2025-04-12T08:16:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().base()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Okay, let us keep it as it is for now.
created_at: 2025-04-12T08:17:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: I'm not sure your code solves the problem I raised (loops in the coercion graph).
created_at: 2025-04-12T08:20:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self.base()}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self.base().cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self.base()(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self.base().characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self.base().characteristic()
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self.base().is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self):
+        """
+        Return the Witt product polynomials.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials()
+            [X0*Y0, X1*Y0^2 + X0^2*Y1 + 2*X1*Y1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials(self.base())
+        return self._prod_polynomials
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: By the way, shouldn't we let the possibility to the user to ask for other variable names?
created_at: 2025-04-12T08:22:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: `self.__class__` is recommended in comparison to naming explicitly the class.
However, I would say that using `element_class` is even better (but maybe we should ask @fchapoton about this).

In any case, the `TypeError` you got is an issue that needs to be addressed.

created_at: 2025-04-12T08:28:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Take a look at the following examples:

```
sage: W = WittVectorRing(GF(3), p=3, prec=4)
sage: w = W([1,1,1,1])
sage: ~w
(1, 2, 0, 0)
sage: x = w
sage: x = 2*x-w*x^2;x
(1, 2, 2, 0)
sage: x = 2*x-w*x^2;x
(1, 2, 0, 0)
sage: W = WittVectorRing(QQ, p=3, prec=4)
sage: w = W([1,1,1,1])
sage: ~w
(1, -1/4, -81/832, -12887559/359956480)
sage: x = w
sage: x = 2*x-w*x^2;x
(1, -19, 2045, 33003439233)
sage: x = 2*x-w*x^2;x
(1, -4219, 25025869989, 41834538050458156753788300872625)
```

I agree with your method as long as the coefficient ring has characteristic $p$. Actually, as long as $p$ is nilpotent in the coefficient ring, Newton iterations converge (but the precision has to be changed). It fails however when the coefficient ring has characteristic zero, as can be seen in this example. That is because $V^iV^j\subset V^{i+j}$ is only true when the coefficient ring has characteristic $p$.
created_at: 2025-04-12T17:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
+        var_names = [f'Y{i}' for i in range(1, self._prec)]
+        poly_ring = PolynomialRing(P.base(), var_names)
+        inv_vec = list((self[0]**-1,) + poly_ring.gens())
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        prod_vec = (W(self._vec) * W(inv_vec)).vec()
+        for i in range(1, self._prec):
+            poly = prod_vec[i](inv_vec[1:])
+            Y_i = poly.parent().gens()[i - 1]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(Y_i))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+            try:
+                inv_vec[i] = P.base()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+
+        return P(inv_vec)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(ZZ, p=7, prec=3)
+            sage: t=W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` is lifted to
+    another Witt vector with coefficients in `\mathbb Z_q` whose phantom
+    components are stored. Computations are done with these phantom
+    components, and the corresponding Witt vectors in `\mathbb F_q` are
+    computed from these ghost components only when needed.
+
+    EXAMPLES:
+
+        sage: W=WittVectorRing(GF(7), prec=5)
+        sage: t=W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u=7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.base()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            y = base_lift(vec)
+            self._vec = padic_to_vector(y, R)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f'{vec} has not the correct length. '
+                                 'Expected length has to be at least '
+                                 f'{self._prec}.')
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f'{vec} cannot be interpreted as a Witt vector.')
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Okay I must admit here that I did as much as I could understand with the documentation. I have absolutely no idea what's best or what's left to fix, so indeed if @fchapoton has some insight I would be very greatful.
created_at: 2025-04-12T17:33:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: I see, I thought the problem was to chose the best algorithm, and in edge cases allow both coercions. So I made a few choices to remove the loops. I don't think it will always be the best, but also I don't believe there will be that much coercions between various algorithms in the first place.
created_at: 2025-04-12T17:33:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,835 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+    """
+    Given the parameters, chooses the adequate algorithm to compute Witt
+    vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)  # indirect doctest
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+    """
+    if prime == char:
+        if algorithm == 'p_invertible':
+            raise ValueError("The 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients.")
+        elif (base_ring in Fields().Finite()
+              or (isinstance(base_ring, PolynomialRing_generic)
+                  or isinstance(base_ring, MPolynomialRing_base))
+              and base_ring.base() in Fields().Finite()):
+            if algorithm is None:
+                algorithm = 'phantom'
+        elif algorithm is None:
+            algorithm = 'finotti'
+        elif algorithm == 'phantom':
+            raise ValueError("The 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field.")
+    elif algorithm == 'finotti':
+        raise ValueError("The 'finotti' algorithm only works for "
+                         "coefficients rings of characteristic p.")
+    elif algorithm == 'phantom':
+        raise ValueError("The 'phantom' algorithm only works when the "
+                         "coefficient ring is a finite field of "
+                         "characteristic p, or a polynomial ring on that "
+                         "field.")
+    elif base_ring(prime).is_unit():
+        if algorithm is None:
+            algorithm = 'p_invertible'
+    elif algorithm is None:
+        algorithm = 'standard'
+    elif algorithm == 'p_invertible':
+        raise ValueError("The 'p_invertible' algorithm only works when p is "
+                         "a unit in the ring of coefficients.")
+
+    return algorithm
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``base_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``base_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the base ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the base
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of the base ring, assuming that it is either `\mathbb F_q`
+      for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+    """
+    def __classcall_private__(cls, base_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if base_ring not in CommutativeRings():
+            raise TypeError(f'{base_ring} is not a commutative ring')
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f'{prec} is not an integer')
+        elif prec <= 0:
+            raise ValueError(f'{prec} must be positive')
+        elif algorithm not in [None, 'standard', 'p_invertible', 'finotti',
+                               'phantom']:
+            raise ValueError("algorithm must be one of None, 'standard', "
+                             "'p_invertible', 'finotti', 'phantom'")
+
+        prec = Integer(prec)
+        char = base_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f'{base_ring} has non-prime characteristic '
+                                 'and no prime was supplied')
+            prime = char
+        else:
+            if p not in Primes():
+                raise ValueError('p must be a prime number')
+            prime = p
+
+        match choose_witt_vector_algorithm(algorithm, base_ring, prime, char):
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case _:
+                child = WittVectorRing_standard
+
+        return child.__classcall__(child, base_ring, prec, prime)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self.base(), repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(25), p=5,prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), p=5, prec=3))  # indirect doctest
+            True
+        """
+        if isinstance(S, WittVectorRing):
+            return (
+                S.precision() >= self._prec
+                and self.base().has_coerce_map_from(S.base()))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self, base):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the base ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(base, x_y_vars, implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W=WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W(\Bold{F}_{3}[t])
+        """
+        return "W(%s)" % latex(self.base())
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self.base()}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self.base().cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self.base()(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self.base().characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self.base().characteristic()
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self.base().is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self):
+        """
+        Return the Witt product polynomials.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials()
+            [X0*Y0, X1*Y0^2 + X0^2*Y1 + 2*X1*Y1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials(self.base())
+        return self._prod_polynomials
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: Indeed we could, but I am not sure this method will be that much used in the first place.
created_at: 2025-04-12T17:34:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Okay, you're right!
Couldn't we nevertheless implement Newton iteration in the Finotti class (which is reserved for base rings with characteristic p)?
created_at: 2025-04-13T02:40:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,914 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W=WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW=WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.base()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f'{vec} has not the correct length. '
+                                     'Expected length has to be at least '
+                                     f'{self._prec}.')
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f'{vec} cannot be interpreted as a Witt '
+                                 'vector.')
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return other._invert_()
+
+        return self * other._invert_()
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.base()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _invert_(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: ~t
+            (1, 1, 1, 0)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"Inverse of {self} does not exist.")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        # Strategy: Multiply ``self`` by ``(Y_0, Y_1, ...)``, set equal
+        # to (1, 0, 0, ...), and solve.
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Done! I made it in the common class, because coefficient rings with characteristic $p$ can have different algorithms.
created_at: 2025-04-13T20:11:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: ```suggestion
        S = PolynomialRing(self._coefficient_ring, x_y_vars)
```
I'm not sure we really want to specify the implementation here; I think it's preferable to let Sage do as it prefers.
created_at: 2025-05-04T07:40:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: ```suggestion
        return "W_{%s}\\left(%s\\right)" % (latex(self._prec),
                               latex(self._coefficient_ring))
```
created_at: 2025-05-04T07:41:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: ```suggestion
        R = PolynomialRing(self._coefficient_ring, variables)
```
created_at: 2025-05-04T07:45:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: This description is not the correct one, I guess.
created_at: 2025-05-04T07:46:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: ```suggestion
        R = PolynomialRing(self._coefficient_ring, variables)
```
created_at: 2025-05-04T07:47:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Godwin point.
Is this remark really useful?
created_at: 2025-05-04T07:48:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Godwin point.
Is this remark really useful?
created_at: 2025-05-04T07:48:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: I don't know if you want to do it but it's probably better to factor this code in the `__init__` function of the generic class and call it (probably here) instead of calling `CommutativeRing.__init__` (at the end of the method).
created_at: 2025-05-04T07:53:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Which paper? Maybe, it should be cited correctly.
created_at: 2025-05-04T07:54:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: I think you are supposed to add a test in the doctest above to cover this case.
created_at: 2025-05-04T07:56:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
+        k = eta_index
+        p = self._prime
+        # if vec = (x,y), we know what to do: Theorem 8.6
+        if len(vec) == 2:
+            # Here we have to check if we've pre-computed already
+            x, y = vec
+            scriptN = [[None] for _ in range(k+1)]  # each list starts with
+            # None, so that indexing matches paper
+
+            # calculate first N_t scriptN's
+            for t in range(1, k+1):
+                for i in range(1, p**t):
+                    scriptN[t].append(self._binomial_table[t][i]
+                                      * fast_char_p_power(x, i)
+                                      * fast_char_p_power(y, p**t - i))
+            indexN = [p**i - 1 for i in range(k+1)]
+            for t in range(2, k+1):
+                for i in range(1, t):
+                    # append scriptN_{t, N_t+l}
+                    next_scriptN = self._eta_bar(
+                        scriptN[t-i][1:indexN[t-i]+t-i], i
+                    )
+                    scriptN[t].append(next_scriptN)
+            return sum(scriptN[k][1:])
+
+        # if vec is longer, we split and recurse: Proposition 5.4
+        # This is where we need to using multiprocessing.
+        else:
+            m = len(vec) // 2
+            v_1 = vec[:m]
+            v_2 = vec[m:]
+            s_1 = sum(v_1)
+            s_2 = sum(v_2)
+            scriptM = [[] for _ in range(k+1)]
+            for t in range(1, k+1):
+                scriptM[t].append(self._eta_bar(v_1, t))
+                scriptM[t].append(self._eta_bar(v_2, t))
+                scriptM[t].append(self._eta_bar((s_1, s_2), t))
+            for t in range(2, k+1):
+                for s in range(1, t):
+                    result = self._eta_bar(scriptM[t-s], s)
+                    scriptM[t].append(result)
+            return sum(scriptM[k])
+
+
+class WittVectorRing_phantom(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``phantom`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(19), prec=20)
+        sage: W
+        Ring of truncated 19-typical Witt vectors of length 20 over Finite Field of size 19
+    """
+    Element = WittVector_phantom
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), p=23, prec=2)
+            sage: W
+            Ring of truncated 23-typical Witt vectors of length 2 over Finite Field of size 23
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not (coefficient_ring.characteristic() == prime
+                and (coefficient_ring in Fields().Finite()
+                     or ((isinstance(coefficient_ring, PolynomialRing_generic)
+                          or isinstance(coefficient_ring,
+                                        MPolynomialRing_base))
+                         and coefficient_ring.base() in Fields().Finite()))):
+            raise ValueError("the 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field")
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: I think you are supposed to add a test in the doctest above to cover this case.
created_at: 2025-05-04T07:56:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
+        k = eta_index
+        p = self._prime
+        # if vec = (x,y), we know what to do: Theorem 8.6
+        if len(vec) == 2:
+            # Here we have to check if we've pre-computed already
+            x, y = vec
+            scriptN = [[None] for _ in range(k+1)]  # each list starts with
+            # None, so that indexing matches paper
+
+            # calculate first N_t scriptN's
+            for t in range(1, k+1):
+                for i in range(1, p**t):
+                    scriptN[t].append(self._binomial_table[t][i]
+                                      * fast_char_p_power(x, i)
+                                      * fast_char_p_power(y, p**t - i))
+            indexN = [p**i - 1 for i in range(k+1)]
+            for t in range(2, k+1):
+                for i in range(1, t):
+                    # append scriptN_{t, N_t+l}
+                    next_scriptN = self._eta_bar(
+                        scriptN[t-i][1:indexN[t-i]+t-i], i
+                    )
+                    scriptN[t].append(next_scriptN)
+            return sum(scriptN[k][1:])
+
+        # if vec is longer, we split and recurse: Proposition 5.4
+        # This is where we need to using multiprocessing.
+        else:
+            m = len(vec) // 2
+            v_1 = vec[:m]
+            v_2 = vec[m:]
+            s_1 = sum(v_1)
+            s_2 = sum(v_2)
+            scriptM = [[] for _ in range(k+1)]
+            for t in range(1, k+1):
+                scriptM[t].append(self._eta_bar(v_1, t))
+                scriptM[t].append(self._eta_bar(v_2, t))
+                scriptM[t].append(self._eta_bar((s_1, s_2), t))
+            for t in range(2, k+1):
+                for s in range(1, t):
+                    result = self._eta_bar(scriptM[t-s], s)
+                    scriptM[t].append(result)
+            return sum(scriptM[k])
+
+
+class WittVectorRing_phantom(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``phantom`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(19), prec=20)
+        sage: W
+        Ring of truncated 19-typical Witt vectors of length 20 over Finite Field of size 19
+    """
+    Element = WittVector_phantom
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), p=23, prec=2)
+            sage: W
+            Ring of truncated 23-typical Witt vectors of length 2 over Finite Field of size 23
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not (coefficient_ring.characteristic() == prime
+                and (coefficient_ring in Fields().Finite()
+                     or ((isinstance(coefficient_ring, PolynomialRing_generic)
+                          or isinstance(coefficient_ring,
+                                        MPolynomialRing_base))
+                         and coefficient_ring.base() in Fields().Finite()))):
+            raise ValueError("the 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Same remark about factoring.
created_at: 2025-05-04T07:56:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
+        k = eta_index
+        p = self._prime
+        # if vec = (x,y), we know what to do: Theorem 8.6
+        if len(vec) == 2:
+            # Here we have to check if we've pre-computed already
+            x, y = vec
+            scriptN = [[None] for _ in range(k+1)]  # each list starts with
+            # None, so that indexing matches paper
+
+            # calculate first N_t scriptN's
+            for t in range(1, k+1):
+                for i in range(1, p**t):
+                    scriptN[t].append(self._binomial_table[t][i]
+                                      * fast_char_p_power(x, i)
+                                      * fast_char_p_power(y, p**t - i))
+            indexN = [p**i - 1 for i in range(k+1)]
+            for t in range(2, k+1):
+                for i in range(1, t):
+                    # append scriptN_{t, N_t+l}
+                    next_scriptN = self._eta_bar(
+                        scriptN[t-i][1:indexN[t-i]+t-i], i
+                    )
+                    scriptN[t].append(next_scriptN)
+            return sum(scriptN[k][1:])
+
+        # if vec is longer, we split and recurse: Proposition 5.4
+        # This is where we need to using multiprocessing.
+        else:
+            m = len(vec) // 2
+            v_1 = vec[:m]
+            v_2 = vec[m:]
+            s_1 = sum(v_1)
+            s_2 = sum(v_2)
+            scriptM = [[] for _ in range(k+1)]
+            for t in range(1, k+1):
+                scriptM[t].append(self._eta_bar(v_1, t))
+                scriptM[t].append(self._eta_bar(v_2, t))
+                scriptM[t].append(self._eta_bar((s_1, s_2), t))
+            for t in range(2, k+1):
+                for s in range(1, t):
+                    result = self._eta_bar(scriptM[t-s], s)
+                    scriptM[t].append(result)
+            return sum(scriptM[k])
+
+
+class WittVectorRing_phantom(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``phantom`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(19), prec=20)
+        sage: W
+        Ring of truncated 19-typical Witt vectors of length 20 over Finite Field of size 19
+    """
+    Element = WittVector_phantom
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), p=23, prec=2)
+            sage: W
+            Ring of truncated 23-typical Witt vectors of length 2 over Finite Field of size 23
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not (coefficient_ring.characteristic() == prime
+                and (coefficient_ring in Fields().Finite()
+                     or ((isinstance(coefficient_ring, PolynomialRing_generic)
+                          or isinstance(coefficient_ring,
+                                        MPolynomialRing_base))
+                         and coefficient_ring.base() in Fields().Finite()))):
+            raise ValueError("the 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if (self._coefficient_ring in Fields().Finite()
+            or isinstance(self._coefficient_ring,
+                          PolynomialRing_generic)):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_finotti]
+
+        CommutativeRing.__init__(self, self)
+
+
+class WittVectorRing_pinvertible(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``p_invertible`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(QQ, p=31, prec=20)
+        sage: W
+        Ring of truncated 31-typical Witt vectors of length 20 over Rational Field
+    """
+    Element = WittVector_pinvertible
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=3)
+            sage: W
+            Ring of truncated 11-typical Witt vectors of length 3 over Rational Field
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_pinvertible_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not coefficient_ring(prime).is_unit():
+            raise ValueError("the 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients")
+
+        self._always_coerce = [WittVectorRing_pinvertible,
+                               WittVectorRing_standard]
+        self._coefficient_ring = coefficient_ring
+        self._coerce_when_different = []
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: Same remarks as above.
created_at: 2025-05-04T07:57:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
+        k = eta_index
+        p = self._prime
+        # if vec = (x,y), we know what to do: Theorem 8.6
+        if len(vec) == 2:
+            # Here we have to check if we've pre-computed already
+            x, y = vec
+            scriptN = [[None] for _ in range(k+1)]  # each list starts with
+            # None, so that indexing matches paper
+
+            # calculate first N_t scriptN's
+            for t in range(1, k+1):
+                for i in range(1, p**t):
+                    scriptN[t].append(self._binomial_table[t][i]
+                                      * fast_char_p_power(x, i)
+                                      * fast_char_p_power(y, p**t - i))
+            indexN = [p**i - 1 for i in range(k+1)]
+            for t in range(2, k+1):
+                for i in range(1, t):
+                    # append scriptN_{t, N_t+l}
+                    next_scriptN = self._eta_bar(
+                        scriptN[t-i][1:indexN[t-i]+t-i], i
+                    )
+                    scriptN[t].append(next_scriptN)
+            return sum(scriptN[k][1:])
+
+        # if vec is longer, we split and recurse: Proposition 5.4
+        # This is where we need to using multiprocessing.
+        else:
+            m = len(vec) // 2
+            v_1 = vec[:m]
+            v_2 = vec[m:]
+            s_1 = sum(v_1)
+            s_2 = sum(v_2)
+            scriptM = [[] for _ in range(k+1)]
+            for t in range(1, k+1):
+                scriptM[t].append(self._eta_bar(v_1, t))
+                scriptM[t].append(self._eta_bar(v_2, t))
+                scriptM[t].append(self._eta_bar((s_1, s_2), t))
+            for t in range(2, k+1):
+                for s in range(1, t):
+                    result = self._eta_bar(scriptM[t-s], s)
+                    scriptM[t].append(result)
+            return sum(scriptM[k])
+
+
+class WittVectorRing_phantom(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``phantom`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(19), prec=20)
+        sage: W
+        Ring of truncated 19-typical Witt vectors of length 20 over Finite Field of size 19
+    """
+    Element = WittVector_phantom
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), p=23, prec=2)
+            sage: W
+            Ring of truncated 23-typical Witt vectors of length 2 over Finite Field of size 23
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not (coefficient_ring.characteristic() == prime
+                and (coefficient_ring in Fields().Finite()
+                     or ((isinstance(coefficient_ring, PolynomialRing_generic)
+                          or isinstance(coefficient_ring,
+                                        MPolynomialRing_base))
+                         and coefficient_ring.base() in Fields().Finite()))):
+            raise ValueError("the 'phantom' algorithm only works when the "
+                             "coefficient ring is a finite field of "
+                             "p, or a polynomial ring on that field")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if (self._coefficient_ring in Fields().Finite()
+            or isinstance(self._coefficient_ring,
+                          PolynomialRing_generic)):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_finotti]
+
+        CommutativeRing.__init__(self, self)
+
+
+class WittVectorRing_pinvertible(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``p_invertible`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(QQ, p=31, prec=20)
+        sage: W
+        Ring of truncated 31-typical Witt vectors of length 20 over Rational Field
+    """
+    Element = WittVector_pinvertible
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=3)
+            sage: W
+            Ring of truncated 11-typical Witt vectors of length 3 over Rational Field
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_pinvertible_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if not coefficient_ring(prime).is_unit():
+            raise ValueError("the 'p_invertible' algorithm only works when p "
+                             "is a unit in the ring of coefficients")
+
+        self._always_coerce = [WittVectorRing_pinvertible,
+                               WittVectorRing_standard]
+        self._coefficient_ring = coefficient_ring
+        self._coerce_when_different = []
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        CommutativeRing.__init__(self, self)
+
+
+class WittVectorRing_standard(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using the ``standard`` algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(3), prec=3, algorithm='standard')
+        sage: W
+        Ring of truncated 3-typical Witt vectors of length 3 over Finite Field of size 3
+    """
+    Element = WittVector_standard
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 2 over Integer Ring
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        self._always_coerce = []
+        self._coefficient_ring = coefficient_ring
+        self._coerce_when_different = [WittVectorRing]
+        self._prec = prec
+        self._prime = prime
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: And again.
created_at: 2025-05-04T07:58:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: It seems that this function is never used.
(The algorithm `Fqisomorphism` has gone; I don't know whether it was intentional or not.)
created_at: 2025-05-04T07:59:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Intermediate calculations can be handled at smaller precisions. Doing so, we gain a factor $\log(\text{prec})$ in the complexity.
If you are willing to implement this, it's probably better but it won't be entirely straightforward because creating new Witt rings recomputes, I think, all the sum and product polynomials.

(Maybe, the good solution is to implement a method `change_precision` which avoids this recomputation... but this can be probably delayed to another PR.)
created_at: 2025-05-04T08:05:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Don't you need to return after this line? Otherwise, the computation is done twice.
created_at: 2025-05-04T08:07:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: I don't really like `vec`. What about `coordinates`?
created_at: 2025-05-04T08:09:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: You should add a `INPUT` section to explain what is `prec` here.
created_at: 2025-05-04T08:11:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Maybe `phantom` should also be implemented in the other classes. (Again, this can be done in another PR.)
created_at: 2025-05-04T08:13:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
+        """
+        Return the phantom components of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5,'t'), prec=3)
+            sage: t = W([1,1,3])
+            sage: t.phantom()
+            [1, 1 + 5, 1 + 5 + 3*5^2]
+        """
+        return self._phantom
+
+    def vec(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Again `vec` $\rightarrow$ `coordinates`?
created_at: 2025-05-04T08:14:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: This is legacy code, I guess there was a problem with previous versions, but indeed removing it runs fine. I made another commit because the comment was also outdated.
created_at: 2025-05-05T18:32:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: Done, but with another commit for doctests.
created_at: 2025-05-05T18:33:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: See above.
created_at: 2025-05-05T18:33:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: See above.
created_at: 2025-05-05T18:33:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: The useful part of the remark is "This lift is sometimes known as the multiplicative lift of ``x``". There are many recent papers which use this new terminology, so some users might only know it under that name, so it will help them find it.
This part has to stay.

The rest of the sentence might be removed if you like.
As you can see I did not change the name of the method. I wonder why do some papers remove the name of Teichmüller, and not the name of Witt who was a member of the SA? Another Godwin point just for you!
created_at: 2025-05-05T18:40:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(125,'t'), prec=2).teichmuller_lift(3)
+            (3, 0)
+        """
+        if x not in self._coefficient_ring:
+            raise TypeError(f"{x} not in {self._coefficient_ring}")
+        return self((x,) + tuple(0 for _ in range(self._prec-1)))
+
+
+class WittVectorRing_finotti(WittVectorRing):
+    """
+    Child class for truncated Witt vectors using Finotti's algorithm.
+
+    .. WARNING::
+
+        This class should never be called directly, use ``WittVectorRing``
+        instead.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(49), prec=3, algorithm='finotti')
+        sage: W
+        Ring of truncated 7-typical Witt vectors of length 3 over Finite Field in z2 of size 7^2
+    """
+    Element = WittVector_finotti
+
+    def __init__(self, coefficient_ring, prec, prime):
+        r"""
+        Initialises ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PowerSeriesRing(GF(3), 't'), p=3, prec=3)
+            sage: W
+            Ring of truncated 3-typical Witt vectors of length 3 over Power Series Ring in t over Finite Field of size 3
+            sage: type(W)
+            <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+
+            sage: TestSuite(W).run()
+        """
+        if coefficient_ring.characteristic() != prime:
+            raise ValueError("the 'finotti' algorithm only works for "
+                             "coefficients rings of characteristic p")
+
+        self._coefficient_ring = coefficient_ring
+        self._prec = prec
+        self._prime = prime
+        self._prod_polynomials = None
+        self._sum_polynomials = None
+
+        if isinstance(self._coefficient_ring, MPolynomialRing_base):
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_phantom,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = []
+        else:
+            self._always_coerce = [WittVectorRing_finotti,
+                                   WittVectorRing_standard]
+            self._coerce_when_different = [WittVectorRing_phantom]
+
+        import numpy as np
+        p = self._prime
+        R = Zp(p, prec=self._prec+1, type='fixed-mod')
+        v_p = ZZ.valuation(p)
+        table = [[0]]
+        for k in range(1, self._prec+1):
+            pk = p**k
+            row = np.empty(pk, dtype=int)
+            row[0] = 0
+            prev_bin = 1
+            for i in range(1, pk // 2 + 1):
+                val = v_p(i)
+                # Instead of calling binomial each time, we compute the
+                # coefficients recursively. This is MUCH faster.
+                next_bin = prev_bin * (pk - (i-1)) // i
+                prev_bin = next_bin
+                series = R(-next_bin // p**(k-val))
+                for _ in range(val):
+                    temp = series % p
+                    series = (series - R.teichmuller(temp)) // p
+                row[i] = ZZ(series % p)
+                row[pk - i] = row[i]  # binomial coefficients are symmetric
+            table.append(row)
+        self._binomial_table = table
+
+        CommutativeRing.__init__(self, self)
+
+    def _eta_bar(self, vec, eta_index):
+        r"""
+        Generate the `\eta_i` for ``finotti``'s algorithm.
+
+        EXAMPLES::
+
+            sage: R.<x,y,z,t> = PolynomialRing(GF(5))
+            sage: W = WittVectorRing(R, prec=2, algorithm='finotti')
+            sage: (W([x,y]) + W([z,t]))[1]  # indirect doctest
+            -x^4*z - 2*x^3*z^2 - 2*x^2*z^3 - x*z^4 + y + t
+        """
+        vec = tuple(x for x in vec if x != 0)  # strip zeroes
+
+        # special cases
+        if len(vec) <= 1:
+            return 0
+        if eta_index == 0:
+            return sum(vec)
+
+        # renaming to match notation in paper
path: src/sage/rings/padics/witt_vector_ring.py
user: login: r-mb
body: This is legacy code, but I would bet it is Finotti's paper. I added it.
created_at: 2025-05-05T18:41:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: This was intentional, the algorithm has been generalised. I removed the function.
created_at: 2025-05-05T18:50:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: This would be for another PR, but I agree with you. I am quite unhappy with how the computation of Witt polynomials is handled in the legacy code. A future PR should take care that they are only computed once in $\mathbb Z$, and should be stored in a cache somewhere.
created_at: 2025-05-05T18:52:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: I am not sure, here the phantom components are the phantom components of a lift, it is very specific to this algorithm. I guess a user would expect a general method to compute the phantom components only in the coefficient ring.
created_at: 2025-05-05T18:54:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Oh, yes, you're right.
But actually, I think that the public method `phantom` should return the standard phantom components, i.e. phantom components in the base ring.
created_at: 2025-05-05T18:57:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,917 @@
+r"""
+Witt vector rings
+
+This module provides the class :class:`WittVectorRing` of rings of truncated
+Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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 itertools import product
+
+from sage.categories.commutative_rings import CommutativeRings
+from sage.categories.fields import Fields
+from sage.misc.latex import latex
+from sage.rings.integer import Integer
+from sage.rings.integer_ring import ZZ
+from sage.rings.padics.factory import Zp
+from sage.rings.padics.witt_vector import (
+    WittVector_finotti,
+    WittVector_phantom,
+    WittVector_pinvertible,
+    WittVector_standard,
+)
+from sage.rings.polynomial.multi_polynomial import MPolynomial
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_element import Polynomial
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.ring import CommutativeRing
+from sage.sets.primes import Primes
+from sage.structure.unique_representation import UniqueRepresentation
+
+
+def fast_char_p_power(x, n, p=None):
+    r"""
+    Return `x^n` assuming that `x` lives in a ring of
+    characteristic `p`.
+
+    If `x` is not an element of a ring of characteristic `p`,
+    this throws an error.
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector_ring import fast_char_p_power
+        sage: t = GF(1913)(33)
+        sage: fast_char_p_power(t, 77)
+        1371
+
+    ::
+
+        sage: K.<t> = GF(5^3)
+        sage: fast_char_p_power(t, 385)
+        4*t^2 + 1
+        sage: t^385
+        4*t^2 + 1
+
+    ::
+
+        sage: A.<x> = K[]
+        sage: fast_char_p_power(x + 1, 10)
+        x^10 + 2*x^5 + 1
+
+    ::
+
+        sage: B.<u,v> = K[]
+        sage: fast_char_p_power(u + v, 1250)
+        u^1250 + 2*u^625*v^625 + v^1250
+    """
+    x_is_Polynomial = isinstance(x, Polynomial)
+    x_is_MPolynomial = isinstance(x, MPolynomial)
+
+    if not (x_is_Polynomial or x_is_MPolynomial):
+        return x**n
+    if x.is_gen():
+        return x**n
+    if n < 0:
+        x = ~x
+        n = -n
+
+    P = x.parent()
+    if p is None:
+        p = P.characteristic()
+    base_p_digits = ZZ(n).digits(base=p)
+
+    xn = 1
+
+    for p_exp, digit in enumerate(base_p_digits):
+        if digit == 0:
+            continue
+        inner_term = x**digit
+        term_dict = {}
+        for e_int_or_tuple, c in inner_term.dict().items():
+            power = p**p_exp
+            new_c = fast_char_p_power(c, power)
+            new_e_tuple = None
+            if x_is_Polynomial:  # Then the dict keys are ints
+                new_e_tuple = e_int_or_tuple * power
+            elif x_is_MPolynomial:  # Then the dict keys are ETuples
+                new_e_tuple = e_int_or_tuple.emul(power)
+            term_dict[new_e_tuple] = new_c
+        term = P(term_dict)
+        xn *= term
+
+    return xn
+
+
+class WittVectorRing(CommutativeRing, UniqueRepresentation):
+    r"""
+    Return the appropriate `p`-typical truncated Witt vector ring.
+
+    INPUT:
+
+    - ``coefficient_ring`` -- commutative ring of coefficients
+
+    - ``prec`` -- integer (default: `1`), length of the truncated Witt
+      vectors in the ring
+
+    - ``p`` -- a prime number (default: ``None``); when it is not set, it
+      defaults to the characteristic of ``coefficient_ring`` when it is prime.
+
+    - ``algorithm`` -- the name of the algorithm to use for the ring laws
+      (default: ``None``); when it is not set, the most adequate algorithm
+      is chosen
+
+    Available algorithms are:
+
+    - ``standard`` -- the schoolbook algorithm;
+
+    - ``p_invertible`` -- uses some optimisations when `p` is invertible
+      in the coefficient ring;
+
+    - ``finotti`` -- Finotti's algorithm; it can be used when the coefficient
+      ring has characteristic `p`;
+
+    - ``phantom`` -- computes the ring laws using the phantom components
+      using a lift of ``coefficient_ring``, assuming that it is either
+      `\mathbb F_q` for a power `q` of `p`, or a polynomial ring on that ring.
+
+    EXAMPLES::
+
+        sage: WittVectorRing(QQ, p=5)
+        Ring of truncated 5-typical Witt vectors of length 1 over
+        Rational Field
+
+    ::
+
+        sage: WittVectorRing(GF(3))
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(GF(3)['t'])
+        Ring of truncated 3-typical Witt vectors of length 1 over
+        Univariate Polynomial Ring in t over Finite Field of size 3
+
+    ::
+
+        sage: WittVectorRing(Qp(7), prec=30, p=5)
+        Ring of truncated 5-typical Witt vectors of length 30 over
+        7-adic Field with capped relative precision 20
+
+    TESTS::
+
+        sage: A = SymmetricGroup(3).algebra(QQ)
+        sage: WittVectorRing(A)
+        Traceback (most recent call last):
+        ...
+        TypeError: Symmetric group algebra of order 3 over Rational Field is not a commutative ring
+
+        sage: WittVectorRing(QQ)
+        Traceback (most recent call last):
+        ...
+        ValueError: Rational Field has non-prime characteristic and no prime was supplied
+
+        sage: WittVectorRing(QQ, p=5, algorithm='moon')
+        Traceback (most recent call last):
+        ...
+        ValueError: algorithm must be one of None, 'standard', 'p_invertible', 'finotti', 'phantom'
+
+        sage: W = WittVectorRing(ZZ, p=53)
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_standard_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(13), 't'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_phantom_with_category'>
+
+        sage: W = WittVectorRing(PolynomialRing(GF(5), 't,u'))
+        sage: type(W)
+        <class 'sage.rings.padics.witt_vector_ring.WittVectorRing_finotti_with_category'>
+    """
+    def __classcall_private__(cls, coefficient_ring, prec=1, p=None, algorithm=None):
+        r"""
+        Construct the ring of truncated Witt vectors from the parameters.
+
+        TESTS::
+
+            sage: W = WittVectorRing(QQ, p=5)
+            sage: W
+            Ring of truncated 5-typical Witt vectors of length 1 over Rational Field
+        """
+        if coefficient_ring not in CommutativeRings():
+            raise TypeError(f"{coefficient_ring} is not a commutative ring")
+        elif not (isinstance(prec, int) or isinstance(prec, Integer)):
+            raise TypeError(f"{prec} is not an integer")
+        elif prec <= 0:
+            raise ValueError(f"{prec} must be positive")
+
+        prec = Integer(prec)
+        char = coefficient_ring.characteristic()
+
+        if p is None:
+            if char not in Primes():
+                raise ValueError(f"{coefficient_ring} has non-prime "
+                                 "characteristic and no prime was supplied")
+            p = char
+        elif p not in Primes():
+            raise ValueError(f"p must be a prime number, here {p} was given")
+
+        match algorithm:
+            case None:
+                if p == char:
+                    if (coefficient_ring in Fields().Finite()
+                        or isinstance(coefficient_ring,
+                                      PolynomialRing_generic)
+                        and coefficient_ring.base()
+                            in Fields().Finite()):
+                        child = WittVectorRing_phantom
+                    else:
+                        child = WittVectorRing_finotti
+                elif coefficient_ring(p).is_unit():
+                    child = WittVectorRing_pinvertible
+                else:
+                    child = WittVectorRing_standard
+            case 'finotti':
+                child = WittVectorRing_finotti
+            case 'phantom':
+                child = WittVectorRing_phantom
+            case 'p_invertible':
+                child = WittVectorRing_pinvertible
+            case 'standard':
+                child = WittVectorRing_standard
+            case _:
+                raise ValueError("algorithm must be one of None, 'standard', "
+                                 "'p_invertible', 'finotti', 'phantom'")
+
+        return child.__classcall__(child, coefficient_ring, prec, p)
+
+    def __iter__(self):
+        """
+        Iterator for truncated Witt vector rings.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), p=3, prec=2)
+            sage: [w for w in W]
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1),
+            (2, 2)]
+        """
+        for t in product(self._coefficient_ring, repeat=self._prec):
+            yield self(t)
+
+    def _coerce_map_from_(self, S):
+        """"
+        Check whether there is a coerce map from ``S``.
+
+        EXAMPLES::
+
+            sage: K = GF(7)
+            sage: WK = WittVectorRing(K, prec=3)
+            sage: W = WittVectorRing(PolynomialRing(K, 't'), prec=3)
+            sage: Wf = WittVectorRing(PolynomialRing(K, 't'), prec=3, algorithm='finotti')
+            sage: WW = WittVectorRing(PolynomialRing(K, 't,u'), prec=3)
+            sage: Wf.has_coerce_map_from(WK)  # indirect doctest
+            True
+            sage: Wf.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(Wf)  # indirect doctest
+            True
+            sage: WW.has_coerce_map_from(WK)  # indirect doctest
+            True
+
+            sage: W = WittVectorRing(GF(25), prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(GF(5), prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(PolynomialRing(GF(5), 't,u'), prec=3))  # indirect doctest
+            False
+            sage: WW = WittVectorRing(PolynomialRing(GF(5), 't'), prec=3)
+            sage: WW.has_coerce_map_from(W)  # indirect doctest
+            False
+            sage: W.has_coerce_map_from(WW)  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(QQ, p=3, prec=3)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(QQ, p=3, prec=2))  # indirect doctest
+            False
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ, 'x'), p=5, prec=2)
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=5, prec=3))  # indirect doctest
+            True
+            sage: W.has_coerce_map_from(WittVectorRing(ZZ, p=3, prec=3))  # indirect doctest
+            False
+        """
+        if (isinstance(S, WittVectorRing)
+            and S.precision() >= self._prec and S.prime() == self._prime
+            and self._coefficient_ring.has_coerce_map_from(
+                     S.coefficient_ring())):
+            return (any(isinstance(S, rng) for rng in self._always_coerce)
+                    or (S.precision() != self._prec
+                        or S.coefficient_ring() is not self._coefficient_ring)
+                    and any(isinstance(S, rng)
+                            for rng in self._coerce_when_different))
+        if S is ZZ:
+            return True
+
+    def _generate_sum_and_product_polynomials(self):
+        """
+        Generates the sum and product polynomials defining the ring laws of
+        truncated Witt vectors for the ``standard`` algorithm.
+
+        EXAMPLES::
+
+            sage: P.<X1,X2,Y1,Y2> = PolynomialRing(GF(3),'X1,X2,Y1,Y2')
+            sage: W = WittVectorRing(P, p=3, prec=2, algorithm='standard')
+            sage: W([X1,X2]) + W([Y1,Y2])  # indirect doctest
+            (X1 + Y1, -X1^2*Y1 - X1*Y1^2 + X2 + Y2)
+            sage: W([X1,X2]) * W([Y1,Y2])  # indirect doctest
+            (X1*Y1, X2*Y1^3 + X1^3*Y2)
+        """
+        p = self._prime
+        prec = self._prec
+        x_var_names = [f'X{i}' for i in range(prec)]
+        y_var_names = [f'Y{i}' for i in range(prec)]
+        var_names = x_var_names + y_var_names
+
+        # Okay, what's going on here? Sage, by default, relies on
+        # Singular for Multivariate Polynomial Rings, but Singular uses
+        # only SIXTEEN bits (unsigned) to store its exponents. So if we
+        # want exponents larger than 2^16 - 1, we have to use the
+        # generic implementation. However, after some experimentation,
+        # it seems like the generic implementation is faster?
+        #
+        # After trying to compute S_4 for p=5, it looks like generic is
+        # faster for  very small polys, and MUCH slower for large polys.
+        # So we'll default to singular unless we can't use it.
+        #
+        # Remark: Since when is SIXTEEN bits sufficient for anyone???
+        #
+        if p**(prec-1) >= 2**16:
+            implementation = 'generic'
+        else:
+            implementation = 'singular'
+
+        # We first generate the "universal" polynomials and then project
+        # to the coefficient ring.
+        R = PolynomialRing(ZZ, var_names, implementation=implementation)
+        x_y_vars = R.gens()
+        x_vars = x_y_vars[:prec]
+        y_vars = x_y_vars[prec:]
+
+        self._sum_polynomials = [0]*(self._prec)
+        for n in range(prec):
+            s_n = x_vars[n] + y_vars[n]
+            for i in range(n):
+                s_n += ((x_vars[i]**(p**(n-i)) + y_vars[i]**(p**(n-i))
+                        - self._sum_polynomials[i]**(p**(n-i))) / p**(n-i))
+            self._sum_polynomials[n] = R(s_n)
+
+        self._prod_polynomials = [x_vars[0] * y_vars[0]] + [0]*(self._prec-1)
+        for n in range(1, prec):
+            x_poly = sum([p**i * x_vars[i]**(p**(n-i)) for i in range(n+1)])
+            y_poly = sum([p**i * y_vars[i]**(p**(n-i)) for i in range(n+1)])
+            p_poly = sum([p**i * self._prod_polynomials[i]**(p**(n-i))
+                         for i in range(n)])
+            p_n = (x_poly*y_poly - p_poly) // p**n
+            self._prod_polynomials[n] = p_n
+
+        # We have to use generic here, because Singular doesn't support
+        # Polynomial Rings over Polynomial Rings. For example,
+        # ``PolynomialRing(GF(5)['x'], ['X', 'Y'],
+        #                  implementation='singular')``
+        # will fail.
+        S = PolynomialRing(self._coefficient_ring, x_y_vars,
+                           implementation='generic')
+        for n in range(prec):
+            self._sum_polynomials[n] = S(self._sum_polynomials[n])
+            self._prod_polynomials[n] = S(self._prod_polynomials[n])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        .. WARNING::
+
+            This representation follows the standard representation in the
+            literature which does not mention `p`.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(PolynomialRing(GF(3),'t'))
+            sage: latex(W)
+            W_{1}(\Bold{F}_{3}[t])
+        """
+        return "W_{%s}(%s)" % (latex(self._prec),
+                               latex(self._coefficient_ring))
+
+    def _repr_(self):
+        """
+        Return a string representation of the ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(QQ, p=2, prec=5)
+            Ring of truncated 2-typical Witt vectors of length 5 over Rational Field
+        """
+        return f"Ring of truncated {self._prime}-typical Witt vectors of "\
+               f"length {self._prec} over {self._coefficient_ring}"
+
+    def cardinality(self):
+        """
+        Return the cardinality of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(17), prec=2).cardinality()
+            289
+            sage: WittVectorRing(QQ, p=2).cardinality()
+            +Infinity
+        """
+        return self._coefficient_ring.cardinality()**(self._prec)
+
+    def characteristic(self):
+        """
+        Return the characteristic of ``self``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(25), p=5, prec=3).characteristic()
+            125
+            sage: WittVectorRing(ZZ, p=2, prec=4).characteristic()
+            0
+            sage: WittVectorRing(Integers(18), p=3, prec=3).characteristic()
+            162
+        """
+        p = self._prime
+        if self._coefficient_ring(p).is_unit():
+            # If p is invertible, W_n(R) is isomorphic to R^n.
+            return self._coefficient_ring.characteristic()
+
+        # This is Jacob Dennerlein's Corollary 3.3. in "Computational
+        # Aspects of Mixed Characteristic Witt Vectors" (preprint)
+        return p**(self._prec-1) * self._coefficient_ring.characteristic()
+
+    def coefficient_ring(self):
+        """
+        Return the coefficient ring of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(Zp(5), p=5)
+            sage: W.coefficient_ring()
+            5-adic Ring with capped relative precision 20
+        """
+        return self._coefficient_ring
+
+    def is_finite(self):
+        """
+        Return whether ``self`` is a finite ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(23)).is_finite()
+            True
+            sage: WittVectorRing(ZZ, p=2).is_finite()
+            False
+        """
+        return self._coefficient_ring.is_finite()
+
+    def precision(self):
+        """
+        Return the length of the truncated Witt vectors in ``length``.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(9), p=3, prec=3).precision()
+            3
+        """
+        return self._prec
+
+    def prime(self):
+        """
+        Return the prime from which the truncated Witt vector ring has been
+        constructed.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(81), prec=3)
+            sage: W.prime()
+            3
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=2)
+            sage: W.prime()
+            7
+        """
+        return self._prime
+
+    def prod_polynomials(self, variables=None):
+        """
+        Return the Witt product polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=3)
+            sage: W.prod_polynomials()
+            [X0*Y0,
+            X1*Y0^5 + X0^5*Y1,
+            -X0^5*X1^4*Y0^20*Y1 + 3*X0^10*X1^3*Y0^15*Y1^2 + 3*X0^15*X1^2*Y0^10*Y1^3 - X0^20*X1*Y0^5*Y1^4 + X2*Y0^25 + X0^25*Y2 + X1^5*Y1^5]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=2)
+            sage: W.prod_polynomials('T0, T1, U0, U1')
+            [T0*U0, T1*U0^2 + T0^2*U1 + 2*T1*U1]
+        """
+        if self._prod_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._prod_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._prod_polynomials[i]) for i in range(self._prec)]
+
+    def random_element(self, *args, **kwds):
+        """
+        Return the length of the truncated Witt vectors in ``length``. Extra
+        arguments are passed to the random generator of the coefficient ring.
+
+        EXAMPLES::
+
+            sage: WittVectorRing(GF(27,'t'), prec=2).random_element()  # random
+            (z3, 2*z3^2 + 1)
+
+            sage: W = WittVectorRing(PolynomialRing(ZZ,'x'), p=3, prec=3)
+            sage: W.random_element(5)  # random
+            (x^5 - 2*x^4 - 4*x^3 - 2*x^2 + 1, -x^5 + 2*x^4 - x - 1,
+            -x^5 + 7*x^4 + 3*x^3 - 24*x^2 - 1)
+        """
+        return self(tuple(self._coefficient_ring.random_element(*args, **kwds)
+                          for _ in range(self._prec)))
+
+    def sum_polynomials(self, variables=None):
+        """
+        Return the Witt sum polynomials.
+
+        INPUT:
+
+        - ``variables`` -- names of the indeterminates (default: ``None``),
+          given as a string, or as a list of strings, whose length must be the
+          double of the precision of the ring. When nothing is given,
+          variables indexed by `X` and `Y` are used.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(5), prec=2)
+            sage: W.sum_polynomials(['T0', 'T1', 'U0', 'U1'])
+            [T0 + U0, -T0^4*U0 + 3*T0^3*U0^2 + 3*T0^2*U0^3 - T0*U0^4 + T1 + U1]
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=3)
+            sage: W.sum_polynomials()
+            [X0 + Y0,
+            -X0*Y0 + X1 + Y1,
+            -X0^3*Y0 - 2*X0^2*Y0^2 - X0*Y0^3 + X0*X1*Y0 + X0*Y0*Y1 - X1*Y1 + X2 + Y2]
+        """
+        if self._sum_polynomials is None:
+            self._generate_sum_and_product_polynomials()
+        if variables is None:
+            return self._sum_polynomials.copy()
+        R = PolynomialRing(self._coefficient_ring, variables,
+                           implementation='generic')
+        return [R(self._sum_polynomials[i]) for i in range(self._prec)]
+
+    def teichmuller_lift(self, x):
+        """
+        Return the Teichmüller lift of ``x`` in ``self``. This lift is
+        sometimes known as the multiplicative lift of ``x``, in order to avoid
+        referring to a nazi mathematician.
path: src/sage/rings/padics/witt_vector_ring.py
user: login: xcaruso
body: > The useful part of the remark is "This lift is sometimes known as the multiplicative lift of `x`". There are many recent papers which use this new terminology, so some users might only know it under that name, so it will help them find it. This part has to stay.

Sure!

> The rest of the sentence might be removed if you like.

I would prefer, yes.
But if you insist for keeping it, that's okay.
created_at: 2025-05-05T18:58:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Okay, I agree with you, but changing this changes the behaviour of many doctests, some of which were interesting. So maybe another PR?
created_at: 2025-05-05T20:45:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: It's a little bit annoying the change the behavior of the public method in a separate PR because it implies to implement a deprecation: during a period of one year, you just display that the behavior will soon change, and only after this, you can make the modification.
created_at: 2025-05-06T05:25:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Oh, that's right! Then what would you think to change this public method name to `phantom_lift`, and add the phantom components method later?
created_at: 2025-05-06T08:20:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: I would prefer then to simply remove this method.
But I don't really understand what's the problem with just implementing `phantom` as follows:
```
    return tuple(self.base_ring()(x) for x in self._phantom)
```
and use `._phantom` instead of `.phantom()` in the code (or possibly also in the doctest, but I think it's not really necessary to expose this in the doctest).
created_at: 2025-05-06T08:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: Yes it was mainly an issue (or should I say, a matter of taste) with the doctests. They are now less interesting with the method mod $p$ (the method almost always only return a tuple with the first component repeated, expect in caracteristic $2$). Anyway, I did the change.
created_at: 2025-05-06T09:11:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: A compromise: you can add the keyword `lift` (with default `False`) to the method `phantom`. Something like:
```
    def phantom(lift=False):
        if lift:
            return tuple(self._phantom)
        else:
            return tuple(self.base_ring()(x) for x in self._phantom)
```
In any case, I think it's better not to return `self._phantom` because it's a list and we do not want to let the possibility to the user to modify it by mistake.
created_at: 2025-05-06T10:12:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: r-mb
body: I like this idea! Done. You are right about the list, I forgot the user could still change it.
created_at: 2025-05-06T12:28:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,941 @@
+r"""
+Witt vectors
+
+Implementation of the class :class:`WittVector` of truncated Witt vectors.
+
+AUTHORS:
+
+- Jacob Dennerlein (2022-11-28): initial version
+- Rubén Muñoz-\-Bertrand (2025-02-13): major refactoring and clean-up
+"""
+
+# ****************************************************************************
+#       Copyright (C) 2025 Rubén Muñoz--Bertrand
+#                          <ruben.munoz--bertrand@univ-fcomte.fr>
+#
+# 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.misc.functional import log
+from sage.misc.latex import tuple_function
+from sage.modules.free_module_element import vector
+from sage.rings.integer import Integer
+from sage.rings.padics.factory import QqFP, Zp
+from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base
+from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.structure.element import CommutativeRingElement
+from sage.structure.richcmp import op_EQ, op_NE
+
+
+def padic_to_vector(x, F):
+    r"""
+    Return the coordinates in `W(F)` of `x \in \mathbb Q_q`.
+
+    INPUT:
+
+    - ``x`` -- an element in an unramified `p`-adic ring
+
+    - ``F`` -- (a field isomorphic to) the residue field
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import padic_to_vector
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: x = (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+        sage: padic_to_vector(x, F)
+        (0, 2*b + 2, 2*b + 3, 4*b, 2*b + 2)
+    """
+    prec = x.parent().precision_cap()
+    if x == 0:
+        return tuple(F.zero() for i in range(prec))
+    p = x.parent().prime()
+    return tuple(F(x.teichmuller_expansion(i).residue().polynomial()) ** (p**i)
+                 for i in range(prec))
+
+
+def vector_to_padic(v, R):
+    r"""
+    Return the element `x \in R` corresponding to the element
+    of `W(\mathbb F_q)` whose coordinates are `v`.
+
+    INPUT:
+
+    - ``v`` -- a tuple of elements in a finite field
+
+    - ``R`` -- (a ring isomorphic to) the corresponding `p`-adic ring
+
+    EXAMPLES::
+
+        sage: from sage.rings.padics.witt_vector import vector_to_padic
+        sage: R.<a> = ZqFM(5^2, prec=5)
+        sage: F.<b> = GF(5^2)
+        sage: v = [F(0), 2*b + 2, 2*b + 3, 4*b, 2*b + 2]
+        sage: vector_to_padic(v, R)
+        (3*a + 4)*5 + (2*a + 1)*5^2 + 5^4
+    """
+    p = R.prime()
+    F = R.residue_field()
+    v = [F(c.polynomial()) for c in v]
+    return sum(R.teichmuller(v[i].nth_root(p**i)) << i for i in range(len(v)))
+
+
+class WittVector(CommutativeRingElement):
+    """
+    Base class for truncated Witt vectors.
+
+    EXAMPLES::
+
+        sage: W = WittVectorRing(GF(25), p=5, prec=3)
+        sage: W(12)
+        (2, 1, 3)
+
+        sage: W = WittVectorRing(Integers(6), p=3, prec=4)
+        sage: w = W([1,2,3,4]) * W([4,5,0,0])
+        sage: w
+        (4, 1, 3, 4)
+
+        sage: TestSuite(w).run()
+    """
+    def __init__(self, parent, vec=None):
+        """
+        Common class for all kinds of Witt vectors.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3))
+            sage: e = W.one(); e
+            (1)
+            sage: e^2
+            (1)
+            sage: -e
+            (2)
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: t^2
+            (1, 1, 0, 2)
+            sage: -t
+            (2, 1, 0, 2)
+            sage: 1/t
+            (1, 1, 1, 0)
+
+            sage: W = WittVectorRing(ZZ, p=5, prec=2)
+            sage: WW = WittVectorRing(ZZ, p=5, prec=2)
+            sage: W((4,10)) * WW((-5,12,1))
+            (-20, -18362)
+            sage: WW((1,2,3)) + W((1,2))
+            (2, -2)
+        """
+        self._prec = parent.precision()
+        B = parent.coefficient_ring()
+        if vec is not None:
+            if isinstance(vec, int) or isinstance(vec, Integer):
+                self._int_to_vector(vec, parent)
+            elif (isinstance(vec, tuple) or isinstance(vec, list)
+                    or isinstance(vec, WittVector)):
+                if len(vec) < self._prec:
+                    raise ValueError(f"{vec} has not the correct length, "
+                                     "expected length has to be at least "
+                                     f"{self._prec}")
+                self._vec = tuple(B(vec[i]) for i in range(self._prec))
+            else:
+                raise ValueError(f"{vec} cannot be interpreted as a Witt "
+                                 "vector")
+        else:
+            self._vec = (B(0) for i in range(self._prec))
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=2, prec=4)
+            sage: t = W([-1,2,-4,8])
+            sage: t[2]
+            -4
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        return self._vec[i]
+
+    def __hash__(self) -> int:
+        """
+        Return the hash of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: hash(t)  # random
+            -2438844084280889141
+        """
+        return hash(self._vec)
+
+    def __invert__(self):
+        """
+        Return the inverse of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=3)
+            sage: w = W([1,1,1])
+            sage: ~w
+            (1, 2, 0)
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: w = W([1,2,0,1])
+            sage: ~w
+            (1, 1, 1, 0)
+            sage: W = WittVectorRing(QQ, p=3, prec=4)
+            sage: w = W([1,1,1,1])
+            sage: ~w
+            (1, -1/4, -81/832, -12887559/359956480)
+        """
+        if not self[0].is_unit():
+            raise ZeroDivisionError(f"inverse of {self} does not exist")
+        P = self.parent()
+
+        if self == P.one():
+            return self
+        if self._prec == 1:
+            return P((self[0]**-1, ))
+
+        if P.coefficient_ring().characteristic() == P.prime():
+            res = P(list([self[0]**-1])
+                    + list(P.coefficient_ring().zero()
+                           for _ in range(self._prec-1)))
+
+            for _ in range(log(self._prec, 2).n().ceil()):
+                res = 2*res - self*res*res
+
+            return res
+
+        # Strategy: Multiply ``self`` by an unknown Witt vector, set equal
+        # to (1, 0, 0, ...), and solve.
+        poly_ring = PolynomialRing(P.coefficient_ring(), 'x')
+        x = poly_ring.gen()
+        inv_vec = (list([self[0]**-1])
+                   + list(poly_ring.zero() for _ in range(self._prec-1)))
+        # We'll fill this in one-by-one
+
+        from sage.rings.padics.witt_vector_ring import WittVectorRing
+        W = WittVectorRing(poly_ring, p=P.prime(), prec=self._prec)
+        for i in range(1, self._prec):
+            inv_vec[i] = x
+            prod_vec = (W(self._vec) * W(inv_vec)).vec()
+            poly = prod_vec[i]
+            try:
+                inv_vec[i] = (-poly.constant_coefficient()
+                              / poly.monomial_coefficient(x))
+            except ZeroDivisionError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+            try:
+                inv_vec[i] = P.coefficient_ring()(inv_vec[i])
+            except ValueError:
+                raise ZeroDivisionError(f"inverse of {self} does not exist")
+
+        return P(inv_vec)
+
+    def __len__(self):
+        """
+        Return the length of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(QQ, p=11, prec=100)
+            sage: t = W.zero()
+            sage: len(t)
+            100
+        """
+        return self._prec
+
+    def _div_(self, other):
+        """
+        Return the quotient of ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t + 1
+            sage: u / t
+            (2, 1, 2, 1)
+        """
+        P = self.parent()
+        # As a slight optimization, we'll check for one ahead of time.
+        if other == P.one():
+            return self
+        elif self == P.one():
+            return ~other
+
+        return self * ~other
+
+    def _int_to_vector(self, k, parent):
+        """
+        Return the image of ``k`` in ``self`` with coefficients in ``parent``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=23, prec=2)
+            sage: W(-123)  # indirect doctest
+            (-123, 50826444131062300759362981690761165250849615528)
+        """
+        p = parent.prime()
+        R = parent.coefficient_ring()
+
+        if p == R.characteristic():
+            Z = Zp(p, prec=self._prec + 1, type='fixed-mod')
+            self._vec = padic_to_vector(Z(k), R)
+
+        should_negate = False
+        if k < 0:
+            k = -k
+            should_negate = True
+
+        vec_k = [k]
+        for n in range(1, self._prec):
+            total = (
+                k - k**(p**n)
+                - sum(p**(n-i) * vec_k[n-i]**(p**i) for i in range(1, n))
+            )
+            total //= p**n
+            vec_k.append(total)
+
+        if should_negate:
+            if p == 2:
+                vec_k = (
+                    parent(vec_k)
+                    * parent((tuple(-1 for _ in range(self._prec))))
+                ).vec()
+            else:
+                vec_k = (-x for x in vec_k)
+
+        self._vec = tuple([R(x) for x in vec_k])
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=7, prec=3)
+            sage: t = W([6,1,6])
+            sage: latex(t)
+            \left(6, 1, 6\right)
+        """
+        return tuple_function(self._vec)
+
+    def _neg_(self):
+        """
+        Return the opposite of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: -t
+            (2, 1, 0, 2)
+        """
+        P = self.parent()
+        # If p == 2, -1 == (-1, -1, -1, ...)
+        # Otherwise, -1 == (-1, 0, 0, ...)
+        if P.prime() == 2:
+            all_ones = P(tuple(-1 for _ in range(self._prec)))
+            return all_ones * self
+        neg_vec = tuple(-self[i] for i in range(self._prec))
+        return P(neg_vec)
+
+    def _repr_(self) -> str:
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(ZZ, p=3, prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        return '(' + ', '.join(map(str, self._vec)) + ')'
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+        """
+        if not isinstance(other, WittVector):
+            return NotImplemented
+        if op == op_EQ:
+            return self._vec == other.vec()
+        if op == op_NE:
+            return self._vec != other.vec()
+        return NotImplemented
+
+    def _vector_(self, R=None):
+        """
+        Return the underlying vector from ``self``.
+
+        INPUT:
+
+        - ``R`` -- the base ring (default: ``None``) of the returned vector,
+          when no ring is given the coefficient ring of ``self`` is used.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(QQ, p=29, prec=3)
+             sage: t = W([-10,50,2/5])
+             sage: vector(t)
+             (-10, 50, 2/5)
+             sage: vector(GF(3), t)
+             (2, 2, 1)
+        """
+        if R is None:
+            return vector(self._vec)
+        return vector(R, self._vec)
+
+    def vec(self):
+        """
+        Return the underlying tuple of the truncated Witt vector.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), p=7, prec=3)
+            sage: v = W([1,2,3])
+            sage: v.vec()
+            (1, 2, 3)
+        """
+        return self._vec
+
+
+class WittVector_phantom(WittVector):
+    r"""
+    Child class for truncated Witt vectors using the ``phantom``
+    algorithm.
+
+    Here, a Witt vector with coefficients in `\mathbb F_q` (respectively in a
+    polynomial ring over that field, is lifted to another Witt vector with
+    coefficients in `\mathbb Q_q` (respectively in the corresponding
+    polynomial ring with coefficients in that field), whose phantom components
+    are stored. Computations are done with these phantom components, and the
+    corresponding Witt vectors in `\mathbb F_q` (respectively in the
+    polynomial ring) are computed from them only when needed.
+
+    EXAMPLES:
+
+        sage: W = WittVectorRing(GF(7), prec=5)
+        sage: t = W.one()
+        sage: t
+        (1, 0, 0, 0, 0)
+        sage: t.phantom()
+        [1, 1, 1, 1, 1]
+        sage: u = 7*t
+        sage: u.phantom()
+        [7, 7, 7, 7, 7]
+        sage: u[1]
+        1
+    """
+    def __init__(self, parent, vec=None, phantom=None):
+        """
+        Initialises ``self`` from the data.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7), prec=3)
+            sage: e = W.one(); e
+            (1, 0, 0)
+            sage: 7*e
+            (0, 1, 0)
+        """
+        self._prec = parent.precision()
+        R = parent.coefficient_ring()
+        p = parent.prime()
+        base = R
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            base = R.base()
+        base_lift = QqFP(base.cardinality(), prec=self._prec,
+                         modulus=base.modulus(), names=(base.variable_name(),),
+                         res_name=base.variable_name())
+        lift = base_lift
+        if (isinstance(R, PolynomialRing_generic)
+                or isinstance(R, MPolynomialRing_base)):
+            lift = R.change_ring(base_lift)
+        if phantom is not None:
+            self._phantom = phantom
+            self._vec = (R(phantom[0]),)
+            self._powers = [phantom[0]]
+        elif vec is None:
+            zero = R.zero()
+            self._vec = (zero for i in range(self._prec))
+            self._phantom = self._prec * [zero]
+        elif isinstance(vec, WittVector_phantom):
+            self._vec = vec._vec
+            self._phantom = vec._phantom
+            self._powers = vec._powers
+        elif isinstance(vec, int) or isinstance(vec, Integer):
+            self._int_to_vector(vec, parent)
+            y = base_lift(vec)
+            self._powers = [y]
+            self._phantom = self._prec * [y]
+        elif (isinstance(vec, tuple) or isinstance(vec, list)
+                or isinstance(vec, WittVector)):
+            if len(vec) < self._prec:
+                raise ValueError(f"{vec} has not the correct length, "
+                                 "expected length has to be at least "
+                                 f"{self._prec}")
+            # We compute the phantom components
+            self._vec = tuple(R(vec[i]) for i in range(self._prec))
+            x = [lift(v) for v in self._vec]
+            self._phantom = [x[0]]
+            for n in range(1, self._prec):
+                for i in range(n):
+                    x[i] = x[i] ** p
+                self._phantom.append(sum(x[i] * p**i for i in range(n+1)))
+            self._powers = None
+        else:
+            raise ValueError(f"{vec} cannot be interpreted as a Witt vector")
+        CommutativeRingElement.__init__(self, parent)
+
+    def __getitem__(self, i):
+        """
+        Return the ``i``-th component of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(13,'t'), prec=3)
+            sage: t = W([10,5,2])
+            sage: t[1]
+            5
+        """
+        if i < 0 or i >= self._prec:
+            raise IndexError("index out of the truncated Witt vector range")
+        self._compute_vector(i+1)
+        return self._vec[i]
+
+    def _add_(self, other):
+        """
+        Return the sum of the phantom components of the lift of ``self`` and
+        ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(11,'t'), prec=3)
+            sage: t = W([6,1,6])
+            sage: u = W([0,5,0])
+            sage: r = t + u
+            sage: r.phantom()
+            [6, 6 + 3*11 + 9*11^2, 6 + 3*11 + 3*11^2]
+        """
+        phantom = [self._phantom[i] + other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _compute_vector(self, prec=None):
+        """
+        Computes the Witt vector ``self`` from the ghost components of its
+        lift.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(17), prec=3)
+            sage: t = W(phantom=[1,1,290]); t  # indirect doctest
+            (1, 0, 1)
+        """
+        maxprec = self._prec
+        if prec is None:
+            prec = maxprec
+        else:
+            prec = min(prec, maxprec)
+        phantom = self._phantom
+        powers = self._powers
+        p = self.parent()._prime
+        mod = self.parent().coefficient_ring()
+        for n in range(len(self._vec), prec):
+            for i in range(n):
+                powers[i] = powers[i] ** p
+            c = (phantom[n] - sum(powers[i] * p**i for i in range(n))) // p**n
+            self._vec += (mod(c),)
+            self._powers.append(c)
+
+    def _latex_(self):
+        r"""
+        Return a `\LaTeX` representation of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(2), prec=4)
+            sage: t = W(phantom=[1,1,1,9])
+            sage: latex(t)
+            \left(1, 0, 0, 1\right)
+        """
+        self._compute_vector()
+        return super()._latex_()
+
+    def _mul_(self, other):
+        """
+        Return the product of the phantom components of the lift of ``self``
+        and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(7,'t'), prec=3)
+            sage: t = W([1,0,5])
+            sage: u = W([0,1,3])
+            sage: r = t * u
+            sage: r.phantom()
+            [0, 7, 7 + 3*7^2 + 5*7^3]
+        """
+        phantom = [self._phantom[i] * other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _neg_(self):
+        """
+        Return the opposite of the phantom component of the lift of ``self``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(23,'t'), prec=4)
+            sage: t = W([1,0,1,17])
+            sage: r = -t
+            sage: r.phantom()
+            [22 + 22*23 + 22*23^2 + 22*23^3, 22 + 22*23 + 22*23^2 + 22*23^3,
+            22 + 22*23 + 21*23^2 + 22*23^3, 22 + 22*23 + 21*23^2 + 5*23^3]
+        """
+        phantom = [-v for v in self._phantom]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _repr_(self):
+        """
+        Return a string representation.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1]); t
+            (1, 2, 0, 1)
+        """
+        self._compute_vector()
+        return super()._repr_()
+
+    def _richcmp_(self, other, op) -> bool:
+        """
+        Compare ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3), prec=4)
+            sage: t = W([1,2,0,1])
+            sage: u = 1/t
+            sage: u == t
+            False
+            sage: t != t
+            False
+
+            sage: W_standard = WittVectorRing(GF(3), prec=4, algorithm='standard')
+            sage: u + t == W_standard(u) + W_standard(t)
+            True
+            sage: W_finotti = WittVectorRing(GF(3), prec=4, algorithm='finotti')
+            sage: u * t == W_finotti(u) * W_standard(t)
+            True
+        """
+        self._compute_vector()
+        return super()._richcmp_(other, op)
+
+    def _sub_(self, other):
+        """
+        Return the difference of the phantom components of the lift of
+        ``self`` and ``other``.
+
+        EXAMPLES::
+
+            sage: W = WittVectorRing(GF(3,'t'), prec=3)
+            sage: t = W([1,2,2])
+            sage: u = W([1,0,2])
+            sage: r = t - u
+            sage: r.phantom()
+            [0, 2*3, 2*3 + 2*3^2]
+        """
+        phantom = [self._phantom[i] - other.phantom()[i] for i in range(self._prec)]
+        return self.__class__(self.parent(), phantom=phantom)
+
+    def _vector_(self, R):
+        """
+        Return the underlying vector from ``self``.
+
+        EXAMPLES::
+
+             sage: W = WittVectorRing(GF(13), prec=2)
+             sage: t = W([1, 3])
+             sage: vector(t)
+             (1, 3)
+        """
+        self._compute_vector()
+        return super()._vector_(R)
+
+    def phantom(self):
path: src/sage/rings/padics/witt_vector.py
user: login: xcaruso
body: Perfect!
created_at: 2025-05-06T13:06:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-01-21T09:04:51Z
body: [Documentation preview for this PR](https://doc-pr-39355--sagemath.netlify.app/html/en) (built with commit 65bcbdc8d81e17014fce2493adc193d82f5bb6fd; [changes](https://doc-pr-39355--sagemath.netlify.app/CHANGES.html)) is ready! :tada:
This preview will update shortly after each push to this PR.
<!-- Sticky Pull Request Commentpreview-comment -->
author_association: NONE
user: login: r-mb
created_at: 2025-02-13T22:56:38Z
body: @fchapoton C'est bon pour moi, si les tests passent.
author_association: CONTRIBUTOR
user: login: r-mb
created_at: 2025-02-13T23:05:16Z
body: To everyone: I did a major cleanup of @nielrenned's code on Witt vectors during SageDays 128. My hope is to see this code finally merged into SageMath.
There was a few edge case bugs and some rare implementations missing, but from what I can tell after being a long time user of that code is that it is robust and mathematically correct, and I hope my changes did not affect that.

I wonder though whether the function `_fast_char_p_power` is at the right place, and if we shouldn't instead look into why exponentiation of polynomials is so slow when it shouldn't. Also, I was surprised to see that no function in all of SageMath does what `WittVectorRing._generate_binomial_table` does: computing a table of binomial coefficients.
author_association: CONTRIBUTOR
user: login: xcaruso
created_at: 2025-05-06T13:10:11Z
body: For me, everything is fine! Thanks for taking into account all my numerous comments!
I will give a positive review as soon as the automatic checks will all pass.
author_association: CONTRIBUTOR
user: login: xcaruso
created_at: 2025-05-06T20:14:32Z
body: The reported failures are nothing to do with this PR. So everything's in order.
author_association: CONTRIBUTOR