From eb5314fe5806fda55c8a1f9afecd6cc0bdde79f3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 9 Aug 2021 20:51:25 +0200 Subject: [PATCH 1/9] take care of negative degree of f in __call__ --- src/sage/rings/lazy_laurent_series.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 5df39c57e5a..b8a1f23509a 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -1262,6 +1262,12 @@ def __call__(self, g): sage: g^-3 + g^-2 + 1 / (1 + g^2) z^-9 + 3*z^-8 + 3*z^-7 - z^-6 - 4*z^-5 - 2*z^-4 + z^-3 + O(z^-2) + sage: f = z^-3; g = z^-2 + z^-1; + sage: g^(-3) + z^6 - 3*z^7 + 6*z^8 - 10*z^9 + 15*z^10 - 21*z^11 + 28*z^12 + O(z^13) + sage: f(g) + z^6 - 3*z^7 + 6*z^8 - 10*z^9 + 15*z^10 - 21*z^11 + 28*z^12 + O(z^13) + sage: f = L(lambda n: n); f z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: f(z^2) @@ -1466,15 +1472,17 @@ def __call__(self, g): # We build this iteratively so each power can benefit from the caching # Equivalent to P.sum(poly[i] * g**i for i in range(poly.valuation(), poly.degree()+1)) # We could just do "return poly(g)" if we don't care about speed - deg = poly.degree() - for i in range(deg): - ret += poly[i] * gp - gp *= g - ret += poly[deg] * gp - if poly.valuation() < 0: + d = poly.degree() + if d >= 0: + for i in range(d): + ret += poly[i] * gp + gp *= g + ret += poly[d] * gp + v = poly.valuation() + if v < 0: gi = ~g gp = P.one() - for i in range(-1, poly.valuation()-1, -1): + for i in range(-1, v-1, -1): gp *= gi ret += poly[i] * gp return ret From 883a01ef43848ed22a0d0f6a48d52bf3cd553bbe Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 9 Aug 2021 21:22:01 +0200 Subject: [PATCH 2/9] catch more bad input in element_constructor --- src/sage/rings/lazy_laurent_series_ring.py | 39 ++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series_ring.py b/src/sage/rings/lazy_laurent_series_ring.py index 8e43a7acc37..84316f9c15c 100644 --- a/src/sage/rings/lazy_laurent_series_ring.py +++ b/src/sage/rings/lazy_laurent_series_ring.py @@ -350,6 +350,26 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: g z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13) + TESTS:: + + This gives zero:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L(lambda n: 0, degree=3) + 0 + + This does not:: + + sage: L(lambda n: 0, degree=3, constant=1) + z^3 + z^4 + z^5 + O(z^6) + + This raises an error:: + + sage: L(lambda n: 0, valuation=3, constant=1) + Traceback (most recent call last): + ... + ValueError: constant may only be specified if the degree is specified + .. TODO:: Add a method to change the sparse/dense implementation. @@ -385,7 +405,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No return self.element_class(self, coeff_stream) initial_coefficients = [x[i] for i in range(x.valuation(), x.degree() + 1)] coeff_stream = CoefficientStream_exact(initial_coefficients, self._sparse, - valuation=x.valuation(), constant=constant, degree=degree) + valuation=x.valuation(), constant=constant, degree=degree) return self.element_class(self, coeff_stream) if isinstance(x, LazyLaurentSeries): if x._coeff_stream._is_sparse is self._sparse: @@ -395,14 +415,22 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if callable(x): if valuation is None: valuation = 0 + if degree is None: + if constant is not None: + raise ValueError("constant may only be specified if the degree is specified") + coeff_stream = CoefficientStream_coefficient_function(x, self.base_ring(), self._sparse, valuation) + return self.element_class(self, coeff_stream) if degree is not None: if constant is None: constant = ZZ.zero() - p = [x(i) for i in range(valuation, degree)] - coeff_stream = CoefficientStream_exact(p, self._sparse, valuation=valuation, - constant=constant, degree=degree) + p = [BR(x(i)) for i in range(valuation, degree)] + if not any(p) and not constant: + coeff_stream = CoefficientStream_zero(self._sparse) + else: + coeff_stream = CoefficientStream_exact(p, self._sparse, valuation=valuation, + constant=constant, degree=degree) return self.element_class(self, coeff_stream) - return self.element_class(self, CoefficientStream_coefficient_function(x, self.base_ring(), self._sparse, valuation)) + raise ValueError(f"unable to convert {x} into a lazy Laurent series") def _an_element_(self): @@ -608,4 +636,3 @@ def series(self, coefficient, valuation, constant=None, degree=None): t = self(lambda n: coefficient(t, n), valuation=valuation, constant=constant, degree=degree) return t - From 7c9a5dda12e03ffb2a3b32e4008650190634cebb Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 10 Aug 2021 21:29:20 +0200 Subject: [PATCH 3/9] fix invert --- src/sage/rings/lazy_laurent_series.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index b8a1f23509a..836283b5acf 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -1105,6 +1105,14 @@ def __invert__(self): sage: ~z z^-1 + TESTS:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: g = L([2], valuation=-1, constant=1); g + 2*x^-1 + 1 + x + x^2 + O(x^3) + sage: g*g^-1 + 1 + O(x^7) + """ P = self.parent() coeff_stream = self._coeff_stream @@ -1121,14 +1129,16 @@ def __invert__(self): coeff_stream = CoefficientStream_exact((i, -i), P._sparse, valuation=v, constant=c) return P.element_class(P, coeff_stream) - if len(initial_coefficients) == 1: + if len(initial_coefficients) == 1 and not coeff_stream._constant: i = ~initial_coefficients[0] v = -coeff_stream.valuation() c = P._coeff_ring.zero() coeff_stream = CoefficientStream_exact((i,), P._sparse, valuation=v, constant=c) return P.element_class(P, coeff_stream) - if len(initial_coefficients) == 2 and not (initial_coefficients[0] + initial_coefficients[1]): + if (len(initial_coefficients) == 2 + and not (initial_coefficients[0] + initial_coefficients[1]) + and not coeff_stream._constant): v = -coeff_stream.valuation() c = ~initial_coefficients[0] coeff_stream = CoefficientStream_exact((), P._sparse, From 51357a03296b07460be7b36a03e4f3dd3ebe04c6 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 11 Aug 2021 08:32:43 +1000 Subject: [PATCH 4/9] Small reworkings and fixes. Adding another composition test. --- src/sage/rings/lazy_laurent_series.py | 33 ++++++++++++++++------ src/sage/rings/lazy_laurent_series_ring.py | 25 ++++++++-------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 836283b5acf..073ab4a48b0 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -1272,12 +1272,24 @@ def __call__(self, g): sage: g^-3 + g^-2 + 1 / (1 + g^2) z^-9 + 3*z^-8 + 3*z^-7 - z^-6 - 4*z^-5 - 2*z^-4 + z^-3 + O(z^-2) - sage: f = z^-3; g = z^-2 + z^-1; + sage: f = z^-3 + sage: g = z^-2 + z^-1 sage: g^(-3) z^6 - 3*z^7 + 6*z^8 - 10*z^9 + 15*z^10 - 21*z^11 + 28*z^12 + O(z^13) sage: f(g) z^6 - 3*z^7 + 6*z^8 - 10*z^9 + 15*z^10 - 21*z^11 + 28*z^12 + O(z^13) + sage: f = z^2 + z^3 + sage: g = z^-3 + z^-2 + sage: f^-3 + f^-2 + z^-6 - 3*z^-5 + 7*z^-4 - 12*z^-3 + 18*z^-2 - 25*z^-1 + 33 + O(z) + sage: g(f) + z^-6 - 3*z^-5 + 7*z^-4 - 12*z^-3 + 18*z^-2 - 25*z^-1 + 33 + O(z) + sage: g^2 + g^3 + z^-9 + 3*z^-8 + 3*z^-7 + 2*z^-6 + 2*z^-5 + z^-4 + sage: f(g) + z^-9 + 3*z^-8 + 3*z^-7 + 2*z^-6 + 2*z^-5 + z^-4 + sage: f = L(lambda n: n); f z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: f(z^2) @@ -1478,23 +1490,28 @@ def __call__(self, g): # Return the sum since g is not known to be finite or we do not get a Laurent polynomial # TODO: Optimize when f has positive valuation ret = P.zero() - gp = P.one() # We build this iteratively so each power can benefit from the caching # Equivalent to P.sum(poly[i] * g**i for i in range(poly.valuation(), poly.degree()+1)) # We could just do "return poly(g)" if we don't care about speed d = poly.degree() + v = poly.valuation() if d >= 0: - for i in range(d): - ret += poly[i] * gp + ind = max(0, v) + gp = P.one() if ind == 0 else g ** ind + for i in range(ind, d): + if poly[i]: + ret += poly[i] * gp gp *= g ret += poly[d] * gp - v = poly.valuation() if v < 0: gi = ~g - gp = P.one() - for i in range(-1, v-1, -1): + ind = min(d, -1) + gp = gi if ind == -1 else gi ** -ind + for i in range(ind, v, -1): + if poly[i]: + ret += poly[i] * gp gp *= gi - ret += poly[i] * gp + ret += poly[v] * gp return ret # g != 0 and val(g) > 0 diff --git a/src/sage/rings/lazy_laurent_series_ring.py b/src/sage/rings/lazy_laurent_series_ring.py index 84316f9c15c..50eb3f9d397 100644 --- a/src/sage/rings/lazy_laurent_series_ring.py +++ b/src/sage/rings/lazy_laurent_series_ring.py @@ -350,7 +350,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: g z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13) - TESTS:: + TESTS: This gives zero:: @@ -407,11 +407,13 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No coeff_stream = CoefficientStream_exact(initial_coefficients, self._sparse, valuation=x.valuation(), constant=constant, degree=degree) return self.element_class(self, coeff_stream) + if isinstance(x, LazyLaurentSeries): if x._coeff_stream._is_sparse is self._sparse: return self.element_class(self, x._coeff_stream) # TODO: Implement a way to make a self._sparse copy raise NotImplementedError("cannot convert between sparse and dense") + if callable(x): if valuation is None: valuation = 0 @@ -420,16 +422,17 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No raise ValueError("constant may only be specified if the degree is specified") coeff_stream = CoefficientStream_coefficient_function(x, self.base_ring(), self._sparse, valuation) return self.element_class(self, coeff_stream) - if degree is not None: - if constant is None: - constant = ZZ.zero() - p = [BR(x(i)) for i in range(valuation, degree)] - if not any(p) and not constant: - coeff_stream = CoefficientStream_zero(self._sparse) - else: - coeff_stream = CoefficientStream_exact(p, self._sparse, valuation=valuation, - constant=constant, degree=degree) - return self.element_class(self, coeff_stream) + + # degree is not None + if constant is None: + constant = ZZ.zero() + p = [BR(x(i)) for i in range(valuation, degree)] + if not any(p) and not constant: + coeff_stream = CoefficientStream_zero(self._sparse) + else: + coeff_stream = CoefficientStream_exact(p, self._sparse, valuation=valuation, + constant=constant, degree=degree) + return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into a lazy Laurent series") From 7501204e1bd216361a395824d2bff70acc8962da Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 11 Aug 2021 09:30:40 +1000 Subject: [PATCH 5/9] Adding documentation to the user-facing class level documentation. --- src/sage/rings/lazy_laurent_series.py | 68 ++++++++++++++++++---- src/sage/rings/lazy_laurent_series_ring.py | 59 +++++++++++++++++-- 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 073ab4a48b0..24946d6edcd 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -1158,25 +1158,69 @@ class LazyLaurentSeries(LazySequencesModuleElement, LazyCauchyProductSeries): EXAMPLES:: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: L(lambda i: i, valuation=-3, constant=(-1,3)) - -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) - sage: L(lambda i: i, valuation=-3, constant=-1, degree=3) - -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) - :: + We can build a series from a function and specify if the series + eventually takes a constant value:: - sage: f = 1 / (1 - z - z^2); f + sage: f = L(lambda i: i, valuation=-3, constant=-1, degree=3) + sage: f + -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) + sage: f[-2] + -2 + sage: f[10] + -1 + sage: f[-5] + 0 + + sage: f = L(lambda i: i, valuation=-3) + sage: f + -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 + 3*z^3 + O(z^4) + sage: f[20] + 20 + + Anything that converts into a polynomial can be input, where + we can also specify the valuation or if the series eventually + takes a constant value:: + + sage: L([-5,2,0,5]) + -5 + 2*z + 5*z^3 + sage: L([-5,2,0,5], constant=6) + -5 + 2*z + 5*z^3 + 6*z^4 + 6*z^5 + 6*z^6 + O(z^7) + sage: L([-5,2,0,5], degree=6, constant=6) + -5 + 2*z + 5*z^3 + 6*z^6 + 6*z^7 + 6*z^8 + O(z^9) + sage: L([-5,2,0,5], valuation=-2, degree=3, constant=6) + -5*z^-2 + 2*z^-1 + 5*z + 6*z^3 + 6*z^4 + 6*z^5 + O(z^6) + sage: L([-5,2,0,5], valuation=5) + -5*z^5 + 2*z^6 + 5*z^8 + sage: L({-2:9, 3:4}, constant=2, degree=5) + 9*z^-2 + 4*z^3 + 2*z^5 + 2*z^6 + 2*z^7 + O(z^8) + + We can also perform arithmetic:: + + sage: f = 1 / (1 - z - z^2) + sage: f 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) sage: f.coefficient(100) 573147844013817084101 + sage: f = (z^-2 - 1 + 2*z) / (z^-1 - z + 3*z^2) + sage: f + z^-1 - z^2 - z^4 + 3*z^5 + O(z^6) - Lazy Laurent series is picklable:: + However, we may not always be able to know when a result is + exactly a polynomial:: - sage: g = loads(dumps(f)) - sage: g - 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) - sage: g == f - True + sage: f * (z^-1 - z + 3*z^2) + z^-2 - 1 + 2*z + O(z^5) + + TESTS:: + + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) + sage: f = 1 / (1 - z - z^2) + sage: TestSuite(f).run() + + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) + sage: f = 1 / (1 - z - z^2) + sage: TestSuite(f).run() """ def change_ring(self, ring): diff --git a/src/sage/rings/lazy_laurent_series_ring.py b/src/sage/rings/lazy_laurent_series_ring.py index 50eb3f9d397..6d7ee048d23 100644 --- a/src/sage/rings/lazy_laurent_series_ring.py +++ b/src/sage/rings/lazy_laurent_series_ring.py @@ -58,9 +58,9 @@ class LazyLaurentSeriesRing(UniqueRepresentation, Parent): EXAMPLES:: sage: L. = LazyLaurentSeriesRing(QQ) - sage: 1/(1 - z) + sage: 1 / (1 - z) 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - sage: 1/(1 - z) == 1/(1 - z) + sage: 1 / (1 - z) == 1 / (1 - z) True sage: L in Fields True @@ -75,17 +75,57 @@ class LazyLaurentSeriesRing(UniqueRepresentation, Parent): sage: e.coefficient(100).parent() Finite Field of size 3 - Generating functions of integer sequences are Laurent series over the - integer ring:: + Series can be defined by specifying a coefficient function + along with a valuation or a degree where after the series + is evenutally constant:: + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R) + sage: def coeff(n): + ....: if n < 0: + ....: return -2 + n + ....: if n == 0: + ....: return 6 + ....: return x + y^n + sage: f = L(coeff, valuation=-5) + sage: f + -7*z^-5 - 6*z^-4 - 5*z^-3 - 4*z^-2 - 3*z^-1 + 6 + (x + y)*z + O(z^2) + sage: 1 / (1 - f) + 1/7*z^5 - 6/49*z^6 + 1/343*z^7 + 8/2401*z^8 + 64/16807*z^9 + + 17319/117649*z^10 + (1/49*x + 1/49*y - 180781/823543)*z^11 + O(z^12) + sage: L(coeff, valuation=-3, degree=3, constant=x) + -5*z^-3 - 4*z^-2 - 3*z^-1 + 6 + (x + y)*z + (y^2 + x)*z^2 + + x*z^3 + x*z^4 + x*z^5 + O(z^6) + + Similarly, we can specify a polynomial or the initial + coefficients with anything that converts into the + corresponding Laurent polynomial ring:: + + sage: L([1, x, y, 0, x+y]) + 1 + x*z + y*z^2 + (x + y)*z^4 + sage: L([1, x, y, 0, x+y], constant=2) + 1 + x*z + y*z^2 + (x + y)*z^4 + 2*z^5 + 2*z^6 + 2*z^7 + O(z^8) + sage: L([1, x, y, 0, x+y], degree=7, constant=2) + 1 + x*z + y*z^2 + (x + y)*z^4 + 2*z^7 + 2*z^8 + 2*z^9 + O(z^10) + sage: L([1, x, y, 0, x+y], valuation=-2) + z^-2 + x*z^-1 + y + (x + y)*z^2 + sage: L([1, x, y, 0, x+y], valuation=-2, constant=3) + z^-2 + x*z^-1 + y + (x + y)*z^2 + 3*z^3 + 3*z^4 + 3*z^5 + O(z^6) + sage: L([1, x, y, 0, x+y], valuation=-2, degree=4, constant=3) + z^-2 + x*z^-1 + y + (x + y)*z^2 + 3*z^4 + 3*z^5 + 3*z^6 + O(z^7) + + Generating functions of integer sequences are Laurent series + over the integer ring:: sage: L. = LazyLaurentSeriesRing(ZZ); L Lazy Laurent Series Ring in z over Integer Ring sage: L in Fields False - sage: 1/(1 - 2*z)^3 + sage: 1 / (1 - 2*z)^3 1 + 6*z + 24*z^2 + 80*z^3 + 240*z^4 + 672*z^5 + 1792*z^6 + O(z^7) - Power series can be defined recursively:: + Power series can be defined recursively (see :meth:`define()` for + more examples):: sage: L. = LazyLaurentSeriesRing(ZZ) sage: s = L(None) @@ -370,6 +410,13 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No ... ValueError: constant may only be specified if the degree is specified + We support the old input format for ``constant``:: + + sage: f = L(lambda i: i, valuation=-3, constant=-1, degree=3) + sage: g = L(lambda i: i, valuation=-3, constant=(-1,3)) + sage: f == g + True + .. TODO:: Add a method to change the sparse/dense implementation. From 651b3725402fa4ba485671133be73ec9765c31a9 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 11 Aug 2021 10:34:49 +0200 Subject: [PATCH 6/9] Revert "Merge branch 'u/gh-tejasvicsr1/special_functions_for_lazy_series' of git://trac.sagemath.org/sage into t/32324/lazy_taylor_series" This reverts commit 9c83de492be62192ed3e87db94d9c1248578368e, reversing changes made to eb27019f9f77701bcc7930442b52debb6fa63c8c. --- src/sage/rings/lazy_laurent_series.py | 1250 ++++++------------------- 1 file changed, 293 insertions(+), 957 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 31b2215de80..5869acfdec5 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -69,9 +69,11 @@ -1 AUTHORS: + - Kwankyu Lee (2019-02-24): initial version - Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08): refactored and expanded functionality + """ # **************************************************************************** @@ -121,13 +123,13 @@ class LazySequenceElement(ModuleElement): Lazy Laurent Series Ring in z over Integer Ring sage: f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: f = 1/(1 - z) sage: f.parent() Lazy Laurent Series Ring in z over Integer Ring sage: f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - """ def __init__(self, parent, coeff_stream): """ @@ -137,6 +139,7 @@ def __init__(self, parent, coeff_stream): sage: L. = LazyLaurentSeriesRing(ZZ) sage: TestSuite(L.an_element()).run() + sage: L = LazyDirichletSeriesRing(QQbar, 'z') sage: g = L(constant=1) sage: TestSuite(g).run() @@ -168,13 +171,14 @@ def finite_part(self, degree=None): z^-3 + 2 + 3*z^4 sage: f.polynomial() z^-3 + 2 + 3*z^4 + sage: L = LazyDirichletSeriesRing(ZZ, "s") sage: f = L([1,0,0,2,0,0,0,3], valuation=2); f.finite_part() 3/9^s + 2/5^s + 1/2^s + sage: L. = LazyTaylorSeriesRing(ZZ) sage: f = 1/(1+x+y^2); f.finite_part(3) -x^3 + 2*x*y^2 + x^2 - y^2 - x + 1 - """ P = self.parent() L = P._laurent_poly_ring @@ -208,9 +212,11 @@ def __getitem__(self, n): [0, 1, 0, 0, 2, 0, 0, 4, 0, 0, 8, 0, 0, 16, 0, 0, 32, 0, 0, 64] sage: f[0:20] [0, 1, 0, 0, 2, 0, 0, 4, 0, 0, 8, 0, 0, 16, 0, 0, 32, 0, 0, 64] + sage: M = L(lambda n: n) sage: [M[n] for n in range(20)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n) sage: [M[n] for n in range(20)] @@ -224,6 +230,7 @@ def __getitem__(self, n): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] sage: f[1:11] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + sage: M = L(lambda n: n) sage: [M[n] for n in range(1, 11)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -250,9 +257,10 @@ def map_coefficients(self, func, ring=None): coefficient of ``self``. INPUT: + - ``func`` -- function that takes in a coefficient and returns a new coefficient - + EXAMPLES: Dense Implementation:: @@ -270,7 +278,7 @@ def map_coefficients(self, func, ring=None): 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) Sparse Implementation:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) @@ -278,14 +286,14 @@ def map_coefficients(self, func, ring=None): 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) An example where the series is known to be exact:: - + sage: f = z + z^2 + z^3 sage: m = f.map_coefficients(lambda c: c + 1) sage: m 2*z + 2*z^2 + 2*z^3 Similarly for Dirichlet series:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: s = L(lambda n: n-1); s 1/(2^z) + 2/3^z + 3/4^z + 4/5^z + 5/6^z + 6/7^z + ... @@ -316,13 +324,13 @@ def truncate(self, d): Return this series with its terms of degree >= ``d`` truncated. INPUT: - + - ``d`` -- integer; the degree from which the series is truncated EXAMPLES: - + Dense Implementation:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: alpha = 1/(1-z) sage: alpha @@ -336,21 +344,20 @@ def truncate(self, d): z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.truncate(4) z + 2*z^2 + 3*z^3 - + Sparse Implementation:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.truncate(4) z + 2*z^2 + 3*z^3 - + Series which are known to be exact can also be truncated:: - + sage: M = z + z^2 + z^3 + z^4 sage: M.truncate(4) z + z^2 + z^3 - """ P = self.parent() coeff_stream = self._coeff_stream @@ -362,27 +369,26 @@ def truncate(self, d): def prec(self): """ Return the precision of the series, which is infinity. - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: f = 1/(1 - z) sage: f.prec() +Infinity - """ return infinity def valuation(self): r""" Return the valuation of ``self``. - + This method determines the valuation of the series by looking for a nonzero coefficient. Hence if the series happens to be zero, then it may run forever. - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: s = 1/(1 - z) - 1/(1 - 2*z) sage: s.valuation() @@ -395,9 +401,9 @@ def valuation(self): 1 sage: (M - M).valuation() +Infinity - + Similarly for Dirichlet series:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: mu = L(moebius); mu.valuation() 1 @@ -408,7 +414,7 @@ def valuation(self): 2 sage: (g*g).valuation() 4 - + """ return self._coeff_stream.valuation() @@ -416,20 +422,21 @@ def _richcmp_(self, other, op): r""" Compare ``self` with ``other`` with respect to the comparison operator ``op``. - + Equality is verified if the corresponding coefficients of both series can be checked for equality without computing coefficients indefinitely. Otherwise an exception is raised to declare that equality is not decidable. + Inequality is not defined for lazy Laurent series. INPUT: - ``other`` -- another Laurent series - ``op`` -- comparison operator - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(QQ) sage: z + z^2 == z^2 + z True @@ -439,7 +446,6 @@ def _richcmp_(self, other, op): False sage: z + z^2 < z^2 + z False - """ if op is op_EQ: if isinstance(self._coeff_stream, CoefficientStream_zero): # self == 0 @@ -465,14 +471,15 @@ def _richcmp_(self, other, op): if op is op_NE: return not (self == other) + return False def __hash__(self): """ Return the hash of ``self`` - + TESTS:: - + sage: L = LazyLaurentSeriesRing(ZZ, 'z') sage: f = L([1,2,3,4], -5) sage: hash(f) == hash(f) @@ -480,16 +487,15 @@ def __hash__(self): sage: g = (1 + f)/(1 - f)^2 sage: {g: 1} {z^5 - 2*z^6 + z^7 + 5*z^9 - 11*z^10 + z^11 + O(z^12): 1} - """ return hash(self._coeff_stream) def __bool__(self): """ Test whether ``self`` is not zero. - + TESTS:: - + sage: L. = LazyLaurentSeriesRing(GF(2)) sage: bool(z-z) False @@ -510,6 +516,7 @@ def __bool__(self): 1 sage: bool(M) True + sage: L. = LazyLaurentSeriesRing(GF(2), sparse=True) sage: M = L(lambda n: 2*n if n < 10 else 1, 0); M O(z^7) @@ -521,7 +528,6 @@ def __bool__(self): 1 sage: bool(M) True - """ if isinstance(self._coeff_stream, CoefficientStream_zero): return False @@ -541,30 +547,30 @@ def __bool__(self): def define(self, s): r""" Define an equation by ``self = s``. - + INPUT: - + - ``s`` -- a Laurent polynomial - + EXAMPLES: - + We begin by constructing the Catalan numbers:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: C = L(None) sage: C.define(1 + z*C^2) sage: C 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) - + The Catalan numbers but with a valuation 1:: - + sage: B = L(None, 1) sage: B.define(z + B^2) sage: B z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) - + We can define multiple series that are linked:: - + sage: s = L(None) sage: t = L(None) sage: s.define(1 + z*t^3) @@ -573,9 +579,9 @@ def define(self, s): [1, 1, 3, 9, 34, 132, 546, 2327, 10191] sage: t[:9] [1, 1, 2, 7, 24, 95, 386, 1641, 7150] - + A bigger example:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: A = L(None, 5) sage: B = L(None) @@ -589,17 +595,17 @@ def define(self, s): [0, 0, 0, 0, 1, 1, 2, 0, 5, 0, 14, 0, 44, 0, 138] sage: C[0:15] [0, 0, 1, 0, 1, 0, 2, 0, 5, 0, 15, 0, 44, 2, 142] - + Counting binary trees:: - + sage: L. = LazyLaurentSeriesRing(QQ) sage: s = L(None, valuation=1) sage: s.define(z + (s^2+s(z^2))/2) sage: [s[i] for i in range(9)] [0, 1, 1, 1, 2, 3, 6, 11, 23] - + The `q`-Catalan numbers:: - + sage: R. = ZZ[] sage: L. = LazyLaurentSeriesRing(R) sage: s = L(None) @@ -610,10 +616,10 @@ def define(self, s): + (q^10 + q^9 + 2*q^8 + 3*q^7 + 5*q^6 + 5*q^5 + 7*q^4 + 7*q^3 + 6*q^2 + 4*q + 1)*z^5 + (q^15 + q^14 + 2*q^13 + 3*q^12 + 5*q^11 + 7*q^10 + 9*q^9 + 11*q^8 + 14*q^7 + 16*q^6 + 16*q^5 + 17*q^4 + 14*q^3 + 10*q^2 + 5*q + 1)*z^6 + O(z^7) - + We count unlabeled ordered trees by total number of nodes and number of internal nodes:: - + sage: R. = QQ[] sage: Q. = LazyLaurentSeriesRing(R) sage: leaf = z @@ -623,9 +629,9 @@ def define(self, s): sage: T.define(leaf + internal_node * L(T)) sage: [T[i] for i in range(6)] [0, 1, q, q^2 + q, q^3 + 3*q^2 + q, q^4 + 6*q^3 + 6*q^2 + q] - + Similarly for Dirichlet series:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: g = L(constant=1, valuation=2) sage: F = L(None); F.define(1 + g*F) @@ -635,17 +641,19 @@ def define(self, s): 0: A002033: Number of perfect partitions of n. 1: A074206: Kalmár's [Kalmar's] problem: number of ordered factorizations of n. ... + sage: F = L(None); F.define(1 + g*F*F) sage: [F[i] for i in range(1, 16)] [1, 1, 1, 3, 1, 5, 1, 10, 3, 5, 1, 24, 1, 5, 5] - + TESTS:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: s = L(None) sage: s.define(1 + z*s^3) sage: s[:10] [1, 1, 3, 12, 55, 273, 1428, 7752, 43263, 246675] + sage: e = L(None) sage: e.define(1 + z*e) sage: e.define(1 + z*e) @@ -656,18 +664,18 @@ def define(self, s): Traceback (most recent call last): ... ValueError: series already defined - """ if not isinstance(self._coeff_stream, CoefficientStream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") self._coeff_stream._target = s._coeff_stream + def _repr_(self): r""" Return a string representation of ``self``. - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: z^-3 + z - 5 z^-3 - 5 + z @@ -689,17 +697,18 @@ def _repr_(self): z + 2*z^2 + 3*z^3 + 4*z^4 + O(z^5) sage: L(lambda x: x if x > 0 else 0, valuation=-10) O(z^-3) + sage: L(None) Uninitialized Lazy Laurent Series sage: L(0) 0 + sage: R. = QQ[] sage: L. = LazyLaurentSeriesRing(R) sage: z^-2 / (1 - (x-y)*z) + x^4*z^-3 + (1-y)*z^-4 (-y + 1)*z^-4 + x^4*z^-3 + z^-2 + (x - y)*z^-1 + (x^2 - 2*x*y + y^2) + (x^3 - 3*x^2*y + 3*x*y^2 - y^3)*z + (x^4 - 4*x^3*y + 6*x^2*y^2 - 4*x*y^3 + y^4)*z^2 + O(z^3) - """ if isinstance(self._coeff_stream, CoefficientStream_zero): return '0' @@ -710,9 +719,9 @@ def _repr_(self): def _latex_(self): r""" Return a latex representation of ``self``. - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(ZZ) sage: latex(z^-3 + z - 5) \frac{1}{z^{3}} - 5 + z @@ -735,10 +744,12 @@ def _latex_(self): z + 2z^{2} + 3z^{3} + 4z^{4} + O(z^{5}) sage: latex(L(lambda x: x if x > 0 else 0, valuation=-10)) O(\frac{1}{z^{3}}) + sage: latex(L(None)) \text{\texttt{Undef}} sage: latex(L(0)) 0 + sage: R. = QQ[] sage: L. = LazyLaurentSeriesRing(R) sage: latex(z^-2 / (1 - (x-y)*z) + x^4*z^-3 + (1-y)*z^-4) @@ -747,7 +758,6 @@ def _latex_(self): + \left(x^{3} - 3 x^{2} y + 3 x y^{2} - y^{3}\right)z + \left(x^{4} - 4 x^{3} y + 6 x^{2} y^{2} - 4 x y^{3} + y^{4}\right)z^{2} + O(z^{3}) - """ from sage.misc.latex import latex if isinstance(self._coeff_stream, CoefficientStream_zero): @@ -759,16 +769,15 @@ def _latex_(self): def _ascii_art_(self): r""" Return an ascii art representation of ``self``. - + EXAMPLES:: - + sage: e = SymmetricFunctions(QQ).e() sage: L. = LazyLaurentSeriesRing(e) sage: L.options.display_length = 3 sage: ascii_art(1 / (1 - e[1]*z)) e[] + e[1]*z + e[1, 1]*z^2 + O(e[]*z^3) sage: L.options._reset() - """ from sage.typeset.ascii_art import ascii_art, AsciiArt if isinstance(self._coeff_stream, CoefficientStream_zero): @@ -789,7 +798,6 @@ def _unicode_art_(self): sage: unicode_art(1 / (1 - e[1]*z)) e[] + e[1]*z + e[1, 1]*z^2 + O(e[]*z^3) sage: L.options._reset() - """ from sage.typeset.unicode_art import unicode_art, UnicodeArt if isinstance(self._coeff_stream, CoefficientStream_zero): @@ -842,7 +850,6 @@ class LazySequencesModuleElement(LazySequenceElement): sage: R = -M sage: R[:10] [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] - """ def _add_(self, other): """ @@ -895,12 +902,15 @@ def _add_(self, other): 1 + 1/(2^z) + 1/(3^z) + ... sage: s + t 2 + 3/2^z + 4/3^z + 5/4^z + 6/5^z + 7/6^z + 8/7^z + ... + sage: r = L(constant=-1) sage: r + t 0 + sage: r = L([1,2,3]) sage: r + t 2 + 3/2^z + 4/3^z + 1/(4^z) + 1/(5^z) + 1/(6^z) + ... + sage: r = L([1,2,3], constant=-1) sage: r + t 2 + 3/2^z + 4/3^z @@ -963,6 +973,7 @@ def _sub_(self, other): sage: O = M - N sage: O[0:10] [1, -1, 0, 0, 0, 0, 0, 0, 0, 0] + sage: M = L([1, 0, 1]) sage: N = L([0, 0, 1]) sage: O = M - L.one() - N @@ -976,7 +987,6 @@ def _sub_(self, other): True sage: 0 - M == -M True - """ P = self.parent() left = self._coeff_stream @@ -1046,7 +1056,6 @@ def _lmul_(self, scalar): True sage: N * 0 0 - """ P = self.parent() if not scalar: @@ -1068,15 +1077,15 @@ def _rmul_(self, scalar): r""" Scalar multiplication for module elements with the module element on the right and the scalar on the left. - + INPUT: - + - ``scalar`` -- an element of the base ring - + EXAMPLES: - + Dense series can be multiplied with a scalar:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: M = L(lambda n: 1 + n, valuation=0) sage: O = 2 * M @@ -1086,9 +1095,9 @@ def _rmul_(self, scalar): True sage: 0 * M 0 - + Sparse series can be multiplied with a scalar:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: 1 + n, valuation=0) sage: O = 2 * M @@ -1098,9 +1107,9 @@ def _rmul_(self, scalar): True sage: 0 * M 0 - + Series which are known to be exact can be multiplied with a scalar:: - + sage: N = L([0, 1]) sage: O = -1 * N sage: O[0:10] @@ -1109,9 +1118,9 @@ def _rmul_(self, scalar): True sage: 0 * N 0 - + Similarly for Dirichlet series:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: g = L.gen(2) sage: 2 * g @@ -1124,11 +1133,12 @@ def _rmul_(self, scalar): 1 + 2/2^z + 3/3^z + 4/4^z + 5/5^z + 6/6^z + 7/7^z + O(1/(8^z)) sage: 3 * M 3 + 6/2^z + 9/3^z + 12/4^z + 15/5^z + 18/6^z + 21/7^z + O(1/(8^z)) + sage: 1 * M is M True - + We take care of noncommutative base rings:: - + sage: M = MatrixSpace(ZZ, 2) sage: L. = LazyTaylorSeriesRing(M) sage: m = M([[1, 1],[0, 1]]) @@ -1137,7 +1147,7 @@ def _rmul_(self, scalar): sage: (m*a - a*m)[3] [ 3 0] [ 0 -3] - + """ P = self.parent() if not scalar: @@ -1160,11 +1170,11 @@ def _rmul_(self, scalar): def _neg_(self): """ Return the negative of ``self``. - + EXAMPLES: - + Dense series can be negated:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: M = L(lambda n: n, valuation=0) sage: N = L(lambda n: -n, valuation=0) @@ -1179,9 +1189,9 @@ def _neg_(self): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] sage: O == M True - + Sparse series can be negated:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n, valuation=0) sage: N = L(lambda n: -n, valuation=0) @@ -1196,9 +1206,9 @@ def _neg_(self): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] sage: O == M True - + Series which are known to be exact can be negated:: - + sage: M = L.one() sage: N = L([0, 1]) sage: O = -N @@ -1212,7 +1222,6 @@ def _neg_(self): [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] sage: O == M True - """ P = self.parent() coeff_stream = self._coeff_stream @@ -1232,33 +1241,29 @@ def _neg_(self): class LazyCauchyProductSeries(RingElement): """ A class for series where multiplication is the Cauchy product. - - We are assuming that :meth:`polynomial` - """ def __init__(self, parent): """ Initialize. - + TESTS:: - + sage: from sage.rings.lazy_laurent_series import LazyCauchyProductSeries sage: L. = LazyLaurentSeriesRing(ZZ) sage: M = LazyCauchyProductSeries(L) - """ RingElement.__init__(self, parent) def _mul_(self, other): """ Return the product of this series with ``other``. - + INPUT: - + - ``other`` -- other series - + TESTS:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: (1 - z)*(1 - z) 1 - 2*z + z^2 @@ -1270,6 +1275,7 @@ def _mul_(self, other): sage: N = M * (1 - M) sage: N z + z^2 - z^3 - 6*z^4 - 15*z^5 - 29*z^6 + ... + sage: p = (1 - z)*(1 + z^2)^3 * z^-2 sage: p z^-2 - z^-1 + 3 - 3*z + 3*z^2 - 3*z^3 + z^4 - z^5 @@ -1281,6 +1287,7 @@ def _mul_(self, other): + 7*z^4 - 2*z^5 + 4*z^6 - 5*z^7 + z^8 - 2*z^9 sage: M * p == p * M True + sage: q = (1 - 2*z)*(1 + z^2)^3 * z^-2 sage: q * M -2*z^-4 + 3*z^-3 - 4*z^-2 + 10*z^-1 + 11*z + 2*z^2 - 3*z^3 @@ -1288,6 +1295,7 @@ def _mul_(self, other): - 16*z^10 - 16*z^11 - 16*z^12 + O(z^13) sage: q * M == M * q True + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + ... @@ -1295,21 +1303,22 @@ def _mul_(self, other): 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + ... sage: M * N z + 3*z^2 + 6*z^3 + 10*z^4 + 15*z^5 + 21*z^6 + ... + sage: L.one() * M is M True sage: M * L.one() is M True - + Similarly for Taylor series:: - + sage: L. = LazyTaylorSeriesRing(ZZ) sage: (1 - x)*(1 - y) 1 + (-x-y) + x*y sage: (1 - x)*(1 - y)*(1 - z) 1 + (-x-y-z) + (x*y+x*z+y*z) + (-x*y*z) - + We take care of noncommutative base rings:: - + sage: M = MatrixSpace(ZZ, 2) sage: L. = LazyTaylorSeriesRing(M) sage: m = M([[1, 1],[0, 1]]) @@ -1319,10 +1328,10 @@ def _mul_(self, other): sage: (b*a-a*b)[20] [-19 0] [ 0 19] - + Multiplication of series with eventually constant coefficients may yield another such series:: - + sage: L. = LazyLaurentSeriesRing(SR) sage: var("a b c d e u v w") (a, b, c, d, e, u, v, w) @@ -1332,9 +1341,9 @@ def _mul_(self, other): sage: t1 = t.approximate_series(44) sage: s1 * t1 - (s * t).approximate_series(42) O(z^42) - + Noncommutative:: - + sage: M = MatrixSpace(ZZ, 2) sage: L. = LazyTaylorSeriesRing(M) sage: a = L([m], degree=1) @@ -1342,7 +1351,7 @@ def _mul_(self, other): sage: (a*b)[0] [2 1] [1 1] - + """ P = self.parent() left = self._coeff_stream @@ -1400,11 +1409,11 @@ def _mul_(self, other): def __invert__(self): """ Return the multiplicative inverse of the element. - + EXAMPLES: - + Lazy Laurent series that have a dense implementation can be inverted:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: ~(1 - z) 1 + z + z^2 + O(z^3) @@ -1412,22 +1421,23 @@ def __invert__(self): z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: P = ~M; P z^-1 - 2 + z + O(z^6) - + Lazy Laurent series that have a sparse implementation can be inverted:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: P = ~M; P z^-1 - 2 + z + O(z^6) + sage: ~(~(1 - z)) 1 - z - + Lazy Laurent series that are known to be exact can be inverted:: - + sage: ~z z^-1 - + """ P = self.parent() coeff_stream = self._coeff_stream @@ -1466,15 +1476,15 @@ def __invert__(self): def _div_(self, other): r""" Return ``self`` divided by ``other``. - + INPUT: - + - ``other`` -- nonzero series - + EXAMPLES: - + Lazy Laurent series that have a dense implementation can be divided:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: z/(1 - z) z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + O(z^8) @@ -1484,9 +1494,9 @@ def _div_(self, other): 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: P = M / N; P z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - + Lazy Laurent series that have a sparse implementation can be divided:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) @@ -1494,22 +1504,21 @@ def _div_(self, other): 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: P = M / N; P z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - + Lazy Laurent series that are known to be exact can be divided:: - + M = z^2 + 2*z + 1 N = z + 1 O = M / N; O z + 1 - + An example over the ring of symmetric functions:: - + sage: e = SymmetricFunctions(QQ).e() sage: R. = LazyLaurentSeriesRing(e) sage: 1 / (1 - e[1]*z) e[] + e[1]*z + e[1, 1]*z^2 + e[1, 1, 1]*z^3 + e[1, 1, 1, 1]*z^4 + e[1, 1, 1, 1, 1]*z^5 + e[1, 1, 1, 1, 1, 1]*z^6 + O(e[]*z^7) - """ if isinstance(other._coeff_stream, CoefficientStream_zero): raise ZeroDivisionError("cannot divide by 0") @@ -1545,16 +1554,16 @@ def _div_(self, other): def __pow__(self, n): """ Return the ``n``-th power of the series. - + INPUT: - + - ``n`` -- integer; the power to which to raise the series - + EXAMPLES: - + Lazy Laurent series that have a dense implementation can be raised to the power ``n``:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: (1 - z)^-1 1 + z + z^2 + O(z^3) @@ -1568,32 +1577,31 @@ def __pow__(self, n): z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M^2 z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) - + We can create a really large power of a monomial, even with the dense implementation:: - + sage: z^1000000 z^1000000 - + Lazy Laurent series that have a sparse implementation can be raised to the power ``n``:: - + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: M = L(lambda n: n); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M^2 z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) - + Lazy Laurent series that are known to be exact can be raised to the power ``n``:: - + sage: z^2 z^2 sage: (1 - z)^2 1 - 2*z + z^2 sage: (1 + z)^2 1 + 2*z + z^2 - """ if n == 0: return self.parent().one() @@ -1614,735 +1622,92 @@ def __pow__(self, n): return generic_power(self, n) -class LazySpecialFunctions(): +class LazyLaurentSeries(LazySequencesModuleElement, LazyCauchyProductSeries): r""" - A class for lazily computing special functions. - - EXAMPLES: - - The exponential series can be found. :: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: exp(z) - 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) - - The sine of a series can be found. :: - - sage: sin(z) - z - 1/6*z^3 + 1/120*z^5 + O(z^7) - - The cosine of a series can be found. :: - - sage: cos(z) - 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) - - The tangent of a series can be found. :: - - sage: tan(z) - z + 1/3*z^3 + 2/15*z^5 + O(z^7) - - The arcsin of a series can be found. :: - - sage: arcsin(z) - z + 1/6*z^3 + 3/40*z^5 + O(z^7) - - The sinh of a series can be found. :: - - sage: sinh(z) - z + 1/6*z^3 + 1/120*z^5 + O(z^7) - - The cosh of a series can be found. :: - - sage: cosh(z) - 1 + 1/2*z^2 + 1/24*z^4 + 1/720*z^6 + O(z^7) - - The arctangent of a series can be found. :: - - sage: arctan(z) - z - 1/3*z^3 + 1/5*z^5 + O(z^7) - - The tanh of a series can be found. :: - - sage: tanh(z) - z - 1/3*z^3 + 2/15*z^5 + O(z^7) - - The arccosine of a series can be found. :: - - sage: L. = LazyLaurentSeriesRing(RR) - sage: arccos(z) - 1.57079632679490 - 1.00000000000000*z + 0.000000000000000*z^2 - 0.166666666666667*z^3 + 0.000000000000000*z^4 - 0.0750000000000000*z^5 + O(1.00000000000000*z^7) - - """ - def exp(self): - r""" - Return the exponential series of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: exp(z) - 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) - sage: exp(z^2) - 1 + z^2 + 1/2*z^4 + 1/6*z^6 + O(z^7) - sage: exp(z + z^2) - 1 + z + 3/2*z^2 + 7/6*z^3 + 25/24*z^4 + 27/40*z^5 + 331/720*z^6 + O(z^7) - sage: exp(0) - 1 - sage: exp(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial + A Laurent series where the coefficients are computed lazily. - P = self.parent() - return P(lambda n: 1/factorial(n), 0)(self) + EXAMPLES:: - def sin(self): - r""" - Return the sine of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: sin(z) - z - 1/6*z^3 + 1/120*z^5 + O(z^7) - sage: sin(z^2) - z^2 - 1/6*z^6 + O(z^7) - sage: sin(z + z^2) - z + z^2 - 1/6*z^3 - 1/2*z^4 - 59/120*z^5 - 1/8*z^6 + O(z^7) - sage: sin(0) - 0 - sage: sin(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: L(lambda i: i, valuation=-3, constant=(-1,3)) + -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) + sage: L(lambda i: i, valuation=-3, constant=-1, degree=3) + -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) - P = self.parent() - return P(lambda n: (n % 2)/factorial(n) if n % 4 == 1 else -(n % 2)/factorial(n), 0)(self) + :: - def cos(self): - r""" - Return the cosine of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: cos(z) - 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) - sage: cos(z^2) - 1 - 1/2*z^4 + O(z^7) - sage: cos(z + z^2) - 1 - 1/2*z^2 - z^3 - 11/24*z^4 + 1/6*z^5 + 179/720*z^6 + O(z^7) - sage: cos(0) - 1 - sage: cos(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial + sage: f = 1 / (1 - z - z^2); f + 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) + sage: f.coefficient(100) + 573147844013817084101 - P = self.parent() - return P(lambda n: 1/factorial(n) if n % 4 == 0 else (n % 2 - 1)/factorial(n), 0)(self) + Lazy Laurent series is picklable:: - def arcsin(self): + sage: g = loads(dumps(f)) + sage: g + 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) + sage: g == f + True + """ + def change_ring(self, ring): r""" - Return the arcsin of ``self``. - - EXAMPLES:: + Return ``self`` with coefficients converted to elements of ``ring``. - sage: L. = LazyLaurentSeriesRing(QQ) - sage: arcsin(z) - z + 1/6*z^3 + 3/40*z^5 + O(z^7) - sage: arcsin(z)[7] - 5/112 - sage: arcsin(z) - z + 1/6*z^3 + 3/40*z^5 + O(z^7) - sage: arcsin(z^2) - z^2 + 1/6*z^6 + O(z^7) - sage: arcsin(z + z^2) - z + z^2 + 1/6*z^3 + 1/2*z^4 + 23/40*z^5 + 13/24*z^6 + O(z^7) - sage: arcsin(0) - 0 - sage: arcsin(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series + INPUT: - """ - from sage.functions.other import factorial + - ``ring`` -- a ring - P = self.parent() - return P(lambda n: factorial(n-1)/((4**((n-1)/2))*(factorial((n-1)/2)**2)*n) if n % 2 else ZZ.zero(), 0)(self) + EXAMPLES: - def arccos(self): - r""" - Return the arccos of ``self``. + Dense Implementation:: - EXAMPLES:: + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) + sage: s = 2 + z + sage: t = s.change_ring(QQ) + sage: t^-1 + 1/2 - 1/4*z + 1/8*z^2 - 1/16*z^3 + 1/32*z^4 - 1/64*z^5 + 1/128*z^6 + O(z^7) + sage: M = L(lambda n: n); M + z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) + sage: N = M.change_ring(QQ) + sage: N.parent() + Lazy Laurent Series Ring in z over Rational Field + sage: M.parent() + Lazy Laurent Series Ring in z over Integer Ring - sage: L. = LazyLaurentSeriesRing(RR) - sage: arccos(z) - 1.57079632679490 - 1.00000000000000*z + 0.000000000000000*z^2 - 0.166666666666667*z^3 + 0.000000000000000*z^4 - 0.0750000000000000*z^5 + O(1.00000000000000*z^7) - sage: arccos(z^2) - 1.57079632679490 + 0.000000000000000*z - 1.00000000000000*z^2 + 0.000000000000000*z^3 + 0.000000000000000*z^4 + 0.000000000000000*z^5 - 0.166666666666667*z^6 + O(1.00000000000000*z^7) - sage: arccos(z + z^2) - 1.57079632679490 - 1.00000000000000*z - 1.00000000000000*z^2 - 0.166666666666667*z^3 - 0.500000000000000*z^4 - 0.575000000000000*z^5 - 0.541666666666667*z^6 + O(1.00000000000000*z^7) - sage: arccos(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series + Sparse Implementation:: + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) + sage: M = L(lambda n: n); M + z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) + sage: M.parent() + Lazy Laurent Series Ring in z over Integer Ring + sage: N = M.change_ring(QQ) + sage: N.parent() + Lazy Laurent Series Ring in z over Rational Field + sage: M ^-1 + z^-1 - 2 + z + O(z^6) """ - from sage.symbolic.constants import pi - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - P = self.parent() - return P(pi/2) - LazySpecialFunctions.arcsin(self) + from .lazy_laurent_series_ring import LazyLaurentSeriesRing + Q = LazyLaurentSeriesRing(ring, names=self.parent().variable_names()) + return Q.element_class(Q, self._coeff_stream) - def sinh(self): + def __call__(self, g): r""" - Return the sinh of ``self``. + Return the composition of ``self`` with ``g``. - EXAMPLES:: + Given two Laurent Series `f` and `g` over the same base ring, the + composition `(f \circ g)(z) = f(g(z))` is defined if and only if: - sage: L. = LazyLaurentSeriesRing(QQ) - sage: sinh(z) - z + 1/6*z^3 + 1/120*z^5 + O(z^7) - sage: sinh(z^2) - z^2 + 1/6*z^6 + O(z^7) - sage: sinh(z + z^2) - z + z^2 + 1/6*z^3 + 1/2*z^4 + 61/120*z^5 + 5/24*z^6 + O(z^7) - sage: sinh(0) - 0 - sage: sinh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - - from sage.functions.other import factorial - - P = self.parent() - return P(lambda n: 1/factorial(n) if n % 2 else ZZ.zero(), 0)(self) - - def cosh(self): - r""" - Return the cosh of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: cosh(z) - 1 + 1/2*z^2 + 1/24*z^4 + 1/720*z^6 + O(z^7) - sage: cosh(z^2) - 1 + 1/2*z^4 + O(z^7) - sage: cosh(z + z^2) - 1 + 1/2*z^2 + z^3 + 13/24*z^4 + 1/6*z^5 + 181/720*z^6 + O(z^7) - sage: cosh(0) - 1 - sage: cosh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - - from sage.functions.other import factorial - - P = self.parent() - return P(lambda n: ZZ.zero() if n % 2 else 1/factorial(n), 0)(self) - - def tan(self): - r""" - Return the tangent of ``self``. - - EXAMPLES: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: tan(z) - z + 1/3*z^3 + 2/15*z^5 + O(z^7) - sage: tan(z^2) - z^2 + 1/3*z^6 + O(z^7) - sage: tan(z + z^2) - z + z^2 + 1/3*z^3 + z^4 + 17/15*z^5 + z^6 + O(z^7) - sage: tan(0) - 0 - sage: tan(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - return LazySpecialFunctions.sin(self)/LazySpecialFunctions.cos(self) - - def arctan(self): - r""" - Return the arctangent of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: arctan(z) - z - 1/3*z^3 + 1/5*z^5 + O(z^7) - sage: arctan(z^2) - z^2 - 1/3*z^6 + O(z^7) - sage: arctan(z + z^2) - z + z^2 - 1/3*z^3 - z^4 - 4/5*z^5 + 2/3*z^6 + O(z^7) - sage: arctan(0) - 0 - sage: arctan(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - P = self.parent() - return P(lambda n: 1/n if n % 4 == 1 else ZZ.zero() if n % 2 == 0 else -1/n, 0)(self) - - def tanh(self): - r""" - Return the tanh of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: tanh(z) - z - 1/3*z^3 + 2/15*z^5 + O(z^7) - sage: tanh(z)[7] - -17/315 - sage: tanh(z) - z - 1/3*z^3 + 2/15*z^5 + O(z^7) - sage: tanh(z^2) - z^2 - 1/3*z^6 + O(z^7) - sage: tanh(z + z^2) - z + z^2 - 1/3*z^3 - z^4 - 13/15*z^5 + 1/3*z^6 + O(z^7) - sage: tanh(0) - 0 - sage: tanh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - from sage.arith.misc import bernoulli - from sage.rings.rational_field import QQ - - P = self.parent() - return P(lambda n: (QQ((bernoulli(n + 1)) * ((4) ** ((n + 1)/2))) * (-1 + (4 ** ((n + 1)/2))))/factorial(n + 1) if n % 2 else ZZ.zero(), 0)(self) - - def csc(self): - r""" - Return the cosecant of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: csc(z) - z^-1 + 1/6*z + 7/360*z^3 + 31/15120*z^5 + O(z^6) - sage: csc(0) - Infinity - sage: csc(z^2) - z^-2 + 1/6*z^2 + O(z^5) - sage: csc(z + z^2) - z^-1 - 1 + 7/6*z - 5/6*z^2 + 367/360*z^3 - 113/120*z^4 + 16033/15120*z^5 + O(z^6) - sage: csc(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - return ~LazySpecialFunctions.sin(self) - - def sec(self): - r""" - Return the secant of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: sec(z) - 1 + 1/2*z^2 + 5/24*z^4 + 61/720*z^6 + O(z^7) - sage: sec(z^2) - 1 + 1/2*z^4 + O(z^7) - sage: sec(z + z^2) - 1 + 1/2*z^2 + z^3 + 17/24*z^4 + 5/6*z^5 + 961/720*z^6 + O(z^7) - sage: sec(0) - 1 - sage: sec(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - return ~LazySpecialFunctions.cos(self) - - def cot(self): - r""" - Return the cotangent of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: cot(z) - z^-1 - 1/3*z - 1/45*z^3 - 2/945*z^5 + O(z^6) - sage: cot(z^2) - z^-2 - 1/3*z^2 + O(z^5) - sage: cot(z + z^2) - z^-1 - 1 + 2/3*z - 4/3*z^2 + 44/45*z^3 - 16/15*z^4 + 176/189*z^5 + O(z^6) - sage: cot(0) - Infinity - sage: cot(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - return ~LazySpecialFunctions.tan(self) - - def asinh(self): - r""" - Return the inverse of the hyperbolic sine of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: LazySpecialFunctions.asinh(z) - z - 1/6*z^3 + 3/40*z^5 + O(z^7) - sage: LazySpecialFunctions.asinh(z^2) - z^2 - 1/6*z^6 + O(z^7) - sage: LazySpecialFunctions.asinh(z + z^2) - z + z^2 - 1/6*z^3 - 1/2*z^4 - 17/40*z^5 + 5/24*z^6 + O(z^7) - sage: LazySpecialFunctions.asinh(L(0)) - 0 - sage: LazySpecialFunctions.asinh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - - P = self.parent() - return P(lambda n: ZZ.zero() if n % 2 == 0 else (((-1) ** ((n - 1)/2)) * factorial(n - 1))/(4 ** ((n - 1)/2) * (factorial((n - 1)/2) ** 2) * n), 0)(self) - - def atanh(self): - r""" - Return the inverse of the hyperbolic tangent of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: LazySpecialFunctions.atanh(z) - z + 1/3*z^3 + 1/5*z^5 + O(z^7) - sage: LazySpecialFunctions.atanh(z^2) - z^2 + 1/3*z^6 + O(z^7) - sage: LazySpecialFunctions.atanh(L(0)) - 0 - sage: LazySpecialFunctions.atanh(z + z^2) - z + z^2 + 1/3*z^3 + z^4 + 6/5*z^5 + 4/3*z^6 + O(z^7) - sage: LazySpecialFunctions.atanh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - P = self.parent() - return P(lambda n: 1/n if n % 2 else ZZ.zero(), 0)(self) - - def acosh(self): - r""" - Return the inverse of the hyperbolic tangent of ``self``. - - EXAMPLES:: - - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: L. = LazyLaurentSeriesRing(QQ) - sage: LazySpecialFunctions.acosh(z) - 1 + 1/2*z^2 + 1/24*z^4 + 1/720*z^6 + O(z^7) - sage: LazySpecialFunctions.acosh(z^2) - 1 + 1/2*z^4 + O(z^7) - sage: LazySpecialFunctions.acosh(z + z^2) - 1 + 1/2*z^2 + z^3 + 13/24*z^4 + 1/6*z^5 + 181/720*z^6 + O(z^7) - sage: LazySpecialFunctions.acosh(L(0)) - 1 - sage: LazySpecialFunctions.acosh(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - - P = self.parent() - return P(lambda n: 1/factorial(n) if n % 2 == 0 else ZZ.zero(), 0)(self) - - def sech(self): - r""" - Return the hyperbolic secant of ``self``. - - EXAMPLES:: - - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: L. = LazyLaurentSeriesRing(QQ) - sage: LazySpecialFunctions.sech(z) - 1 - 1/2*z^2 + 5/24*z^4 - 61/720*z^6 + O(z^7) - sage: LazySpecialFunctions.sech(z^2) - 1 - 1/2*z^4 + O(z^7) - sage: LazySpecialFunctions.sech(z + z^2) - 1 - 1/2*z^2 - z^3 - 7/24*z^4 + 5/6*z^5 + 839/720*z^6 + O(z^7) - sage: LazySpecialFunctions.sech(L(0)) - 1 - sage: LazySpecialFunctions.sech(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - from sage.combinat.combinat import euler_number - - P = self.parent() - return P(lambda n: euler_number(n)/factorial(n) if n % 2 == 0 else ZZ.zero(), 0)(self) - - def cosech(self): - r""" - Return the hyperbolic cosecant of ``self``. - - EXAMPLES:: - - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: L. = LazyLaurentSeriesRing(QQ) - sage: LazySpecialFunctions.cosech(z) - z^-1 - 1/6*z + 7/360*z^3 - 31/15120*z^5 + O(z^6) - sage: LazySpecialFunctions.cosech(z^2) - z^-2 - 1/6*z^2 + O(z^5) - sage: LazySpecialFunctions.cosech(z + z^2) - z^-1 - 1 + 5/6*z - 7/6*z^2 + 367/360*z^3 - 113/120*z^4 + 15971/15120*z^5 + O(z^6) - sage: LazySpecialFunctions.cosech(L(0)) - Traceback (most recent call last): - ... - ZeroDivisionError: the valuation of the series must be nonnegative - sage: LazySpecialFunctions.cosech(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - from sage.arith.misc import bernoulli - - P = self.parent() - return P(lambda n: (2 * (1 - 2 ** n) * bernoulli(n + 1))/factorial(n + 1) if n % 2 else ZZ.zero(), -1)(self) - - def coth(self): - r""" - Return the hyperbolic cotangent of ``self``. - - EXAMPLES:: - - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: L. = LazyLaurentSeriesRing(QQ) - sage: LazySpecialFunctions.coth(z) - z^-1 + 1/3*z - 1/45*z^3 + 2/945*z^5 + O(z^6) - sage: LazySpecialFunctions.coth(z^2) - z^-2 + 1/3*z^2 + O(z^5) - sage: LazySpecialFunctions.coth(z + z^2) - z^-1 - 1 + 4/3*z - 2/3*z^2 + 44/45*z^3 - 16/15*z^4 + 884/945*z^5 + O(z^6) - sage: LazySpecialFunctions.coth(L(0)) - Traceback (most recent call last): - ... - ZeroDivisionError: the valuation of the series must be nonnegative - sage: LazySpecialFunctions.coth(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - from sage.functions.other import factorial - from sage.arith.misc import bernoulli - - P = self.parent() - return P(lambda n: ((2 ** (n + 1)) * bernoulli(n + 1))/factorial(n + 1) if n % 2 else ZZ.zero(), -1)(self) - - def arccot(self): - r""" - Return the arctangent of ``self``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(RR) - sage: arccot(z) - 1.57079632679490 - 1.00000000000000*z + 0.000000000000000*z^2 + 0.333333333333333*z^3 + 0.000000000000000*z^4 - 0.200000000000000*z^5 + O(1.00000000000000*z^7) - sage: arccot(z^2) - 1.57079632679490 + 0.000000000000000*z - 1.00000000000000*z^2 + 0.000000000000000*z^3 + 0.000000000000000*z^4 + 0.000000000000000*z^5 + 0.333333333333333*z^6 + O(1.00000000000000*z^7) - sage: arccot(z + z^2) - 1.57079632679490 - 1.00000000000000*z - 1.00000000000000*z^2 + 0.333333333333333*z^3 + 1.00000000000000*z^4 + 0.800000000000000*z^5 - 0.666666666666667*z^6 + O(1.00000000000000*z^7) - sage: arccot(L(0)) - 1.57079632679490 - sage: arccot(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - """ - - from sage.symbolic.constants import pi - from sage.rings.lazy_laurent_series import LazySpecialFunctions - - P = self.parent() - return P(pi/2) - LazySpecialFunctions.arctan(self) - - def hypergeometric(self, a, b): - r""" - Return the `{}_{p}F_{q}`-hypergeometric function - `\,_pF_{q}` where `(p,q)` is the parameterization of ``self``. - - INPUT:: - - - ``a`` -- the first parameter of the hypergeometric function - - ``b`` -- the second parameter of the hypergeometric function - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: from sage.rings.lazy_laurent_series import LazySpecialFunctions - sage: LazySpecialFunctions.hypergeometric(z, [1, 1], [1]) - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, [1, 2], [1]) - 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, 1, 1) - 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, [2, 3], [4]) - 1 + 3/2*z + 9/5*z^2 + 2*z^3 + 15/7*z^4 + 9/4*z^5 + 7/3*z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, [2, 3], [2]) - 1 + 3*z + 6*z^2 + 10*z^3 + 15*z^4 + 21*z^5 + 28*z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, [2, 3], [2]) - 1 + 3*z + 6*z^2 + 10*z^3 + 15*z^4 + 21*z^5 + 28*z^6 + O(z^7) - sage: LazySpecialFunctions.hypergeometric(z, (1, 1), [1, 1]) - 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) - - """ - from sage.functions.other import factorial - from sage.arith.misc import rising_factorial - if not isinstance(a, (list, tuple)): - a = [a] - if not isinstance(b, (list, tuple)): - b = [b] - def coeff(n, c): - num = 1 - for term in range(len(c)): - num *= rising_factorial(c[term], n) - return num - P = self.parent() - return P(lambda n: coeff(n, a)/(coeff(n, b) * factorial(n)), 0)(self) - - -class LazyLaurentSeries(LazySequencesModuleElement, LazyCauchyProductSeries, LazySpecialFunctions): - r""" - A Laurent series where the coefficients are computed lazily. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: L(lambda i: i, valuation=-3, constant=(-1,3)) - -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) - sage: L(lambda i: i, valuation=-3, constant=-1, degree=3) - -3*z^-3 - 2*z^-2 - z^-1 + z + 2*z^2 - z^3 - z^4 - z^5 + O(z^6) - - :: - - sage: f = 1 / (1 - z - z^2); f - 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) - sage: f.coefficient(100) - 573147844013817084101 - - Lazy Laurent series is picklable:: - - sage: g = loads(dumps(f)) - sage: g - 1 + z + 2*z^2 + 3*z^3 + 5*z^4 + 8*z^5 + 13*z^6 + O(z^7) - sage: g == f - True - - """ - def change_ring(self, ring): - r""" - Return ``self`` with coefficients converted to elements of ``ring``. - - INPUT: - - - ``ring`` -- a ring - - EXAMPLES: - - Dense Implementation:: - - sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) - sage: s = 2 + z - sage: t = s.change_ring(QQ) - sage: t^-1 - 1/2 - 1/4*z + 1/8*z^2 - 1/16*z^3 + 1/32*z^4 - 1/64*z^5 + 1/128*z^6 + O(z^7) - sage: M = L(lambda n: n); M - z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) - sage: N = M.change_ring(QQ) - sage: N.parent() - Lazy Laurent Series Ring in z over Rational Field - sage: M.parent() - Lazy Laurent Series Ring in z over Integer Ring - - Sparse Implementation:: - - sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M - z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) - sage: M.parent() - Lazy Laurent Series Ring in z over Integer Ring - sage: N = M.change_ring(QQ) - sage: N.parent() - Lazy Laurent Series Ring in z over Rational Field - sage: M ^-1 - z^-1 - 2 + z + O(z^6) - - """ - from .lazy_laurent_series_ring import LazyLaurentSeriesRing - Q = LazyLaurentSeriesRing(ring, names=self.parent().variable_names()) - return Q.element_class(Q, self._coeff_stream) - - def __call__(self, g): - r""" - Return the composition of ``self`` with ``g``. - - Given two Laurent Series `f` and `g` over the same base ring, the - composition `(f \circ g)(z) = f(g(z))` is defined if and only if: - `g = 0` and `val(f) >= 0`, - `g` is non-zero and `f` has only finitely many non-zero coefficients, - `g` is non-zero and `val(g) > 0`. - + INPUT: - + - ``g`` -- other series - + EXAMPLES:: - + sage: L. = LazyLaurentSeriesRing(QQ) sage: f = z^2 + 1 + z sage: f(0) @@ -2357,6 +1722,7 @@ def __call__(self, g): z^-6 + 4*z^-5 + 12*z^-4 + 33*z^-3 + 82*z^-2 + 196*z^-1 + 457 + O(z) sage: g^2 + 1 + g z^-6 + 4*z^-5 + 12*z^-4 + 33*z^-3 + 82*z^-2 + 196*z^-1 + 457 + O(z) + sage: f = z^-2 + z + 4*z^3 sage: f(f) 4*z^-6 + 12*z^-3 + z^-2 + 48*z^-1 + 12 + O(z) @@ -2366,6 +1732,7 @@ def __call__(self, g): 4*z^-9 + 24*z^-8 + 96*z^-7 + 320*z^-6 + 960*z^-5 + 2688*z^-4 + 7169*z^-3 + O(z^-2) sage: g^-2 + g + 4*g^3 4*z^-9 + 24*z^-8 + 96*z^-7 + 320*z^-6 + 960*z^-5 + 2688*z^-4 + 7169*z^-3 + O(z^-2) + sage: f = z^-3 + z^-2 + 1 / (1 + z^2); f z^-3 + z^-2 + 1 - z^2 + O(z^4) sage: g = z^3 / (1 + z - z^3); g @@ -2385,15 +1752,16 @@ def __call__(self, g): z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: f(z^2) z^2 + 2*z^4 + 3*z^6 + O(z^7) + sage: f = L(lambda n: n, -2); f -2*z^-2 - z^-1 + z + 2*z^2 + 3*z^3 + 4*z^4 + O(z^5) sage: f3 = f(z^3); f3 -2*z^-6 - z^-3 + O(z) sage: [f3[i] for i in range(-6,13)] [-2, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4] - + We compose a Laurent polynomial with a generic element:: - + sage: R. = QQ[] sage: f = z^2 + 1 + z^-1 sage: g = x^2 + x + 3 @@ -2401,9 +1769,9 @@ def __call__(self, g): (x^6 + 3*x^5 + 12*x^4 + 19*x^3 + 37*x^2 + 28*x + 31)/(x^2 + x + 3) sage: f(g) == g^2 + 1 + g^-1 True - + We compose with another lazy Laurent series:: - + sage: LS. = LazyLaurentSeriesRing(QQ) sage: f = z^2 + 1 + z^-1 sage: fy = f(y); fy @@ -2415,11 +1783,13 @@ def __call__(self, g): Traceback (most recent call last): ... ZeroDivisionError: the valuation of the series must be nonnegative + sage: g = 1 - y sage: f(g) 3 - y + 2*y^2 + y^3 + y^4 + y^5 + O(y^6) sage: g^2 + 1 + g^-1 3 - y + 2*y^2 + y^3 + y^4 + y^5 + O(y^6) + sage: f = L(lambda n: n, 0); f z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: f(0) @@ -2431,18 +1801,20 @@ def __call__(self, g): True sage: fp.parent() is LS True + sage: f = z^2 + 3 + z sage: f(y - y) 3 - + With both of them sparse:: - + sage: L. = LazyLaurentSeriesRing(QQ, sparse=True) sage: LS. = LazyLaurentSeriesRing(QQ, sparse=True) sage: f = L(lambda n: 1); f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: f(y^2) 1 + y^2 + y^4 + y^6 + O(y^7) + sage: fp = f - 1 + z^-2; fp z^-2 + z + z^2 + z^3 + z^4 + O(z^5) sage: fpy = fp(y^2); fpy @@ -2451,12 +1823,14 @@ def __call__(self, g): True sage: [fpy[i] for i in range(-4,11)] [1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + sage: g = LS(valuation=2, constant=1); g y^2 + y^3 + y^4 + O(y^5) sage: fg = f(g); fg 1 + y^2 + y^3 + 2*y^4 + 3*y^5 + 5*y^6 + O(y^7) sage: 1 + g + g^2 + g^3 + g^4 + g^5 + g^6 1 + y^2 + y^3 + 2*y^4 + 3*y^5 + 5*y^6 + O(y^7) + sage: h = LS(lambda n: 1 if n % 2 else 0, 2); h y^3 + y^5 + y^7 + O(y^9) sage: fgh = fg(h); fgh @@ -2466,9 +1840,9 @@ def __call__(self, g): sage: t = 1 + h^2 + h^3 + 2*h^4 + 3*h^5 + 5*h^6 sage: [t[i] for i in range(0, 15)] [1, 0, 0, 0, 0, 0, 1, 0, 2, 1, 3, 3, 6, 6, 13] - + We look at mixing the sparse and the dense:: - + sage: L. = LazyLaurentSeriesRing(QQ) sage: f = L(lambda n: 1); f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) @@ -2476,6 +1850,7 @@ def __call__(self, g): y + y^2 + y^3 + y^4 + y^5 + y^6 + y^7 + O(y^8) sage: f(g) 1 + y + 2*y^2 + 4*y^3 + 8*y^4 + 16*y^5 + 32*y^6 + O(y^7) + sage: f = z^-2 + 1 + z sage: g = 1/(y*(1-y)); g y^-1 + 1 + y + y^2 + y^3 + y^4 + y^5 + O(y^6) @@ -2483,6 +1858,7 @@ def __call__(self, g): y^-1 + 2 + y + 2*y^2 - y^3 + 2*y^4 + y^5 + O(y^6) sage: g^-2 + 1 + g y^-1 + 2 + y + 2*y^2 - y^3 + 2*y^4 + y^5 + O(y^6) + sage: f = z^-3 + z^-2 + 1 sage: g = 1/(y^2*(1-y)); g y^-2 + y^-1 + 1 + y + y^2 + y^3 + y^4 + O(y^5) @@ -2492,10 +1868,10 @@ def __call__(self, g): 1 + y^4 - 2*y^5 + 2*y^6 + O(y^7) sage: z(y) y - + We look at cases where the composition does not exist. - `g = 0` and `val(f) < 0`:: + sage: g = L(0) sage: f = z^-1 + z^-2 sage: f.valuation() < 0 @@ -2504,7 +1880,7 @@ def __call__(self, g): Traceback (most recent call last): ... ZeroDivisionError: the valuation of the series must be nonnegative - + `g \neq 0` and `val(g) \leq 0` and `f` has infinitely many non-zero coefficients`:: @@ -2525,7 +1901,6 @@ def __call__(self, g): Traceback (most recent call last): ... ValueError: can only compose with a positive valuation series - """ # f = self and compute f(g) P = g.parent() @@ -2610,71 +1985,30 @@ def revert(self): r""" Return the compositional inverse of ``self``. - Given a Laurent Series `f` we want to find a Laurent Series `g` over the same base ring, such - that the composition `(f \circ g)(z) = f(g(z)) = z`. The composition inverse exists if and only if: - - `val(f) = 1' or `f(0) * f(1) != 0`, - - EXAMPLES:: - sage: L. = LazyLaurentSeriesRing(QQ) + sage: L. = LazyLaurentSeriesRing(ZZ) sage: z.revert() z + O(z^8) sage: (1/z).revert() z^-1 + O(z^6) - sage: (z - z^2).revert() - z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) - sage: (z + z^2).revert() - z - z^2 + 2*z^3 - 5*z^4 + 14*z^5 - 42*z^6 + 132*z^7 + O(z^8) - sage: (2*z).revert() - 1/2*z + O(z^8) - sage: s = L(lambda n: 1 if n == 1 else 0) - sage: s.revert() - z + O(z^8) - - We look at some cases where the compositional inverse does not exist.: - - `f = 0`:: - sage: L(0).revert() - Traceback (most recent call last): - ... - ValueError: compositional inverse does not exist - sage: (z - z).revert() - Traceback (most recent call last): - ... - ValueError: compositional inverse does not exist - - `val(f) ! = 1 and f(0) * f(1) = 0`:: - - sage: (z^2).revert() - Traceback (most recent call last): - ... - ValueError: compositional inverse does not exist - sage: L(1).revert() - Traceback (most recent call last): - ... - ValueError: compositional inverse does not exist + sage: (z-z^2).revert() + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) """ P = self.parent() z = P.gen() - # g = 0 case - if isinstance(self._coeff_stream, CoefficientStream_zero): - raise ValueError('compositional inverse does not exist') - # val g > 0 and ensure that val(g) == 1 - if self.valuation() >= 0 and self.valuation() != 1: - raise ValueError('compositional inverse does not exist') - if self.valuation() == 1: + if self._coeff_stream._approximate_valuation == 1: g = self else: - g = self * z**(1 - self.valuation()) + g = self * z**(1 - self._coeff_stream._approximate_valuation) f = P(None, valuation=1) f.define(z/((g/z)(f))) - if self.valuation() == 1: + if self._coeff_stream._approximate_valuation == 1: return f else: - return f / z**(1 - self.valuation()) + return f / z**(1 - self._coeff_stream._approximate_valuation) def approximate_series(self, prec, name=None): """ @@ -2706,7 +2040,6 @@ def approximate_series(self, prec, name=None): z^-5 - 2*z^-4 + 10*z^-3 - 20*z^-2 + 60*z^-1 - 120 + 280*z - 560*z^2 + O(z^3) sage: h.parent() Laurent Series Ring in z over Integer Ring - """ S = self.parent() @@ -2737,6 +2070,7 @@ def polynomial(self, degree=None, name=None): A Laurent polynomial if the valuation of the series is negative or a polynomial otherwise. + If ``degree`` is not ``None``, the terms of the series of degree greater than ``degree`` are truncated first. If ``degree`` is ``None`` and the series is not a polynomial or a Laurent polynomial, a @@ -2772,14 +2106,15 @@ def polynomial(self, degree=None, name=None): sage: M = L(lambda n: n^2, 0) sage: M.polynomial(5) 25*z^5 + 16*z^4 + 9*z^3 + 4*z^2 + z + sage: f = 1/(1 + z) sage: f.polynomial() Traceback (most recent call last): ... ValueError: not a polynomial + sage: L.zero().polynomial() 0 - """ S = self.parent() @@ -2816,7 +2151,6 @@ def _format_series(self, formatter, format_strings=False): sage: f = 1 / (2 - z^2) sage: f._format_series(ascii_art, True) 1/2 + 1/4*z^2 + 1/8*z^4 + 1/16*z^6 + O(z^7) - """ P = self.parent() R = P._laurent_poly_ring @@ -2846,7 +2180,7 @@ def _format_series(self, formatter, format_strings=False): return formatter(poly) + strformat(" + O({})".format(formatter(z**m))) -class LazyTaylorSeries(LazySequencesModuleElement, LazyCauchyProductSeries, LazySpecialFunctions): +class LazyTaylorSeries(LazySequencesModuleElement, LazyCauchyProductSeries): r""" A Taylor series where the coefficients are computed lazily. @@ -2868,52 +2202,54 @@ class LazyTaylorSeries(LazySequencesModuleElement, LazyCauchyProductSeries, Lazy True """ def __call__(self, *g): - r""" - Return the composition of ``self`` with ``g``. - + r"""Return the composition of ``self`` with ``g``. + The arity of ``self`` must be equal to the number of arguments provided. + Given two Taylor Series `f` and `g` over the same base ring, the composition `(f \circ g)(z) = f(g(z))` is defined if and only if: + - `g = 0` and `val(f) >= 0`, - `g` is non-zero and `f` has only finitely many non-zero coefficients, - `g` is non-zero and `val(g) > 0`. - + INPUT: - + - ``g`` -- other series, all of the same parent. - + EXAMPLES:: - + sage: L. = LazyTaylorSeriesRing(QQ) sage: M. = LazyTaylorSeriesRing(ZZ) sage: g1 = 1/(1-x); g2 = x+y^2 sage: p = a^2 + b + 1 sage: p(g1, g2) - g1^2 - g2 - 1 O(x,y,z)^7 + sage: L. = LazyTaylorSeriesRing(QQ) sage: M. = LazyTaylorSeriesRing(QQ) - + The number of mappings from a set with `m` elements to a set with `n` elements:: - + sage: Ea = M(lambda n: 1/factorial(n)) sage: Ex = L(lambda n: 1/factorial(n)*x^n) sage: Ea(Ex*y)[5] 1/24*x^4*y + 2/3*x^3*y^2 + 3/4*x^2*y^3 + 1/6*x*y^4 + 1/120*y^5 - + So, there are `3! 2! 2/3 = 8` mappings from a three element set to a two element set. - + TESTS:: - + sage: L. = LazyTaylorSeriesRing(ZZ) sage: f = 1/(1-x-y) sage: f(f) Traceback (most recent call last): ... ValueError: arity of must be equal to the number of arguments provided - + """ if len(g) != len(self.parent().variable_names()): raise ValueError("arity of must be equal to the number of arguments provided") @@ -2948,13 +2284,13 @@ def coefficient(n): def change_ring(self, ring): """ Return this series with coefficients converted to elements of ``ring``. - + INPUT: - + - ``ring`` -- a ring - + EXAMPLES:: - + sage: L. = LazyTaylorSeriesRing(ZZ) sage: s = 2 + z sage: t = s.change_ring(QQ) @@ -2962,7 +2298,7 @@ def change_ring(self, ring): 1/2 - 1/4*z + 1/8*z^2 - 1/16*z^3 + 1/32*z^4 - 1/64*z^5 + 1/128*z^6 + O(z^7) sage: t.parent() Lazy Taylor Series Ring in z over Rational Field - + """ from .lazy_laurent_series_ring import LazyTaylorSeriesRing Q = LazyTaylorSeriesRing(ring, names=self.parent().variable_names()) @@ -2971,17 +2307,17 @@ def change_ring(self, ring): def _format_series(self, formatter, format_strings=False): """ Return nonzero ``self`` formatted by ``formatter``. - + TESTS:: - + sage: L. = LazyTaylorSeriesRing(QQ) sage: f = 1 / (2 - x^2 + y) sage: f._format_series(repr) '1/2 + (-1/4*y) + (1/4*x^2+1/8*y^2) + (-1/4*x^2*y-1/16*y^3) + (1/8*x^4+3/16*x^2*y^2+1/32*y^4) + (-3/16*x^4*y-1/8*x^2*y^3-1/64*y^5) + (1/16*x^6+3/16*x^4*y^2+5/64*x^2*y^4+1/128*y^6) + O(x,y)^7' + sage: f = (2 - x^2 + y) sage: f._format_series(repr) '2 + y + (-x^2)' - """ P = self.parent() cs = self._coeff_stream @@ -3038,42 +2374,42 @@ def parenthesize(m): return poly -class LazyDirichletSeries(LazySequencesModuleElement, LazySpecialFunctions): +class LazyDirichletSeries(LazySequencesModuleElement): r""" A Dirichlet series where the coefficients are computed lazily. - + INPUT: - + - ``parent`` -- The base ring for the series + - ``coeff_stream`` -- The auxiliary class that handles the coefficient stream - + EXAMPLES:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: f = L(constant=1)^2; f 1 + 2/2^z + 2/3^z + 3/4^z + 2/5^z + 4/6^z + 2/7^z + ... sage: f.coefficient(100) == number_of_divisors(100) True - + Lazy Dirichlet series is picklable:: - + sage: g = loads(dumps(f)) sage: g 1 + 2/2^z + 2/3^z + 3/4^z + 2/5^z + 4/6^z + 2/7^z + ... sage: g == f True - """ def _mul_(self, other): """ Return the product of this series with ``other``. - + INPUT: - + - ``other`` -- other series - + TESTS:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") sage: g = L(constant=1); g 1 + 1/(2^z) + 1/(3^z) + O(1/(4^z)) @@ -3081,6 +2417,7 @@ def _mul_(self, other): 1 + 2/2^z + 2/3^z + 3/4^z + 2/5^z + 4/6^z + 2/7^z + O(1/(8^z)) sage: [number_of_divisors(n) for n in range(1, 8)] [1, 2, 2, 3, 2, 4, 2] + sage: mu = L(moebius); mu 1 - 1/(2^z) - 1/(3^z) - 1/(5^z) + 1/(6^z) - 1/(7^z) + O(1/(8^z)) sage: g*mu @@ -3089,7 +2426,6 @@ def _mul_(self, other): True sage: mu * L.one() is mu True - """ P = self.parent() left = self._coeff_stream @@ -3109,16 +2445,16 @@ def _mul_(self, other): def __invert__(self): """ Return the multiplicative inverse of the element. - + TESTS:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z", sparse=False) sage: ~L(constant=1) - L(moebius) O(1/(8^z)) sage: L = LazyDirichletSeriesRing(ZZ, "z", sparse=True) sage: ~L(constant=1) - L(moebius) O(1/(8^z)) - + """ P = self.parent() return P.element_class(P, CoefficientStream_dirichlet_inv(self._coeff_stream)) @@ -3126,15 +2462,14 @@ def __invert__(self): def change_ring(self, ring): """ Return this series with coefficients converted to elements of ``ring``. - + INPUT: - + - ``ring`` -- a ring - + TESTS:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z", sparse=False) - """ from .lazy_laurent_series_ring import LazyDirichletSeriesRing Q = LazyDirichletSeriesRing(ring, names=self.parent().variable_names()) @@ -3143,15 +2478,14 @@ def change_ring(self, ring): def __pow__(self, n): """ Return the ``n``-th power of the series. - + INPUT: - + - ``n`` -- integer, the power to which to raise the series - + TESTS:: - + sage: L = LazyDirichletSeriesRing(ZZ, "z") - """ if n == 0: return self.parent().one() @@ -3161,15 +2495,17 @@ def __pow__(self, n): def _format_series(self, formatter, format_strings=False): """ Return nonzero ``self`` formatted by ``formatter``. - + TESTS:: - + sage: L = LazyDirichletSeriesRing(QQ, "s") sage: f = L(constant=1) sage: f._format_series(repr) '1 + 1/(2^s) + 1/(3^s) + O(1/(4^s))' + sage: L([1,-1,1])._format_series(repr) '1 - 1/(2^s) + 1/(3^s)' + sage: L([1,-1,1])._format_series(ascii_art) -s -s 1 + -2 + 3 From 4723f6bc6002985ae0d0592f45d8c840af8dd200 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 11 Aug 2021 19:03:09 +1000 Subject: [PATCH 7/9] Refactoring we discussed at the meeting today. --- .../data_structures/coefficient_stream.py | 142 ++++----- src/sage/rings/lazy_laurent_series.py | 273 ++++++++---------- src/sage/rings/lazy_laurent_series_ring.py | 64 ++-- 3 files changed, 244 insertions(+), 235 deletions(-) diff --git a/src/sage/data_structures/coefficient_stream.py b/src/sage/data_structures/coefficient_stream.py index af761ae25b5..572460fb10b 100644 --- a/src/sage/data_structures/coefficient_stream.py +++ b/src/sage/data_structures/coefficient_stream.py @@ -118,10 +118,10 @@ class CoefficientStream(): INPUT: - ``sparse`` -- boolean; whether the implementation of the stream is sparse - - ``approximate_valuation`` -- integer; a lower bound for the valuation + - ``approximate_order`` -- integer; a lower bound for the order of the stream """ - def __init__(self, sparse, approximate_valuation): + def __init__(self, sparse, approximate_order): """ Initialize ``self``. @@ -131,7 +131,7 @@ def __init__(self, sparse, approximate_valuation): sage: CS = CoefficientStream(True, 1) """ self._is_sparse = sparse - self._approximate_valuation = approximate_valuation + self._approximate_order = approximate_order class CoefficientStream_inexact(CoefficientStream): @@ -142,10 +142,10 @@ class CoefficientStream_inexact(CoefficientStream): INPUT: - ``sparse`` -- boolean; whether the implementation of the stream is sparse - - ``approximate_valuation`` -- integer; a lower bound for the valuation + - ``approximate_order`` -- integer; a lower bound for the order of the stream """ - def __init__(self, is_sparse, approximate_valuation): + def __init__(self, is_sparse, approximate_order): """ Initialize the stream class for a CoefficientStream when it is not or it cannot be determined if it is eventually geometric. @@ -158,13 +158,13 @@ def __init__(self, is_sparse, approximate_valuation): sage: isinstance(g, CoefficientStream_inexact) True """ - super().__init__(is_sparse, approximate_valuation) + super().__init__(is_sparse, approximate_order) if self._is_sparse: self._cache = dict() # cache of known coefficients else: self._cache = list() - self._offset = approximate_valuation + self._offset = approximate_order self._iter = self.iterate_coefficients() def __getstate__(self): @@ -268,7 +268,7 @@ def __getitem__(self, n): sage: f._cache [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] """ - if n < self._approximate_valuation: + if n < self._approximate_order: return ZZ.zero() if self._is_sparse: @@ -303,48 +303,49 @@ def iterate_coefficients(self): sage: [next(n) for i in range(10)] [1, 9, 44, 207, 991, 4752, 22769, 109089, 522676, 2504295] """ - n = self._approximate_valuation + n = self._approximate_order while True: yield self.get_coefficient(n) n += 1 - def valuation(self): - """ - Return the valuation of ``self``. + def order(self): + r""" + Return the order of ``self``, which is the minimum index ``n`` such + that ``self[n]`` is nonzero. EXAMPLES:: sage: from sage.data_structures.coefficient_stream import CoefficientStream_coefficient_function sage: f = CoefficientStream_coefficient_function(lambda n: n, QQ, True, 0) - sage: f.valuation() + sage: f.order() 1 """ if self._is_sparse: - n = self._approximate_valuation + n = self._approximate_order cache = self._cache while True: if n in cache: if cache[n]: - self._approximate_valuation = n + self._approximate_order = n return n n += 1 else: if self[n] != 0: - self._approximate_valuation = n + self._approximate_order = n return n n += 1 else: - n = self._approximate_valuation + n = self._approximate_order cache = self._cache while True: if n - self._offset < len(cache): if cache[n - self._offset]: - self._approximate_valuation = n + self._approximate_order = n return n n += 1 else: if self[n] != 0: - self._approximate_valuation = n + self._approximate_order = n return n n += 1 @@ -357,14 +358,14 @@ class CoefficientStream_exact(CoefficientStream): - ``initial_values`` -- a list of initial values - ``is_sparse`` -- boolean; specifies whether the stream is sparse - - ``valuation`` -- integer (default: 0); determining the degree + - ``order`` -- integer (default: 0); determining the degree of the first element of ``initial_values`` - ``degree`` -- integer (optional); determining the degree of the first element which is known to be equal to ``constant`` - ``constant`` -- integer (default: 0); the coefficient of every index larger than or equal to ``degree`` """ - def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, valuation=None): + def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, order=None): """ Initialize a series that is known to be eventually geometric. @@ -380,21 +381,21 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, self._constant = ZZ.zero() else: self._constant = constant - if valuation is None: - valuation = 0 + if order is None: + order = 0 if degree is None: - self._degree = valuation + len(initial_coefficients) + self._degree = order + len(initial_coefficients) else: self._degree = degree - assert valuation + len(initial_coefficients) <= self._degree + assert order + len(initial_coefficients) <= self._degree # We do not insist that the last entry of initial_coefficients # is different from constant in case comparisons can be # expensive such as in the symbolic ring for i, v in enumerate(initial_coefficients): if v: - valuation += i + order += i initial_coefficients = initial_coefficients[i:] for j, w in enumerate(reversed(initial_coefficients)): if w: @@ -403,12 +404,12 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, self._initial_coefficients = tuple(initial_coefficients) break else: - valuation = self._degree + order = self._degree self._initial_coefficients = tuple() assert self._initial_coefficients or self._constant, "CoefficientStream_exact should only be used for non-zero streams" - super().__init__(is_sparse, valuation) + super().__init__(is_sparse, order) def __getitem__(self, n): """ @@ -433,37 +434,38 @@ def __getitem__(self, n): sage: [s[i] for i in range(-2, 5)] [0, 0, 2, 1, 1, 1, 1] - sage: s = CoefficientStream_exact([2], False, valuation=-1, constant=1) + sage: s = CoefficientStream_exact([2], False, order=-1, constant=1) sage: [s[i] for i in range(-2, 5)] [0, 2, 1, 1, 1, 1, 1] - sage: s = CoefficientStream_exact([2], False, valuation=-1, degree=2, constant=1) + sage: s = CoefficientStream_exact([2], False, order=-1, degree=2, constant=1) sage: [s[i] for i in range(-2, 5)] [0, 2, 0, 0, 1, 1, 1] - sage: t = CoefficientStream_exact([0, 2, 0], False, valuation=-2, degree=2, constant=1) + sage: t = CoefficientStream_exact([0, 2, 0], False, order=-2, degree=2, constant=1) sage: t == s True """ if n >= self._degree: return self._constant - i = n - self._approximate_valuation + i = n - self._approximate_order if i < 0 or i >= len(self._initial_coefficients): return ZZ.zero() return self._initial_coefficients[i] - def valuation(self): - """ - Return the valuation of ``self``. + def order(self): + r""" + Return the order of ``self``, which is the minimum index ``n`` such + that ``self[n]`` is nonzero. EXAMPLES:: sage: from sage.data_structures.coefficient_stream import CoefficientStream_exact sage: s = CoefficientStream_exact([1], False) - sage: s.valuation() + sage: s.order() 0 """ - return self._approximate_valuation + return self._approximate_order def __hash__(self): """ @@ -489,7 +491,7 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.coefficient_stream import CoefficientStream_exact - sage: s = CoefficientStream_exact([2], False, valuation=-1, degree=2, constant=1) + sage: s = CoefficientStream_exact([2], False, order=-1, degree=2, constant=1) sage: t = CoefficientStream_exact([0, 2, 0], False, 1, 2, -2) sage: [s[i] for i in range(10)] [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] @@ -498,7 +500,7 @@ def __eq__(self, other): sage: s == t True sage: s = CoefficientStream_exact([2], False, constant=1) - sage: t = CoefficientStream_exact([2], False, valuation=-1, constant=1) + sage: t = CoefficientStream_exact([2], False, order=-1, constant=1) sage: [s[i] for i in range(10)] [2, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: [t[i] for i in range(10)] @@ -520,12 +522,12 @@ def polynomial_part(self, R): EXAMPLES:: sage: from sage.data_structures.coefficient_stream import CoefficientStream_exact - sage: s = CoefficientStream_exact([2], False, valuation=-1, degree=2, constant=1) + sage: s = CoefficientStream_exact([2], False, order=-1, degree=2, constant=1) sage: L. = LazyLaurentSeriesRing(ZZ) sage: s.polynomial_part(L._laurent_poly_ring) 2*z^-1 """ - v = self._approximate_valuation + v = self._approximate_order return R(self._initial_coefficients).shift(v) @@ -539,7 +541,7 @@ class CoefficientStream_coefficient_function(CoefficientStream_inexact): coefficients of the stream - ``ring`` -- the base ring - ``is_sparse`` -- boolean; specifies whether the stream is sparse - - ``approximate_valuation`` -- integer; a lower bound for the valuation + - ``approximate_order`` -- integer; a lower bound for the order of the stream EXAMPLES:: @@ -552,7 +554,7 @@ class CoefficientStream_coefficient_function(CoefficientStream_inexact): [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] """ - def __init__(self, coefficient_function, ring, is_sparse, approximate_valuation): + def __init__(self, coefficient_function, ring, is_sparse, approximate_order): """ Initialize. @@ -564,7 +566,7 @@ def __init__(self, coefficient_function, ring, is_sparse, approximate_valuation) """ self._coefficient_function = coefficient_function self._ring = ring - super().__init__(is_sparse, approximate_valuation) + super().__init__(is_sparse, approximate_order) def get_coefficient(self, n): """ @@ -609,7 +611,7 @@ class CoefficientStream_uninitialized(CoefficientStream_inexact): INPUT: - ``is_sparse`` -- boolean; which specifies whether the stream is sparse - - ``approximate_valuation`` -- integer; a lower bound for the valuation + - ``approximate_order`` -- integer; a lower bound for the order of the stream EXAMPLES:: @@ -623,7 +625,7 @@ class CoefficientStream_uninitialized(CoefficientStream_inexact): sage: C.get_coefficient(4) 0 """ - def __init__(self, is_sparse, approximate_valuation): + def __init__(self, is_sparse, approximate_order): """ Initialize ``self``. @@ -634,7 +636,7 @@ def __init__(self, is_sparse, approximate_valuation): sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None - super().__init__(is_sparse, approximate_valuation) + super().__init__(is_sparse, approximate_order) def get_coefficient(self, n): """ @@ -665,7 +667,7 @@ def iterate_coefficients(self): sage: from sage.data_structures.coefficient_stream import CoefficientStream_uninitialized sage: from sage.data_structures.coefficient_stream import CoefficientStream_exact - sage: z = CoefficientStream_exact([1], True, valuation=1) + sage: z = CoefficientStream_exact([1], True, order=1) sage: C = CoefficientStream_uninitialized(True, 0) sage: C._target sage: C._target = z @@ -673,7 +675,7 @@ def iterate_coefficients(self): sage: [next(n) for _ in range(10)] [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] """ - n = self._approximate_valuation + n = self._approximate_order while True: yield self._target[n] n += 1 @@ -955,15 +957,15 @@ def __getitem__(self, n): """ return ZZ.zero() - def valuation(self): - """ - Return the valuation of ``self``. + def order(self): + r""" + Return the order of ``self``, which is ``infinity``. EXAMPLES:: sage: from sage.data_structures.coefficient_stream import CoefficientStream_zero sage: s = CoefficientStream_zero(True) - sage: s.valuation() + sage: s.order() +Infinity """ return infinity @@ -1037,7 +1039,7 @@ def __init__(self, left, right): if left._is_sparse != right._is_sparse: raise NotImplementedError - a = min(left._approximate_valuation, right._approximate_valuation) + a = min(left._approximate_order, right._approximate_order) super().__init__(left, right, left._is_sparse, a) def get_coefficient(self, n): @@ -1098,7 +1100,7 @@ def __init__(self, left, right): if left._is_sparse != right._is_sparse: raise NotImplementedError - a = min(left._approximate_valuation, right._approximate_valuation) + a = min(left._approximate_order, right._approximate_order) super().__init__(left, right, left._is_sparse, a) def get_coefficient(self, n): @@ -1162,7 +1164,7 @@ def __init__(self, left, right): if left._is_sparse != right._is_sparse: raise NotImplementedError - a = left._approximate_valuation + right._approximate_valuation + a = left._approximate_order + right._approximate_order super().__init__(left, right, left._is_sparse, a) def get_coefficient(self, n): @@ -1185,8 +1187,8 @@ def get_coefficient(self, n): [0, 0, 1, 6, 20, 50, 105, 196, 336, 540] """ c = ZZ.zero() - for k in range(self._left._approximate_valuation, - n - self._right._approximate_valuation + 1): + for k in range(self._left._approximate_order, + n - self._right._approximate_order + 1): val = self._left[k] if val: c += val * self._right[n-k] @@ -1202,7 +1204,7 @@ class CoefficientStream_composition(CoefficientStream_binary): INPUT: - ``f`` -- a :class:`CoefficientStream` - - ``g`` -- a :class:`CoefficientStream` with positive valuation + - ``g`` -- a :class:`CoefficientStream` with positive order EXAMPLES:: @@ -1227,9 +1229,9 @@ def __init__(self, f, g): sage: g = CoefficientStream_coefficient_function(lambda n: n^2, ZZ, True, 1) sage: h = CoefficientStream_composition(f, g) """ - assert g._approximate_valuation > 0 - self._fv = f._approximate_valuation - self._gv = g._approximate_valuation + assert g._approximate_order > 0 + self._fv = f._approximate_order + self._gv = g._approximate_order if self._fv < 0: ginv = CoefficientStream_cauchy_inverse(g) # the constant part makes no contribution to the negative @@ -1306,7 +1308,7 @@ def __init__(self, series, scalar): sage: g = CoefficientStream_rmul(f, 3) """ self._scalar = scalar - super().__init__(series, series._is_sparse, series._approximate_valuation) + super().__init__(series, series._is_sparse, series._approximate_order) def get_coefficient(self, n): """ @@ -1360,7 +1362,7 @@ def __init__(self, series, scalar): sage: g = CoefficientStream_lmul(f, 3) """ self._scalar = scalar - super().__init__(series, series._is_sparse, series._approximate_valuation) + super().__init__(series, series._is_sparse, series._approximate_order) def get_coefficient(self, n): """ @@ -1410,7 +1412,7 @@ def __init__(self, series): sage: f = CoefficientStream_coefficient_function(lambda n: -1, ZZ, True, 0) sage: g = CoefficientStream_neg(f) """ - super().__init__(series, series._is_sparse, series._approximate_valuation) + super().__init__(series, series._is_sparse, series._approximate_order) def get_coefficient(self, n): """ @@ -1459,7 +1461,7 @@ def __init__(self, series): sage: f = CoefficientStream_exact([1, -1], False) sage: g = CoefficientStream_cauchy_inverse(f) """ - v = series.valuation() + v = series.order() super().__init__(series, series._is_sparse, -v) self._ainv = ~series[v] @@ -1483,7 +1485,7 @@ def get_coefficient(self, n): sage: [g.get_coefficient(i) for i in range(10)] [-2, 1, 0, 0, 0, 0, 0, 0, 0, 0] """ - v = self._approximate_valuation + v = self._approximate_order if n == v: return self._ainv c = self._zero @@ -1504,8 +1506,8 @@ def iterate_coefficients(self): sage: [next(n) for i in range(10)] [1, -4, 7, -8, 8, -8, 8, -8, 8, -8] """ - v = self._approximate_valuation # shorthand name - n = 0 # Counts the number of places from the valuation + v = self._approximate_order # shorthand name + n = 0 # Counts the number of places from the order yield self._ainv # Note that first entry of the cache will correspond to z^v while True: @@ -1551,7 +1553,7 @@ def __init__(self, series, function, ring): """ self._function = function self._ring = ring - super().__init__(series, series._is_sparse, series._approximate_valuation) + super().__init__(series, series._is_sparse, series._approximate_order) def get_coefficient(self, n): """ diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 24946d6edcd..062c8d9b8ea 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -27,7 +27,7 @@ implementation. For the sparse implementation, only the coefficients that are needed are computed.:: - sage: s = L(lambda n: n); s + sage: s = L(lambda n: n, valuation=0); s z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: s.coefficient(10) 10 @@ -38,7 +38,7 @@ coefficient are computed.:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) - sage: s = L(lambda n: n); s + sage: s = L(lambda n: n, valuation=0); s x + 2*x^2 + 3*x^3 + 4*x^4 + 5*x^5 + 6*x^6 + O(x^7) sage: s.coefficient(10) 10 @@ -88,7 +88,7 @@ from sage.rings.infinity import infinity -from sage.structure.element import ModuleElement, RingElement +from sage.structure.element import Element from sage.rings.integer_ring import ZZ from sage.structure.richcmp import op_EQ, op_NE from sage.arith.power import generic_power @@ -108,25 +108,44 @@ CoefficientStream_uninitialized ) -class LazySequenceElement(ModuleElement): +class LazyModuleElement(Element): r""" - An element of a lazy series. + A lazy sequence with a module structure given by term-wise + addition and scalar multiplication. EXAMPLES:: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: f = 1/(1 - z) - sage: f.parent() - Lazy Laurent Series Ring in z over Integer Ring - sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + sage: M = L(lambda n: n, valuation=0) + sage: N = L(lambda n: 1, valuation=0) + sage: M[:10] + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + sage: N[:10] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: f = 1/(1 - z) - sage: f.parent() - Lazy Laurent Series Ring in z over Integer Ring - sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + Two sequences can be added:: + + sage: O = M + N + sage: O[0:10] + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + Two sequences can be subracted:: + + sage: P = M - N + sage: P[:10] + [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] + + A sequence can be multiplied by a scalar:: + + sage: Q = 2 * M + sage: Q[:10] + [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] + + The negation of a sequence can also be found:: + + sage: R = -M + sage: R[:10] + [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] """ def __init__(self, parent, coeff_stream): """ @@ -138,7 +157,7 @@ def __init__(self, parent, coeff_stream): sage: TestSuite(L.an_element()).run() """ - ModuleElement.__init__(self, parent) + Element.__init__(self, parent) self._coeff_stream = coeff_stream def __getitem__(self, n): @@ -158,12 +177,12 @@ def __getitem__(self, n): sage: f[0:20] [0, 1, 0, 0, 2, 0, 0, 4, 0, 0, 8, 0, 0, 16, 0, 0, 32, 0, 0, 64] - sage: M = L(lambda n: n) + sage: M = L(lambda n: n, valuation=0) sage: [M[n] for n in range(20)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n) + sage: M = L(lambda n: n, valuation=0) sage: [M[n] for n in range(20)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] """ @@ -171,7 +190,7 @@ def __getitem__(self, n): if isinstance(n, slice): if n.stop is None: raise NotImplementedError("cannot list an infinite set") - start = n.start if n.start is not None else self._coeff_stream._approximate_valuation + start = n.start if n.start is not None else self._coeff_stream._approximate_order step = n.step if n.step is not None else 1 return [R(self._coeff_stream[k]) for k in range(start, n.stop, step)] return R(self._coeff_stream[n]) @@ -199,7 +218,7 @@ def map_coefficients(self, func, ring=None): z + 2*z^3 + 4*z^5 + 8*z^7 + O(z^8) sage: t 2*z + 3*z^3 + 5*z^5 + 9*z^7 + O(z^8) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: N = M.map_coefficients(lambda c: c + 1); N 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) @@ -207,7 +226,7 @@ def map_coefficients(self, func, ring=None): Sparse Implementation:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: N = M.map_coefficients(lambda c: c + 1); N 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) @@ -230,7 +249,7 @@ def map_coefficients(self, func, ring=None): return P.zero() coeff_stream = CoefficientStream_exact(initial_coefficients, self._coeff_stream._is_sparse, - valuation=coeff_stream._approximate_valuation, + order=coeff_stream._approximate_order, degree=coeff_stream._degree, constant=BR(c)) return P.element_class(P, coeff_stream) @@ -258,7 +277,7 @@ def truncate(self, d): 1 + z + z^2 + z^3 + z^4 sage: alpha - beta z^5 + z^6 + O(z^7) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.truncate(4) z + 2*z^2 + 3*z^3 @@ -266,7 +285,7 @@ def truncate(self, d): Sparse Implementation:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.truncate(4) z + 2*z^2 + 3*z^3 @@ -279,10 +298,10 @@ def truncate(self, d): """ P = self.parent() coeff_stream = self._coeff_stream - v = coeff_stream._approximate_valuation + v = coeff_stream._approximate_order initial_coefficients = [coeff_stream[i] for i in range(v, d)] return P.element_class(P, CoefficientStream_exact(initial_coefficients, P._sparse, - valuation=v)) + order=v)) def prec(self): """ @@ -320,7 +339,7 @@ def valuation(self): sage: (M - M).valuation() +Infinity """ - return self._coeff_stream.valuation() + return self._coeff_stream.order() def _richcmp_(self, other, op): r""" @@ -361,8 +380,8 @@ def _richcmp_(self, other, op): or not isinstance(other._coeff_stream, CoefficientStream_exact)): # One of the lazy laurent series is not known to eventually be constant # Implement the checking of the caches here. - n = min(self._coeff_stream._approximate_valuation, other._coeff_stream._approximate_valuation) - m = max(self._coeff_stream._approximate_valuation, other._coeff_stream._approximate_valuation) + n = min(self._coeff_stream._approximate_order, other._coeff_stream._approximate_order) + m = max(self._coeff_stream._approximate_order, other._coeff_stream._approximate_order) for i in range(n, m): if self[i] != other[i]: return False @@ -444,7 +463,7 @@ def __bool__(self): else: if any(a for a in self._coeff_stream._cache): return True - if self[self._coeff_stream._approximate_valuation]: + if self[self._coeff_stream._approximate_order]: return True raise ValueError("undecidable as lazy Laurent series") @@ -461,7 +480,7 @@ def define(self, s): We begin by constructing the Catalan numbers:: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: C = L(None) + sage: C = L(None, valuation=0) sage: C.define(1 + z*C^2) sage: C 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) @@ -475,8 +494,8 @@ def define(self, s): We can define multiple series that are linked:: - sage: s = L(None) - sage: t = L(None) + sage: s = L(None, valuation=0) + sage: t = L(None, valuation=0) sage: s.define(1 + z*t^3) sage: t.define(1 + z*s^2) sage: s[:9] @@ -487,9 +506,9 @@ def define(self, s): A bigger example:: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: A = L(None, 5) - sage: B = L(None) - sage: C = L(None, 2) + sage: A = L(None, valuation=5) + sage: B = L(None, valuation=0) + sage: C = L(None, valuation=2) sage: A.define(z^5 + B^2) sage: B.define(z^5 + C^2) sage: C.define(z^2 + C^2 + A^2) @@ -512,7 +531,7 @@ def define(self, s): sage: R. = ZZ[] sage: L. = LazyLaurentSeriesRing(R) - sage: s = L(None) + sage: s = L(None, valuation=0) sage: s.define(1+z*s*s(q*z)) sage: s 1 + z + (q + 1)*z^2 + (q^3 + q^2 + 2*q + 1)*z^3 @@ -537,12 +556,12 @@ def define(self, s): TESTS:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: s = L(None) + sage: s = L(None, valuation=0) sage: s.define(1 + z*s^3) sage: s[:10] [1, 1, 3, 12, 55, 273, 1428, 7752, 43263, 246675] - sage: e = L(None) + sage: e = L(None, valuation=0) sage: e.define(1 + z*e) sage: e.define(1 + z*e) Traceback (most recent call last): @@ -557,51 +576,8 @@ def define(self, s): raise ValueError("series already defined") self._coeff_stream._target = s._coeff_stream + # === module structure === -class LazySequencesModuleElement(LazySequenceElement): - r""" - A lazy series where addition and scalar multiplication - are done term-wise. - - INPUT: - - - ``parent`` -- the parent ring of the series - - ``coeff_stream`` -- the stream of coefficients of the series - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: M = L(lambda n: n, valuation=0) - sage: N = L(lambda n: 1, valuation=0) - sage: M[:10] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - sage: N[:10] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - - Two sequences can be added:: - - sage: O = M + N - sage: O[0:10] - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - - Two sequences can be subracted:: - - sage: P = M - N - sage: P[:10] - [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] - - A sequence can be multiplied by a scalar:: - - sage: Q = 2 * M - sage: Q[:10] - [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] - - The negation of a sequence can also be found:: - - sage: R = -M - sage: R[:10] - [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] - """ def _add_(self, other): """ Return the sum of ``self`` and ``other``. @@ -653,16 +629,16 @@ def _add_(self, other): return self if (isinstance(left, CoefficientStream_exact) and isinstance(right, CoefficientStream_exact)): - approximate_valuation = min(left.valuation(), right.valuation()) + approximate_order = min(left.order(), right.order()) degree = max(left._degree, right._degree) - initial_coefficients = [left[i] + right[i] for i in range(approximate_valuation, degree)] + initial_coefficients = [left[i] + right[i] for i in range(approximate_order, degree)] constant = left._constant + right._constant if not any(initial_coefficients) and not constant: return P.zero() coeff_stream = CoefficientStream_exact(initial_coefficients, P._sparse, constant=constant, degree=degree, - valuation=approximate_valuation) + order=approximate_order) return P.element_class(P, coeff_stream) return P.element_class(P, CoefficientStream_add(self._coeff_stream, other._coeff_stream)) @@ -724,16 +700,16 @@ def _sub_(self, other): if isinstance(right, CoefficientStream_zero): return self if (isinstance(left, CoefficientStream_exact) and isinstance(right, CoefficientStream_exact)): - approximate_valuation = min(left.valuation(), right.valuation()) + approximate_order = min(left.order(), right.order()) degree = max(left._degree, right._degree) - initial_coefficients = [left[i] - right[i] for i in range(approximate_valuation, degree)] + initial_coefficients = [left[i] - right[i] for i in range(approximate_order, degree)] constant = left._constant - right._constant if not any(initial_coefficients) and not constant: return P.zero() coeff_stream = CoefficientStream_exact(initial_coefficients, P._sparse, constant=constant, degree=degree, - valuation=approximate_valuation) + order=approximate_order) return P.element_class(P, coeff_stream) if left == right: return P.zero() @@ -793,11 +769,11 @@ def _lmul_(self, scalar): if isinstance(self._coeff_stream, CoefficientStream_exact): c = self._coeff_stream._constant * scalar - v = self._coeff_stream.valuation() + v = self._coeff_stream.order() init_coeffs = self._coeff_stream._initial_coefficients initial_coefficients = [val * scalar for val in init_coeffs] return P.element_class(P, CoefficientStream_exact(initial_coefficients, P._sparse, - valuation=v, constant=c, + order=v, constant=c, degree=self._coeff_stream._degree)) return P.element_class(P, CoefficientStream_lmul(self._coeff_stream, scalar)) @@ -855,11 +831,11 @@ def _rmul_(self, scalar): if isinstance(self._coeff_stream, CoefficientStream_exact): c = scalar * self._coeff_stream._constant - v = self._coeff_stream.valuation() + v = self._coeff_stream.order() init_coeffs = self._coeff_stream._initial_coefficients initial_coefficients = [scalar * val for val in init_coeffs] return P.element_class(P, CoefficientStream_exact(initial_coefficients, P._sparse, - valuation=v, constant=c, + order=v, constant=c, degree=self._coeff_stream._degree)) if P.base_ring().is_commutative(): return P.element_class(P, CoefficientStream_lmul(self._coeff_stream, scalar)) @@ -928,7 +904,7 @@ def _neg_(self): constant = -coeff_stream._constant coeff_stream = CoefficientStream_exact(initial_coefficients, P._sparse, constant=constant, - valuation=coeff_stream.valuation()) + order=coeff_stream.order()) return P.element_class(P, coeff_stream) # -(-f) = f if isinstance(coeff_stream, CoefficientStream_neg): @@ -936,22 +912,24 @@ def _neg_(self): return P.element_class(P, CoefficientStream_neg(coeff_stream)) -class LazyCauchyProductSeries(RingElement): - """ +class LazyCauchyProductSeries(LazyModuleElement): + r""" A class for series where multiplication is the Cauchy product. - """ - def __init__(self, parent): - """ - Initialize. - TESTS:: + EXAMPLES:: - sage: from sage.rings.lazy_laurent_series import LazyCauchyProductSeries - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: M = LazyCauchyProductSeries(L) - """ - RingElement.__init__(self, parent) + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: f = 1 / (1 - z) + sage: f + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + sage: f * (1 - z) + 1 + O(z^7) + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) + sage: f = 1 / (1 - z) + sage: f + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + """ def _mul_(self, other): """ Return the product of this series with ``other``. @@ -967,7 +945,7 @@ def _mul_(self, other): 1 - 2*z + z^2 sage: (1 - z)*(1 - z)*(1 - z) 1 - 3*z + 3*z^2 - z^3 - sage: M = L(lambda n: n) + sage: M = L(lambda n: n, valuation=0) sage: M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + ... sage: N = M * (1 - M) @@ -995,9 +973,9 @@ def _mul_(self, other): True sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + ... - sage: N = L(lambda n: 1); N + sage: N = L(lambda n: 1, valuation=0); N 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + ... sage: M * N z + 3*z^2 + 6*z^3 + 10*z^4 + 15*z^5 + 21*z^6 + ... @@ -1027,9 +1005,9 @@ def _mul_(self, other): # Check some trivial products if isinstance(left, CoefficientStream_zero) or isinstance(right, CoefficientStream_zero): return P.zero() - if isinstance(left, CoefficientStream_exact) and left._initial_coefficients == (P._coeff_ring.one(),) and left.valuation() == 0: + if isinstance(left, CoefficientStream_exact) and left._initial_coefficients == (P._coeff_ring.one(),) and left.order() == 0: return other # self == 1 - if isinstance(right, CoefficientStream_exact) and right._initial_coefficients == (P._coeff_ring.one(),) and right.valuation() == 0: + if isinstance(right, CoefficientStream_exact) and right._initial_coefficients == (P._coeff_ring.one(),) and right.order() == 0: return self # right == 1 # the product is exact if and only if both of the factors are @@ -1045,8 +1023,8 @@ def _mul_(self, other): for k in range(max(n - len(ir) + 1, 0), min(len(il) - 1, n) + 1)) for n in range(len(il) + len(ir) - 1)] - lv = left.valuation() - rv = right.valuation() + lv = left.order() + rv = right.order() # (a x^d q)/(1-x) has constant a q(1), and the initial # values are the cumulative sums of the coeffcients of q if right._constant: @@ -1068,7 +1046,7 @@ def _mul_(self, other): else: c = left._constant # this is zero coeff_stream = CoefficientStream_exact(initial_coefficients, P._sparse, - valuation=lv+rv, constant=c) + order=lv+rv, constant=c) return P.element_class(P, coeff_stream) return P.element_class(P, CoefficientStream_cauchy_product(left, right)) @@ -1084,7 +1062,7 @@ def __invert__(self): sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: ~(1 - z) 1 + z + z^2 + O(z^3) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: P = ~M; P z^-1 - 2 + z + O(z^6) @@ -1092,7 +1070,7 @@ def __invert__(self): Lazy Laurent series that have a sparse implementation can be inverted:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: P = ~M; P z^-1 - 2 + z + O(z^6) @@ -1124,25 +1102,25 @@ def __invert__(self): initial_coefficients = coeff_stream._initial_coefficients if not initial_coefficients: i = ~coeff_stream._constant - v = -coeff_stream.valuation() + v = -coeff_stream.order() c = P._coeff_ring.zero() coeff_stream = CoefficientStream_exact((i, -i), P._sparse, - valuation=v, constant=c) + order=v, constant=c) return P.element_class(P, coeff_stream) if len(initial_coefficients) == 1 and not coeff_stream._constant: i = ~initial_coefficients[0] - v = -coeff_stream.valuation() + v = -coeff_stream.order() c = P._coeff_ring.zero() coeff_stream = CoefficientStream_exact((i,), P._sparse, - valuation=v, constant=c) + order=v, constant=c) return P.element_class(P, coeff_stream) if (len(initial_coefficients) == 2 and not (initial_coefficients[0] + initial_coefficients[1]) and not coeff_stream._constant): - v = -coeff_stream.valuation() + v = -coeff_stream.order() c = ~initial_coefficients[0] coeff_stream = CoefficientStream_exact((), P._sparse, - valuation=v, constant=c) + order=v, constant=c) return P.element_class(P, coeff_stream) # (f^-1)^-1 = f @@ -1151,7 +1129,7 @@ def __invert__(self): return P.element_class(P, CoefficientStream_cauchy_inverse(coeff_stream)) -class LazyLaurentSeries(LazySequencesModuleElement, LazyCauchyProductSeries): +class LazyLaurentSeries(LazyCauchyProductSeries): r""" A Laurent series where the coefficients are computed lazily. @@ -1240,7 +1218,7 @@ def change_ring(self, ring): sage: t = s.change_ring(QQ) sage: t^-1 1/2 - 1/4*z + 1/8*z^2 - 1/16*z^3 + 1/32*z^4 - 1/64*z^5 + 1/128*z^6 + O(z^7) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: N = M.change_ring(QQ) sage: N.parent() @@ -1251,7 +1229,7 @@ def change_ring(self, ring): Sparse Implementation:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.parent() Lazy Laurent Series Ring in z over Integer Ring @@ -1334,7 +1312,7 @@ def __call__(self, g): sage: f(g) z^-9 + 3*z^-8 + 3*z^-7 + 2*z^-6 + 2*z^-5 + z^-4 - sage: f = L(lambda n: n); f + sage: f = L(lambda n: n, 0); f z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: f(z^2) z^2 + 2*z^4 + 3*z^6 + O(z^7) @@ -1396,7 +1374,7 @@ def __call__(self, g): sage: L. = LazyLaurentSeriesRing(QQ, sparse=True) sage: LS. = LazyLaurentSeriesRing(QQ, sparse=True) - sage: f = L(lambda n: 1); f + sage: f = L(lambda n: 1, 0); f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: f(y^2) 1 + y^2 + y^4 + y^6 + O(y^7) @@ -1430,7 +1408,7 @@ def __call__(self, g): We look at mixing the sparse and the dense:: sage: L. = LazyLaurentSeriesRing(QQ) - sage: f = L(lambda n: 1); f + sage: f = L(lambda n: 1, 0); f 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: g = LS(lambda n: 1, 1); g y + y^2 + y^3 + y^4 + y^5 + y^6 + y^7 + O(y^8) @@ -1495,12 +1473,12 @@ def __call__(self, g): if ((not isinstance(g, LazyLaurentSeries) and not g) or (isinstance(g, LazyLaurentSeries) and isinstance(g._coeff_stream, CoefficientStream_zero))): - if self._coeff_stream._approximate_valuation >= 0: + if self._coeff_stream._approximate_order >= 0: return P(self[0]) # Perhaps we just don't yet know if the valuation is non-negative - if any(self._coeff_stream[i] for i in range(self._coeff_stream._approximate_valuation, 0)): + if any(self._coeff_stream[i] for i in range(self._coeff_stream._approximate_order, 0)): raise ZeroDivisionError("the valuation of the series must be nonnegative") - self._coeff_stream._approximate_valuation = 0 + self._coeff_stream._approximate_order = 0 return P(self[0]) # f has finite length @@ -1528,7 +1506,7 @@ def __call__(self, g): initial_coefficients = [ret[i] for i in range(val, deg)] coeff_stream = CoefficientStream_exact(initial_coefficients, self._coeff_stream._is_sparse, constant=P.base_ring().zero(), - degree=deg, valuation=val) + degree=deg, order=val) return P.element_class(P, coeff_stream) # Return the sum since g is not known to be finite or we do not get a Laurent polynomial @@ -1565,10 +1543,10 @@ def __call__(self, g): except (TypeError, ValueError): raise NotImplementedError("can only compose with a lazy Laurent series") # Perhaps we just don't yet know if the valuation is positive - if g._coeff_stream._approximate_valuation <= 0: - if any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_valuation, 1)): + if g._coeff_stream._approximate_order <= 0: + if any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_order, 1)): raise ValueError("can only compose with a positive valuation series") - g._coeff_stream._approximate_valuation = 1 + g._coeff_stream._approximate_order = 1 return P.element_class(P, CoefficientStream_composition(self._coeff_stream, g._coeff_stream)) @@ -1587,9 +1565,9 @@ def _div_(self, other): sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: z/(1 - z) z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + O(z^8) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, 0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) - sage: N = L(lambda n: 1); N + sage: N = L(lambda n: 1, 0); N 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: P = M / N; P z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) @@ -1597,9 +1575,9 @@ def _div_(self, other): Lazy Laurent series that have a sparse implementation can be divided:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, 0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) - sage: N = L(lambda n: 1); N + sage: N = L(lambda n: 1, 0); N 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: P = M / N; P z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) @@ -1638,7 +1616,7 @@ def _div_(self, other): ret = P._laurent_poly_ring(ret) initial_coefficients = [ret[i] for i in range(ret.valuation(), ret.degree() + 1)] return P.element_class(P, CoefficientStream_exact(initial_coefficients, P._sparse, - valuation=ret.valuation(), constant=left._constant)) + order=ret.valuation(), constant=left._constant)) except (TypeError, ValueError, NotImplementedError): # We cannot divide the polynomials, so the result must be a series pass @@ -1667,7 +1645,7 @@ def __pow__(self, n): 1 - 3*z + 3*z^2 - z^3 sage: (1 - z)^-3 1 + 3*z + 6*z^2 + 10*z^3 + 15*z^4 + 21*z^5 + 28*z^6 + O(z^7) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M^2 z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) @@ -1682,7 +1660,7 @@ def __pow__(self, n): raised to the power ``n``:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) - sage: M = L(lambda n: n); M + sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M^2 z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) @@ -1710,7 +1688,7 @@ def __pow__(self, n): deg = ret.degree() + 1 initial_coefficients = [ret[i] for i in range(val, deg)] return P.element_class(P, CoefficientStream_exact(initial_coefficients, P._sparse, - constant=cs._constant, degree=deg, valuation=val)) + constant=cs._constant, degree=deg, order=val)) return generic_power(self, n) @@ -1860,7 +1838,7 @@ def _format_series(self, formatter, format_strings=False): R = P._laurent_poly_ring z = R.gen() cs = self._coeff_stream - v = cs._approximate_valuation + v = cs._approximate_order if format_strings: strformat = formatter else: @@ -1911,7 +1889,7 @@ def _repr_(self): sage: L(lambda x: x if x > 0 else 0, valuation=-10) O(z^-3) - sage: L(None) + sage: L(None, valuation=0) Uninitialized Lazy Laurent Series sage: L(0) 0 @@ -1958,7 +1936,7 @@ def _latex_(self): sage: latex(L(lambda x: x if x > 0 else 0, valuation=-10)) O(\frac{1}{z^{3}}) - sage: latex(L(None)) + sage: latex(L(None, valuation=0)) \text{\texttt{Undef}} sage: latex(L(0)) 0 @@ -2018,3 +1996,4 @@ def _unicode_art_(self): if isinstance(self._coeff_stream, CoefficientStream_uninitialized) and self._coeff_stream._target is None: return UnicodeArt('Uninitialized Lazy Laurent Series') return self._format_series(unicode_art, True) + diff --git a/src/sage/rings/lazy_laurent_series_ring.py b/src/sage/rings/lazy_laurent_series_ring.py index 6d7ee048d23..ec0f501b987 100644 --- a/src/sage/rings/lazy_laurent_series_ring.py +++ b/src/sage/rings/lazy_laurent_series_ring.py @@ -30,7 +30,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing -from sage.rings.lazy_laurent_series import LazyLaurentSeries +from sage.rings.lazy_laurent_series import LazyLaurentSeries, LazyModuleElement from sage.structure.global_options import GlobalOptions from sage.data_structures.coefficient_stream import ( @@ -128,7 +128,7 @@ class LazyLaurentSeriesRing(UniqueRepresentation, Parent): more examples):: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: s = L(None) + sage: s = L(None, valuation=0) sage: s.define(1 + z*s^2) sage: s 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) @@ -252,7 +252,7 @@ def gen(self, n=0): raise IndexError("there is only one generator") R = self.base_ring() coeff_stream = CoefficientStream_exact([R.one()], self._sparse, - constant=R.zero(), valuation=1) + constant=R.zero(), order=1) return self.element_class(self, coeff_stream) def ngens(self): @@ -334,6 +334,10 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No - ``constant`` -- (optional) the eventual constant of the series - ``degree`` -- (optional) the degree when the series is ``constant`` + If ``valuation`` is specified and ``x`` is convertable into a Laurent + polynomial or is a lazy Laurent series, then the data is shifted so + that the result has the specified valuation. + EXAMPLES:: sage: L = LazyLaurentSeriesRing(GF(2), 'z') @@ -342,7 +346,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: L(3) 1 - sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L. = LazyLaurentSeriesRing(ZZ) sage: L(lambda i: i, 5, 1, 10) 5*z^5 + 6*z^6 + 7*z^7 + 8*z^8 + 9*z^9 + z^10 + z^11 + z^12 + O(z^13) @@ -390,17 +394,37 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: g z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13) + Checking the valuation is consistant:: + + sage: L([0,0,2,3], valuation=-4) + 2*z^-4 + 3*z^-3 + sage: L(range(5), valuation=-4) + z^-4 + 2*z^-3 + 3*z^-2 + 4*z^-1 + sage: P. = ZZ[] + sage: L(x^2 + x^5, valuation=-4) + z^-4 + z^-1 + sage: L(1, valuation=-4) + z^-4 + sage: L(L(1), valuation=-4) + z^-4 + sage: L(1/(1-z), valuation=-4) + z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + sage: L(z^-3/(1-z), valuation=-4) + z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + sage: L(z^3/(1-z), valuation=-4) + z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + TESTS: This gives zero:: sage: L = LazyLaurentSeriesRing(ZZ, 'z') - sage: L(lambda n: 0, degree=3) + sage: L(lambda n: 0, degree=3, valuation=0) 0 This does not:: - sage: L(lambda n: 0, degree=3, constant=1) + sage: L(lambda n: 0, degree=3, constant=1, valuation=0) z^3 + z^4 + z^5 + O(z^6) This raises an error:: @@ -423,7 +447,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No """ if x is None: if valuation is None: - valuation = 0 + raise ValueError("the valuation must be specified") return self.element_class(self, CoefficientStream_uninitialized(self._sparse, valuation)) R = self._laurent_poly_ring @@ -448,22 +472,25 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No raise ValueError("you must specify the degree for the polynomial 0") degree = valuation if x == R.zero(): - coeff_stream = CoefficientStream_exact([x], self._sparse, valuation=degree-1, constant=constant) + coeff_stream = CoefficientStream_exact([x], self._sparse, order=degree-1, constant=constant) return self.element_class(self, coeff_stream) initial_coefficients = [x[i] for i in range(x.valuation(), x.degree() + 1)] coeff_stream = CoefficientStream_exact(initial_coefficients, self._sparse, - valuation=x.valuation(), constant=constant, degree=degree) + order=x.valuation(), constant=constant, degree=degree) return self.element_class(self, coeff_stream) - if isinstance(x, LazyLaurentSeries): - if x._coeff_stream._is_sparse is self._sparse: - return self.element_class(self, x._coeff_stream) - # TODO: Implement a way to make a self._sparse copy - raise NotImplementedError("cannot convert between sparse and dense") + if isinstance(x, LazyModuleElement): + if x._coeff_stream._is_sparse is not self._sparse: + # TODO: Implement a way to make a self._sparse copy + raise NotImplementedError("cannot convert between sparse and dense") + ret = self.element_class(self, x._coeff_stream) + if not valuation: + return ret + return self.gen()**(valuation-ret.valuation()) * ret if callable(x): if valuation is None: - valuation = 0 + raise ValueError("the valuation must be specified") if degree is None: if constant is not None: raise ValueError("constant may only be specified if the degree is specified") @@ -477,7 +504,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if not any(p) and not constant: coeff_stream = CoefficientStream_zero(self._sparse) else: - coeff_stream = CoefficientStream_exact(p, self._sparse, valuation=valuation, + coeff_stream = CoefficientStream_exact(p, self._sparse, order=valuation, constant=constant, degree=degree) return self.element_class(self, coeff_stream) @@ -495,7 +522,7 @@ def _an_element_(self): """ R = self.base_ring() coeff_stream = CoefficientStream_exact([R.an_element(), 3, 0, 2*R.an_element(), 1], - self._sparse, valuation=-2, constant=R.one()) + self._sparse, order=-2, constant=R.one()) return self.element_class(self, coeff_stream) def some_elements(self): @@ -675,7 +702,7 @@ def series(self, coefficient, valuation, constant=None, degree=None): constant = self.base_ring().zero() if degree is None: degree = valuation + len(coefficient) - coeff_stream = CoefficientStream_exact(coefficient, self._sparse, valuation=valuation, + coeff_stream = CoefficientStream_exact(coefficient, self._sparse, order=valuation, constant=constant, degree=degree) return self.element_class(self, coeff_stream) @@ -686,3 +713,4 @@ def series(self, coefficient, valuation, constant=None, degree=None): t = self(lambda n: coefficient(t, n), valuation=valuation, constant=constant, degree=degree) return t + From 651c3eddc75238ce607052adac7f2d2950cd6f7c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 11 Aug 2021 14:35:34 +0200 Subject: [PATCH 8/9] only get coefficient if necessary --- .../data_structures/coefficient_stream.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/coefficient_stream.py b/src/sage/data_structures/coefficient_stream.py index 572460fb10b..6be22bd7561 100644 --- a/src/sage/data_structures/coefficient_stream.py +++ b/src/sage/data_structures/coefficient_stream.py @@ -1490,7 +1490,9 @@ def get_coefficient(self, n): return self._ainv c = self._zero for k in range(v, n): - c += self[k] * self._series[n - v - k] + l = self[k] + if l: + c += l * self._series[n - v - k] return -c * self._ainv def iterate_coefficients(self): @@ -1515,9 +1517,13 @@ def iterate_coefficients(self): c = self._zero m = min(len(self._cache), n) for k in range(m): - c += self._cache[k] * self._series[n - v - k] + l = self._cache[k] + if l: + c += l * self._series[n - v - k] for k in range(v+m, v+n): - c += self[k] * self._series[n - k] + l = self[k] + if l: + c += l * self._series[n - k] yield -c * self._ainv @@ -1579,6 +1585,7 @@ def get_coefficient(self, n): sage: [g.get_coefficient(i) for i in range(-1, 3)] [1, 0, 1, 1] """ - return self._function(self._ring(self._series[n])) if self._series[n] else self._series[n] - - + c = self._series[n] + if c: + return self._function(self._ring(c)) + return c From b3327282df6f49d2367c5703a9b72433f5fe06ce Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 11 Aug 2021 20:26:15 +0200 Subject: [PATCH 9/9] fix and document series reversion --- src/sage/rings/lazy_laurent_series.py | 90 +++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/src/sage/rings/lazy_laurent_series.py b/src/sage/rings/lazy_laurent_series.py index 182f57fe3da..a395c91f257 100644 --- a/src/sage/rings/lazy_laurent_series.py +++ b/src/sage/rings/lazy_laurent_series.py @@ -2034,30 +2034,96 @@ def revert(self): r""" Return the compositional inverse of ``self``. + Given a Laurent Series `f`. the compositional inverse is a + Laurent Series `g` over the same base ring, such that + `(f \circ g)(z) = f(g(z)) = z`. + + The compositional inverse exists if and only if: + + - `val(f) = 1', or + + - `f = a + b z` with `a b \neq 0`, or + + - `f = a/z' with `a \neq 0` + EXAMPLES:: sage: L. = LazyLaurentSeriesRing(ZZ) sage: z.revert() z + O(z^8) sage: (1/z).revert() - z^-1 + O(z^6) + z^-1 sage: (z-z^2).revert() z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + TESTS:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: s = L(lambda n: 1 if n == 1 else 0, valuation=1); s + z + O(z^8) + sage: s.revert() + z + O(z^8) + + sage: (2+3*z).revert() + -2/3 + 1/3*z + + sage: s = L(lambda n: 2 if n == 0 else 3 if n == 1 else 0, valuation=0); s + 2 + 3*z + O(z^7) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: cannot determine whether the compositional inverse exists + + We look at some cases where the compositional inverse does not exist.: + + `f = 0`:: + + sage: L(0).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + sage: (z - z).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + `val(f) ! = 1 and f(0) * f(1) = 0`:: + + sage: (z^2).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: L(1).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + """ P = self.parent() - z = P.gen() - if self._coeff_stream._approximate_order == 1: - g = self - else: - g = self * z**(1 - self._coeff_stream._approximate_order) - f = P(None, valuation=1) - f.define(z/((g/z)(f))) - if self._coeff_stream._approximate_order == 1: - return f - else: - return f / z**(1 - self._coeff_stream._approximate_order) + if self.valuation() == 1: + z = P.gen() + g = P(None, valuation=1) + g.define(z/((self/z)(g))) + return g + if self.valuation() not in [-1, 0]: + raise ValueError("compositional inverse does not exist") + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, CoefficientStream_exact): + if (coeff_stream.order() == 0 + and coeff_stream._degree == 2): + a = coeff_stream[0] + b = coeff_stream[1] + coeff_stream = CoefficientStream_exact((-a/b, 1/b), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + if (coeff_stream.order() == -1 + and coeff_stream._degree == 0): + return self + raise ValueError("compositional inverse does not exist") + raise ValueError("cannot determine whether the compositional inverse exists") def approximate_series(self, prec, name=None): """