diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 002e925f02f..3b03346f59b 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -122,13 +122,13 @@ class Stream(): If an approximate order or even the true order is known, it must be set after calling ``super().__init__``. - Otherwise, a lazy attribute `_approximate_order` has to be + Otherwise, a lazy attribute ``_approximate_order`` has to be defined. Any initialization code depending on the approximate orders of input streams can be put into this definition. However, keep in mind that (trivially) this initialization - code is not executed if `_approximate_order` is set to a + code is not executed if ``_approximate_order`` is set to a value before it is accessed. """ @@ -159,11 +159,15 @@ def _approximate_order(self): def __ne__(self, other): """ - Check inequality of ``self`` and ``other``. + Return whether ``self`` and ``other`` are known to be different. The default is to always return ``False`` as it usually cannot be decided whether they are equal. + INPUT: + + - ``other`` -- a stream + EXAMPLES:: sage: from sage.data_structures.stream import Stream @@ -172,6 +176,7 @@ def __ne__(self, other): False sage: CS != Stream(-2) False + """ return False @@ -204,14 +209,7 @@ class Stream_inexact(Stream): - ``is_sparse`` -- boolean; whether the implementation of the stream is sparse - ``true_order`` -- boolean; if the approximate order is the actual order - .. TODO:: - - The ``approximate_order`` is currently only updated when - invoking :meth:`order`. It might make sense to update it - whenever the coefficient one larger than the current - ``approximate_order`` is computed, since in some methods this - will allow shortcuts. - + If the cache is dense, it begins with the first non-zero term. """ def __init__(self, is_sparse, true_order): """ @@ -235,27 +233,6 @@ def __init__(self, is_sparse, true_order): self._cache = list() self._iter = self.iterate_coefficients() - @lazy_attribute - def _offset(self): - """ - Return the offset of a stream with a dense cache. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, False, -3) - sage: f._offset - -3 - sage: [f[i] for i in range(-3, 5)] - [-3, -2, -1, 0, 1, 2, 3, 4] - sage: f._cache - [-3, -2, -1, 0, 1, 2, 3, 4] - """ - # self[n] = self._cache[n-self._offset] - if self._is_sparse: - raise ValueError("_offset is only for dense streams") - return self._approximate_order - def is_nonzero(self): r""" Return ``True`` if and only if the cache contains a nonzero element. @@ -364,38 +341,67 @@ def __getitem__(self, n): sage: [f[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] sage: f._cache - {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} + {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} sage: f = Stream_function(lambda n: n^2, False, 0) sage: f[3] 9 sage: f._cache - [0, 1, 4, 9] + [1, 4, 9] sage: [f[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] sage: f._cache - [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + [1, 4, 9, 16, 25, 36, 49, 64, 81] """ if n < self._approximate_order: return ZZ.zero() if self._is_sparse: try: - c = self._cache[n] + return self._cache[n] except KeyError: - c = self.get_coefficient(n) + pass + + c = self.get_coefficient(n) + if self._true_order or n > self._approximate_order: self._cache[n] = c - else: - i = n - self._offset - if i >= len(self._cache): - a = len(self._cache) + self._offset - # It is important to extend by generator: - # self._iter might recurse, and thereby extend the - # cache itself, too. - self._cache.extend(next(self._iter) for _ in range(a, n+1)) - c = self._cache[i] + return c - return c + if c: + self._true_order = True + self._cache[n] = c + return c + + # self._approximate_order is not in self._cache if + # self._true_order is False + ao = self._approximate_order + 1 + while ao in self._cache: + if self._cache[ao]: + self._true_order = True + break + ao += 1 + self._approximate_order = ao + return c + + # Dense implementation + while not self._true_order and n >= self._approximate_order: + c = next(self._iter) + if c: + self._true_order = True + self._cache.append(c) + else: + self._approximate_order += 1 + + if self._true_order: + # It is important to extend by generator: + # self._iter might recurse, and thereby extend the + # cache itself, too. + i = n - self._approximate_order + self._cache.extend(next(self._iter) + for _ in range(i - len(self._cache) + 1)) + return self._cache[i] + + return ZZ.zero() def iterate_coefficients(self): """ @@ -427,48 +433,37 @@ def order(self): sage: f = Stream_function(lambda n: n, True, 0) sage: f.order() 1 + + TESTS:: + + sage: f = Stream_function(lambda n: n*(n+1), False, -1) + sage: f.order() + 1 + sage: f._true_order + True + + sage: f = Stream_function(lambda n: n*(n+1), True, -1) + sage: f.order() + 1 + sage: f._true_order + True """ if self._true_order: return self._approximate_order - if self._is_sparse: - n = self._approximate_order - cache = self._cache - while True: - if n in cache: - if cache[n]: - self._approximate_order = n - self._true_order = True - return n - n += 1 - else: - if self[n]: - self._approximate_order = n - self._true_order = True - return n - n += 1 - else: - n = self._approximate_order - cache = self._cache - while True: - if n - self._offset < len(cache): - if cache[n - self._offset]: - self._approximate_order = n - self._true_order = True - return n - n += 1 - else: - if self[n]: - self._approximate_order = n - self._true_order = True - return n - n += 1 + n = self._approximate_order + while not self[n]: + n += 1 + return n def __ne__(self, other): """ - Check inequality of ``self`` and ``other``. + Return whether ``self`` and ``other`` are known to be different. + + Only the elements in the caches are considered. - Check if there are any differences in the caches to see if they - are known to be not equal. + INPUT: + + - ``other`` -- a stream EXAMPLES:: @@ -534,31 +529,30 @@ def __ne__(self, other): True sage: g != f True + """ + # TODO: more cases, in particular mixed implementations, + # could be detected if not isinstance(other, Stream_inexact): return False - if self._is_sparse: + if self._is_sparse and other._is_sparse: for i in self._cache: if i in other._cache and other._cache[i] != self._cache[i]: return True - else: # they are dense - # Make ``self`` have the smaller approximate order. - if self._approximate_order > other._approximate_order: - self, other = other, self - saorder = self._approximate_order - soffset = self._offset - oaorder = other._approximate_order - ooffset = other._offset - end = min(oaorder, soffset + len(self._cache)) - for i in range(saorder, end): - if self._cache[i-soffset]: - return True - # now check all known values - end = min(soffset + len(self._cache), ooffset + len(other._cache)) - for i in range(oaorder, end): - if self._cache[i-soffset] != other._cache[i-ooffset]: - return True + + elif not self._is_sparse and not other._is_sparse: + if ((self._true_order + and other._approximate_order > self._approximate_order) + or (other._true_order + and self._approximate_order > other._approximate_order)): + return True + + if not self._true_order or not other._true_order: + return False + + if any(i != j for i, j in zip(self._cache, other._cache)): + return True return False @@ -763,7 +757,9 @@ def __hash__(self): def __eq__(self, other): """ - Test the equality between ``self`` and ``other``. + Return whether ``self`` and ``other`` are known to be equal. + + If ``other`` is also exact, equality is computable. INPUT: @@ -795,6 +791,7 @@ def __eq__(self, other): sage: t = Stream_exact([2], order=-1, degree=5, constant=1) sage: s == t False + """ return (isinstance(other, type(self)) and self._degree == other._degree @@ -804,8 +801,10 @@ def __eq__(self, other): def __ne__(self, other): """ - Test inequality between ``self`` and ``other``, where - other is exact or inexact, but not zero. + Return whether ``self`` and ``other`` are known to be different. + + The argument ``other`` may be exact or inexact, but is + assumed to be non-zero. INPUT: @@ -850,9 +849,12 @@ def __ne__(self, other): if self[i] != other._cache[i]: return True else: - if other._offset > self._approximate_order: - return False - return any(self[i] != c for i, c in enumerate(other._cache, other._offset)) + if other._true_order: + return any(self[i] != c + for i, c in enumerate(other._cache, + other._approximate_order)) + if other._approximate_order > self._approximate_order: + return True return False @@ -1092,11 +1094,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -1176,11 +1178,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a :class:`Stream` of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -1239,11 +1241,11 @@ def __hash__(self): def __eq__(self, other): """ - Test the equality between ``self`` and ``other``. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a :class:`Stream` of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -1328,7 +1330,11 @@ def order(self): def __eq__(self, other): """ - Check equality of ``self`` and ``other``. + Return whether ``self`` and ``other`` are known to be equal. + + INPUT: + + - ``other`` -- a stream EXAMPLES:: @@ -1853,16 +1859,19 @@ def get_coefficient(self, n): fv = self._left._approximate_order gv = self._right._approximate_order if n < 0: - return sum(self._left[i] * self._neg_powers[-i][n] - for i in range(fv, n // gv + 1)) + return sum(f_coeff_i * self._neg_powers[-i][n] + for i in range(fv, n // gv + 1) + if (f_coeff_i := self._left[i])) # n > 0 while len(self._pos_powers) <= n // gv: # TODO: possibly we always want a dense cache here? self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right, self._is_sparse)) - ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(fv, 0)) + ret = sum(f_coeff_i * self._neg_powers[-i][n] for i in range(fv, 0) + if (f_coeff_i := self._left[i])) if n == 0: ret += self._left[0] - return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // gv+1)) + return ret + sum(f_coeff_i * self._pos_powers[i][n] for i in range(1, n // gv + 1) + if (f_coeff_i := self._left[i])) class Stream_plethysm(Stream_binary): @@ -2230,11 +2239,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -2685,11 +2694,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -2800,11 +2809,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a stream EXAMPLES:: @@ -2929,11 +2938,11 @@ def __hash__(self): def __eq__(self, other): """ - Test equality. + Return whether ``self`` and ``other`` are known to be equal. INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a stream EXAMPLES:: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 2e614e225ea..8a9f6f19687 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -39,7 +39,7 @@ sage: s.coefficient(10) 10 sage: s._coeff_stream._cache - {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 10: 10} + {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 10: 10} Using the dense implementation, all coefficients up to the required coefficient are computed. :: @@ -50,7 +50,7 @@ sage: s.coefficient(10) 10 sage: s._coeff_stream._cache - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] We can do arithmetic with lazy power series:: @@ -94,7 +94,7 @@ sage: h.parent() Lazy Laurent Series Ring in z over Rational Field sage: h - 4*z + 6*z^2 + 8*z^3 + 19*z^4 + 38*z^5 + 71*z^6 + O(z^7) + 4*z + 6*z^2 + 8*z^3 + 19*z^4 + 38*z^5 + 71*z^6 + 130*z^7 + O(z^8) sage: hinv = h^-1; hinv 1/4*z^-1 - 3/8 + 1/16*z - 17/32*z^2 + 5/64*z^3 - 29/128*z^4 + 165/256*z^5 + O(z^6) sage: hinv.valuation() @@ -366,7 +366,7 @@ def __getitem__(self, n): sage: f[:3] [] sage: f._coeff_stream._cache - {1: 0, 2: 0} + {} """ R = self.parent()._internal_poly_ring.base_ring() coeff_stream = self._coeff_stream @@ -489,7 +489,7 @@ def map_coefficients(self, f): 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.map_coefficients(lambda c: c + 1) - 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + 8*z^7 + O(z^8) Similarly for Dirichlet series:: @@ -497,7 +497,7 @@ def map_coefficients(self, f): 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 + O(1/(8^z)) sage: s.map_coefficients(lambda c: c + 1) - 2/2^z + 3/3^z + 4/4^z + 5/5^z + 6/6^z + 7/7^z + O(1/(8^z)) + 2/2^z + 3/3^z + 4/4^z + 5/5^z + 6/6^z + 7/7^z + 8/8^z + O(1/(9^z)) Similarly for multivariate power series:: @@ -540,7 +540,7 @@ def map_coefficients(self, f): 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.map_coefficients(lambda c: c + 1) - 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + 8*z^7 + O(z^8) Test the zero series:: @@ -2620,7 +2620,7 @@ def _mul_(self, other): z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: N = M * (1 - M) sage: N - z + z^2 - z^3 - 6*z^4 - 15*z^5 - 29*z^6 + O(z^7) + z + z^2 - z^3 - 6*z^4 - 15*z^5 - 29*z^6 - 49*z^7 + O(z^8) sage: p = (1 - z)*(1 + z^2)^3 * z^-2 sage: p @@ -2648,7 +2648,7 @@ def _mul_(self, other): sage: N = L(lambda n: 1, valuation=0); N 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) sage: M * N - z + 3*z^2 + 6*z^3 + 10*z^4 + 15*z^5 + 21*z^6 + O(z^7) + z + 3*z^2 + 6*z^3 + 10*z^4 + 15*z^5 + 21*z^6 + 28*z^7 + O(z^8) sage: L.one() * M is M True @@ -2765,7 +2765,7 @@ def __pow__(self, n): 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) + z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + 56*z^7 + 84*z^8 + O(z^9) We can create a really large power of a monomial, even with the dense implementation:: @@ -2780,7 +2780,7 @@ def __pow__(self, n): 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) + z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + 56*z^7 + 84*z^8 + O(z^9) Lazy Laurent series that are known to be exact can be raised to the power ``n``:: @@ -2947,7 +2947,7 @@ def _div_(self, other): 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) + z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + O(z^8) Lazy Laurent series that have a sparse implementation can be divided:: @@ -2957,7 +2957,7 @@ def _div_(self, other): 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) + z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + O(z^8) If the division of exact Lazy Laurent series yields a Laurent polynomial, it is represented as an exact series:: @@ -3455,7 +3455,7 @@ def __call__(self, g, *, check=True): sage: f = L(lambda n: n, valuation=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) + z^2 + 2*z^4 + 3*z^6 + 4*z^8 + O(z^9) sage: f = L(lambda n: n, valuation=-2); f -2*z^-2 - z^-1 + z + 2*z^2 + 3*z^3 + 4*z^4 + O(z^5) @@ -3499,7 +3499,7 @@ def __call__(self, g, *, check=True): sage: f(0) 0 sage: f(y) - y + 2*y^2 + 3*y^3 + 4*y^4 + 5*y^5 + 6*y^6 + O(y^7) + y + 2*y^2 + 3*y^3 + 4*y^4 + 5*y^5 + 6*y^6 + 7*y^7 + O(y^8) sage: fp = f(y - y) sage: fp == 0 True @@ -4405,7 +4405,7 @@ def __call__(self, *g, check=True): sage: fg = 1 / (1 - g - g*g); fg 1 + 1/(2^s) + 1/(3^s) + 3/4^s + 1/(5^s) + 5/6^s + 1/(7^s) + O(1/(8^s)) sage: fog - fg - O(1/(7^s)) + O(1/(8^s)) sage: f = 1 / (1 - 2*a) sage: f(g) @@ -4507,6 +4507,21 @@ def __call__(self, *g, check=True): sage: T(1-x-2*y + x*y^2)(1/(1-a), 3) 3 + 8*a + 8*a^2 + 8*a^3 + 8*a^4 + 8*a^5 + 8*a^6 + O(a,b)^7 + Check that issue :trac:`35261` is fixed:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: fun = lambda n: 1 if ZZ(n).is_power_of(2) else 0 + sage: f = L(fun) + sage: g = L.undefined(valuation=1) + sage: g.define((~f.shift(-1)(g)).shift(1)) + sage: g + z - z^2 + 2*z^3 - 6*z^4 + 20*z^5 - 70*z^6 + 256*z^7 + O(z^8) + + sage: f = L(fun) + sage: g = L.undefined(valuation=1) + sage: g.define((z - (f - z)(g))) + sage: g + z - z^2 + 2*z^3 - 6*z^4 + 20*z^5 - 70*z^6 + 256*z^7 + O(z^8) """ fP = parent(self) if len(g) != fP._arity: @@ -4712,6 +4727,15 @@ def revert(self): sage: f = L([-1], valuation=1, degree=3, constant=-1) sage: f.revert() (-z) + z^3 + (-z^4) + (-2*z^5) + 6*z^6 + z^7 + O(z^8) + + Check that issue :trac:`35261` is fixed:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L(lambda n: 1 if ZZ(n).is_power_of(2) else 0) + sage: f + z + z^2 + z^4 + O(z^7) + sage: f.revert() + z - z^2 + 2*z^3 - 6*z^4 + 20*z^5 - 70*z^6 + 256*z^7 + O(z^8) """ P = self.parent() if P._arity != 1: @@ -5499,7 +5523,7 @@ def revert(self): sage: L = LazySymmetricFunctions(h) sage: f = L(lambda n: h[n]) - 1 sage: f(f.revert()) - h[1] + O^7 + h[1] + O^8 TESTS:: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index b8613ba2b9c..9f942c93251 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -293,7 +293,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: L.has_coerce_map_from(R) True sage: L(R(lambda n: n)) - z + z^3 + z^5 + O(z^7) + z + z^3 + z^5 + z^7 + O(z^8) sage: L(R([2,4,6])) == L.zero() True sage: L(R([2,4,6], valuation=2, constant=4)) == L.zero()