diff --git a/src/sage/combinat/species/recursive_species.py b/src/sage/combinat/species/recursive_species.py index a361bdfad20..8fd01494c1a 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 :issue:`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() + 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 87b5b1e7ecb..8cd49565fa8 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -160,44 +160,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): @@ -235,25 +209,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``. @@ -326,7 +281,7 @@ def __setstate__(self, d): def __getitem__(self, n): """ - Return the `n`-th coefficient of ``self``. + Return the ``n``-th coefficient of ``self``. INPUT: @@ -427,7 +382,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:: @@ -457,107 +412,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""" @@ -653,7 +507,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: @@ -732,7 +586,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:: @@ -801,81 +655,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``. @@ -944,6 +723,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 @@ -976,6 +760,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""" @@ -1359,6 +1180,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 @@ -1551,25 +1385,13 @@ def get_coefficient(self, n): n - self._right._approximate_order + 1) if (l := self._left[k])) - 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): @@ -1620,7 +1442,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): @@ -2212,6 +2034,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 @@ -2295,27 +2122,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): """ @@ -2325,7 +2131,7 @@ class Stream_rmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a non-zero scalar + - ``scalar`` -- a non-zero, non-one scalar EXAMPLES:: @@ -2367,7 +2173,7 @@ class Stream_lmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a non-zero scalar + - ``scalar`` -- a non-zero, non-one scalar EXAMPLES:: @@ -2471,27 +2277,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): """ @@ -2610,27 +2395,10 @@ 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""" - The stream with ``function`` applied to each nonzero coefficient + The stream with ``function`` applied to each non-zero coefficient of ``series``. INPUT: @@ -2638,6 +2406,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) @@ -2751,7 +2524,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. @@ -2797,7 +2570,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:: @@ -2864,27 +2637,10 @@ def __eq__(self, other): 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): """ - 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. @@ -3045,7 +2801,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:: @@ -3088,52 +2844,10 @@ 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): """ - Operator for taking derivatives of a stream. + Operator for taking derivatives of a non-exact stream. INPUT: @@ -3242,19 +2956,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 3278c8eed7c..616ec163c58 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -196,6 +196,19 @@ sage: L = LazySymmetricFunctions(s) # optional - sage.combinat sage.rings.finite_rings sage: check(L, lambda n: sum(k*s(la) for k, la in enumerate(Partitions(n))), # optional - sage.combinat sage.rings.finite_rings ....: 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)] + """ # **************************************************************************** @@ -219,6 +232,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 @@ -228,6 +242,7 @@ from sage.data_structures.stream import ( Stream_add, Stream_cauchy_mul, + Stream_cauchy_mul_commutative, Stream_sub, Stream_cauchy_compose, Stream_lmul, @@ -912,12 +927,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: @@ -938,13 +953,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:: @@ -956,43 +967,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): @@ -1011,100 +999,6 @@ def __hash__(self): """ return hash(self._coeff_stream) - 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. - - 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 - Traceback (most recent call last): - ... - ValueError: undecidable as lazy Laurent series - 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) # optional - sage.rings.finite_rings - Traceback (most recent call last): - ... - ValueError: undecidable as lazy Laurent series - 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 - """ - if isinstance(self._coeff_stream, Stream_zero): - return False - if isinstance(self._coeff_stream, Stream_exact): - 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(self._coeff_stream._cache): - return True - - 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)) - def define(self, s): r""" Define an equation by ``self = s``. @@ -1125,7 +1019,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) @@ -1350,6 +1244,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") @@ -1784,9 +1696,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:: @@ -1851,6 +1761,19 @@ def _acted_upon_(self, scalar, self_on_left): 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] + Check that :issue:`36154` is fixed:: sage: L. = LazyPowerSeriesRing(Zmod(4)) @@ -1897,7 +1820,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, @@ -2863,6 +2786,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)) @@ -2910,7 +2836,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 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()) + return P.element_class(P, coeff_stream) def __pow__(self, n): r""" @@ -3052,7 +2982,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) @@ -3196,8 +3126,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() @@ -3215,7 +3165,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 @@ -3290,7 +3240,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 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()) + return P.element_class(P, coeff_stream) def _floordiv_(self, other): r""" @@ -3316,7 +3270,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(): @@ -3381,7 +3335,9 @@ 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) + # 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 @@ -3430,9 +3386,11 @@ 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) + # 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) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 32545f65574..b989747a54c 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -488,7 +488,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)] @@ -1150,9 +1150,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 @@ -2319,10 +2317,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():