From 21098f04c0c07cb84865a19f4862f9b395b9f88c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Apr 2023 17:25:34 +0200 Subject: [PATCH 01/11] simplify bool and make uninitialized streams nonzero unless they are trivially zero --- src/sage/data_structures/stream.py | 15 +++++++++++++++ src/sage/rings/lazy_series.py | 28 ++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 4bffa84bfc9..c481df939eb 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1039,6 +1039,21 @@ def iterate_coefficients(self): yield self._target[n] n += 1 + def is_nonzero(self): + r""" + Return ``True`` if and only if this stream is known + to be nonzero. + + Uninitialized streams are considered to be non-zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: C = Stream_uninitialized(0) + sage: C.is_nonzero() + True + """ + return True class Stream_unary(Stream_inexact): r""" diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8b0b8aa1dff..30d0b74c05c 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1064,23 +1064,11 @@ def __bool__(self): """ if isinstance(self._coeff_stream, Stream_zero): return False - if isinstance(self._coeff_stream, Stream_exact): + if self._coeff_stream.is_nonzero(): return True - if isinstance(self._coeff_stream, Stream_uninitialized): - if self._coeff_stream._target is None: - return True - if isinstance(self._coeff_stream._target, Stream_zero): - return False - if isinstance(self._coeff_stream._target, Stream_exact): - return True - if self._coeff_stream._is_sparse: - cache = self._coeff_stream._cache - if any(cache[a] for a in cache): - return True - else: - if any(a for a in self._coeff_stream._cache): - return True + # TODO: the following might not be allowed, because it may + # step the iterator v = self._coeff_stream._approximate_order if self[v]: return True @@ -1110,7 +1098,7 @@ def define(self, s): sage: binomial(2000, 1000) / C[1000] 1001 - The Catalan numbers but with a valuation 1:: + The Catalan numbers but with a valuation `1`:: sage: B = L.undefined(valuation=1) sage: B.define(z + B^2) @@ -4662,6 +4650,14 @@ def __call__(self, *g, check=True): 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) + + Check that issue :trac:`35071` is fixed:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = z^2 + sage: g = L.undefined(valuation=1) + sage: g.define(z*f(f(g))) + """ fP = parent(self) if len(g) != fP._arity: From bbe2ddd606db0c0d89f5bd7ebe7d3aca9cc4927f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Apr 2023 17:37:43 +0200 Subject: [PATCH 02/11] add (currently failing) original doctest --- src/sage/combinat/species/recursive_species.py | 9 +++++++++ src/sage/rings/lazy_series.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/sage/combinat/species/recursive_species.py b/src/sage/combinat/species/recursive_species.py index a361bdfad20..09a0fb19ee1 100644 --- a/src/sage/combinat/species/recursive_species.py +++ b/src/sage/combinat/species/recursive_species.py @@ -401,6 +401,15 @@ def define(self, x): [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] sage: F.isotype_generating_series()[0:10] [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + + Check that issues :trac:`35071` is fixed:: + + sage: X = species.SingletonSpecies() + sage: E = species.SetSpecies(max=3) + sage: B = species.CombinatorialSpecies(min=1) + sage: B.define(X*E(B)) + sage: B.generating_series() + """ if not isinstance(x, GenericCombinatorialSpecies): raise TypeError("x must be a combinatorial species") diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 30d0b74c05c..1884def0351 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -4657,6 +4657,8 @@ def __call__(self, *g, check=True): sage: f = z^2 sage: g = L.undefined(valuation=1) sage: g.define(z*f(f(g))) + sage: g + O(z^8) """ fP = parent(self) From deff76a03e3338094331bb559b95e50c47863350 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 6 Apr 2023 19:07:30 +0200 Subject: [PATCH 03/11] make equality never raise an error, return False if undecidable --- .../combinat/species/recursive_species.py | 2 +- src/sage/data_structures/stream.py | 417 +----------------- src/sage/rings/lazy_series.py | 119 ++--- src/sage/rings/lazy_series_ring.py | 4 +- 4 files changed, 69 insertions(+), 473 deletions(-) diff --git a/src/sage/combinat/species/recursive_species.py b/src/sage/combinat/species/recursive_species.py index 09a0fb19ee1..d3c669e2162 100644 --- a/src/sage/combinat/species/recursive_species.py +++ b/src/sage/combinat/species/recursive_species.py @@ -409,7 +409,7 @@ def define(self, x): sage: B = species.CombinatorialSpecies(min=1) sage: B.define(X*E(B)) sage: B.generating_series() - + z + z^2 + 3/2*z^3 + 5/2*z^4 + 9/2*z^5 + 17/2*z^6 + 133/8*z^7 + O(z^8) """ if not isinstance(x, GenericCombinatorialSpecies): raise TypeError("x must be a combinatorial species") diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index c481df939eb..69b1ce9bcef 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -157,44 +157,18 @@ def _approximate_order(self): """ raise NotImplementedError - def __ne__(self, 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 - sage: CS = Stream(1) - sage: CS != CS - False - sage: CS != Stream(-2) - False - - """ - return False - - def is_nonzero(self): + def __bool__(self): r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - The default implementation is ``False``. + Return ``True`` if ``self`` is not known to be zero. EXAMPLES:: sage: from sage.data_structures.stream import Stream sage: CS = Stream(1) - sage: CS.is_nonzero() - False + sage: bool(CS) + True """ - return False + return True class Stream_inexact(Stream): @@ -232,25 +206,6 @@ def __init__(self, is_sparse, true_order): self._cache = list() self._iter = self.iterate_coefficients() - def is_nonzero(self): - r""" - Return ``True`` if and only if the cache contains a nonzero element. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: CS = Stream_function(lambda n: 1/n, False, 1) - sage: CS.is_nonzero() - False - sage: CS[1] - 1 - sage: CS.is_nonzero() - True - """ - if self._is_sparse: - return any(self._cache.values()) - return any(self._cache) - def __getstate__(self): """ Build the dictionary for pickling ``self``. @@ -454,107 +409,6 @@ def order(self): n += 1 return n - def __ne__(self, other): - """ - Return whether ``self`` and ``other`` are known to be different. - - Only the elements in the caches are considered. - - INPUT: - - - ``other`` -- a stream - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, True, 0) - sage: g = Stream_function(lambda n: n^2, True, 0) - sage: f != g - False - sage: f[1], g[1] - (1, 1) - sage: f != g - False - sage: f[3], g[4] - (3, 16) - sage: f != g - False - sage: f[2], g[2] - (2, 4) - sage: f != g - True - - Checking the dense implementation:: - - sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) - sage: g = Stream_function(lambda n: n^2, False, 0) - sage: f != g - False - sage: g != f - False - sage: _ = f[1], g[1] - sage: f != g - False - sage: g != f - False - sage: _ = f[2], g[2] - sage: f != g - True - sage: g != f - True - - sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) - sage: g = Stream_function(lambda n: n^2, False, 0) - sage: _ = f[5], g[1] - sage: f != g - False - sage: g != f - False - sage: _ = g[2] - sage: f != g - True - sage: g != f - True - - sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) - sage: g = Stream_function(lambda n: n^2, False, 0) - sage: _ = g[5], f[1] - sage: f != g - False - sage: g != f - False - sage: _ = f[2] - sage: f != g - 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 and other._is_sparse: - for i in self._cache: - if i in other._cache and other._cache[i] != self._cache[i]: - 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 - class Stream_exact(Stream): r""" @@ -798,81 +652,6 @@ def __eq__(self, other): and self._initial_coefficients == other._initial_coefficients and self._constant == other._constant) - def __ne__(self, other): - """ - 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: - - - ``other`` -- a stream - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], order=-1, degree=2, constant=1) - sage: t = Stream_exact([0, 2, 0], 1, 2, -2) - sage: s != t - False - sage: s = Stream_exact([2], constant=1) - sage: t = Stream_exact([2], order=-1, constant=1) - sage: s != t - True - - When it is not known, then both equality and inequality - return ``False``:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 2 if n == 0 else 1, False, 0) - sage: s == f - False - sage: s != f - False - sage: [s[i] for i in range(-3, 5)] - [0, 0, 0, 2, 1, 1, 1, 1] - sage: [f[i] for i in range(-3, 5)] - [0, 0, 0, 2, 1, 1, 1, 1] - - """ - if isinstance(other, type(self)): - return (self._degree != other._degree - or self._approximate_order != other._approximate_order - or self._initial_coefficients != other._initial_coefficients - or self._constant != other._constant) - # if other is not exact, we can at least compare with the - # elements in its cache - if other._is_sparse: - for i in other._cache: - if self[i] != other._cache[i]: - return True - else: - 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 - - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - An assumption of this class is that it is nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], order=-1, degree=2, constant=1) - sage: s.is_nonzero() - True - """ - return True - def _polynomial_part(self, R): """ Return the initial part of ``self`` as a Laurent polynomial in ``R``. @@ -1039,21 +818,6 @@ def iterate_coefficients(self): yield self._target[n] n += 1 - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - Uninitialized streams are considered to be non-zero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: C = Stream_uninitialized(0) - sage: C.is_nonzero() - True - """ - return True class Stream_unary(Stream_inexact): r""" @@ -1371,6 +1135,19 @@ def __hash__(self): """ return 0 + def __bool__(self): + r""" + Return ``True`` if ``self`` is not known to be zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_zero + sage: CS = Stream_zero() + sage: bool(CS) + False + """ + return False + ##################################################################### # Binary operations @@ -1566,25 +1343,13 @@ def get_coefficient(self, n): c += val * self._right[n-k] return c - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - EXAMPLES:: - sage: from sage.data_structures.stream import (Stream_function, - ....: Stream_cauchy_mul, Stream_cauchy_invert) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_cauchy_mul(f, f, True) - sage: g.is_nonzero() - False - sage: fi = Stream_cauchy_invert(f) - sage: h = Stream_cauchy_mul(fi, fi, True) - sage: h.is_nonzero() - True - """ - return self._left.is_nonzero() and self._right.is_nonzero() +class Stream_cauchy_mul_commutative(Stream_cauchy_mul, Stream_binaryCommutative): + """ + Operator for multiplication of two coefficient streams using the + Cauchy product for commutative multiplication of coefficients. + """ + pass class Stream_dirichlet_convolve(Stream_binary): @@ -2277,27 +2042,6 @@ def __eq__(self, other): return (isinstance(other, type(self)) and self._series == other._series and self._scalar == other._scalar) - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_rmul(f, 2, True) - sage: g.is_nonzero() - False - - sage: from sage.data_structures.stream import Stream_cauchy_invert - sage: fi = Stream_cauchy_invert(f) - sage: g = Stream_rmul(fi, 2, True) - sage: g.is_nonzero() - True - """ - return self._series.is_nonzero() - class Stream_rmul(Stream_scalar): """ @@ -2451,27 +2195,6 @@ def get_coefficient(self, n): """ return -self._series[n] - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_neg(f, True) - sage: g.is_nonzero() - False - - sage: from sage.data_structures.stream import Stream_cauchy_invert - sage: fi = Stream_cauchy_invert(f) - sage: g = Stream_neg(fi, True) - sage: g.is_nonzero() - True - """ - return self._series.is_nonzero() - class Stream_cauchy_invert(Stream_unary): """ @@ -2589,23 +2312,6 @@ def iterate_coefficients(self): c += l * self._series[n - k] yield -c * self._ainv - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - An assumption of this class is that it is nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, False, 1) - sage: g = Stream_cauchy_invert(f) - sage: g.is_nonzero() - True - """ - return True - class Stream_map_coefficients(Stream_inexact): r""" @@ -2842,23 +2548,6 @@ def __eq__(self, other): return (isinstance(other, type(self)) and self._shift == other._shift and self._series == other._series) - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - An assumption of this class is that it is nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, False, 1) - sage: g = Stream_cauchy_invert(f) - sage: g.is_nonzero() - True - """ - return self._series.is_nonzero() - class Stream_truncated(Stream_inexact): """ @@ -3066,48 +2755,6 @@ def order(self): # dense case return super().order() - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function, Stream_truncated - sage: def fun(n): return 1 if ZZ(n).is_power_of(2) else 0 - sage: f = Stream_function(fun, False, 0) - sage: [f[i] for i in range(0, 4)] - [0, 1, 1, 0] - sage: f._cache - [1, 1, 0] - sage: s = Stream_truncated(f, -5, 0) - sage: s.is_nonzero() - False - sage: [f[i] for i in range(7,10)] # updates the cache of s - [0, 1, 0] - sage: s.is_nonzero() - True - - sage: f = Stream_function(fun, True, 0) - sage: [f[i] for i in range(0, 4)] - [0, 1, 1, 0] - sage: f._cache - {1: 1, 2: 1, 3: 0} - sage: s = Stream_truncated(f, -5, 0) - sage: s.is_nonzero() - False - sage: [f[i] for i in range(7,10)] # updates the cache of s - [0, 1, 0] - sage: s.is_nonzero() - True - """ - if self._is_sparse: - return any(c for n, c in self._series._cache.items() - if n + self._shift >= self._approximate_order) - offset = self._series._approximate_order + self._shift - start = self._approximate_order - offset - return any(self._cache[start:]) - class Stream_derivative(Stream_inexact): """ @@ -3219,19 +2866,3 @@ def __eq__(self, other): """ return (isinstance(other, type(self)) and self._shift == other._shift and self._series == other._series) - - def is_nonzero(self): - r""" - Return ``True`` if and only if this stream is known - to be nonzero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_exact, Stream_derivative - sage: f = Stream_exact([1,2]) - sage: Stream_derivative(f, 1, True).is_nonzero() - True - sage: Stream_derivative(f, 2, True).is_nonzero() # it might be nice if this gave False - True - """ - return self._series.is_nonzero() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 1884def0351..7f233bb07d2 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -228,6 +228,7 @@ from sage.data_structures.stream import ( Stream_add, Stream_cauchy_mul, + Stream_cauchy_mul_commutative, Stream_sub, Stream_cauchy_compose, Stream_lmul, @@ -897,12 +898,12 @@ def _richcmp_(self, other, op): 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. + Equality is verified if the corresponding coefficients of + both series can be checked for equality without computing + coefficients indefinitely. This is the case if ``self`` and + ``other`` are trivially equal, either by construction, or + because they are known to have only finitely many + coefficients. INPUT: @@ -923,13 +924,9 @@ def _richcmp_(self, other, op): sage: fz = L(lambda n: 0, valuation=0) sage: L.zero() == fz - Traceback (most recent call last): - ... - ValueError: undecidable + False sage: fz == L.zero() - Traceback (most recent call last): - ... - ValueError: undecidable + False TESTS:: @@ -941,43 +938,20 @@ def _richcmp_(self, other, op): """ if op is op_EQ: - if isinstance(self._coeff_stream, Stream_zero): - if isinstance(other._coeff_stream, Stream_zero): - return True - if other._coeff_stream.is_nonzero(): - return False - elif isinstance(other._coeff_stream, Stream_zero): - if self._coeff_stream.is_nonzero(): - return False - elif isinstance(self._coeff_stream, Stream_exact): - if isinstance(other._coeff_stream, Stream_exact): - return self._coeff_stream == other._coeff_stream - if self._coeff_stream != other._coeff_stream: - return False - elif isinstance(other._coeff_stream, Stream_exact): - if other._coeff_stream != self._coeff_stream: - return False - else: - # both streams are inexact, perhaps they are equal by - # construction - if self._coeff_stream == other._coeff_stream: - return True - # perhaps their caches are different - if self._coeff_stream != other._coeff_stream: - return False - - # undecidable otherwise prec = self.parent().options['halting_precision'] if prec is None: - raise ValueError("undecidable") - # at least one of the approximate orders is not infinity + return self._coeff_stream == other._coeff_stream + + # they may be trivially equal + if self._coeff_stream == other._coeff_stream: + return True + # otherwise we check the first prec coefficients m = min(self._coeff_stream._approximate_order, other._coeff_stream._approximate_order) return all(self[i] == other[i] for i in range(m, m + prec)) if op is op_NE: return not (self == other) - return False def __hash__(self): @@ -998,11 +972,7 @@ def __hash__(self): def __bool__(self): """ - Test whether ``self`` is not zero. - - An uninitialized series returns ``True`` as it is considered - as a formal variable, such as a generator of a polynomial - ring. + Return ``True`` if ``self`` is not known to be zero. TESTS:: @@ -1019,9 +989,7 @@ def __bool__(self): sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M O(z^7) sage: bool(M) - Traceback (most recent call last): - ... - ValueError: undecidable as lazy Laurent series + True sage: M[15] 1 sage: bool(M) @@ -1031,9 +999,7 @@ def __bool__(self): sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M O(z^7) sage: bool(M) - Traceback (most recent call last): - ... - ValueError: undecidable as lazy Laurent series + True sage: M[15] 1 sage: bool(M) @@ -1062,21 +1028,7 @@ def __bool__(self): sage: bool(g) True """ - if isinstance(self._coeff_stream, Stream_zero): - return False - if self._coeff_stream.is_nonzero(): - return True - - # TODO: the following might not be allowed, because it may - # step the iterator - v = self._coeff_stream._approximate_order - if self[v]: - return True - - prec = self.parent().options['halting_precision'] - if prec is None: - raise ValueError("undecidable as lazy Laurent series") - return any(self[i] for i in range(v, v + prec)) + return bool(self._coeff_stream) def define(self, s): r""" @@ -1750,9 +1702,7 @@ def _acted_upon_(self, scalar, self_on_left): Different scalars potentially give different series:: sage: 2 * M == 3 * M - Traceback (most recent call last): - ... - ValueError: undecidable + False Sparse series can be multiplied with a scalar:: @@ -2855,7 +2805,11 @@ def _mul_(self, other): constant=c) return P.element_class(P, coeff_stream) - return P.element_class(P, Stream_cauchy_mul(left, right, P.is_sparse())) + if self.base_ring().is_commutative(): + coeff_stream = Stream_cauchy_mul_commutative(left, right, P.is_sparse()) + else: + coeff_stream = Stream_cauchy_mul(left, right, P.is_sparse()) + return P.element_class(P, coeff_stream) def __pow__(self, n): r""" @@ -3226,7 +3180,11 @@ def _div_(self, other): # P._minimal_valuation is zero, because we allow division by # series of positive valuation right_inverse = Stream_cauchy_invert(right) - return P.element_class(P, Stream_cauchy_mul(left, right_inverse, P.is_sparse())) + if self.base_ring().is_commutative(): + coeff_stream = Stream_cauchy_mul_commutative(left, right_inverse, P.is_sparse()) + else: + coeff_stream = Stream_cauchy_mul(left, right_inverse, P.is_sparse()) + return P.element_class(P, coeff_stream) def _floordiv_(self, other): @@ -3318,7 +3276,10 @@ def exp(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], False, 0) f = P.undefined(valuation=0) - d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) + if self.base_ring().is_commutative(): + d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) + else: + d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), False, 0) f._coeff_stream._target = int_d_self_f @@ -3367,9 +3328,15 @@ def log(self): # multivariate power series d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) - d_self_quo_self = Stream_cauchy_mul(d_self, - Stream_cauchy_invert(coeff_stream), - P.is_sparse()) + coeff_stream_inverse = Stream_cauchy_invert(coeff_stream) + if self.base_ring().is_commutative(): + d_self_quo_self = Stream_cauchy_mul_commutative(d_self, + coeff_stream_inverse, + P.is_sparse()) + else: + d_self_quo_self = Stream_cauchy_mul(d_self, + coeff_stream_inverse, + P.is_sparse()) int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), P.is_sparse(), 1) return P.element_class(P, int_d_self_quo_self) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 9f942c93251..5b93da917e2 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1117,9 +1117,7 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: f2 = f * 2 # currently no coefficients computed sage: f3 = f * 3 # currently no coefficients computed sage: f2 == f3 - Traceback (most recent call last): - ... - ValueError: undecidable + False sage: f2 # computes some of the coefficients of f2 2*z^-1 - 2 + 2*z - 2*z^2 + 2*z^3 - 2*z^4 + 2*z^5 + O(z^6) sage: f3 # computes some of the coefficients of f3 From afecec625e0ec24c66edb357c3082dc9218eb1d7 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 6 Apr 2023 19:24:40 +0200 Subject: [PATCH 04/11] correct check for commutativity when selecting Cauchy product --- src/sage/rings/lazy_series.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 7f233bb07d2..a0cb7ea084f 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -2805,7 +2805,7 @@ def _mul_(self, other): constant=c) return P.element_class(P, coeff_stream) - if self.base_ring().is_commutative(): + if P.is_commutative(): coeff_stream = Stream_cauchy_mul_commutative(left, right, P.is_sparse()) else: coeff_stream = Stream_cauchy_mul(left, right, P.is_sparse()) @@ -3180,7 +3180,7 @@ def _div_(self, other): # P._minimal_valuation is zero, because we allow division by # series of positive valuation right_inverse = Stream_cauchy_invert(right) - if self.base_ring().is_commutative(): + if P.is_commutative(): coeff_stream = Stream_cauchy_mul_commutative(left, right_inverse, P.is_sparse()) else: coeff_stream = Stream_cauchy_mul(left, right_inverse, P.is_sparse()) @@ -3276,7 +3276,7 @@ def exp(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], False, 0) f = P.undefined(valuation=0) - if self.base_ring().is_commutative(): + if P.is_commutative(): d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) else: d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) @@ -3329,7 +3329,7 @@ def log(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) coeff_stream_inverse = Stream_cauchy_invert(coeff_stream) - if self.base_ring().is_commutative(): + if P.is_commutative(): d_self_quo_self = Stream_cauchy_mul_commutative(d_self, coeff_stream_inverse, P.is_sparse()) From f4e3204f3e9649d0bc94b2af18cd4eac8a30566c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 6 Apr 2023 19:47:10 +0200 Subject: [PATCH 05/11] fix commutativity issue --- src/sage/rings/lazy_series.py | 11 ++++++----- src/sage/rings/lazy_series_ring.py | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index a0cb7ea084f..3cdbea2e2c1 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -219,6 +219,7 @@ from sage.combinat.partition import Partition, Partitions from sage.misc.derivative import derivative_parse from sage.categories.integral_domains import IntegralDomains +from sage.categories.rings import Rings from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ @@ -1802,7 +1803,7 @@ def _acted_upon_(self, scalar, self_on_left): order=v, constant=c, degree=coeff_stream._degree)) - if self_on_left or R.is_commutative(): + if self_on_left or R in Rings().Commutative(): return P.element_class(P, Stream_lmul(coeff_stream, scalar, P.is_sparse())) return P.element_class(P, Stream_rmul(coeff_stream, scalar, @@ -2805,7 +2806,7 @@ def _mul_(self, other): constant=c) return P.element_class(P, coeff_stream) - if P.is_commutative(): + if P in Rings().Commutative(): coeff_stream = Stream_cauchy_mul_commutative(left, right, P.is_sparse()) else: coeff_stream = Stream_cauchy_mul(left, right, P.is_sparse()) @@ -3180,7 +3181,7 @@ def _div_(self, other): # P._minimal_valuation is zero, because we allow division by # series of positive valuation right_inverse = Stream_cauchy_invert(right) - if P.is_commutative(): + if P in Rings().Commutative(): coeff_stream = Stream_cauchy_mul_commutative(left, right_inverse, P.is_sparse()) else: coeff_stream = Stream_cauchy_mul(left, right_inverse, P.is_sparse()) @@ -3276,7 +3277,7 @@ def exp(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], False, 0) f = P.undefined(valuation=0) - if P.is_commutative(): + if P in Rings().Commutative(): d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) else: d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) @@ -3329,7 +3330,7 @@ def log(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) coeff_stream_inverse = Stream_cauchy_invert(coeff_stream) - if P.is_commutative(): + if P in Rings().Commutative(): d_self_quo_self = Stream_cauchy_mul_commutative(d_self, coeff_stream_inverse, P.is_sparse()) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 5b93da917e2..02a496fdef1 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2260,10 +2260,10 @@ def __init__(self, basis, sparse=True, category=None): if basis not in GradedAlgebrasWithBasis: raise ValueError("basis should be in GradedAlgebrasWithBasis") self._arity = 1 - category = Algebras(base_ring.category()) - if base_ring in IntegralDomains(): + category = Algebras(basis.category()) + if basis in IntegralDomains(): category &= IntegralDomains() - elif base_ring in Rings().Commutative(): + elif basis in Rings().Commutative(): category = category.Commutative() if base_ring.is_zero(): From d3a70517b093f61968112e7afacc4d60221e7d3d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 7 Apr 2023 17:44:33 +0200 Subject: [PATCH 06/11] be slightly smarter with Cauchy multiplication --- src/sage/rings/lazy_series.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 3cdbea2e2c1..bd7965e7fbe 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -195,6 +195,19 @@ sage: s = SymmetricFunctions(GF(2)).s() sage: L = LazySymmetricFunctions(s) sage: check(L, lambda n: sum(k*s(la) for k, la in enumerate(Partitions(n))), valuation=0) + +Check that we can invert matrices:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: a11 = 1 + L(lambda n: 1 if not n else 0, valuation=0) + sage: a12 = 1 + L(lambda n: 1 if n == 1 else 0, valuation=0) + sage: a21 = 1 + L(lambda n: 1 if n == 2 else 0, valuation=0) + sage: a22 = 1 + L(lambda n: 1 if n == 3 else 0, valuation=0) + sage: m = matrix([[a11, a12], [a21, a22]]) + sage: m.inverse() + [ 1 + z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) -1 - 2*z - 3*z^2 - 4*z^3 - 5*z^4 - 6*z^5 - 7*z^6 + O(z^7)] + [ -1 - z - 3*z^2 - 3*z^3 - 5*z^4 - 5*z^5 - 7*z^6 + O(z^7) 2 + 2*z + 4*z^2 + 4*z^3 + 6*z^4 + 6*z^5 + 8*z^6 + O(z^7)] + """ # **************************************************************************** @@ -2761,6 +2774,9 @@ def _mul_(self, other): and right.order() == 0 and not right._constant): return self # right == 1 + if ((isinstance(left, Stream_cauchy_invert) and left._series == right) + or (isinstance(right, Stream_cauchy_invert) and right._series == left)): + return P.one() # The product is exact if and only if both factors are exact # and one of the factors has eventually 0 coefficients: # (p + a x^d/(1-x))(q + b x^e/(1-x)) @@ -3106,7 +3122,7 @@ def _div_(self, other): return self # self is right - if left is right: + if left == right: return P.one() if (P._minimal_valuation is not None From 541d751ca982c83d2a71a4ca0b1debc79c76524f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Apr 2023 11:15:44 +0200 Subject: [PATCH 07/11] remove useless non-commutative case --- src/sage/rings/lazy_series.py | 24 +++++++++++------------- src/sage/rings/lazy_series_ring.py | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index bd7965e7fbe..a30aaec283c 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -2959,7 +2959,7 @@ def __invert__(self): 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) + 1 sage: L. = LazyPowerSeriesRing(QQ) sage: ~(x + x^2) @@ -3293,10 +3293,9 @@ def exp(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], False, 0) f = P.undefined(valuation=0) - if P in Rings().Commutative(): - d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) - else: - d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) + # d_self and f._coeff_stream always commute, the coefficients + # of the product are of the form sum_{k=1}^n a_k a_{n+1-k}. + d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), False, 0) f._coeff_stream._target = int_d_self_f @@ -3346,14 +3345,10 @@ def log(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) coeff_stream_inverse = Stream_cauchy_invert(coeff_stream) - if P in Rings().Commutative(): - d_self_quo_self = Stream_cauchy_mul_commutative(d_self, - coeff_stream_inverse, - P.is_sparse()) - else: - d_self_quo_self = Stream_cauchy_mul(d_self, - coeff_stream_inverse, - P.is_sparse()) + # d_self and coeff_stream_inverse always commute + d_self_quo_self = Stream_cauchy_mul_commutative(d_self, + coeff_stream_inverse, + P.is_sparse()) int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), P.is_sparse(), 1) return P.element_class(P, int_d_self_quo_self) @@ -4644,6 +4639,9 @@ def __call__(self, *g, check=True): sage: g O(z^8) + sage: g = L(lambda n: n) + sage: f(g) + z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) """ fP = parent(self) if len(g) != fP._arity: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 02a496fdef1..7644fef465f 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -467,7 +467,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if valuation is None: raise ValueError("you must specify the degree for the polynomial 0") degree = valuation - if x == R.zero(): + if not x: coeff_stream = Stream_exact([], order=degree, constant=constant) return self.element_class(self, coeff_stream) initial_coefficients = [x[i] for i in range(x.valuation(), x.degree() + 1)] From 27198de8443f448dc9d5c4d7f1adfac65f71e138 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Apr 2023 12:31:45 +0200 Subject: [PATCH 08/11] polish, introduce equaltiy for Stream_function --- src/sage/data_structures/stream.py | 62 +++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 69b1ce9bcef..e194791410c 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -720,6 +720,11 @@ class Stream_function(Stream_inexact): - ``approximate_order`` -- integer; a lower bound for the order of the stream + .. WARNING:: + + We assume for equality that ``function`` is a function in the + mathematical sense. + EXAMPLES:: sage: from sage.data_structures.stream import Stream_function @@ -752,6 +757,43 @@ def __init__(self, function, is_sparse, approximate_order, true_order=False): super().__init__(is_sparse, true_order) self._approximate_order = approximate_order + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, False, 1) + sage: hash(f) == hash(g) + True + """ + # We don't hash the function as it might not be hashable. + return hash(type(self)) + + def __eq__(self, other): + """ + Return whether ``self`` and ``other`` are known to be equal. + + INPUT: + + - ``other`` -- a stream + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: fun = lambda n: n + sage: f = Stream_function(fun, True, 0) + sage: g = Stream_function(fun, False, 0) + sage: h = Stream_function(lambda n: n, False, 0) + sage: f == g + True + sage: f == h + False + """ + return isinstance(other, type(self)) and self.get_coefficient == other.get_coefficient + class Stream_uninitialized(Stream_inexact): r""" @@ -1959,6 +2001,11 @@ class Stream_scalar(Stream_inexact): Base class for operators multiplying a coefficient stream by a scalar. + INPUT: + + - ``series`` -- a :class:`Stream` + - ``scalar`` -- a non-zero, non-one scalar + .. TODO:: This does not inherit from :class:`Stream_unary`, because of @@ -2051,7 +2098,7 @@ class Stream_rmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a scalar + - ``scalar`` -- a non-zero, non-one scalar EXAMPLES:: @@ -2092,7 +2139,7 @@ class Stream_lmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a scalar + - ``scalar`` -- a non-zero, non-one scalar EXAMPLES:: @@ -2323,6 +2370,11 @@ class Stream_map_coefficients(Stream_inexact): - ``series`` -- a :class:`Stream` - ``function`` -- a function that modifies the elements of the stream + .. WARNING:: + + We assume for equality that ``function`` is a function in the + mathematical sense. + EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) @@ -2436,7 +2488,7 @@ def __eq__(self, other): class Stream_shift(Stream): """ - Operator for shifting a nonzero, nonexact stream. + Operator for shifting a non-zero, non-exact stream. Instances of this class share the cache with its input stream. @@ -2551,7 +2603,7 @@ def __eq__(self, other): class Stream_truncated(Stream_inexact): """ - Operator for shifting a nonzero, nonexact stream that has + Operator for shifting a non-zero, non-exact stream that has been shifted below its minimal valuation. Instances of this class share the cache with its input stream. @@ -2758,7 +2810,7 @@ def order(self): class Stream_derivative(Stream_inexact): """ - Operator for taking derivatives of a stream. + Operator for taking derivatives of a non-exact stream. INPUT: From 0b582ae308632ed3877a3976f948c2ccb47c1915 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Apr 2023 15:47:25 +0200 Subject: [PATCH 09/11] add doctest for codecov --- src/sage/rings/lazy_series.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index a30aaec283c..e6f181ffc8e 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1778,6 +1778,20 @@ def _acted_upon_(self, scalar, self_on_left): sage: 1 * M is M True + TESTS: + + Check that non-commutativity is taken into account:: + + sage: M = MatrixSpace(ZZ, 2) + sage: L. = LazyPowerSeriesRing(M) + sage: f = L(lambda n: matrix([[1,n],[0,1]])) + sage: m = matrix([[1,0],[1,1]]) + sage: (m * f - f * m)[1] + [-1 0] + [ 0 1] + sage: m * f[1] - f[1] * m + [-1 0] + [ 0 1] """ # With the current design, the coercion model does not have # enough information to detect a priori that this method only From 716d673a1f7984897a4b62bc1a0fd32a52786971 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 20 Aug 2023 18:01:14 +0200 Subject: [PATCH 10/11] unify docstrings and uncontroversial doctests from #35480 and #35485 --- .../combinat/species/recursive_species.py | 2 +- src/sage/data_structures/stream.py | 16 +++++----- src/sage/rings/lazy_series.py | 31 +++++++++++-------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/sage/combinat/species/recursive_species.py b/src/sage/combinat/species/recursive_species.py index d3c669e2162..8fd01494c1a 100644 --- a/src/sage/combinat/species/recursive_species.py +++ b/src/sage/combinat/species/recursive_species.py @@ -402,7 +402,7 @@ def define(self, x): sage: F.isotype_generating_series()[0:10] [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - Check that issues :trac:`35071` is fixed:: + Check that :issue:`35071` is fixed:: sage: X = species.SingletonSpecies() sage: E = species.SetSpecies(max=3) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index dfebcd3a577..233c37295f9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -280,7 +280,7 @@ def __setstate__(self, d): def __getitem__(self, n): """ - Return the `n`-th coefficient of ``self``. + Return the ``n``-th coefficient of ``self``. INPUT: @@ -381,7 +381,7 @@ def iterate_coefficients(self): def order(self): r""" Return the order of ``self``, which is the minimum index ``n`` such - that ``self[n]`` is nonzero. + that ``self[n]`` is non-zero. EXAMPLES:: @@ -506,7 +506,7 @@ def __init__(self, initial_coefficients, constant=None, degree=None, order=None) # complicated otherwise for i, v in enumerate(initial_coefficients): if v: - # We have found the first nonzero coefficient + # We have found the first non-zero coefficient order += i initial_coefficients = initial_coefficients[i:] if order + len(initial_coefficients) == self._degree: @@ -585,7 +585,7 @@ def __getitem__(self, n): def order(self): r""" Return the order of ``self``, which is the minimum index - ``n`` such that ``self[n]`` is nonzero. + ``n`` such that ``self[n]`` is non-zero. EXAMPLES:: @@ -1444,7 +1444,7 @@ def _approximate_order(self): or self._right._approximate_order <= 0): raise ValueError("Dirichlet convolution is only defined for " "coefficient streams with minimal index of " - "nonzero coefficient at least 1") + "non-zero coefficient at least 1") return self._left._approximate_order * self._right._approximate_order def get_coefficient(self, n): @@ -2364,7 +2364,7 @@ def iterate_coefficients(self): class Stream_map_coefficients(Stream_inexact): r""" - The stream with ``function`` applied to each nonzero coefficient + The stream with ``function`` applied to each non-zero coefficient of ``series``. INPUT: @@ -2536,7 +2536,7 @@ def _approximate_order(self): def order(self): r""" Return the order of ``self``, which is the minimum index - ``n`` such that ``self[n]`` is nonzero. + ``n`` such that ``self[n]`` is non-zero. EXAMPLES:: @@ -2766,7 +2766,7 @@ def __eq__(self, other): def order(self): """ Return the order of ``self``, which is the minimum index ``n`` such - that ``self[n]`` is nonzero. + that ``self[n]`` is non-zero. EXAMPLES:: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index e81bb2fedb0..de9586d7607 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1293,6 +1293,24 @@ def define(self, s): sage: (f*s[1]).revert() + 1 - f # optional - sage.combinat O^7 + Undefined series inside of another series (see :issue:`35071`):: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = z^2 + sage: b = L.undefined(valuation=1) + sage: b.define(z*f(f(b))) + sage: b + O(z^8) + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: f = L.undefined() + sage: f.define(L(lambda n: 0 if not n else sigma(f[n-1]+1))) + sage: f + x + 3*x^2 + 7*x^3 + 15*x^4 + 31*x^5 + 63*x^6 + O(x^7) + sage: f = L.undefined() + sage: f.define((1/(1-L(lambda n: 0 if not n else sigma(f[n-1]+1))))) + sage: f + 1 + 3*x + 16*x^2 + 87*x^3 + 607*x^4 + 4518*x^5 + 30549*x^6 + O(x^7) """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") @@ -4659,19 +4677,6 @@ def __call__(self, *g, check=True): 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) - - Check that issue :trac:`35071` is fixed:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: f = z^2 - sage: g = L.undefined(valuation=1) - sage: g.define(z*f(f(g))) - sage: g - O(z^8) - - sage: g = L(lambda n: n) - sage: f(g) - z^2 + 4*z^3 + 10*z^4 + 20*z^5 + 35*z^6 + O(z^7) """ fP = parent(self) if len(g) != fP._arity: From b8d27dae9a5d275f6ad679fc1fabb93cf815d1d9 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 20 Aug 2023 18:52:17 +0200 Subject: [PATCH 11/11] remove wrong __bool__, further consolidate with #35485 --- src/sage/rings/lazy_series.py | 84 +++++++++-------------------------- 1 file changed, 22 insertions(+), 62 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index de9586d7607..dd3459eb6e8 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -988,66 +988,6 @@ def __hash__(self): """ return hash(self._coeff_stream) - def __bool__(self): - """ - Return ``True`` if ``self`` is not known to be zero. - - TESTS:: - - sage: L. = LazyLaurentSeriesRing(GF(2)) # optional - sage.rings.finite_rings - sage: bool(z - z) # optional - sage.rings.finite_rings - False - sage: f = 1/(1 - z) # optional - sage.rings.finite_rings - sage: bool(f) # optional - sage.rings.finite_rings - True - sage: M = L(lambda n: n, valuation=0); M # optional - sage.rings.finite_rings - z + z^3 + z^5 + O(z^7) - sage: M.is_zero() # optional - sage.rings.finite_rings - False - sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M # optional - sage.rings.finite_rings - O(z^7) - sage: bool(M) # optional - sage.rings.finite_rings - True - sage: M[15] # optional - sage.rings.finite_rings - 1 - sage: bool(M) # optional - sage.rings.finite_rings - True - - sage: L. = LazyLaurentSeriesRing(GF(2), sparse=True) # optional - sage.rings.finite_rings - sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M # optional - sage.rings.finite_rings - O(z^7) - sage: bool(M) - True - sage: M[15] # optional - sage.rings.finite_rings - 1 - sage: bool(M) # optional - sage.rings.finite_rings - True - - Uninitialized series:: - - sage: g = L.undefined(valuation=0) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - True - sage: g.define(0) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - False - - sage: g = L.undefined(valuation=0) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - True - sage: g.define(1 + z) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - True - - sage: g = L.undefined(valuation=0) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - True - sage: g.define(1 + z*g) # optional - sage.rings.finite_rings - sage: bool(g) # optional - sage.rings.finite_rings - True - """ - return bool(self._coeff_stream) - def define(self, s): r""" Define an equation by ``self = s``. @@ -3147,8 +3087,28 @@ def _div_(self, other): sage: f / f s[] + Dividing when the coefficient ring is a lazy Dirichlet ring:: + + sage: D = LazyDirichletSeriesRing(QQ, "s") + sage: zeta = D(constant=1) + sage: L. = LazyLaurentSeriesRing(D) + sage: 1 / (1 - t*zeta) + (1 + O(1/(8^s))) + + (1 + 1/(2^s) + 1/(3^s) + 1/(4^s) + 1/(5^s) + 1/(6^s) + 1/(7^s) + O(1/(8^s)))*t + + ... + O(t^7) + + Check for dividing by other type of `0` series:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L(lambda n: 0, valuation=0) + sage: L.options.halting_precision = 20 + sage: 1 / f + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by 0 + sage: L.options._reset() """ - if isinstance(other._coeff_stream, Stream_zero): + if not other: raise ZeroDivisionError("cannot divide by 0") P = self.parent() @@ -3271,7 +3231,7 @@ def _floordiv_(self, other): sage: g // f 1 + x + 3*x^3 + x^4 + 6*x^5 + 5*x^6 + O(x^7) """ - if isinstance(other._coeff_stream, Stream_zero): + if not other: raise ZeroDivisionError("cannot divide by 0") P = self.parent() if P not in IntegralDomains():