From 289fcf59f5b0592d5c0b9fafa5e5464619c3e253 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 7 May 2024 14:48:00 +0900 Subject: [PATCH 1/3] Adding add_bigoh methods to lazy power/Laurent series and coercions to finite precision cases. --- src/sage/rings/laurent_series_ring.py | 30 ++++++++++++++++- src/sage/rings/lazy_series.py | 41 +++++++++++++++++++++++ src/sage/rings/multi_power_series_ring.py | 40 ++++++++++++++++++---- src/sage/rings/power_series_ring.py | 37 ++++++++++++++++---- 4 files changed, 133 insertions(+), 15 deletions(-) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 4e0c6f0d557..ad7fd75bcd0 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -63,8 +63,12 @@ def is_LaurentSeriesRing(x): sage: K. = LaurentSeriesRing(QQ) sage: is_LaurentSeriesRing(K) True + sage: L. = LazyLaurentSeriesRing(QQ) + sage: is_LaurentSeriesRing(L) + True """ - return isinstance(x, LaurentSeriesRing) + from sage.rings.lazy_series_ring import LazyLaurentSeriesRing + return isinstance(x, (LaurentSeriesRing, LazyLaurentSeriesRing)) class LaurentSeriesRing(UniqueRepresentation, CommutativeRing): @@ -439,6 +443,17 @@ def _element_constructor_(self, x, n=0, prec=infinity): 1/64*I*u^10 - 1/128*u^12 - 1/256*I*u^14 + 1/512*u^16 + 1/1024*I*u^18 + O(u^20) + Lazy series:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: R = LaurentSeriesRing(QQ, names='z') + sage: R(z^-5 + 1/(1-z)) + z^-5 + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + z^8 + z^9 + z^10 + + z^11 + z^12 + z^13 + z^14 + z^15 + z^16 + z^17 + z^18 + z^19 + O(z^20) + sage: L. = LazyPowerSeriesRing(QQ) + sage: R(5 + z - 5*z^7) + 5 + z - 5*z^7 + TESTS: Check that :issue:`28993` is fixed:: @@ -488,6 +503,7 @@ def _element_constructor_(self, x, n=0, prec=infinity): x^-3 """ from sage.rings.fraction_field_element import is_FractionFieldElement + from sage.rings.lazy_series import LazyPowerSeries, LazyLaurentSeries from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.polynomial.polynomial_element import Polynomial from sage.structure.element import parent @@ -524,6 +540,14 @@ def _element_constructor_(self, x, n=0, prec=infinity): and isinstance(x.numerator(), (Polynomial, MPolynomial))): x = self(x.numerator()) / self(x.denominator()) return (x << n).add_bigoh(prec) + elif isinstance(x, (LazyPowerSeries, LazyLaurentSeries)): + if prec is infinity: + try: + x = self.power_series_ring()(x.polynomial()) + except ValueError: + x = x.add_bigoh(self.default_prec()) + else: + x = x.add_bigoh(prec) return self.element_class(self, x, n).add_bigoh(prec) def random_element(self, algorithm='default'): @@ -617,6 +641,10 @@ def _coerce_map_from_(self, P): True sage: S.has_coerce_map_from(S) True + sage: S.has_coerce_map_from(LazyLaurentSeriesRing(QQ, 't')) + True + sage: S.has_coerce_map_from(LazyPowerSeriesRing(ZZ, 't')) + True sage: S.has_coerce_map_from(QQ) False diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index f4c46a03ca5..69b7f4fcebb 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -4746,6 +4746,9 @@ def approximate_series(self, prec, name=None): R = PowerSeriesRing(S.base_ring(), name=name) return R([self[i] for i in range(prec)]).add_bigoh(prec) + add_bigoh = approximate_series + O = approximate_series + def polynomial(self, degree=None, name=None): r""" Return ``self`` as a Laurent polynomial if ``self`` is actually so. @@ -6065,6 +6068,44 @@ def polynomial(self, degree=None, names=None): return R(self[0:m]) return R.sum(self[0:m]) + def add_bigoh(self, prec): + r""" + Return the power series of precision at most ``prec`` obtained by + adding `O(q^\text{prec})` to `f`, where `q` is the variable(s). + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = 1 / (1 - x + y) + sage: f + 1 + (x-y) + (x^2-2*x*y+y^2) + (x^3-3*x^2*y+3*x*y^2-y^3) + + (x^4-4*x^3*y+6*x^2*y^2-4*x*y^3+y^4) + + (x^5-5*x^4*y+10*x^3*y^2-10*x^2*y^3+5*x*y^4-y^5) + + (x^6-6*x^5*y+15*x^4*y^2-20*x^3*y^3+15*x^2*y^4-6*x*y^5+y^6) + + O(x,y)^7 + sage: f3 = f.add_bigoh(3); f3 + 1 + x - y + x^2 - 2*x*y + y^2 + O(x, y)^3 + sage: f3.parent() + Multivariate Power Series Ring in x, y over Rational Field + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = 1 / (1 - t^3*x) + sage: f + 1 + t^3*x + t^6*x^2 + t^9*x^3 + t^12*x^4 + t^15*x^5 + t^18*x^6 + O(x^7) + sage: f3 = f.add_bigoh(3); f3 + 1 + t^3*x + t^6*x^2 + O(x^3) + sage: f3.parent() + Power Series Ring in x over Univariate Polynomial Ring in t + over Rational Field + """ + from sage.rings.power_series_ring import PowerSeriesRing + P = self.parent() + PSR = PowerSeriesRing(P.base_ring(), names=P.variable_names()) + return PSR(self.polynomial(degree=prec-1), prec=prec) + + O = add_bigoh + def _floordiv_(self, other): r""" Return ``self`` floor divided by ``other``. diff --git a/src/sage/rings/multi_power_series_ring.py b/src/sage/rings/multi_power_series_ring.py index 0b34dfa003b..f2e47ccc835 100644 --- a/src/sage/rings/multi_power_series_ring.py +++ b/src/sage/rings/multi_power_series_ring.py @@ -246,9 +246,15 @@ def is_MPowerSeriesRing(x): True sage: is_MPowerSeriesRing(T) False - + sage: L = LazyPowerSeriesRing(QQ, 'x') + sage: is_MPowerSeriesRing(L) + True + sage: L = LazyPowerSeriesRing(QQ, 'x, y') + sage: is_MPowerSeriesRing(L) + True """ - return isinstance(x, MPowerSeriesRing_generic) + from sage.rings.lazy_series_ring import LazyPowerSeriesRing + return isinstance(x, (MPowerSeriesRing_generic, LazyPowerSeriesRing)) class MPowerSeriesRing_generic(PowerSeriesRing_generic, Nonexact): @@ -754,13 +760,13 @@ def _coerce_map_from_(self, P): The rings that canonically coerce to this multivariate power series ring are: - - this ring itself + - this ring itself - - a polynomial or power series ring in the same variables or a - subset of these variables (possibly empty), over any base - ring that canonically coerces into this ring + - a polynomial or power series ring in the same variables or a + subset of these variables (possibly empty), over any base + ring that canonically coerces into this ring - - any ring that coerces into the foreground polynomial ring of this ring + - any ring that coerces into the foreground polynomial ring of this ring EXAMPLES:: @@ -817,6 +823,10 @@ def _coerce_map_from_(self, P): sage: H._coerce_map_from_(PolynomialRing(ZZ,'z2,f0')) True + sage: L. = LazyPowerSeriesRing(QQ) + sage: R = PowerSeriesRing(QQ, names=('x','y','z')) + sage: R.has_coerce_map_from(L) + True """ if is_MPolynomialRing(P) or is_MPowerSeriesRing(P) \ or is_PolynomialRing(P) or is_PowerSeriesRing(P): @@ -846,12 +856,28 @@ def _element_constructor_(self, f, prec=None): sage: M._element_constructor_(p).parent() Multivariate Power Series Ring in t0, t1, t2, t3, t4 over Integer Ring + + sage: L. = LazyPowerSeriesRing(QQ) + sage: R = PowerSeriesRing(QQ, names=('x','y','z')) + sage: R(1/(1-x-y), prec=3) + 1 + x + y + x^2 + 2*x*y + y^2 + O(x, y, z)^3 + sage: R(x + y^2) + x + y^2 """ if prec is None: try: prec = f.prec() except AttributeError: prec = infinity + from sage.rings.lazy_series import LazyPowerSeries + if isinstance(f, LazyPowerSeries): + if prec is infinity: + try: + f = f.polynomial() + except ValueError: + f = f.add_bigoh(self.default_prec()) + else: + f = f.add_bigoh(prec) return self.element_class(parent=self, x=f, prec=prec) def laurent_series_ring(self): diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index f4f97428011..08a6d9f2d3f 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -470,8 +470,13 @@ def is_PowerSeriesRing(R): False sage: is_PowerSeriesRing(QQ[['x']]) True + sage: is_PowerSeriesRing(LazyPowerSeriesRing(QQ, 'x')) + True + sage: is_PowerSeriesRing(LazyPowerSeriesRing(QQ, 'x, y')) + False """ - if isinstance(R, PowerSeriesRing_generic): + from sage.rings.lazy_series_ring import LazyPowerSeriesRing + if isinstance(R, (PowerSeriesRing_generic, LazyPowerSeriesRing)): return R.ngens() == 1 else: return False @@ -684,8 +689,8 @@ def _latex_(self): def _coerce_map_from_(self, S): """ - A coercion from `S` exists, if `S` coerces into ``self``'s base ring, - or if `S` is a univariate polynomial or power series ring with the + A coercion from ``S`` exists, if ``S`` coerces into ``self``'s base ring, + or if ``S`` is a univariate polynomial or power series ring with the same variable name as self, defined over a base ring that coerces into ``self``'s base ring. @@ -700,7 +705,8 @@ def _coerce_map_from_(self, S): False sage: A.has_coerce_map_from(ZZ[['x']]) True - + sage: A.has_coerce_map_from(LazyPowerSeriesRing(ZZ, 'x')) + True """ if self.base_ring().has_coerce_map_from(S): return True @@ -712,8 +718,8 @@ def _element_constructor_(self, f, prec=infinity, check=True): """ Coerce object to this power series ring. - Returns a new instance unless the parent of f is self, in which - case f is returned (since f is immutable). + Returns a new instance unless the parent of ``f`` is ``self``, in + which case ``f`` is returned (since ``f`` is immutable). INPUT: @@ -726,7 +732,6 @@ def _element_constructor_(self, f, prec=infinity, check=True): - ``check`` -- bool (default: ``True``), whether to verify that the coefficients, etc., coerce in correctly. - EXAMPLES:: sage: R. = PowerSeriesRing(ZZ) @@ -803,6 +808,14 @@ def _element_constructor_(self, f, prec=infinity, check=True): ... ValueError: prec (= -5) must be non-negative + From lazy series:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: R = PowerSeriesRing(QQ, 'x') + sage: R(1 / (1 + x^3)) + 1 - x^3 + x^6 - x^9 + x^12 - x^15 + x^18 + O(x^20) + sage: R(2 - x^2 + x^6) + 2 - x^2 + x^6 """ if prec is not infinity: prec = integer.Integer(prec) @@ -832,6 +845,16 @@ def _element_constructor_(self, f, prec=infinity, check=True): f.degree(f.default_variable()), check=check) else: raise TypeError("Can only convert series into ring with same variable name.") + else: + from sage.rings.lazy_series import LazyPowerSeries + if isinstance(f, LazyPowerSeries): + if prec is infinity: + try: + f = f.polynomial() + except ValueError: + f = f.add_bigoh(self.default_prec()) + else: + f = f.add_bigoh(prec) return self.element_class(self, f, prec, check=check) def construction(self): From e1e9467e8becec10347ad60fc778ed77a2f10602 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 10 May 2024 13:52:55 +0900 Subject: [PATCH 2/3] Fixing doctest and adding another. --- src/sage/rings/laurent_series_ring.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index ad7fd75bcd0..23fe69d1245 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -641,7 +641,7 @@ def _coerce_map_from_(self, P): True sage: S.has_coerce_map_from(S) True - sage: S.has_coerce_map_from(LazyLaurentSeriesRing(QQ, 't')) + sage: S.has_coerce_map_from(LazyLaurentSeriesRing(ZZ, 't')) True sage: S.has_coerce_map_from(LazyPowerSeriesRing(ZZ, 't')) True @@ -668,6 +668,10 @@ def _coerce_map_from_(self, P): False sage: R.has_coerce_map_from(ZZ['x']) True + sage: R.has_coerce_map_from(LazyLaurentSeriesRing(ZZ, 't')) + True + sage: R.has_coerce_map_from(LazyLaurentSeriesRing(ZZ['x'], 't')) + True """ A = self.base_ring() from sage.rings.polynomial.laurent_polynomial_ring_base import ( From e171ec52eb160a8eaf7791158c6e542a270148f7 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 10 May 2024 21:47:41 +0900 Subject: [PATCH 3/3] Addressing last reviewer comment --- src/sage/rings/lazy_series.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 69b7f4fcebb..c7b90375f7e 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -6071,7 +6071,8 @@ def polynomial(self, degree=None, names=None): def add_bigoh(self, prec): r""" Return the power series of precision at most ``prec`` obtained by - adding `O(q^\text{prec})` to `f`, where `q` is the variable(s). + adding `O(q^\text{prec})` to `f`, where `q` is the (tuple of) + variable(s). EXAMPLES::