Skip to content

Commit

Permalink
sagemathgh-37952: Adding add_bigoh methods to lazy power/Laurent seri…
Browse files Browse the repository at this point in the history
…es and coercions to the finite precision implementations

    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

It is natural to go from the lazy series (infinite precision) to the
finite precision cases. We provide these coercion maps and the analogous
methods `add_bigoh` and `O` for better compatibility.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
    
URL: sagemath#37952
Reported by: Travis Scrimshaw
Reviewer(s): Martin Rubey, Travis Scrimshaw
  • Loading branch information
Release Manager committed May 18, 2024
2 parents 74f0cc5 + e171ec5 commit 4a1fbaa
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 15 deletions.
34 changes: 33 additions & 1 deletion src/sage/rings/laurent_series_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ def is_LaurentSeriesRing(x):
sage: K.<q> = LaurentSeriesRing(QQ)
sage: is_LaurentSeriesRing(K)
True
sage: L.<z> = 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):
Expand Down Expand Up @@ -440,6 +444,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.<z> = 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.<z> = LazyPowerSeriesRing(QQ)
sage: R(5 + z - 5*z^7)
5 + z - 5*z^7
TESTS:
Check that :issue:`28993` is fixed::
Expand Down Expand Up @@ -489,6 +504,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
Expand Down Expand Up @@ -525,6 +541,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'):
Expand Down Expand Up @@ -618,6 +642,10 @@ def _coerce_map_from_(self, P):
True
sage: S.has_coerce_map_from(S)
True
sage: S.has_coerce_map_from(LazyLaurentSeriesRing(ZZ, 't'))
True
sage: S.has_coerce_map_from(LazyPowerSeriesRing(ZZ, 't'))
True
sage: S.has_coerce_map_from(QQ)
False
Expand All @@ -641,6 +669,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 (
Expand Down
42 changes: 42 additions & 0 deletions src/sage/rings/lazy_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -6065,6 +6068,45 @@ 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 (tuple of)
variable(s).
EXAMPLES::
sage: L.<x,y> = 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.<t> = QQ[]
sage: L.<x> = 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``.
Expand Down
40 changes: 33 additions & 7 deletions src/sage/rings/multi_power_series_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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::
Expand Down Expand Up @@ -817,6 +823,10 @@ def _coerce_map_from_(self, P):
sage: H._coerce_map_from_(PolynomialRing(ZZ,'z2,f0'))
True
sage: L.<x,y> = 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):
Expand Down Expand Up @@ -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.<x,y> = 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):
Expand Down
37 changes: 30 additions & 7 deletions src/sage/rings/power_series_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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.<t> = PowerSeriesRing(ZZ)
Expand Down Expand Up @@ -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.<x> = 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)
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 4a1fbaa

Please sign in to comment.