From 2368519b76c203ec17c68dfff688b1650a6108fe Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 19 Aug 2025 18:32:42 +0200 Subject: [PATCH 01/24] completion of polynomial rings and their fraction fields --- src/sage/rings/fraction_field.py | 91 +++++++++++++++ src/sage/rings/laurent_series_ring.py | 6 +- src/sage/rings/polynomial/meson.build | 1 + src/sage/rings/polynomial/polynomial_ring.py | 111 +++++++++++++++---- src/sage/rings/power_series_ring.py | 15 ++- src/sage/structure/category_object.pyx | 32 +++++- 6 files changed, 225 insertions(+), 31 deletions(-) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 5a633c8a846..82603ed6be1 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1206,6 +1206,97 @@ def _coerce_map_from_(self, R): return super()._coerce_map_from_(R) + def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + r""" + Return the completion of this rational functions ring with + respect to the irreducible polynomial `p`. + + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``name`` (default: ``None``) -- a string, the variable name; + if ``None`` and the completion is at `0`, the name of the + variable is this polynomial ring is reused + + - ``residue_name`` (default: ``None``) -- a string, the variable + name for the residue field (only relevant for places of degree + at least `2`) + + - ``names`` (default: ``None``) -- a tuple of strings with the + previous variable names + + EXAMPLES:: + + sage: A. = PolynomialRing(QQ) + sage: K = A.fraction_field() + + Without any argument, this method constructs the completion at + the ideal `x`:: + + sage: Kx = K.completion() + sage: Kx + Laurent Series Ring in x over Rational Field + + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: K1. = K.completion(x - 1) + sage: K1 + Laurent Series Ring in u over Rational Field + sage: x - u + 1 + + :: + + sage: K2. = K.completion(x^2 + x + 1) + sage: K2 + Laurent Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + When the precision is infinity, a lazy series ring is returned:: + + sage: # needs sage.combinat + sage: L = K.completion(x, prec=oo); L + Lazy Laurent Series Ring in x over Rational Field + """ + from sage.rings.polynomial.morphism import MorphismToCompletion + if names is not None: + if name is not None or residue_name is not None: + raise ValueError("") + if not isinstance(names, (list, tuple)): + raise TypeError("names must a a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + x = self.gen() + if p is None: + p = x + if p == x and name is None: + name = self.variable_name() + incl = MorphismToCompletion(self, p, prec, name, residue_name) + C = incl.codomain() + if C.has_coerce_map_from(self): + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + ring = self.ring() + if not C.has_coerce_map_from(ring): + C.register_coercion(incl * self.coerce_map_from(ring)) + return C + class FractionFieldEmbedding(DefaultConvertMap_unique): r""" diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 80a84b67f26..c95cfbbbb4a 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -221,12 +221,14 @@ def __classcall__(cls, *args, **kwds): 'q' """ from .power_series_ring import PowerSeriesRing - + if 'default_prec' in kwds and kwds['default_prec'] is infinity: + from sage.rings.lazy_series_ring import LazyLaurentSeriesRing + del kwds['default_prec'] + return LazyLaurentSeriesRing(*args, **kwds) if not kwds and len(args) == 1 and isinstance(args[0], (PowerSeriesRing_generic, LazyPowerSeriesRing)): power_series = args[0] else: power_series = PowerSeriesRing(*args, **kwds) - return UniqueRepresentation.__classcall__(cls, power_series) def __init__(self, power_series): diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index e7de57dc2fb..954fe217845 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -19,6 +19,7 @@ py.install_sources( 'laurent_polynomial_mpair.pxd', 'laurent_polynomial_ring.py', 'laurent_polynomial_ring_base.py', + 'morphism.py', 'msolve.py', 'multi_polynomial.pxd', 'multi_polynomial_element.py', diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index e0d1ac37eb3..61ea633186d 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -671,23 +671,42 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, extras=None): + def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): r""" - Return the completion of ``self`` with respect to the irreducible - polynomial ``p``. + Return the completion of this polynomial ring with respect to + the irreducible polynomial ``p``. - Currently only implemented for ``p=self.gen()`` (the default), i.e. you - can only complete `R[x]` with respect to `x`, the result being a ring - of power series in `x`. The ``prec`` variable controls the precision - used in the power series ring. If ``prec`` is `\infty`, then this - returns a :class:`LazyPowerSeriesRing`. + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``name`` (default: ``None``) -- a string, the variable name; + if ``None`` and the completion is at `0`, the name of the + variable is this polynomial ring is reused + + - ``residue_name`` (default: ``None``) -- a string, the variable + name for the residue field (only relevant for places of degree + at least `2`) + + - ``names`` (default: ``None``) -- a tuple of strings with the + previous variable names EXAMPLES:: sage: P. = PolynomialRing(QQ) sage: P Univariate Polynomial Ring in x over Rational Field - sage: PP = P.completion(x) + + Without any argument, this method constructs the completion at + the ideal `x`:: + + sage: PP = P.completion() sage: PP Power Series Ring in x over Rational Field sage: f = 1 - x @@ -701,6 +720,42 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g 1 - x + O(x^20) + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: C1. = P.completion(x - 1) + sage: C1 + Power Series Ring in u over Rational Field + + A coercion map from the polynomial ring to its completion is set:: + + sage: x - u + 1 + + It is possible to complete at an ideal generated by a polynomial + of higher degree. In this case, we should nevertheless provide an + extra name for the generator of the residue field:: + + sage: C2. = P.completion(x^2 + x + 1) + sage: C2 + Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + Constructing the completion at the place of infinity also works:: + + sage: C3. = P.completion(infinity) + sage: C3 + Laurent Series Ring in w over Rational Field + sage: C3(x) + w^-1 + + When the precision is infinity, a lazy series ring is returned:: + sage: # needs sage.combinat sage: PP = P.completion(x, prec=oo); PP Lazy Taylor Series Ring in x over Rational Field @@ -709,16 +764,34 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g == f True """ - if p is None or str(p) == self._names[0]: - if prec == float('inf'): - from sage.rings.lazy_series_ring import LazyPowerSeriesRing - return LazyPowerSeriesRing(self.base_ring(), names=(self._names[0],), - sparse=self.is_sparse()) - from sage.rings.power_series_ring import PowerSeriesRing - return PowerSeriesRing(self.base_ring(), name=self._names[0], - default_prec=prec, sparse=self.is_sparse()) - - raise NotImplementedError("cannot complete %s with respect to %s" % (self, p)) + from sage.rings.polynomial.morphism import MorphismToCompletion + if names is not None: + if name is not None or residue_name is not None: + raise ValueError("") + if not isinstance(names, (list, tuple)): + raise TypeError("names must a a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + x = self.gen() + if p is None: + p = x + if p == x and name is None: + name = self.variable_name() + if p is sage.rings.infinity.infinity: + ring = self.fraction_field() + else: + ring = self + incl = MorphismToCompletion(ring, p, prec, name, residue_name) + C = incl.codomain() + if C.has_coerce_map_from(ring): + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + if not (ring is self and C.has_coerce_map_from(self)): + C.register_coercion(incl * ring.coerce_map_from(self)) + return C def _coerce_map_from_base_ring(self): """ diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index 129477ccc8c..a3bdbd178d1 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -392,7 +392,7 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, # the following is the original, univariate-only code - if isinstance(name, (int, integer.Integer)): + if isinstance(name, (int, integer.Integer)) or name is infinity: default_prec = name if names is not None: name = names @@ -412,17 +412,21 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, if not (name is None or isinstance(name, str)): raise TypeError("variable name must be a string or None") - if base_ring in _Fields: + if base_ring not in _CommutativeRings: + raise TypeError("base_ring must be a commutative ring") + + if default_prec is infinity: + from sage.rings.lazy_series_ring import LazyPowerSeriesRing + R = LazyPowerSeriesRing(base_ring, name, sparse) + elif base_ring in _Fields: R = PowerSeriesRing_over_field(base_ring, name, default_prec, sparse=sparse, implementation=implementation) elif base_ring in _IntegralDomains: R = PowerSeriesRing_domain(base_ring, name, default_prec, sparse=sparse, implementation=implementation) - elif base_ring in _CommutativeRings: + else: R = PowerSeriesRing_generic(base_ring, name, default_prec, sparse=sparse, implementation=implementation) - else: - raise TypeError("base_ring must be a commutative ring") return R @@ -614,7 +618,6 @@ def variable_names_recursive(self, depth=None): ('y', 'z') """ if depth is None: - from sage.rings.infinity import infinity depth = infinity if depth <= 0: diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index aaf0c285322..5620bf06189 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -370,13 +370,37 @@ cdef class CategoryObject(SageObject): sage: B. = EquationOrder(x^2 + 3) # needs sage.rings.number_field sage: z.minpoly() # needs sage.rings.number_field x^2 + 3 + + If the ring has less than `n` generators, the generators + of the base rings are happened recursively:: + + sage: S. = PolynomialRing(R) + sage: T. = PolynomialRing(S) + sage: T._first_ngens(1) + (z,) + sage: T._first_ngens(2) + (z, y) + sage: T._first_ngens(3) + (z, y, x) """ names = self._defining_names() - if isinstance(names, (list, tuple)): + if not isinstance(names, (list, tuple)): + # case of Family + it = iter(names) + names = [] + for _ in range(n): + try: + names.append(next(it)) + except StopIteration: + break + names = tuple(names) + m = n - len(names) + if m <= 0: return names[:n] - # case of Family - it = iter(names) - return tuple(next(it) for i in range(n)) + else: + if hasattr(self, 'base'): + names += tuple(self(x) for x in self.base()._first_ngens(m)) + return names @cached_method def _defining_names(self): From 573dae0c900079008640f142bb969d0aca66f3b8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 19 Aug 2025 23:47:14 +0200 Subject: [PATCH 02/24] file morphism.py --- src/sage/rings/polynomial/morphism.py | 158 ++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/sage/rings/polynomial/morphism.py diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py new file mode 100644 index 00000000000..edeea117064 --- /dev/null +++ b/src/sage/rings/polynomial/morphism.py @@ -0,0 +1,158 @@ +r""" +Morphisms attached to polynomial rings. +""" + +# ***************************************************************************** +# Copyright (C) 2025 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.structure.category_object import normalize_names +from sage.categories.rings import Rings + +from sage.rings.morphism import RingHomomorphism +from sage.rings.integer_ring import ZZ +from sage.rings.infinity import Infinity +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.laurent_series_ring import LaurentSeriesRing + + +class MorphismToCompletion(RingHomomorphism): + r""" + Morphisms from a polynomial ring (or its fraction field) + to the completion at one place. + + TESTS:: + + sage: A. = GF(5)[] + sage: B. = A.completion(t+1) + sage: f = B.coerce_map_from(A) + sage: type(f) + + + sage: TestSuite(f).run(skip='_test_category') + """ + def __init__(self, domain, place, prec, name, residue_name): + r""" + Initialize this morphism. + + INPUT: + + - ``domain`` -- a polynomial ring of its fraction field + + - ``place`` -- an irreducible polynomial or ``Infinity`` + + - ``prec`` -- an integer or ``Infinity`` + + - ``name`` -- a string, the variable name of the uniformizer + + - ``residue_name`` -- a string, the variable name of the + generator of the residue ring + + TESTS:: + + sage: A. = ZZ[] + sage: B. = A.completion(2*t + 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of the place is not a unit + + :: + + sage: A. = QQ[] + sage: B. = A.completion(x^2 + 2*x + 1) + Traceback (most recent call last): + ... + ValueError: place must be Infinity or an irreducible polynomial + """ + if domain.is_field(): + ring = domain.ring() + SeriesRing = LaurentSeriesRing + else: + ring = domain + SeriesRing = PowerSeriesRing + k = base = ring.base_ring() + x = ring.gen() + if place is Infinity: + pass + elif place in ring: + place = ring(place) + if place.leading_coefficient().is_unit(): + place = ring(place.monic()) + if not place.is_irreducible(): + raise ValueError("place must be Infinity or an irreducible polynomial") + else: + raise NotImplementedError("the leading coefficient of the place is not a unit") + else: + raise ValueError("place must be Infinity or an irreducible polynomial") + self._place = place + if name is None: + raise ValueError("you must specify a variable name") + name = normalize_names(1, name) + if place is Infinity: + codomain = LaurentSeriesRing(base, names=name, default_prec=prec) + image = codomain.one() >> 1 + elif place.degree() == 1: + codomain = SeriesRing(base, names=name, default_prec=prec) + image = codomain.gen() - place[0] + else: + if residue_name is None: + raise ValueError("you must specify a variable name for the residue field") + residue_name = normalize_names(1, residue_name) + k = base.extension(place, names=residue_name) + codomain = SeriesRing(k, names=name, default_prec=prec) + image = codomain.gen() + k.gen() + parent = domain.Hom(codomain, category=Rings()) + RingHomomorphism.__init__(self, parent) + self._image = image + self._k = k + self._q = k.cardinality() + + def _repr_type(self): + r""" + Return a string that describes the type of this morphism. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f # indirect doctest + Completion morphism: + From: Univariate Polynomial Ring in x over Rational Field + To: Power Series Ring in u over Rational Field + """ + return "Completion" + + def place(self): + r""" + Return the place at which we completed. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f.place() + x + 1 + """ + return self._place + + def _call_(self, P): + r""" + Return the image of ``P`` under this morphism. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f(x) # indirect doctest + -1 + u + """ + return P(self._image) From ce729800871080c4f52357d59a8e6d42bdec161b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 08:54:03 +0200 Subject: [PATCH 03/24] fix failing doctests --- src/sage/rings/polynomial/morphism.py | 14 +++---- src/sage/rings/polynomial/polynomial_ring.py | 39 +++++++++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py index edeea117064..7651573e108 100644 --- a/src/sage/rings/polynomial/morphism.py +++ b/src/sage/rings/polynomial/morphism.py @@ -78,14 +78,15 @@ def __init__(self, domain, place, prec, name, residue_name): SeriesRing = PowerSeriesRing k = base = ring.base_ring() x = ring.gen() + sparse = ring.is_sparse() if place is Infinity: pass elif place in ring: place = ring(place) if place.leading_coefficient().is_unit(): place = ring(place.monic()) - if not place.is_irreducible(): - raise ValueError("place must be Infinity or an irreducible polynomial") + # We do not check irreducibility; it causes too much troubles: + # it can be long, be not implemented and even sometimes fail else: raise NotImplementedError("the leading coefficient of the place is not a unit") else: @@ -95,23 +96,22 @@ def __init__(self, domain, place, prec, name, residue_name): raise ValueError("you must specify a variable name") name = normalize_names(1, name) if place is Infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec) + codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) image = codomain.one() >> 1 - elif place.degree() == 1: - codomain = SeriesRing(base, names=name, default_prec=prec) + elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 + codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) image = codomain.gen() - place[0] else: if residue_name is None: raise ValueError("you must specify a variable name for the residue field") residue_name = normalize_names(1, residue_name) k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec) + codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) image = codomain.gen() + k.gen() parent = domain.Hom(codomain, category=Rings()) RingHomomorphism.__init__(self, parent) self._image = image self._k = k - self._q = k.cardinality() def _repr_type(self): r""" diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 61ea633186d..395500af1ee 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -671,7 +671,7 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + def completion(self, p=None, prec=20, extras=None, names=None): r""" Return the completion of this polynomial ring with respect to the irreducible polynomial ``p``. @@ -686,16 +686,14 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): ``Infinity``, return a :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - ``name`` (default: ``None``) -- a string, the variable name; - if ``None`` and the completion is at `0`, the name of the - variable is this polynomial ring is reused + - ``extras`` (default: ``None``) -- ignored; for compatibility + with the construction mecanism - - ``residue_name`` (default: ``None``) -- a string, the variable - name for the residue field (only relevant for places of degree - at least `2`) - - - ``names`` (default: ``None``) -- a tuple of strings with the - previous variable names + - ``names`` (default: ``None``) -- a tuple of strings containing + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + - the variable name for the residue field (only relevant for + places of degree at least `2`) EXAMPLES:: @@ -763,19 +761,34 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): 1 + x + x^2 + O(x^3) sage: 1 / g == f True + + TESTS:: + + sage: P.completion('x') + Power Series Ring in x over Rational Field + sage: P.completion('y') + Power Series Ring in y over Rational Field + sage: Pz. = P.completion('y') + Traceback (most recent call last): + ... + ValueError: conflict of variable names """ from sage.rings.polynomial.morphism import MorphismToCompletion + name = residue_name = None if names is not None: - if name is not None or residue_name is not None: - raise ValueError("") if not isinstance(names, (list, tuple)): - raise TypeError("names must a a list or a tuple") + raise TypeError("names must be a list or a tuple") name = names[0] if len(names) > 1: residue_name = names[1] x = self.gen() if p is None: p = x + elif isinstance(p, str): + if name is not None and name != p: + raise ValueError("conflict of variable names") + name = p + p = x if p == x and name is None: name = self.variable_name() if p is sage.rings.infinity.infinity: From d990b8f66d2cab1f4099bfa105fb6692b366e20d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 09:57:25 +0200 Subject: [PATCH 04/24] fix doctest --- src/sage/rings/polynomial/polynomial_ring.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 395500af1ee..47c447d4374 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -690,8 +690,10 @@ def completion(self, p=None, prec=20, extras=None, names=None): with the construction mecanism - ``names`` (default: ``None``) -- a tuple of strings containing + - the variable name; if not given and the completion is at `0`, the name of the variable is this polynomial ring is reused + - the variable name for the residue field (only relevant for places of degree at least `2`) From eff3e2250acbba2be964461c4c6a4c471c1f9507 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 11:44:26 +0200 Subject: [PATCH 05/24] avoid recursive loop in coercion --- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 47c447d4374..d379f50644d 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -800,7 +800,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): incl = MorphismToCompletion(ring, p, prec, name, residue_name) C = incl.codomain() if C.has_coerce_map_from(ring): - if C(x) != incl(x): + if C(x) != incl._image: raise ValueError("a different coercion map is already set; try to change the variable name") else: C.register_coercion(incl) From 2f06fbc7373af55c6643b63ab8c41dafe3681303 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 13:49:09 +0200 Subject: [PATCH 06/24] address easy remarks by user202729 --- src/sage/rings/polynomial/polynomial_ring.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index d379f50644d..abca12bc27e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -697,6 +697,9 @@ def completion(self, p=None, prec=20, extras=None, names=None): - the variable name for the residue field (only relevant for places of degree at least `2`) + The argument ``names`` is usually implicitly given by the `.<...>` + syntactic sugar (see examples below). + EXAMPLES:: sage: P. = PolynomialRing(QQ) @@ -704,7 +707,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): Univariate Polynomial Ring in x over Rational Field Without any argument, this method constructs the completion at - the ideal `x`:: + the ideal `(x)`:: sage: PP = P.completion() sage: PP From e6a894242980b6f3669e5854213b60d9a71f3a64 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 13:52:16 +0200 Subject: [PATCH 07/24] typo --- src/sage/structure/category_object.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 5620bf06189..880ebf51cb8 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -372,7 +372,7 @@ cdef class CategoryObject(SageObject): x^2 + 3 If the ring has less than `n` generators, the generators - of the base rings are happened recursively:: + of the base rings are appended recursively:: sage: S. = PolynomialRing(R) sage: T. = PolynomialRing(S) From 5f008e644fd2cfcd68fec5202c10bb5a190106e2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:32:25 +0200 Subject: [PATCH 08/24] remove class MorphismToCompletion --- src/sage/rings/fraction_field.py | 30 +- src/sage/rings/polynomial/meson.build | 1 - src/sage/rings/polynomial/morphism.py | 158 -------- src/sage/rings/polynomial/polynomial_ring.py | 373 ++++++++++++------- 4 files changed, 235 insertions(+), 327 deletions(-) delete mode 100644 src/sage/rings/polynomial/morphism.py diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 82603ed6be1..163d5c46233 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1206,7 +1206,7 @@ def _coerce_map_from_(self, R): return super()._coerce_map_from_(R) - def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + def completion(self, p=None, prec=20, names=None): r""" Return the completion of this rational functions ring with respect to the irreducible polynomial `p`. @@ -1221,14 +1221,6 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): ``Infinity``, return a :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - ``name`` (default: ``None``) -- a string, the variable name; - if ``None`` and the completion is at `0`, the name of the - variable is this polynomial ring is reused - - - ``residue_name`` (default: ``None``) -- a string, the variable - name for the residue field (only relevant for places of degree - at least `2`) - - ``names`` (default: ``None``) -- a tuple of strings with the previous variable names @@ -1238,7 +1230,7 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): sage: K = A.fraction_field() Without any argument, this method constructs the completion at - the ideal `x`:: + the ideal `(x)`:: sage: Kx = K.completion() sage: Kx @@ -1271,23 +1263,11 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): sage: L = K.completion(x, prec=oo); L Lazy Laurent Series Ring in x over Rational Field """ - from sage.rings.polynomial.morphism import MorphismToCompletion - if names is not None: - if name is not None or residue_name is not None: - raise ValueError("") - if not isinstance(names, (list, tuple)): - raise TypeError("names must a a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - x = self.gen() - if p is None: - p = x - if p == x and name is None: - name = self.variable_name() - incl = MorphismToCompletion(self, p, prec, name, residue_name) + from sage.rings.polynomial.polynomial_ring import morphism_to_completion + incl = morphism_to_completion(self, p, prec, names) C = incl.codomain() if C.has_coerce_map_from(self): + x = self.gen() if C(x) != incl(x): raise ValueError("a different coercion map is already set; try to change the variable name") else: diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index 954fe217845..e7de57dc2fb 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -19,7 +19,6 @@ py.install_sources( 'laurent_polynomial_mpair.pxd', 'laurent_polynomial_ring.py', 'laurent_polynomial_ring_base.py', - 'morphism.py', 'msolve.py', 'multi_polynomial.pxd', 'multi_polynomial_element.py', diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py deleted file mode 100644 index 7651573e108..00000000000 --- a/src/sage/rings/polynomial/morphism.py +++ /dev/null @@ -1,158 +0,0 @@ -r""" -Morphisms attached to polynomial rings. -""" - -# ***************************************************************************** -# Copyright (C) 2025 Xavier Caruso -# -# 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. -# http://www.gnu.org/licenses/ -# ***************************************************************************** - -from sage.structure.category_object import normalize_names -from sage.categories.rings import Rings - -from sage.rings.morphism import RingHomomorphism -from sage.rings.integer_ring import ZZ -from sage.rings.infinity import Infinity -from sage.rings.power_series_ring import PowerSeriesRing -from sage.rings.laurent_series_ring import LaurentSeriesRing - - -class MorphismToCompletion(RingHomomorphism): - r""" - Morphisms from a polynomial ring (or its fraction field) - to the completion at one place. - - TESTS:: - - sage: A. = GF(5)[] - sage: B. = A.completion(t+1) - sage: f = B.coerce_map_from(A) - sage: type(f) - - - sage: TestSuite(f).run(skip='_test_category') - """ - def __init__(self, domain, place, prec, name, residue_name): - r""" - Initialize this morphism. - - INPUT: - - - ``domain`` -- a polynomial ring of its fraction field - - - ``place`` -- an irreducible polynomial or ``Infinity`` - - - ``prec`` -- an integer or ``Infinity`` - - - ``name`` -- a string, the variable name of the uniformizer - - - ``residue_name`` -- a string, the variable name of the - generator of the residue ring - - TESTS:: - - sage: A. = ZZ[] - sage: B. = A.completion(2*t + 1) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit - - :: - - sage: A. = QQ[] - sage: B. = A.completion(x^2 + 2*x + 1) - Traceback (most recent call last): - ... - ValueError: place must be Infinity or an irreducible polynomial - """ - if domain.is_field(): - ring = domain.ring() - SeriesRing = LaurentSeriesRing - else: - ring = domain - SeriesRing = PowerSeriesRing - k = base = ring.base_ring() - x = ring.gen() - sparse = ring.is_sparse() - if place is Infinity: - pass - elif place in ring: - place = ring(place) - if place.leading_coefficient().is_unit(): - place = ring(place.monic()) - # We do not check irreducibility; it causes too much troubles: - # it can be long, be not implemented and even sometimes fail - else: - raise NotImplementedError("the leading coefficient of the place is not a unit") - else: - raise ValueError("place must be Infinity or an irreducible polynomial") - self._place = place - if name is None: - raise ValueError("you must specify a variable name") - name = normalize_names(1, name) - if place is Infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.one() >> 1 - elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 - codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() - place[0] - else: - if residue_name is None: - raise ValueError("you must specify a variable name for the residue field") - residue_name = normalize_names(1, residue_name) - k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() + k.gen() - parent = domain.Hom(codomain, category=Rings()) - RingHomomorphism.__init__(self, parent) - self._image = image - self._k = k - - def _repr_type(self): - r""" - Return a string that describes the type of this morphism. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f # indirect doctest - Completion morphism: - From: Univariate Polynomial Ring in x over Rational Field - To: Power Series Ring in u over Rational Field - """ - return "Completion" - - def place(self): - r""" - Return the place at which we completed. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f.place() - x + 1 - """ - return self._place - - def _call_(self, P): - r""" - Return the image of ``P`` under this morphism. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f(x) # indirect doctest - -1 + u - """ - return P(self._image) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index abca12bc27e..ab1fa3be791 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -144,6 +144,7 @@ from sage.misc.superseded import deprecation from sage.structure.element import Element from sage.structure.category_object import check_default_category +from sage.structure.category_object import normalize_names import sage.categories as categories from sage.categories.morphism import IdentityMorphism @@ -154,6 +155,7 @@ from sage.structure.element import RingElement import sage.rings.rational_field as rational_field from sage.rings.rational_field import QQ +from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer from sage.rings.number_field.number_field_base import NumberField @@ -671,146 +673,6 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, extras=None, names=None): - r""" - Return the completion of this polynomial ring with respect to - the irreducible polynomial ``p``. - - INPUT: - - - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infiniity``; if ``None``, the generator of this polynomial - ring - - - ``prec`` (default: 20) -- an integer or ``Infinity``; if - ``Infinity``, return a - :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - - ``extras`` (default: ``None``) -- ignored; for compatibility - with the construction mecanism - - - ``names`` (default: ``None``) -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - The argument ``names`` is usually implicitly given by the `.<...>` - syntactic sugar (see examples below). - - EXAMPLES:: - - sage: P. = PolynomialRing(QQ) - sage: P - Univariate Polynomial Ring in x over Rational Field - - Without any argument, this method constructs the completion at - the ideal `(x)`:: - - sage: PP = P.completion() - sage: PP - Power Series Ring in x over Rational Field - sage: f = 1 - x - sage: PP(f) - 1 - x - sage: 1 / f - -1/(x - 1) - sage: g = 1 / PP(f); g - 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + x^11 - + x^12 + x^13 + x^14 + x^15 + x^16 + x^17 + x^18 + x^19 + O(x^20) - sage: 1 / g - 1 - x + O(x^20) - - We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: - - sage: C1. = P.completion(x - 1) - sage: C1 - Power Series Ring in u over Rational Field - - A coercion map from the polynomial ring to its completion is set:: - - sage: x - u - 1 - - It is possible to complete at an ideal generated by a polynomial - of higher degree. In this case, we should nevertheless provide an - extra name for the generator of the residue field:: - - sage: C2. = P.completion(x^2 + x + 1) - sage: C2 - Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a - - Constructing the completion at the place of infinity also works:: - - sage: C3. = P.completion(infinity) - sage: C3 - Laurent Series Ring in w over Rational Field - sage: C3(x) - w^-1 - - When the precision is infinity, a lazy series ring is returned:: - - sage: # needs sage.combinat - sage: PP = P.completion(x, prec=oo); PP - Lazy Taylor Series Ring in x over Rational Field - sage: g = 1 / PP(f); g - 1 + x + x^2 + O(x^3) - sage: 1 / g == f - True - - TESTS:: - - sage: P.completion('x') - Power Series Ring in x over Rational Field - sage: P.completion('y') - Power Series Ring in y over Rational Field - sage: Pz. = P.completion('y') - Traceback (most recent call last): - ... - ValueError: conflict of variable names - """ - from sage.rings.polynomial.morphism import MorphismToCompletion - name = residue_name = None - if names is not None: - if not isinstance(names, (list, tuple)): - raise TypeError("names must be a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - x = self.gen() - if p is None: - p = x - elif isinstance(p, str): - if name is not None and name != p: - raise ValueError("conflict of variable names") - name = p - p = x - if p == x and name is None: - name = self.variable_name() - if p is sage.rings.infinity.infinity: - ring = self.fraction_field() - else: - ring = self - incl = MorphismToCompletion(ring, p, prec, name, residue_name) - C = incl.codomain() - if C.has_coerce_map_from(ring): - if C(x) != incl._image: - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - if not (ring is self and C.has_coerce_map_from(self)): - C.register_coercion(incl * ring.coerce_map_from(self)) - return C - def _coerce_map_from_base_ring(self): """ Return a coercion map from the base ring of ``self``. @@ -1241,7 +1103,7 @@ def extend_variables(self, added_names, order='degrevlex'): added_names = added_names.split(',') return PolynomialRing(self.base_ring(), names=self.variable_names() + tuple(added_names), order=order) - def variable_names_recursive(self, depth=sage.rings.infinity.infinity): + def variable_names_recursive(self, depth=infinity): r""" Return the list of variable names of this ring and its base rings, as if it were a single multi-variate polynomial. @@ -1833,7 +1695,7 @@ def polynomials(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is sage.rings.infinity.infinity: + if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: return self._polys_degree( of_degree ) @@ -1893,7 +1755,7 @@ def monics(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is sage.rings.infinity.infinity: + if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: return self._monics_degree( of_degree ) @@ -1906,6 +1768,108 @@ def monics(self, of_degree=None, max_degree=None): PolynomialRing_general = PolynomialRing_generic +def morphism_to_completion(domain, place, prec, names): + r""" + Return the inclusion morphism from ``domain`` to its completion + at the place ``place``. + + INPUT: + + - ``domain`` -- a polynomial ring of its fraction field + + - ``place`` -- an irreducible polynomial or ``Infinity`` + + - ``prec`` -- an integer or ``Infinity`` + + - ``names`` -- a tuple of strings containing + + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + + - the variable name for the residue field (only relevant for + places of degree at least `2`) + + TESTS:: + + sage: A. = ZZ[] + sage: B. = A.completion(2*t + 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of the place is not a unit + + :: + + sage: A. = QQ[] + sage: B. = A.completion(x^2 + 2*x + 1) + Traceback (most recent call last): + ... + ValueError: place must be Infinity or an irreducible polynomial + """ + from sage.rings.power_series_ring import PowerSeriesRing + from sage.rings.laurent_series_ring import LaurentSeriesRing + if domain.is_field(): + ring = domain.ring() + SeriesRing = LaurentSeriesRing + else: + ring = domain + SeriesRing = PowerSeriesRing + k = base = ring.base_ring() + x = ring.gen() + sparse = ring.is_sparse() + + # Parse names + name = residue_name = None + if names is not None: + if not isinstance(names, (list, tuple)): + raise TypeError("names must be a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + + # Parse place + if place is None: + place = x + elif isinstance(place, str): + if name is not None and name != place: + raise ValueError("conflict of variable names") + name = p + place = x + + if place == x and name is None: + name = ring.variable_name() + + if place is infinity: + pass + elif place in ring: + place = ring(place) + if place.leading_coefficient().is_unit(): + place = ring(place.monic()) + # We do not check irreducibility; it causes too much troubles: + # it can be long, be not implemented and even sometimes fail + else: + raise NotImplementedError("the leading coefficient of the place is not a unit") + else: + raise ValueError("place must be Infinity or an irreducible polynomial") + + # Construct the completion + if place is infinity: + codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) + image = codomain.one() >> 1 + elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 + codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) + image = codomain.gen() - place[0] + else: + if residue_name is None: + raise ValueError("you must specify a variable name for the residue field") + residue_name = normalize_names(1, residue_name) + k = base.extension(place, names=residue_name) + codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) + image = codomain.gen() + k.gen() + + # Return the morphism + return domain.hom([image]) + + class PolynomialRing_commutative(PolynomialRing_generic): """ Univariate polynomial ring over a commutative ring. @@ -1924,6 +1888,129 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sparse=sparse, implementation=implementation, element_class=element_class, category=category) + def completion(self, p=None, prec=20, extras=None, names=None): + r""" + Return the completion of this polynomial ring with respect to + the irreducible polynomial ``p``. + + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``extras`` (default: ``None``) -- ignored; for compatibility + with the construction mecanism + + - ``names`` (default: ``None``) -- a tuple of strings containing + + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + + - the variable name for the residue field (only relevant for + places of degree at least `2`) + + The argument ``names`` is usually implicitly given by the `.<...>` + syntactic sugar (see examples below). + + EXAMPLES:: + + sage: P. = PolynomialRing(QQ) + sage: P + Univariate Polynomial Ring in x over Rational Field + + Without any argument, this method constructs the completion at + the ideal `(x)`:: + + sage: PP = P.completion() + sage: PP + Power Series Ring in x over Rational Field + sage: f = 1 - x + sage: PP(f) + 1 - x + sage: 1 / f + -1/(x - 1) + sage: g = 1 / PP(f); g + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + x^11 + + x^12 + x^13 + x^14 + x^15 + x^16 + x^17 + x^18 + x^19 + O(x^20) + sage: 1 / g + 1 - x + O(x^20) + + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: C1. = P.completion(x - 1) + sage: C1 + Power Series Ring in u over Rational Field + + A coercion map from the polynomial ring to its completion is set:: + + sage: x - u + 1 + + It is possible to complete at an ideal generated by a polynomial + of higher degree. In this case, we should nevertheless provide an + extra name for the generator of the residue field:: + + sage: C2. = P.completion(x^2 + x + 1) + sage: C2 + Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + Constructing the completion at the place of infinity also works:: + + sage: C3. = P.completion(infinity) + sage: C3 + Laurent Series Ring in w over Rational Field + sage: C3(x) + w^-1 + + When the precision is infinity, a lazy series ring is returned:: + + sage: # needs sage.combinat + sage: PP = P.completion(x, prec=oo); PP + Lazy Taylor Series Ring in x over Rational Field + sage: g = 1 / PP(f); g + 1 + x + x^2 + O(x^3) + sage: 1 / g == f + True + + TESTS:: + + sage: P.completion('x') + Power Series Ring in x over Rational Field + sage: P.completion('y') + Power Series Ring in y over Rational Field + sage: Pz. = P.completion('y') + Traceback (most recent call last): + ... + ValueError: conflict of variable names + """ + if p is infinity: + ring = self.fraction_field() + else: + ring = self + incl = morphism_to_completion(ring, p, prec, names) + C = incl.codomain() + if C.has_coerce_map_from(ring): + x = ring.gen() + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + if not (ring is self and C.has_coerce_map_from(self)): + C.register_coercion(incl * ring.coerce_map_from(self)) + return C + def quotient_by_principal_ideal(self, f, names=None, **kwds): """ Return the quotient of this polynomial ring by the principal From 7468d4a6f7aea43460f57b79b27e8e78b69493b2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:33:18 +0200 Subject: [PATCH 09/24] typo --- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index ab1fa3be791..4e3c67bb3ff 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1896,7 +1896,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): INPUT: - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infiniity``; if ``None``, the generator of this polynomial + ``Infinity``; if ``None``, the generator of this polynomial ring - ``prec`` (default: 20) -- an integer or ``Infinity``; if From 779cb91fef0ddb7737192c3aef9788ebe84cd246 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:43:03 +0200 Subject: [PATCH 10/24] documentation of the function morphism_to_completion --- src/sage/rings/polynomial/polynomial_ring.py | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 4e3c67bb3ff..4b84dd63be3 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1789,21 +1789,31 @@ def morphism_to_completion(domain, place, prec, names): - the variable name for the residue field (only relevant for places of degree at least `2`) - TESTS:: + EXAMPLES:: - sage: A. = ZZ[] - sage: B. = A.completion(2*t + 1) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit + sage: from sage.rings.polynomial.polynomial_ring import morphism_to_completion + sage: A. = QQ[] + sage: morphism_to_completion(A, t, 20, None) + Ring morphism: + From: Univariate Polynomial Ring in t over Rational Field + To: Power Series Ring in t over Rational Field + Defn: t |--> t :: - sage: A. = QQ[] - sage: B. = A.completion(x^2 + 2*x + 1) + sage: morphism_to_completion(A, t^2 + t + 1, 20, ('u','a')) + Ring morphism: + From: Univariate Polynomial Ring in t over Rational Field + To: Power Series Ring in u over Number Field in a with defining polynomial t^2 + t + 1 + Defn: t |--> a + u + + TESTS:: + + sage: A. = ZZ[] + sage: morphism_to_completion(A, 2*t + 1, 20, ('u',)) Traceback (most recent call last): ... - ValueError: place must be Infinity or an irreducible polynomial + NotImplementedError: the leading coefficient of the place is not a unit """ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.laurent_series_ring import LaurentSeriesRing @@ -1832,7 +1842,7 @@ def morphism_to_completion(domain, place, prec, names): elif isinstance(place, str): if name is not None and name != place: raise ValueError("conflict of variable names") - name = p + name = place place = x if place == x and name is None: From aa9e296d4619a1b1901c97f0e831a0a28254ca69 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 24 Aug 2025 15:43:33 +0200 Subject: [PATCH 11/24] back to the initial implementation of _first_ngens --- src/sage/structure/category_object.pyx | 32 ++++---------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 880ebf51cb8..aaf0c285322 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -370,37 +370,13 @@ cdef class CategoryObject(SageObject): sage: B. = EquationOrder(x^2 + 3) # needs sage.rings.number_field sage: z.minpoly() # needs sage.rings.number_field x^2 + 3 - - If the ring has less than `n` generators, the generators - of the base rings are appended recursively:: - - sage: S. = PolynomialRing(R) - sage: T. = PolynomialRing(S) - sage: T._first_ngens(1) - (z,) - sage: T._first_ngens(2) - (z, y) - sage: T._first_ngens(3) - (z, y, x) """ names = self._defining_names() - if not isinstance(names, (list, tuple)): - # case of Family - it = iter(names) - names = [] - for _ in range(n): - try: - names.append(next(it)) - except StopIteration: - break - names = tuple(names) - m = n - len(names) - if m <= 0: + if isinstance(names, (list, tuple)): return names[:n] - else: - if hasattr(self, 'base'): - names += tuple(self(x) for x in self.base()._first_ngens(m)) - return names + # case of Family + it = iter(names) + return tuple(next(it) for i in range(n)) @cached_method def _defining_names(self): From 5388244583f5febd4be268f8f4951e6ae02887ac Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 24 Aug 2025 21:03:17 +0200 Subject: [PATCH 12/24] gives generator to completion functor --- src/sage/rings/power_series_ring.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index a3bdbd178d1..a6d5ea599e8 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -915,7 +915,8 @@ def construction(self): extras = {'sparse': True} else: extras = None - return CompletionFunctor(self._names[0], self.default_prec(), extras), self._poly_ring() + A = self._poly_ring() + return CompletionFunctor(A.gen(), self.default_prec(), extras), A def _coerce_impl(self, x): """ From a5d3d7d6f49aef5c81f92ab42ffd9a8e953fef2e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 25 Aug 2025 10:19:12 +0200 Subject: [PATCH 13/24] a specific class for completions --- src/sage/rings/completion.py | 70 ++++++++++++++++++++ src/sage/rings/polynomial/polynomial_ring.py | 19 ++---- 2 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 src/sage/rings/completion.py diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py new file mode 100644 index 00000000000..269495b6dc9 --- /dev/null +++ b/src/sage/rings/completion.py @@ -0,0 +1,70 @@ +from sage.misc.cachefunc import cached_method +from sage.structure.parent import Parent + +from sage.rings.infinity import Infinity +from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic +from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic +from sage.rings.power_series_ring_element import PowerSeries + + +class CompletionPolynomialRing(PowerSeriesRing_generic): + def __classcall_private__(cls, A, p, default_prec=20, names=None, sparse=False): + if not isinstance(A, PolynomialRing_generic): + raise ValueError("not a polynomial ring") + if p == A.gen(): + return PowerSeriesRing(A.base_ring(), default_prec=default_prec, + names=A.variable_name(), sparse=sparse) + lc = p.leading_coefficient() + if not lc.is_unit(): + raise NotImplementedError("leading coefficient must be a unit") + p = lc.inverse_of_unit() * p + if not isinstance(names, (list, tuple)): + names = (names,) + if p.degree() == 0: + raise ValueError("place must be an irreducible polynomial") + if p.degree() == 1: + if len(names) != 1: + raise ValueError("you should provide a variable name") + else: + if len(names) != 2: + raise ValueError("you should provide two variable names") + return cls.__classcall__(cls, A, p, default_prec, names) + + def __init__(self, A, p, default_prec, names): + name_uniformizer = names[0] + name_residue = None + if len(names) > 1: + name_residue = names[1] + base = A.base_ring() + self._polynomial_ring = A + self._place = p + if p.degree() > 1: + k = base.extension(p, names=name_residue) + a = k.gen() + else: + k = base + a = -p[0] + super().__init__(k, name_uniformizer, default_prec) + self._a = a + self._inject = A.hom([self.gen() + a]) + self.register_coercion(self._inject) + + def _repr_(self): + A = self._polynomial_ring + s = "Completion of %s at %s:\n" % (A, self._place) + s += "Power Series Ring in %s = %s - %s over %s" % (self.gen(), A.gen(), self._a, self.residue_field()) + return s + + def construction(self): + from sage.categories.pushout import CompletionFunctor + extras = { + 'names': [str(x) for x in self._defining_names()], + 'sparse': self.is_sparse() + } + return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() + + def _defining_names(self): + if self._place.degree() > 1: + return (self.gen(), self(self.base_ring().gen())) + else: + return (self.gen()) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 4b84dd63be3..162e3368742 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2006,20 +2006,11 @@ def completion(self, p=None, prec=20, extras=None, names=None): ValueError: conflict of variable names """ if p is infinity: - ring = self.fraction_field() - else: - ring = self - incl = morphism_to_completion(ring, p, prec, names) - C = incl.codomain() - if C.has_coerce_map_from(ring): - x = ring.gen() - if C(x) != incl(x): - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - if not (ring is self and C.has_coerce_map_from(self)): - C.register_coercion(incl * ring.coerce_map_from(self)) - return C + raise NotImplementedError + from sage.rings.completion import CompletionPolynomialRing + if extras is not None and 'names' in extras: + names = extras['names'] + return CompletionPolynomialRing(self, p, sparse=self.is_sparse(), names=names) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From 679114559d63bcf93faaf02caf27b13729056bc1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 26 Aug 2025 15:12:51 +0200 Subject: [PATCH 14/24] implementation using RingExtension --- src/sage/rings/completion.py | 166 ++++++++++++++----- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 2 files changed, 129 insertions(+), 39 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 269495b6dc9..5dfa5961034 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -1,58 +1,124 @@ from sage.misc.cachefunc import cached_method -from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + +from sage.categories.fields import Fields from sage.rings.infinity import Infinity from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic -from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic -from sage.rings.power_series_ring_element import PowerSeries +from sage.rings.power_series_ring import PowerSeriesRing + +from sage.rings.ring_extension import RingExtension_generic +from sage.rings.ring_extension_element import RingExtensionElement + + +class CompletionPolynomial(RingExtensionElement): + def _repr_(self): + prec = self.precision_absolute() + + # Uniformizer + u = self.parent()._place + if u._is_atomic(): + unif = str(u) + else: + unif = "(%s)" % u + + # Bigoh + if prec is Infinity: + prec = self.parent().default_prec() + bigoh = "..." + elif prec == 0: + bigoh = "O(1)" + elif prec == 1: + bigoh = "O(%s)" % unif + else: + bigoh = "O(%s^%s)" % (unif, prec) + + S = self.parent() + A = S._base + incl = S._incl + v = S._place.derivative()(S._a).inverse() + w = v.parent().one() + uu = A.one() + elt = self.backend(force=True) + terms = [] + is_exact = False + for i in range(prec): + if elt.precision_absolute() is Infinity and elt.is_zero(): + is_exact = True + break + coeff = A((w * elt[i]).polynomial()) + elt -= incl(uu * coeff) + uu *= u + w *= v + if coeff.is_zero(): + continue + if coeff.is_one(): + if i == 0: + term = "1" + elif i == 1: + term = unif + else: + term = "%s^%s" % (unif, i) + else: + if coeff._is_atomic(): + coeff = str(coeff) + else: + coeff = "(%s)" % coeff + if i == 0: + term = coeff + elif i == 1: + term = "%s*%s" % (coeff, unif) + else: + term = "%s*%s^%s" % (coeff, unif, i) + terms.append(term) + if len(terms) == 0 and bigoh == "...": + terms = ["0"] + if not is_exact: + terms.append(bigoh) + return " + ".join(terms) + def teichmuller(self): + elt = self.backend(force=True) + lift = elt.parent()(elt[0]) + return self.parent()(lift) -class CompletionPolynomialRing(PowerSeriesRing_generic): - def __classcall_private__(cls, A, p, default_prec=20, names=None, sparse=False): + +class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): + Element = CompletionPolynomial + + def __classcall_private__(cls, A, p, default_prec=20, sparse=False): if not isinstance(A, PolynomialRing_generic): raise ValueError("not a polynomial ring") if p == A.gen(): return PowerSeriesRing(A.base_ring(), default_prec=default_prec, names=A.variable_name(), sparse=sparse) - lc = p.leading_coefficient() - if not lc.is_unit(): - raise NotImplementedError("leading coefficient must be a unit") - p = lc.inverse_of_unit() * p - if not isinstance(names, (list, tuple)): - names = (names,) - if p.degree() == 0: - raise ValueError("place must be an irreducible polynomial") - if p.degree() == 1: - if len(names) != 1: - raise ValueError("you should provide a variable name") - else: - if len(names) != 2: - raise ValueError("you should provide two variable names") - return cls.__classcall__(cls, A, p, default_prec, names) - - def __init__(self, A, p, default_prec, names): - name_uniformizer = names[0] - name_residue = None - if len(names) > 1: - name_residue = names[1] + if not A.base_ring() in Fields(): + raise NotImplementedError + if not p.is_irreducible(): + raise ValueError("the place must be an irreducible polynomial") + p = p.monic() + return cls.__classcall__(cls, A, p, default_prec, sparse) + + def __init__(self, A, p, default_prec, sparse): base = A.base_ring() - self._polynomial_ring = A self._place = p if p.degree() > 1: - k = base.extension(p, names=name_residue) + self._extension = True + k = base.extension(p, names='a') a = k.gen() else: + self._extension = False k = base a = -p[0] - super().__init__(k, name_uniformizer, default_prec) + backend = PowerSeriesRing(k, 'u', sparse=sparse) + self._gen = backend.gen() + a + self._incl = A.hom([self._gen]) + super().__init__(self._incl) self._a = a - self._inject = A.hom([self.gen() + a]) - self.register_coercion(self._inject) def _repr_(self): - A = self._polynomial_ring - s = "Completion of %s at %s:\n" % (A, self._place) - s += "Power Series Ring in %s = %s - %s over %s" % (self.gen(), A.gen(), self._a, self.residue_field()) + A = self._base + s = "Completion of %s at %s" % (A, self._place) return s def construction(self): @@ -63,8 +129,32 @@ def construction(self): } return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() - def _defining_names(self): - if self._place.degree() > 1: - return (self.gen(), self(self.base_ring().gen())) + @cached_method + def uniformizer(self): + return self(self._place) + + def gen(self): + return self._gen + + def residue_field(self, names=None): + base = self._base.base_ring() + if self._extension: + if names is None: + raise ValueError("you must give a variable name") + return base.extension(self._place, names=names) + else: + return base + + def power_series(self, sparse=False, names=None): + if not isinstance(names, (list, tuple)): + names = [names] + base = self._base.base_ring() + if self._extension: + if len(names) < 2: + raise ValueError("you must give variable names for the uniformizer and the generator of the residue field") + k = self.residue_field(names[1]) else: - return (self.gen()) + if len(names) < 1: + raise ValueError("you must give variable names for the uniformizer") + k = self.residue_field() + return PowerSeriesRing(k, names[0], sparse=sparse) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 162e3368742..f9d62375067 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2010,7 +2010,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] - return CompletionPolynomialRing(self, p, sparse=self.is_sparse(), names=names) + return CompletionPolynomialRing(self, p, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From dede4c3cb6c5dcb884506ef9e187184833b65349 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 26 Aug 2025 15:22:15 +0200 Subject: [PATCH 15/24] fix default precision --- src/sage/rings/completion.py | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 5dfa5961034..e91e2cd362a 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -110,7 +110,7 @@ def __init__(self, A, p, default_prec, sparse): self._extension = False k = base a = -p[0] - backend = PowerSeriesRing(k, 'u', sparse=sparse) + backend = PowerSeriesRing(k, 'u', default_prec=default_prec, sparse=sparse) self._gen = backend.gen() + a self._incl = A.hom([self._gen]) super().__init__(self._incl) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index f9d62375067..0fe72ad7121 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2010,7 +2010,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] - return CompletionPolynomialRing(self, p, sparse=self.is_sparse()) + return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From e31740c00354cbd2678421c4493c90ae3ceb35fe Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 10:41:53 +0200 Subject: [PATCH 16/24] use quotient instead of extension --- src/sage/rings/completion.py | 118 ++++++++------ src/sage/rings/polynomial/polynomial_ring.py | 160 ++----------------- src/sage/rings/power_series_ring.py | 8 +- 3 files changed, 80 insertions(+), 206 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index e91e2cd362a..fbb7a54303f 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -3,6 +3,7 @@ from sage.categories.fields import Fields +from sage.rings.morphism import RingHomomorphism from sage.rings.infinity import Infinity from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic from sage.rings.power_series_ring import PowerSeriesRing @@ -11,6 +12,14 @@ from sage.rings.ring_extension_element import RingExtensionElement +class CompletionToPowerSeries(RingHomomorphism): + def __init__(self, parent): + super().__init__(parent) + + def _call_(self, x): + return self.codomain()(x.backend(force=True)) + + class CompletionPolynomial(RingExtensionElement): def _repr_(self): prec = self.precision_absolute() @@ -33,23 +42,15 @@ def _repr_(self): else: bigoh = "O(%s^%s)" % (unif, prec) - S = self.parent() - A = S._base - incl = S._incl - v = S._place.derivative()(S._a).inverse() - w = v.parent().one() - uu = A.one() - elt = self.backend(force=True) - terms = [] + E = self.expansion(include_final_zeroes=False) is_exact = False + terms = [] for i in range(prec): - if elt.precision_absolute() is Infinity and elt.is_zero(): + try: + coeff = next(E) + except StopIteration: is_exact = True break - coeff = A((w * elt[i]).polynomial()) - elt -= incl(uu * coeff) - uu *= u - w *= v if coeff.is_zero(): continue if coeff.is_one(): @@ -77,6 +78,28 @@ def _repr_(self): terms.append(bigoh) return " + ".join(terms) + def expansion(self, include_final_zeroes=True): + S = self.parent() + A = S._base + incl = S._incl + u = self.parent()._place + v = S._place.derivative()(S._xbar).inverse() + vv = v.parent().one() + uu = A.one() + elt = self.backend(force=True) + prec = self.precision_absolute() + n = 0 + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = (vv * elt[n]).lift() + yield coeff + elt -= incl(uu * coeff) + uu *= u + vv *= v + n += 1 + def teichmuller(self): elt = self.backend(force=True) lift = elt.parent()(elt[0]) @@ -89,36 +112,35 @@ class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): def __classcall_private__(cls, A, p, default_prec=20, sparse=False): if not isinstance(A, PolynomialRing_generic): raise ValueError("not a polynomial ring") - if p == A.gen(): + if isinstance(p, str): return PowerSeriesRing(A.base_ring(), default_prec=default_prec, - names=A.variable_name(), sparse=sparse) + names=p, sparse=sparse) if not A.base_ring() in Fields(): raise NotImplementedError - if not p.is_irreducible(): - raise ValueError("the place must be an irreducible polynomial") + try: + p = p.squarefree_part() + except (AttributeError, NotImplementedError): + pass p = p.monic() return cls.__classcall__(cls, A, p, default_prec, sparse) def __init__(self, A, p, default_prec, sparse): + self._A = A base = A.base_ring() self._place = p - if p.degree() > 1: - self._extension = True - k = base.extension(p, names='a') - a = k.gen() - else: - self._extension = False - k = base - a = -p[0] - backend = PowerSeriesRing(k, 'u', default_prec=default_prec, sparse=sparse) - self._gen = backend.gen() + a + self._residue_ring = k = A.quotient(p) + self._xbar = xbar = k(A.gen()) + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + self._gen = backend.gen() + xbar self._incl = A.hom([self._gen]) - super().__init__(self._incl) - self._a = a + self._base_morphism = self._incl * A.coerce_map_from(base) + super().__init__(self._base_morphism) + coerce = A.Hom(self)(self._incl) + self.register_coercion(coerce) def _repr_(self): - A = self._base - s = "Completion of %s at %s" % (A, self._place) + s = "Completion of %s at %s" % (self._A, self._place) return s def construction(self): @@ -127,7 +149,7 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() + return CompletionFunctor(self._place, self.default_prec(), extras), self._A @cached_method def uniformizer(self): @@ -136,25 +158,17 @@ def uniformizer(self): def gen(self): return self._gen - def residue_field(self, names=None): - base = self._base.base_ring() - if self._extension: - if names is None: - raise ValueError("you must give a variable name") - return base.extension(self._place, names=names) - else: - return base + def residue_ring(self): + return self._residue_ring + + residue_field = residue_ring - def power_series(self, sparse=False, names=None): - if not isinstance(names, (list, tuple)): - names = [names] + def power_series(self, names=None, sparse=None): + if isinstance(names, (list, tuple)): + names = names[0] + if sparse is None: + sparse = self.is_sparse() base = self._base.base_ring() - if self._extension: - if len(names) < 2: - raise ValueError("you must give variable names for the uniformizer and the generator of the residue field") - k = self.residue_field(names[1]) - else: - if len(names) < 1: - raise ValueError("you must give variable names for the uniformizer") - k = self.residue_field() - return PowerSeriesRing(k, names[0], sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + S.register_conversion(CompletionToPowerSeries(self.Hom(S))) + return S diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 0fe72ad7121..a2e96a72163 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1768,118 +1768,6 @@ def monics(self, of_degree=None, max_degree=None): PolynomialRing_general = PolynomialRing_generic -def morphism_to_completion(domain, place, prec, names): - r""" - Return the inclusion morphism from ``domain`` to its completion - at the place ``place``. - - INPUT: - - - ``domain`` -- a polynomial ring of its fraction field - - - ``place`` -- an irreducible polynomial or ``Infinity`` - - - ``prec`` -- an integer or ``Infinity`` - - - ``names`` -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - EXAMPLES:: - - sage: from sage.rings.polynomial.polynomial_ring import morphism_to_completion - sage: A. = QQ[] - sage: morphism_to_completion(A, t, 20, None) - Ring morphism: - From: Univariate Polynomial Ring in t over Rational Field - To: Power Series Ring in t over Rational Field - Defn: t |--> t - - :: - - sage: morphism_to_completion(A, t^2 + t + 1, 20, ('u','a')) - Ring morphism: - From: Univariate Polynomial Ring in t over Rational Field - To: Power Series Ring in u over Number Field in a with defining polynomial t^2 + t + 1 - Defn: t |--> a + u - - TESTS:: - - sage: A. = ZZ[] - sage: morphism_to_completion(A, 2*t + 1, 20, ('u',)) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit - """ - from sage.rings.power_series_ring import PowerSeriesRing - from sage.rings.laurent_series_ring import LaurentSeriesRing - if domain.is_field(): - ring = domain.ring() - SeriesRing = LaurentSeriesRing - else: - ring = domain - SeriesRing = PowerSeriesRing - k = base = ring.base_ring() - x = ring.gen() - sparse = ring.is_sparse() - - # Parse names - name = residue_name = None - if names is not None: - if not isinstance(names, (list, tuple)): - raise TypeError("names must be a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - - # Parse place - if place is None: - place = x - elif isinstance(place, str): - if name is not None and name != place: - raise ValueError("conflict of variable names") - name = place - place = x - - if place == x and name is None: - name = ring.variable_name() - - if place is infinity: - pass - elif place in ring: - place = ring(place) - if place.leading_coefficient().is_unit(): - place = ring(place.monic()) - # We do not check irreducibility; it causes too much troubles: - # it can be long, be not implemented and even sometimes fail - else: - raise NotImplementedError("the leading coefficient of the place is not a unit") - else: - raise ValueError("place must be Infinity or an irreducible polynomial") - - # Construct the completion - if place is infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.one() >> 1 - elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 - codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() - place[0] - else: - if residue_name is None: - raise ValueError("you must specify a variable name for the residue field") - residue_name = normalize_names(1, residue_name) - k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() + k.gen() - - # Return the morphism - return domain.hom([image]) - - class PolynomialRing_commutative(PolynomialRing_generic): """ Univariate polynomial ring over a commutative ring. @@ -1898,7 +1786,7 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sparse=sparse, implementation=implementation, element_class=element_class, category=category) - def completion(self, p=None, prec=20, extras=None, names=None): + def completion(self, p=None, prec=20, extras=None): r""" Return the completion of this polynomial ring with respect to the irreducible polynomial ``p``. @@ -1906,8 +1794,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): INPUT: - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infinity``; if ``None``, the generator of this polynomial - ring + ``Infinity`` - ``prec`` (default: 20) -- an integer or ``Infinity``; if ``Infinity``, return a @@ -1916,25 +1803,14 @@ def completion(self, p=None, prec=20, extras=None, names=None): - ``extras`` (default: ``None``) -- ignored; for compatibility with the construction mecanism - - ``names`` (default: ``None``) -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - The argument ``names`` is usually implicitly given by the `.<...>` - syntactic sugar (see examples below). - EXAMPLES:: sage: P. = PolynomialRing(QQ) sage: P Univariate Polynomial Ring in x over Rational Field - Without any argument, this method constructs the completion at - the ideal `(x)`:: + Without any argument, this method returns the power series ring + with the same variable name:: sage: PP = P.completion() sage: PP @@ -1951,30 +1827,14 @@ def completion(self, p=None, prec=20, extras=None, names=None): 1 - x + O(x^20) We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: + irreducible polynomial:: - sage: C1. = P.completion(x - 1) + sage: C1 = P.completion(x - 1) sage: C1 - Power Series Ring in u over Rational Field - - A coercion map from the polynomial ring to its completion is set:: - - sage: x - u - 1 - - It is possible to complete at an ideal generated by a polynomial - of higher degree. In this case, we should nevertheless provide an - extra name for the generator of the residue field:: - - sage: C2. = P.completion(x^2 + x + 1) + Completion of Univariate Polynomial Ring in x over Rational Field at x - 1 + sage: C2 = P.completion(x^2 + x + 1) sage: C2 - Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a + Completion of Univariate Polynomial Ring in x over Rational Field at x^2 + x + 1 Constructing the completion at the place of infinity also works:: @@ -2007,6 +1867,8 @@ def completion(self, p=None, prec=20, extras=None, names=None): """ if p is infinity: raise NotImplementedError + if p is None: + p = self.variable_name() from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index a6d5ea599e8..f272e71e699 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -396,10 +396,9 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, default_prec = name if names is not None: name = names - name = normalize_names(1, name) - if name is None: - raise TypeError("You must specify the name of the indeterminate of the Power series ring.") + raise TypeError("you must specify the name of the indeterminate of the power series ring") + name = normalize_names(1, name) key = (base_ring, name, default_prec, sparse, implementation) if PowerSeriesRing_generic.__classcall__.is_in_cache(key): @@ -915,8 +914,7 @@ def construction(self): extras = {'sparse': True} else: extras = None - A = self._poly_ring() - return CompletionFunctor(A.gen(), self.default_prec(), extras), A + return CompletionFunctor(self._names[0], self.default_prec(), extras), self._poly_ring() def _coerce_impl(self, x): """ From d0bf69c042e1ce7bba4f7dff0cb4bdd9057faede Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 10:48:56 +0200 Subject: [PATCH 17/24] set up correctly the generator --- src/sage/rings/completion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index fbb7a54303f..64bb6f3c86a 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -132,10 +132,11 @@ def __init__(self, A, p, default_prec, sparse): self._xbar = xbar = k(A.gen()) name = "u_%s" % id(self) backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - self._gen = backend.gen() + xbar - self._incl = A.hom([self._gen]) + gen = backend.gen() + xbar + self._incl = A.hom([gen]) self._base_morphism = self._incl * A.coerce_map_from(base) super().__init__(self._base_morphism) + self._gen = self(gen) coerce = A.Hom(self)(self._incl) self.register_coercion(coerce) From b2ee24f55aa1b4040139b4b386fa0c611ec648f7 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 19:32:36 +0200 Subject: [PATCH 18/24] completion of rational function field --- src/sage/rings/completion.py | 119 ++++++++++++++----- src/sage/rings/fraction_field.py | 18 +-- src/sage/rings/polynomial/polynomial_ring.py | 4 - src/sage/rings/ring_extension.pyx | 3 +- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 64bb6f3c86a..9bac661bd1e 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -5,8 +5,11 @@ from sage.rings.morphism import RingHomomorphism from sage.rings.infinity import Infinity + from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic +from sage.rings.fraction_field import FractionField_1poly_field from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.laurent_series_ring import LaurentSeriesRing from sage.rings.ring_extension import RingExtension_generic from sage.rings.ring_extension_element import RingExtensionElement @@ -22,16 +25,16 @@ def _call_(self, x): class CompletionPolynomial(RingExtensionElement): def _repr_(self): - prec = self.precision_absolute() - # Uniformizer - u = self.parent()._place + S = self.parent() + u = S._p if u._is_atomic(): unif = str(u) else: unif = "(%s)" % u # Bigoh + prec = self.precision_absolute() if prec is Infinity: prec = self.parent().default_prec() bigoh = "..." @@ -45,7 +48,13 @@ def _repr_(self): E = self.expansion(include_final_zeroes=False) is_exact = False terms = [] - for i in range(prec): + if S._integer_ring is S: + start = 0 + else: + start = self.valuation() + if start is Infinity: + return "0" + for i in range(start, prec): try: coeff = next(E) except StopIteration: @@ -79,16 +88,25 @@ def _repr_(self): return " + ".join(terms) def expansion(self, include_final_zeroes=True): + # TODO: improve performance S = self.parent() A = S._base incl = S._incl - u = self.parent()._place - v = S._place.derivative()(S._xbar).inverse() - vv = v.parent().one() - uu = A.one() + u = self.parent()._p + v = S._p.derivative()(S._xbar).inverse() elt = self.backend(force=True) prec = self.precision_absolute() n = 0 + if S._integer_ring is not S: + val = elt.valuation() + if val is Infinity: + return + if val < 0: + elt *= incl(u) ** (-val) + else: + n = val + vv = v**n + uu = u**n while n < prec: if (not include_final_zeroes and elt.precision_absolute() is Infinity and elt.is_zero()): @@ -105,43 +123,67 @@ def teichmuller(self): lift = elt.parent()(elt[0]) return self.parent()(lift) + def shift(self, n): + raise NotImplementedError + class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): Element = CompletionPolynomial - def __classcall_private__(cls, A, p, default_prec=20, sparse=False): - if not isinstance(A, PolynomialRing_generic): - raise ValueError("not a polynomial ring") - if isinstance(p, str): - return PowerSeriesRing(A.base_ring(), default_prec=default_prec, - names=p, sparse=sparse) + def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + if isinstance(ring, PolynomialRing_generic): + if isinstance(p, str): + return PowerSeriesRing(ring.base_ring(), + default_prec=default_prec, + names=p, sparse=sparse) + A = ring + elif isinstance(ring, FractionField_1poly_field): + if isinstance(p, str): + return LaurentSeriesRing(ring.base_ring(), + default_prec=default_prec, + names=p, sparse=sparse) + A = ring.ring() + else: + raise ValueError("not a polynomial ring or a rational function field") if not A.base_ring() in Fields(): raise NotImplementedError + p = A(p) try: p = p.squarefree_part() except (AttributeError, NotImplementedError): pass p = p.monic() - return cls.__classcall__(cls, A, p, default_prec, sparse) + return cls.__classcall__(cls, ring, p, default_prec, sparse) - def __init__(self, A, p, default_prec, sparse): - self._A = A + def __init__(self, ring, p, default_prec, sparse): + A = self._ring = ring + if not isinstance(A, PolynomialRing_generic): + A = A.ring() base = A.base_ring() - self._place = p + self._p = p + # Construct backend self._residue_ring = k = A.quotient(p) self._xbar = xbar = k(A.gen()) - name = "u_%s" % id(self) - backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - gen = backend.gen() + xbar - self._incl = A.hom([gen]) - self._base_morphism = self._incl * A.coerce_map_from(base) - super().__init__(self._base_morphism) - self._gen = self(gen) - coerce = A.Hom(self)(self._incl) - self.register_coercion(coerce) + name = "u_%s" % hash(self._p) + if isinstance(ring, PolynomialRing_generic): + self._integer_ring = self + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + else: + self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) + name = "u_%s" % id(self._integer_ring) + backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) + # Set generator + x = backend.gen() + xbar + self._gen = self(x) + # Set coercions + self._incl = ring.hom([x]) + self.register_coercion(A.Hom(self)(self._incl)) + self.register_coercion(self._integer_ring) def _repr_(self): - s = "Completion of %s at %s" % (self._A, self._place) + s = "Completion of %s at %s" % (self._ring, self._p) return s def construction(self): @@ -150,11 +192,11 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._place, self.default_prec(), extras), self._A + return CompletionFunctor(self._p, self.default_prec(), extras), self._ring @cached_method def uniformizer(self): - return self(self._place) + return self(self._p) def gen(self): return self._gen @@ -170,6 +212,21 @@ def power_series(self, names=None, sparse=None): if sparse is None: sparse = self.is_sparse() base = self._base.base_ring() - S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + if self._integer_ring is self: + S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + else: + S = LaurentSeriesRing(self._residue_ring, names, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S + + def integer_ring(self): + return self._integer_ring + + def fraction_field(self): + field = self._ring.fraction_field() + if field is self._ring: + return self + else: + return CompletionPolynomialRing(field, self._p, + default_prec = self.default_prec(), + sparse = self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 163d5c46233..c83c73bdeed 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1263,20 +1263,10 @@ def completion(self, p=None, prec=20, names=None): sage: L = K.completion(x, prec=oo); L Lazy Laurent Series Ring in x over Rational Field """ - from sage.rings.polynomial.polynomial_ring import morphism_to_completion - incl = morphism_to_completion(self, p, prec, names) - C = incl.codomain() - if C.has_coerce_map_from(self): - x = self.gen() - if C(x) != incl(x): - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - ring = self.ring() - if not C.has_coerce_map_from(ring): - C.register_coercion(incl * self.coerce_map_from(ring)) - return C - + if p is None: + p = self.variable_name() + from sage.rings.completion import CompletionPolynomialRing + return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) class FractionFieldEmbedding(DefaultConvertMap_unique): r""" diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index a2e96a72163..5d0f82de77c 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1865,13 +1865,9 @@ def completion(self, p=None, prec=20, extras=None): ... ValueError: conflict of variable names """ - if p is infinity: - raise NotImplementedError if p is None: p = self.variable_name() from sage.rings.completion import CompletionPolynomialRing - if extras is not None and 'names' in extras: - names = extras['names'] return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 9c83ab605ff..7d1527201e7 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -123,6 +123,7 @@ from sage.structure.element cimport Element from sage.structure.category_object import normalize_names from sage.categories.map cimport Map from sage.categories.commutative_rings import CommutativeRings +from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.rings.integer_ring import ZZ from sage.rings.infinity import Infinity @@ -593,7 +594,7 @@ cdef class RingExtension_generic(Parent): # Some checks if (base not in CommutativeRings() or ring not in CommutativeRings() - or not defining_morphism.category_for().is_subcategory(CommutativeRings())): + or not defining_morphism.category_for().is_subcategory(Rings())): raise TypeError("only commutative rings are accepted") f = ring.Hom(ring).identity() b = self From 4dea431bcdc6d05004d16347a7e8d73af707db8b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 28 Aug 2025 15:30:23 +0200 Subject: [PATCH 19/24] infinite place --- src/sage/rings/completion.py | 157 ++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 65 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 9bac661bd1e..1e20c971055 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -28,10 +28,15 @@ def _repr_(self): # Uniformizer S = self.parent() u = S._p - if u._is_atomic(): - unif = str(u) + if u is Infinity: + step = -1 + unif = S._ring.variable_name() else: - unif = "(%s)" % u + step = 1 + if u._is_atomic(): + unif = str(u) + else: + unif = "(%s)" % u # Bigoh prec = self.precision_absolute() @@ -40,10 +45,10 @@ def _repr_(self): bigoh = "..." elif prec == 0: bigoh = "O(1)" - elif prec == 1: + elif step*prec == 1: bigoh = "O(%s)" % unif else: - bigoh = "O(%s^%s)" % (unif, prec) + bigoh = "O(%s^%s)" % (unif, step*prec) E = self.expansion(include_final_zeroes=False) is_exact = False @@ -54,7 +59,7 @@ def _repr_(self): start = self.valuation() if start is Infinity: return "0" - for i in range(start, prec): + for e in range(step*start, step*prec, step): try: coeff = next(E) except StopIteration: @@ -62,30 +67,26 @@ def _repr_(self): break if coeff.is_zero(): continue - if coeff.is_one(): - if i == 0: - term = "1" - elif i == 1: - term = unif - else: - term = "%s^%s" % (unif, i) + if coeff._is_atomic(): + coeff = str(coeff) else: - if coeff._is_atomic(): - coeff = str(coeff) - else: - coeff = "(%s)" % coeff - if i == 0: - term = coeff - elif i == 1: - term = "%s*%s" % (coeff, unif) - else: - term = "%s*%s^%s" % (coeff, unif, i) + coeff = "(%s)" % coeff + if e == 0: + term = coeff + elif e == 1: + term = "%s*%s" % (coeff, unif) + else: + term = "%s*%s^%s" % (coeff, unif, e) terms.append(term) if len(terms) == 0 and bigoh == "...": terms = ["0"] if not is_exact: terms.append(bigoh) - return " + ".join(terms) + s = " " + " + ".join(terms) + s = s.replace(" + -", " - ") + s = s.replace(" 1*"," ") + s = s.replace(" -1*", " -") + return s[1:] def expansion(self, include_final_zeroes=True): # TODO: improve performance @@ -93,30 +94,40 @@ def expansion(self, include_final_zeroes=True): A = S._base incl = S._incl u = self.parent()._p - v = S._p.derivative()(S._xbar).inverse() elt = self.backend(force=True) prec = self.precision_absolute() n = 0 - if S._integer_ring is not S: - val = elt.valuation() - if val is Infinity: - return - if val < 0: - elt *= incl(u) ** (-val) - else: - n = val - vv = v**n - uu = u**n - while n < prec: - if (not include_final_zeroes - and elt.precision_absolute() is Infinity and elt.is_zero()): - break - coeff = (vv * elt[n]).lift() - yield coeff - elt -= incl(uu * coeff) - uu *= u - vv *= v - n += 1 + if u is Infinity: + n = elt.valuation() + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = elt[n] + yield coeff + elt -= elt.parent()(coeff) << n + n += 1 + else: + if S._integer_ring is not S: + val = elt.valuation() + if val is Infinity: + return + if val < 0: + elt *= incl(u) ** (-val) + else: + n = val + vv = v**n + uu = u**n + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = (vv * elt[n]).lift() + yield coeff + elt -= incl(uu * coeff) + uu *= u + vv *= v + n += 1 def teichmuller(self): elt = self.backend(force=True) @@ -147,12 +158,13 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): raise ValueError("not a polynomial ring or a rational function field") if not A.base_ring() in Fields(): raise NotImplementedError - p = A(p) - try: - p = p.squarefree_part() - except (AttributeError, NotImplementedError): - pass - p = p.monic() + if p is not Infinity: + p = A(p) + try: + p = p.squarefree_part() + except (AttributeError, NotImplementedError): + pass + p = p.monic() return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): @@ -162,29 +174,39 @@ def __init__(self, ring, p, default_prec, sparse): base = A.base_ring() self._p = p # Construct backend - self._residue_ring = k = A.quotient(p) - self._xbar = xbar = k(A.gen()) - name = "u_%s" % hash(self._p) - if isinstance(ring, PolynomialRing_generic): - self._integer_ring = self + if p is Infinity: + self._ring = ring.fraction_field() + self._residue_ring = k = base + self._integer_ring = None name = "u_%s" % id(self) - backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - else: - self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) - name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + x = backend.gen().inverse() + else: + self._residue_ring = k = A.quotient(p) + self._xbar = xbar = k(A.gen()) + if isinstance(ring, PolynomialRing_generic): + self._integer_ring = self + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + else: + self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) + name = "u_%s" % id(self._integer_ring) + backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + x = backend.gen() + xbar super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) # Set generator - x = backend.gen() + xbar self._gen = self(x) # Set coercions self._incl = ring.hom([x]) self.register_coercion(A.Hom(self)(self._incl)) - self.register_coercion(self._integer_ring) + if self._integer_ring is not None: + self.register_coercion(self._integer_ring) def _repr_(self): - s = "Completion of %s at %s" % (self._ring, self._p) - return s + if self._p is Infinity: + return "Completion of %s at infinity" % self._ring + else: + return "Completion of %s at %s" % (self._ring, self._p) def construction(self): from sage.categories.pushout import CompletionFunctor @@ -196,7 +218,10 @@ def construction(self): @cached_method def uniformizer(self): - return self(self._p) + if self._p is Infinity: + return self._gen.inverse() + else: + return self(self._p) def gen(self): return self._gen @@ -220,6 +245,8 @@ def power_series(self, names=None, sparse=None): return S def integer_ring(self): + if self._p is Infinity: + raise NotImplementedError return self._integer_ring def fraction_field(self): From 0f8b78d42d98dc2a2cc028020da2c4d06512028d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 28 Aug 2025 21:09:53 +0200 Subject: [PATCH 20/24] bigoh --- src/sage/categories/pushout.py | 6 +-- src/sage/monoids/trace_monoid.py | 2 +- src/sage/rings/big_oh.py | 41 +++++++++++++++---- src/sage/rings/completion.py | 30 +++++++++----- src/sage/rings/fraction_field.py | 32 +++++---------- src/sage/rings/lazy_series.py | 2 + .../rings/polynomial/polynomial_element.pyx | 15 ++++++- src/sage/rings/polynomial/polynomial_ring.py | 22 ++++------ src/sage/rings/power_series_pari.pyx | 2 +- 9 files changed, 93 insertions(+), 59 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index a8458de23fb..078b4ba4d28 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2466,12 +2466,12 @@ class CompletionFunctor(ConstructionFunctor): True sage: P. = ZZ[] - sage: Px = P.completion(x) # currently the only implemented completion of P + sage: Px = P.completion(x) sage: Px - Power Series Ring in x over Integer Ring + Completion of Univariate Polynomial Ring in x over Integer Ring at x sage: F3 = Px.construction()[0] sage: F3(GF(3)['x']) - Power Series Ring in x over Finite Field of size 3 + Completion of Univariate Polynomial Ring in x over Finite Field of size 3 at x TESTS:: diff --git a/src/sage/monoids/trace_monoid.py b/src/sage/monoids/trace_monoid.py index cb1c036e039..8ab2ff14999 100644 --- a/src/sage/monoids/trace_monoid.py +++ b/src/sage/monoids/trace_monoid.py @@ -864,7 +864,7 @@ def number_of_words(self, length): sage: M.number_of_words(3) # needs sage.graphs 48 """ - psr = PowerSeriesRing(ZZ, default_prec=length + 1) + psr = PowerSeriesRing(ZZ, 'x', default_prec=length + 1) return psr(self.dependence_polynomial()).coefficients()[length] @cached_method diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 0d0f362cb47..7060037371f 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -13,6 +13,8 @@ lazy_import('sage.rings.padics.factory', ['Qp', 'Zp']) from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.fraction_field_element import FractionFieldElement +from sage.rings.fraction_field_FpT import FpTElement try: from .puiseux_series_ring_element import PuiseuxSeries @@ -47,6 +49,16 @@ def O(*x, **kwds): O(x^100) sage: 1/(1+x+O(x^5)) 1 - x + x^2 - x^3 + x^4 + O(x^5) + + Completion at other places also works:: + + sage: x^3 + O((x^2 + 1)^10) + -x + x*(x^2 + 1) + O((x^2 + 1)^10) + sage: x^3 + O(1/x^10) # completion at infinity + x^3 + O(x^-10) + + An example with several variables:: + sage: R. = QQ[[]] sage: 1 + u + v^2 + O(u, v)^5 1 + u + v^2 + O(u, v)^5 @@ -126,11 +138,6 @@ def O(*x, **kwds): :: - sage: R. = QQ[] - sage: O(2*x) - Traceback (most recent call last): - ... - NotImplementedError: completion only currently defined for the maximal ideal (x) sage: R. = LazyPowerSeriesRing(QQ) sage: O(x^5) O(x^5) @@ -169,13 +176,31 @@ def O(*x, **kwds): if isinstance(x, power_series_ring_element.PowerSeries): return x.parent()(0, x.degree(), **kwds) + if isinstance(x, (FractionFieldElement, FpTElement)): + if x.denominator().is_one(): + x = x.numerator() + elif x.numerator().is_one(): + x = x.denominator() + if isinstance(x, Polynomial) and x.is_monomial(): + from sage.rings.infinity import infinity + C = x.parent().completion(infinity) + n = x.degree() + return C.zero().add_bigoh(n) + if isinstance(x, Polynomial): - if x.parent().ngens() != 1: + A = x.parent() + if A.ngens() != 1: raise NotImplementedError("completion only currently defined " "for univariate polynomials") + if x.is_monomial(): + C = A.completion(A.variable_name()) + n = x.degree() if not x.is_monomial(): - raise NotImplementedError("completion only currently defined " - "for the maximal ideal (x)") + p, n = x.perfect_power() + C = A.completion(p) + return C.zero().add_bigoh(n) + + if isinstance(x, (int, Integer, Rational)): # p-adic number diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 1e20c971055..75394271445 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -41,7 +41,7 @@ def _repr_(self): # Bigoh prec = self.precision_absolute() if prec is Infinity: - prec = self.parent().default_prec() + prec = self.parent()._default_prec bigoh = "..." elif prec == 0: bigoh = "O(1)" @@ -56,9 +56,11 @@ def _repr_(self): if S._integer_ring is S: start = 0 else: - start = self.valuation() + start = min(prec, self.valuation()) if start is Infinity: return "0" + if prec is Infinity: + prec = start + 10 # ??? for e in range(step*start, step*prec, step): try: coeff = next(E) @@ -116,6 +118,7 @@ def expansion(self, include_final_zeroes=True): elt *= incl(u) ** (-val) else: n = val + v = S._p.derivative()(S._xbar).inverse() vv = v**n uu = u**n while n < prec: @@ -134,6 +137,9 @@ def teichmuller(self): lift = elt.parent()(elt[0]) return self.parent()(lift) + def valuation(self): + return self.backend(force=True).valuation() + def shift(self, n): raise NotImplementedError @@ -156,15 +162,16 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): A = ring.ring() else: raise ValueError("not a polynomial ring or a rational function field") - if not A.base_ring() in Fields(): - raise NotImplementedError if p is not Infinity: p = A(p) + if not p.leading_coefficient().is_unit(): + raise NotImplementedError("the leading coefficient of p must be invertible") + p = A(p.monic()) try: - p = p.squarefree_part() + if not p.is_squarefree(): + raise ValueError("p must be a squarefree polynomial") except (AttributeError, NotImplementedError): pass - p = p.monic() return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): @@ -173,6 +180,7 @@ def __init__(self, ring, p, default_prec, sparse): A = A.ring() base = A.base_ring() self._p = p + self._default_prec = default_prec # Construct backend if p is Infinity: self._ring = ring.fraction_field() @@ -193,7 +201,7 @@ def __init__(self, ring, p, default_prec, sparse): name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) x = backend.gen() + xbar - super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) + super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base), category=backend.category()) # Set generator self._gen = self(x) # Set coercions @@ -214,7 +222,7 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._p, self.default_prec(), extras), self._ring + return CompletionFunctor(self._p, self._default_prec, extras), self._ring @cached_method def uniformizer(self): @@ -238,9 +246,9 @@ def power_series(self, names=None, sparse=None): sparse = self.is_sparse() base = self._base.base_ring() if self._integer_ring is self: - S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) else: - S = LaurentSeriesRing(self._residue_ring, names, sparse=sparse) + S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S @@ -255,5 +263,5 @@ def fraction_field(self): return self else: return CompletionPolynomialRing(field, self._p, - default_prec = self.default_prec(), + default_prec = self._default_prec, sparse = self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index c83c73bdeed..7a5d2e28134 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1229,39 +1229,29 @@ def completion(self, p=None, prec=20, names=None): sage: A. = PolynomialRing(QQ) sage: K = A.fraction_field() - Without any argument, this method constructs the completion at - the ideal `(x)`:: + Without any argument, this method outputs the ring of Laurent + series:: sage: Kx = K.completion() sage: Kx Laurent Series Ring in x over Rational Field We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: + irreducible polynomial:: - sage: K1. = K.completion(x - 1) + sage: K1 = K.completion(x - 1) sage: K1 - Laurent Series Ring in u over Rational Field - sage: x - u - 1 - - :: - - sage: K2. = K.completion(x^2 + x + 1) + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at x - 1 + sage: K2 = K.completion(x^2 + x + 1) sage: K2 - Laurent Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at x^2 + x + 1 - When the precision is infinity, a lazy series ring is returned:: + TESTS:: sage: # needs sage.combinat - sage: L = K.completion(x, prec=oo); L - Lazy Laurent Series Ring in x over Rational Field + sage: L = K.completion(x, prec=oo) + sage: L.backend(force=True) + Lazy Laurent Series Ring in u_... over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x """ if p is None: p = self.variable_name() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 9ffb120eb38..385b2d32f73 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -951,6 +951,8 @@ def prec(self): """ return infinity + precision_absolute = prec + def lift_to_precision(self, absprec=None): """ Return another element of the same parent with absolute diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 80f87ac3a48..2b88725d645 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2491,6 +2491,18 @@ cdef class Polynomial(CommutativePolynomial): # But if any degree is allowed then there should certainly be a factor if self has degree > 0 raise AssertionError(f"no irreducible factor was computed for {self}. Bug.") + def perfect_power(self): + f = self + n = Integer(1) + for e, m in self.degree().factor(): + for _ in range(m): + try: + f = f.nth_root(e) + n *= e + except ValueError: + break + return f, n + def any_root(self, ring=None, degree=None, assume_squarefree=False, assume_equal_deg=False): """ Return a root of this polynomial in the given ring. @@ -10099,7 +10111,8 @@ cdef class Polynomial(CommutativePolynomial): sage: f.add_bigoh(2).parent() Power Series Ring in x over Integer Ring """ - return self._parent.completion(self._parent.gen())(self).add_bigoh(prec) + A = self._parent + return A.completion(A.variable_name())(self).add_bigoh(prec) @cached_method def is_irreducible(self): diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 5d0f82de77c..0101875a169 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1754,7 +1754,6 @@ def monics(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: @@ -1838,32 +1837,29 @@ def completion(self, p=None, prec=20, extras=None): Constructing the completion at the place of infinity also works:: - sage: C3. = P.completion(infinity) + sage: C3 = P.completion(infinity) sage: C3 - Laurent Series Ring in w over Rational Field - sage: C3(x) - w^-1 + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at infinity When the precision is infinity, a lazy series ring is returned:: + TESTS:: + sage: # needs sage.combinat - sage: PP = P.completion(x, prec=oo); PP - Lazy Taylor Series Ring in x over Rational Field + sage: PP = P.completion(x, prec=oo) + sage: PP.backend(force=True) + Lazy Taylor Series Ring in u_... over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x sage: g = 1 / PP(f); g - 1 + x + x^2 + O(x^3) + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + ... sage: 1 / g == f True - TESTS:: + :: sage: P.completion('x') Power Series Ring in x over Rational Field sage: P.completion('y') Power Series Ring in y over Rational Field - sage: Pz. = P.completion('y') - Traceback (most recent call last): - ... - ValueError: conflict of variable names """ if p is None: p = self.variable_name() diff --git a/src/sage/rings/power_series_pari.pyx b/src/sage/rings/power_series_pari.pyx index bd76579346e..6d102a817d3 100644 --- a/src/sage/rings/power_series_pari.pyx +++ b/src/sage/rings/power_series_pari.pyx @@ -434,7 +434,7 @@ cdef class PowerSeries_pari(PowerSeries): if a.valuation() <= 0: raise ValueError("can only substitute elements of positive valuation") elif isinstance(Q, PolynomialRing_generic): - Q = Q.completion(Q.gen()) + Q = Q.completion(Q.variable_name()) elif Q.is_exact() and not a: pass else: From c84ffe648503aa51d1a70abf6968422698741987 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 29 Aug 2025 01:00:55 +0200 Subject: [PATCH 21/24] implement quasi-linear algorithm for expansion (and printing) --- src/sage/rings/completion.py | 324 +++++++++++++++++++++++++++++++---- 1 file changed, 288 insertions(+), 36 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 75394271445..6a61cfb9014 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -1,5 +1,21 @@ +r""" +Completion of polynomial rings and their fraction fields +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import Element from sage.categories.fields import Fields @@ -16,15 +32,81 @@ class CompletionToPowerSeries(RingHomomorphism): - def __init__(self, parent): - super().__init__(parent) + r""" + Conversion morphism from a completion to the + underlying power series ring. + + TESTS:: + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: S. = Ap.power_series_ring() + sage: f = S.convert_map_from(Ap) + sage: type(f) + + + sage: # TestSuite(f).run() + """ def _call_(self, x): + r""" + Return the image of ``x`` by this morphism. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: S. = Ap.power_series_ring() + sage: S(Ap(x)) # indirect doctest + 1 + u + """ return self.codomain()(x.backend(force=True)) class CompletionPolynomial(RingExtensionElement): + r""" + An element in the completion of a polynomial ring + or a field of rational functions. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: u = Ap.random_element() + sage: type(u) + + """ + def __init__(self, parent, f): + if isinstance(f, Element): + R = f.parent() + ring = parent._ring + integer_ring = parent._integer_ring + if ring.has_coerce_map_from(R): + f = ring(f) + f = f(parent._gen) + #elif integer_ring.has_coerce_map_from(R): + # f = integer_ring(f).backend(force=True) + return super().__init__(parent, f) + def _repr_(self): + r""" + Return a string representation of this element. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: y = Ap(x) # indirect doctest + sage: y + 1 + (x - 1) + sage: y.add_bigoh(5) # indirect doctest + 1 + (x - 1) + O((x - 1)^5) + + :: + + sage: Ainf = A.completion(infinity) + sage: Ainf.uniformizer() # indirect doctest + x^-1 + """ # Uniformizer S = self.parent() u = S._p @@ -91,15 +173,84 @@ def _repr_(self): return s[1:] def expansion(self, include_final_zeroes=True): - # TODO: improve performance + r""" + Return a generator producing the list of coefficients + of this element, when written as a series in `p`. + + If the parent of this element does not contain elements + of negative valuation, the expansion starts at `0`; + otherwise, it starts at the valuation of the elment. + + INPUT: + + - ``include_final_zeroes`` (default: ``True``) : a boolean; + if ``False``, stop the iterator as soon as all the next + coefficients are all known to be zero + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: y = Ap(x) # indirect doctest + sage: y + 1 + (x - 1) + sage: E = y.expansion() + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + 0 + + Using ``include_final_zeroes=False`` stops the iterator after + the two first `1`:: + + sage: E = y.expansion(include_final_zeroes=False) + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + Traceback (most recent call last): + ... + StopIteration + + We underline that, for a nonexact element the iterator always + stops at the precision:: + + sage: z = y.add_bigoh(3) + sage: z + 1 + (x - 1) + O((x - 1)^3) + sage: E = z.expansion(include_final_zeroes=False) + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + 0 + sage: next(E) + Traceback (most recent call last): + ... + StopIteration + + Over the completion of a field of rational functions, the + iterator always starts at the first nonzero coefficient + (correspoding to the valuation of the element):: + + sage: Kp = Ap.fraction_field() + sage: u = Kp.uniformizer() + sage: u + (x - 1) + sage: E = u.expansion() + sage: next(E) + 1 + """ S = self.parent() A = S._base - incl = S._incl - u = self.parent()._p - elt = self.backend(force=True) + p = S._p prec = self.precision_absolute() - n = 0 - if u is Infinity: + if p is Infinity: + elt = self.backend(force=True) n = elt.valuation() while n < prec: if (not include_final_zeroes @@ -110,41 +261,118 @@ def expansion(self, include_final_zeroes=True): elt -= elt.parent()(coeff) << n n += 1 else: - if S._integer_ring is not S: - val = elt.valuation() - if val is Infinity: - return - if val < 0: - elt *= incl(u) ** (-val) - else: - n = val - v = S._p.derivative()(S._xbar).inverse() - vv = v**n - uu = u**n + if S._integer_ring is S: + n = 0 + else: + n = self.valuation() + if n < 0: + self *= p ** (-n) + prec -= n + n = 0 + self = S._integer_ring(self) + try: + f = self.polynomial() + current_prec = prec + except ValueError: + current_prec = n + 20 + f = self.add_bigoh(current_prec).polynomial() + if current_prec is not Infinity: + include_final_zeroes = True + f //= p ** n while n < prec: - if (not include_final_zeroes - and elt.precision_absolute() is Infinity and elt.is_zero()): + if n >= current_prec: + current_prec *= 2 + f = self.add_bigoh(current_prec).polynomial() + f //= p ** n + if not include_final_zeroes and f.is_zero(): break - coeff = (vv * elt[n]).lift() + f, coeff = f.quo_rem(p) yield coeff - elt -= incl(uu * coeff) - uu *= u - vv *= v n += 1 - def teichmuller(self): + def teichmuller_lift(self): + r""" + Return the Teichmüller representative of this element. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1, prec=5) + sage: a = Ap(x).teichmuller_lift() + sage: a + x + (4*x + 2)*(x^2 + x + 1) + (4*x + 2)*(x^2 + x + 1)^2 + ... + sage: a^2 + a + 1 + 0 + """ elt = self.backend(force=True) lift = elt.parent()(elt[0]) return self.parent()(lift) def valuation(self): + r""" + Return the valuation of this element. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1) + sage: Ap(x).valuation() + 0 + sage: u = Ap(x^2 + x + 1) + sage: u.valuation() + 1 + sage: (1/u).valuation() + -1 + """ return self.backend(force=True).valuation() + def lift_to_precision(self, prec): + elt = self.backend(force=True) + elt = elt.lift_to_precision(prec) + return self.parent()(elt) + + def polynomial(self): + S = self.parent() + A = S._ring + p = S._p + d = p.degree() + k = S.residue_field() + f = self.backend(force=True).polynomial().change_ring(k) + g = f(f.parent().gen() - k.gen()) + gs = [A([c[i] for c in g.list()]) for i in range(d)] + prec = self.precision_absolute() + if prec is Infinity: + for i in range(1, d): + if gs[i]: + raise ValueError("exact element which is not in the polynomial ring") + return gs[0] + xbar = S._xbar(prec) + modulus = p ** prec + res = gs[0] + xbari = A.one() + for i in range(1, d): + xbari = (xbari * xbar) % modulus + res += xbari * gs[i] + return res % modulus + def shift(self, n): raise NotImplementedError class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): + r""" + A class for completions of polynomial rings and their fraction fields. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: type(Ap) + + + sage: TestSuite(Ap).run() + + """ Element = CompletionPolynomial def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): @@ -178,6 +406,7 @@ def __init__(self, ring, p, default_prec, sparse): A = self._ring = ring if not isinstance(A, PolynomialRing_generic): A = A.ring() + self._A = A base = A.base_ring() self._p = p self._default_prec = default_prec @@ -191,7 +420,9 @@ def __init__(self, ring, p, default_prec, sparse): x = backend.gen().inverse() else: self._residue_ring = k = A.quotient(p) - self._xbar = xbar = k(A.gen()) + self._xbar_approx = A.gen() + self._xbar_modulus = p + self._xbar_prec = 1 if isinstance(ring, PolynomialRing_generic): self._integer_ring = self name = "u_%s" % id(self) @@ -200,13 +431,14 @@ def __init__(self, ring, p, default_prec, sparse): self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - x = backend.gen() + xbar + x = backend.gen() + k(A.gen()) super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base), category=backend.category()) # Set generator self._gen = self(x) # Set coercions - self._incl = ring.hom([x]) - self.register_coercion(A.Hom(self)(self._incl)) + self.register_coercion(ring) + if A is not ring: + self.register_coercion(A) if self._integer_ring is not None: self.register_coercion(self._integer_ring) @@ -234,21 +466,41 @@ def uniformizer(self): def gen(self): return self._gen + def _xbar(self, prec): + if self._p is Infinity: + return + # We solve the equation p(xbar) = 0 in A_p + p = self._p + pp = p.derivative() + while self._xbar_prec < prec: + self._xbar_modulus *= self._xbar_modulus + self._xbar_prec *= 2 + u = p(self._xbar_approx) + _, v, _ = pp(self._xbar_approx).xgcd(self._xbar_modulus) + self._xbar_approx -= u * v + self._xbar_approx %= self._xbar_modulus + return self._xbar_approx + def residue_ring(self): return self._residue_ring residue_field = residue_ring - def power_series(self, names=None, sparse=None): + def power_series_ring(self, names=None, sparse=None): if isinstance(names, (list, tuple)): names = names[0] if sparse is None: sparse = self.is_sparse() - base = self._base.base_ring() - if self._integer_ring is self: - S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) - else: - S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) + S.register_conversion(CompletionToPowerSeries(self.Hom(S))) + return S + + def laurent_series_ring(self, names=None, sparse=None): + if isinstance(names, (list, tuple)): + names = names[0] + if sparse is None: + sparse = self.is_sparse() + S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S From ee5902239dd03b34783a872979546c665be16876 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 29 Aug 2025 18:14:11 +0200 Subject: [PATCH 22/24] doctests --- src/sage/categories/pushout.py | 8 +- src/sage/rings/big_oh.py | 2 - src/sage/rings/completion.py | 377 +++++++++++++++++- .../rings/polynomial/polynomial_element.pyx | 21 + 4 files changed, 383 insertions(+), 25 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 078b4ba4d28..0b8d20bba15 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2484,7 +2484,7 @@ class CompletionFunctor(ConstructionFunctor): (1 + O(5^20))*a + 3 + 2*5 + 2*5^2 + 2*5^3 + 2*5^4 + 2*5^5 + 2*5^6 + 2*5^7 + 2*5^8 + 2*5^9 + 2*5^10 + 2*5^11 + 2*5^12 + 2*5^13 + 2*5^14 + 2*5^15 + 2*5^16 + 2*5^17 + 2*5^18 + 2*5^19 + O(5^20) """ rank = 4 - _real_types = ['Interval', 'Ball', 'MPFR', 'RDF', 'RLF', 'RR'] + _real_types = [None, 'Interval', 'Ball', 'MPFR', 'RDF', 'RLF', 'RR'] _dvr_types = [None, 'fixed-mod', 'floating-point', 'capped-abs', 'capped-rel', 'lattice-cap', 'lattice-float', 'relaxed'] def __init__(self, p, prec, extras=None): @@ -2541,7 +2541,7 @@ def __init__(self, p, prec, extras=None): from sage.rings.infinity import Infinity if self.p == Infinity: if self.type not in self._real_types: - raise ValueError("completion type must be one of %s" % (", ".join(self._real_types))) + raise ValueError("completion type must be one of %s" % (", ".join(self._real_types[1:]))) elif self.type not in self._dvr_types: raise ValueError("completion type must be one of %s" % (", ".join(self._dvr_types[1:]))) @@ -2777,9 +2777,9 @@ def commutes(self, other): functors in opposite order works. It does:: sage: P. = ZZ[] - sage: C = P.completion(x).construction()[0] + sage: C = P.completion('x').construction()[0] sage: R = FractionField(P) - sage: hasattr(R,'completion') + sage: hasattr(R, 'completion') False sage: C(R) is Frac(C(P)) True diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 7060037371f..6a05e39c849 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -200,8 +200,6 @@ def O(*x, **kwds): C = A.completion(p) return C.zero().add_bigoh(n) - - if isinstance(x, (int, Integer, Rational)): # p-adic number if x <= 0: diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 6a61cfb9014..ed1f79c3a05 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -275,7 +275,7 @@ def expansion(self, include_final_zeroes=True): current_prec = prec except ValueError: current_prec = n + 20 - f = self.add_bigoh(current_prec).polynomial() + f = self.polynomial(current_prec) if current_prec is not Infinity: include_final_zeroes = True f //= p ** n @@ -327,24 +327,94 @@ def valuation(self): return self.backend(force=True).valuation() def lift_to_precision(self, prec): + r""" + Return this element lifted to precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1) + sage: y = Ap(x).add_bigoh(10) + sage: y + x + O((x^2 + x + 1)^10) + sage: y.lift_to_precision(20) + x + O((x^2 + x + 1)^20) + + When ``prec`` is less than the absolute precision of + the element, the same element is returned without any + change on the precision:: + + sage: y.lift_to_precision(5) + x + O((x^2 + x + 1)^10) + """ elt = self.backend(force=True) elt = elt.lift_to_precision(prec) return self.parent()(elt) - def polynomial(self): + def polynomial(self, prec=None): + r""" + Return a polynomial (in the underlying polynomial ring) + which is indistinguishable from ``self`` at precision ``prec``. + + INPUT: + + - ``prec`` (optional) -- an integer; if not given, defaults + to the absolute precision of this element + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x - 2, prec=5) + sage: y = 1 / Ap(1 - x) + sage: y + 4 + (x + 3) + 4*(x + 3)^2 + (x + 3)^3 + 4*(x + 3)^4 + O((x + 3)^5) + sage: y.polynomial() + 4*x^4 + 4*x^3 + 4*x^2 + 4*x + 4 + + When called with a nonintegral element, this method raises an error:: + + sage: z = 1 / Ap(2 - x) + sage: z.polynomial() + Traceback (most recent call last): + ... + ValueError: this element of negative valuation cannot be approximated by a polynomial + + TESTS:: + + sage: Kq = A.completion(infinity) + sage: Kq(1/x).polynomial() + Traceback (most recent call last): + ... + ValueError: approximation by a polynomial does not make sense for a completion at infinity + """ S = self.parent() - A = S._ring + A = S._A p = S._p + if p is Infinity: + raise ValueError("approximation by a polynomial does not make sense for a completion at infinity") + backend = self.backend(force=True) + if backend.valuation() < 0: + raise ValueError("this element of negative valuation cannot be approximated by a polynomial") + try: + backend = backend.power_series() + except AttributeError: + pass d = p.degree() k = S.residue_field() - f = self.backend(force=True).polynomial().change_ring(k) + if prec is not None: + backend = backend.add_bigoh(prec) + prec = backend.precision_absolute() + f = backend.polynomial().change_ring(k) g = f(f.parent().gen() - k.gen()) gs = [A([c[i] for c in g.list()]) for i in range(d)] - prec = self.precision_absolute() if prec is Infinity: for i in range(1, d): if gs[i]: - raise ValueError("exact element which is not in the polynomial ring") + raise ValueError("this element is exact and does not lie in the polynomial ring") return gs[0] xbar = S._xbar(prec) modulus = p ** prec @@ -362,20 +432,51 @@ def shift(self, n): class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): r""" A class for completions of polynomial rings and their fraction fields. + """ + Element = CompletionPolynomial - TESTS:: + def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + r""" + Normalize the parameters and call the appropriate constructor. - sage: A. = QQ[] - sage: Ap = A.completion(x - 1) - sage: type(Ap) - + INPUT: - sage: TestSuite(Ap).run() + - ``ring`` -- the underlying polynomial ring or field - """ - Element = CompletionPolynomial + - ``p`` -- a generator of the ideal at which the completion is + done; it could also be ``Infinity`` - def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + - ``default_pres`` (default: ``20``) -- the default precision + of the completion + + - ``sparse`` (default: ``False``) -- a boolean + + TESTS:: + + sage: A. = ZZ[] + sage: A.completion(x^2 + x + 1) + Completion of Univariate Polynomial Ring in x over Integer Ring at x^2 + x + 1 + sage: A.completion(2*x - 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of p must be invertible + + :: + + sage: B. = QQ[] + sage: B.completion(x^2 + x^3) + Traceback (most recent call last): + ... + ValueError: p must be a squarefree polynomial + + When passing a variable name, a standard ring of power series is + constructed:: + + sage: A.completion('x') + Power Series Ring in x over Integer Ring + sage: A.completion(x) + Completion of Univariate Polynomial Ring in x over Integer Ring at x + """ if isinstance(ring, PolynomialRing_generic): if isinstance(p, str): return PowerSeriesRing(ring.base_ring(), @@ -389,8 +490,10 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): names=p, sparse=sparse) A = ring.ring() else: - raise ValueError("not a polynomial ring or a rational function field") - if p is not Infinity: + raise NotImplementedError("not a polynomial ring or a rational function field") + if p is Infinity: + ring = ring.fraction_field() + else: p = A(p) if not p.leading_coefficient().is_unit(): raise NotImplementedError("the leading coefficient of p must be invertible") @@ -403,6 +506,36 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): + r""" + Initialize this ring. + + INPUT: + + - ``ring`` -- the underlying polynomial ring or field + + - ``p`` -- a generator of the ideal at which the completion is + done; it could also be ``Infinity`` + + - ``default_pres`` -- the default precision of the completion + + - ``sparse`` -- a boolean + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: type(Ap) + + sage: TestSuite(Ap).run() + + :: + + sage: K = Frac(A) + sage: Kp = K.completion(x - 1) + sage: type(Kp) + + sage: TestSuite(Kp).run() + """ A = self._ring = ring if not isinstance(A, PolynomialRing_generic): A = A.ring() @@ -412,7 +545,6 @@ def __init__(self, ring, p, default_prec, sparse): self._default_prec = default_prec # Construct backend if p is Infinity: - self._ring = ring.fraction_field() self._residue_ring = k = base self._integer_ring = None name = "u_%s" % id(self) @@ -441,14 +573,60 @@ def __init__(self, ring, p, default_prec, sparse): self.register_coercion(A) if self._integer_ring is not None: self.register_coercion(self._integer_ring) + if p is not Infinity and p.is_gen(): + S = PowerSeriesRing(base, A.variable_name(), default_prec=default_prec, sparse=sparse) + self.register_coercion(S) + if self._integer_ring is not self: + S = LaurentSeriesRing(base, A.variable_name(), default_prec=default_prec, sparse=sparse) + self.register_coercion(S) def _repr_(self): + r""" + Return a string representation of this ring. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: A.completion(x) # indirect doctest + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x + sage: A.completion(infinity) # indirect doctest + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at infinity + + :: + + sage: K = Frac(A) + sage: K.completion(x^2 + 2*x + 2) # indirect doctest + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + """ if self._p is Infinity: return "Completion of %s at infinity" % self._ring else: return "Completion of %s at %s" % (self._ring, self._p) def construction(self): + r""" + Return the functorial construction of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: F, X = Ap.construction() + sage: F + Completion[x^2 - 1, prec=20] + sage: X + Univariate Polynomial Ring in x over Rational Field + sage: F(X) is Ap + True + + TESTS:: + + sage: R = PolynomialRing(QQ, 'y', sparse=True) + sage: Kinf = R.completion(infinity) + sage: F, X = Kinf.construction() + sage: F(X) is Kinf + True + """ from sage.categories.pushout import CompletionFunctor extras = { 'names': [str(x) for x in self._defining_names()], @@ -458,15 +636,55 @@ def construction(self): @cached_method def uniformizer(self): + r""" + Return a uniformizer of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: Ap.uniformizer() + (x^2 - 1) + """ if self._p is Infinity: return self._gen.inverse() else: return self(self._p) def gen(self): + r""" + Return the generator of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: Ap.gen() + x + """ return self._gen def _xbar(self, prec): + r""" + Return an approximation at precision ``prec`` of the + Teichmüller representative of `x`, the generator of + this ring. + + This method is only for internal use. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap._xbar(5) + 4*x^15 + 4*x^14 + x^8 + x + 6 + + The method returns nothing in case of a completion + at infinity:: + + sage: Ap = A.completion(infinity) + sage: Ap._xbar(5) + """ if self._p is Infinity: return # We solve the equation p(xbar) = 0 in A_p @@ -482,11 +700,59 @@ def _xbar(self, prec): return self._xbar_approx def residue_ring(self): + r""" + Return the residue ring of this completion. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap.residue_ring() + Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + """ return self._residue_ring residue_field = residue_ring def power_series_ring(self, names=None, sparse=None): + r""" + Return a power series ring, which is isomorphic to + the integer ring of this completion. + + INPUT: + + - ``names`` -- a string, the variable name + + - ``sparse`` (optional) -- a boolean; if not given, + use the sparcity of this ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: S. = Ap.power_series_ring() + sage: S + Power Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + + A conversion from ``Ap`` to ``S`` is set:: + + sage: xp = Ap.gen() + sage: S(xp) + xbar + u + + :: + + sage: Kp = Frac(Ap) + sage: T. = Kp.power_series_ring() + sage: T + Power Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + sage: T is S + True + + .. SEEALSO:: + + :meth:`laurent_series_ring` + """ if isinstance(names, (list, tuple)): names = names[0] if sparse is None: @@ -496,6 +762,29 @@ def power_series_ring(self, names=None, sparse=None): return S def laurent_series_ring(self, names=None, sparse=None): + r""" + Return a Laurent series ring, which is isomorphic to + the fraction field of this completion. + + INPUT: + + - ``names`` -- a string, the variable name + + - ``sparse`` (optional) -- a boolean; if not given, + use the sparcity of this ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: S. = Ap.laurent_series_ring() + sage: S + Laurent Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + + .. SEEALSO:: + + :meth:`power_series_ring` + """ if isinstance(names, (list, tuple)): names = names[0] if sparse is None: @@ -505,11 +794,61 @@ def laurent_series_ring(self, names=None, sparse=None): return S def integer_ring(self): + r""" + Return the integer ring of this completion. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: K = Frac(A) + sage: Kp = K.completion(x^2 + 2*x + 2) + sage: Kp + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + sage: Kp.integer_ring() + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + """ if self._p is Infinity: raise NotImplementedError return self._integer_ring - def fraction_field(self): + def is_integral_domain(self): + return self._residue_ring.is_integral_domain() + + def fraction_field(self, permissive=False): + r""" + Return the fraction field of this completion. + + - ``permissive`` (default: ``False``) -- a boolean; + if ``True`` and this ring is a domain, return + instead the completion at the same place of the + fraction field of the underlying polynomial ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + sage: Ap.fraction_field() + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + + Trying to apply this method with a completion at a nonprime ideal + produces an error:: + + sage: Aq = A.completion(x^2 + x + 1) + sage: Aq.fraction_field() + Traceback (most recent call last): + ... + ValueError: this ring is not an integral domain + + Nonetheless, if the flag ``permissive`` is set to ``True``, + the localization at all nonzero divisors is returned:: + + sage: Aq.fraction_field(permissive=True) + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + x + 1 + """ + if not (permissive or self.is_integral_domain()): + raise ValueError("this ring is not an integral domain") field = self._ring.fraction_field() if field is self._ring: return self diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 2b88725d645..a1546c9a4ba 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2492,6 +2492,27 @@ cdef class Polynomial(CommutativePolynomial): raise AssertionError(f"no irreducible factor was computed for {self}. Bug.") def perfect_power(self): + r""" + Return ``(P, n)``, where this polynomial is `P^n` and `n` is maximal. + + EXAMPLES:: + + sage: A. = QQ[] + sage: f = x^2 - 2*x + 1 + sage: f.perfect_power() + (-x + 1, 2) + + :: + + sage: P = (x + 1)^100 + sage: Q = (x + 2)^50 + sage: P.perfect_power() + (x + 1, 100) + sage: Q.perfect_power() + (x + 2, 50) + sage: (P*Q).perfect_power() + (x^3 + 4*x^2 + 5*x + 2, 50) + """ f = self n = Integer(1) for e, m in self.degree().factor(): From d816dc5423c07e89bb57d7e3560d4784579df454 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 30 Aug 2025 09:34:39 +0200 Subject: [PATCH 23/24] completion -> completion_polynomial_ring --- .../{completion.py => completion_polynomial_ring.py} | 8 ++++---- src/sage/rings/fraction_field.py | 2 +- src/sage/rings/meson.build | 1 + src/sage/rings/polynomial/polynomial_ring.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/sage/rings/{completion.py => completion_polynomial_ring.py} (98%) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion_polynomial_ring.py similarity index 98% rename from src/sage/rings/completion.py rename to src/sage/rings/completion_polynomial_ring.py index ed1f79c3a05..ec65f68c0c6 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion_polynomial_ring.py @@ -43,7 +43,7 @@ class CompletionToPowerSeries(RingHomomorphism): sage: S. = Ap.power_series_ring() sage: f = S.convert_map_from(Ap) sage: type(f) - + sage: # TestSuite(f).run() """ @@ -73,7 +73,7 @@ class CompletionPolynomial(RingExtensionElement): sage: Ap = A.completion(x - 1) sage: u = Ap.random_element() sage: type(u) - + """ def __init__(self, parent, f): if isinstance(f, Element): @@ -525,7 +525,7 @@ def __init__(self, ring, p, default_prec, sparse): sage: A. = QQ[] sage: Ap = A.completion(x - 1) sage: type(Ap) - + sage: TestSuite(Ap).run() :: @@ -533,7 +533,7 @@ def __init__(self, ring, p, default_prec, sparse): sage: K = Frac(A) sage: Kp = K.completion(x - 1) sage: type(Kp) - + sage: TestSuite(Kp).run() """ A = self._ring = ring diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 7a5d2e28134..c06540b110f 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1255,7 +1255,7 @@ def completion(self, p=None, prec=20, names=None): """ if p is None: p = self.variable_name() - from sage.rings.completion import CompletionPolynomialRing + from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) class FractionFieldEmbedding(DefaultConvertMap_unique): diff --git a/src/sage/rings/meson.build b/src/sage/rings/meson.build index 55a99548aca..6e8648d34f0 100644 --- a/src/sage/rings/meson.build +++ b/src/sage/rings/meson.build @@ -9,6 +9,7 @@ py.install_sources( 'cfinite_sequence.py', 'cif.py', 'commutative_algebra.py', + 'completion_polynomial_ring.py', 'complex_arb.pxd', 'complex_conversion.pxd', 'complex_double.pxd', diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 0101875a169..d77dfb5403e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1863,7 +1863,7 @@ def completion(self, p=None, prec=20, extras=None): """ if p is None: p = self.variable_name() - from sage.rings.completion import CompletionPolynomialRing + from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): From 973dffa9c4b31e759d1faf9314974153d0f43f77 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 30 Aug 2025 10:01:24 +0200 Subject: [PATCH 24/24] oops --- src/sage/rings/completion_polynomial_ring.py | 10 +++++----- src/sage/rings/fraction_field.py | 1 + src/sage/rings/polynomial/polynomial_element.pyx | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 4 +--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/completion_polynomial_ring.py b/src/sage/rings/completion_polynomial_ring.py index ec65f68c0c6..5e33af0ae81 100644 --- a/src/sage/rings/completion_polynomial_ring.py +++ b/src/sage/rings/completion_polynomial_ring.py @@ -85,7 +85,7 @@ def __init__(self, parent, f): f = f(parent._gen) #elif integer_ring.has_coerce_map_from(R): # f = integer_ring(f).backend(force=True) - return super().__init__(parent, f) + super().__init__(parent, f) def _repr_(self): r""" @@ -255,7 +255,7 @@ def expansion(self, include_final_zeroes=True): while n < prec: if (not include_final_zeroes and elt.precision_absolute() is Infinity and elt.is_zero()): - break + break coeff = elt[n] yield coeff elt -= elt.parent()(coeff) << n @@ -285,7 +285,7 @@ def expansion(self, include_final_zeroes=True): f = self.add_bigoh(current_prec).polynomial() f //= p ** n if not include_final_zeroes and f.is_zero(): - break + break f, coeff = f.quo_rem(p) yield coeff n += 1 @@ -854,5 +854,5 @@ def fraction_field(self, permissive=False): return self else: return CompletionPolynomialRing(field, self._p, - default_prec = self._default_prec, - sparse = self.is_sparse()) + default_prec=self._default_prec, + sparse=self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index c06540b110f..595b1589697 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1258,6 +1258,7 @@ def completion(self, p=None, prec=20, names=None): from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) + class FractionFieldEmbedding(DefaultConvertMap_unique): r""" The embedding of an integral domain into its field of fractions. diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index a1546c9a4ba..e6c9118413f 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2510,7 +2510,7 @@ cdef class Polynomial(CommutativePolynomial): (x + 1, 100) sage: Q.perfect_power() (x + 2, 50) - sage: (P*Q).perfect_power() + sage: (P*Q).perfect_power() (x^3 + 4*x^2 + 5*x + 2, 50) """ f = self diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index d77dfb5403e..b1dc3da48dc 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1843,8 +1843,6 @@ def completion(self, p=None, prec=20, extras=None): When the precision is infinity, a lazy series ring is returned:: - TESTS:: - sage: # needs sage.combinat sage: PP = P.completion(x, prec=oo) sage: PP.backend(force=True) @@ -1854,7 +1852,7 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g == f True - :: + TESTS:: sage: P.completion('x') Power Series Ring in x over Rational Field