diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index a8458de23fb..0b8d20bba15 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:: @@ -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/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..6a05e39c849 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,29 @@ 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_polynomial_ring.py b/src/sage/rings/completion_polynomial_ring.py new file mode 100644 index 00000000000..5e33af0ae81 --- /dev/null +++ b/src/sage/rings/completion_polynomial_ring.py @@ -0,0 +1,858 @@ +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 + +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 + + +class CompletionToPowerSeries(RingHomomorphism): + 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) + 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 + if u is Infinity: + step = -1 + unif = S._ring.variable_name() + else: + step = 1 + 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 = "..." + elif prec == 0: + bigoh = "O(1)" + elif step*prec == 1: + bigoh = "O(%s)" % unif + else: + bigoh = "O(%s^%s)" % (unif, step*prec) + + E = self.expansion(include_final_zeroes=False) + is_exact = False + terms = [] + if S._integer_ring is S: + start = 0 + else: + 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) + except StopIteration: + is_exact = True + break + if coeff.is_zero(): + continue + if coeff._is_atomic(): + coeff = str(coeff) + else: + 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) + s = " " + " + ".join(terms) + s = s.replace(" + -", " - ") + s = s.replace(" 1*"," ") + s = s.replace(" -1*", " -") + return s[1:] + + def expansion(self, include_final_zeroes=True): + 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 + p = S._p + prec = self.precision_absolute() + if p is Infinity: + elt = self.backend(force=True) + 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 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.polynomial(current_prec) + if current_prec is not Infinity: + include_final_zeroes = True + f //= p ** n + while n < prec: + 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 + f, coeff = f.quo_rem(p) + yield coeff + n += 1 + + 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): + 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, 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._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() + 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)] + if prec is Infinity: + for i in range(1, d): + if gs[i]: + raise ValueError("this element is exact and does not lie 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. + """ + Element = CompletionPolynomial + + def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + r""" + Normalize the parameters and call the appropriate constructor. + + 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`` (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(), + 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 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") + p = A(p.monic()) + try: + if not p.is_squarefree(): + raise ValueError("p must be a squarefree polynomial") + except (AttributeError, NotImplementedError): + pass + 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() + self._A = A + base = A.base_ring() + self._p = p + self._default_prec = default_prec + # Construct backend + if p is Infinity: + self._residue_ring = k = base + self._integer_ring = None + name = "u_%s" % id(self) + backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + x = backend.gen().inverse() + else: + self._residue_ring = k = A.quotient(p) + 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) + 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() + 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.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) + 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()], + 'sparse': self.is_sparse() + } + return CompletionFunctor(self._p, self._default_prec, extras), self._ring + + @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 + 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): + 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: + sparse = self.is_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): + 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: + 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 + + 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 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 + 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 5a633c8a846..595b1589697 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1206,6 +1206,58 @@ def _coerce_map_from_(self, R): return super()._coerce_map_from_(R) + 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`. + + 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`. + + - ``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 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:: + + sage: K1 = K.completion(x - 1) + sage: K1 + 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 + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at x^2 + x + 1 + + TESTS:: + + sage: # needs sage.combinat + 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() + 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""" 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/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/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_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 80f87ac3a48..e6c9118413f 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2491,6 +2491,39 @@ 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): + 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(): + 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 +10132,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 e0d1ac37eb3..b1dc3da48dc 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,55 +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): - r""" - Return the completion of ``self`` 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`. - - EXAMPLES:: - - sage: P. = PolynomialRing(QQ) - sage: P - Univariate Polynomial Ring in x over Rational Field - sage: PP = P.completion(x) - 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) - - 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 - """ - 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)) - def _coerce_map_from_base_ring(self): """ Return a coercion map from the base ring of ``self``. @@ -1150,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. @@ -1742,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 ) @@ -1801,8 +1754,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 ) @@ -1833,6 +1785,85 @@ 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): + r""" + Return the completion of this polynomial ring with respect to + the irreducible polynomial ``p``. + + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infinity`` + + - ``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 + + EXAMPLES:: + + sage: P. = PolynomialRing(QQ) + sage: P + Univariate Polynomial Ring in x over Rational Field + + Without any argument, this method returns the power series ring + with the same variable name:: + + 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:: + + sage: C1 = P.completion(x - 1) + sage: C1 + Completion of Univariate Polynomial Ring in x over Rational Field at x - 1 + sage: C2 = P.completion(x^2 + x + 1) + sage: C2 + 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:: + + sage: C3 = P.completion(infinity) + sage: C3 + 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:: + + sage: # needs sage.combinat + 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 + 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 + """ + if p is None: + p = self.variable_name() + 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): """ Return the quotient of this polynomial ring by the principal 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: diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index 129477ccc8c..f272e71e699 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -392,14 +392,13 @@ 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 - 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): @@ -412,17 +411,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 +617,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/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