From aaa78a500ee034ceb2cfa436f5aa36dca624b125 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 12 Apr 2023 17:23:13 +0900 Subject: [PATCH 1/2] Adding a new comparison mode to lazy series and added a check for undefined series. --- .../combinat/species/recursive_species.py | 9 + src/sage/data_structures/stream.py | 53 ++++ src/sage/rings/lazy_series.py | 246 ++++++++++++++---- src/sage/rings/lazy_series_ring.py | 117 +++++++-- 4 files changed, 359 insertions(+), 66 deletions(-) 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 b11a27318f5..f3e9d2efc39 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -198,6 +198,12 @@ def is_nonzero(self): """ return False + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return False + class Stream_inexact(Stream): """ @@ -1041,6 +1047,12 @@ def iterate_coefficients(self): yield self._target[n] n += 1 + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._target is None + class Stream_unary(Stream_inexact): r""" @@ -1117,6 +1129,12 @@ def __eq__(self, other): """ return isinstance(other, type(self)) and self._series == other._series + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() + class Stream_binary(Stream_inexact): """ @@ -1205,6 +1223,12 @@ def __eq__(self, other): return False return self._left == other._left and self._right == other._right + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._left.is_undefined() or self._right.is_undefined() + class Stream_binaryCommutative(Stream_binary): r""" @@ -2285,6 +2309,12 @@ def is_nonzero(self): """ return self._series.is_nonzero() + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() + class Stream_rmul(Stream_scalar): """ @@ -2714,6 +2744,12 @@ def __eq__(self, other): return (isinstance(other, type(self)) and self._series == other._series and self._function == other._function) + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() + class Stream_shift(Stream): """ @@ -2846,6 +2882,11 @@ def is_nonzero(self): """ return self._series.is_nonzero() + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() class Stream_truncated(Stream_inexact): """ @@ -3095,6 +3136,12 @@ def is_nonzero(self): start = self._approximate_order - offset return any(self._cache[start:]) + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() + class Stream_derivative(Stream_inexact): """ @@ -3222,3 +3269,9 @@ def is_nonzero(self): True """ return self._series.is_nonzero() + + def is_undefined(self): + """ + Return ``True`` if ``self`` is an undefined stream. + """ + return self._series.is_undefined() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index d1647ec445f..8f5ebbfde61 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -246,6 +246,7 @@ Stream_dirichlet_invert, Stream_plethysm ) +from sage.misc.unknown import Unknown, UnknownError class LazyModuleElement(Element): @@ -927,13 +928,31 @@ 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 + + With using :class:`Unknown`:: + + sage: L.options.use_unknown = True + sage: fz = L(lambda n: 0, valuation=0) + sage: L.zero() == fz + Unknown + sage: fz == L.zero() + Unknown + sage: fz != L.zero() + Unknown + + With using finite halting precision:: + + sage: L.options.halting_precision = 40 + sage: fz = L(lambda n: 0, valuation=0) + sage: L.zero() == fz + True + sage: fz == L.zero() + True + + sage: L.options._reset() TESTS:: @@ -942,24 +961,27 @@ def _richcmp_(self, other, op): sage: g = L([0,0,1,0,1,0,0], degree=7, constant=1) sage: f == g True - """ if op is op_EQ: + if (not self.parent().options['use_unknown'] + and self.parent().options['halting_precision'] is None): + return self._coeff_stream == other._coeff_stream + if isinstance(self._coeff_stream, Stream_zero): if isinstance(other._coeff_stream, Stream_zero): return True - if other._coeff_stream.is_nonzero(): + if other._coeff_stream.is_undefined() or other._coeff_stream.is_nonzero(): return False elif isinstance(other._coeff_stream, Stream_zero): - if self._coeff_stream.is_nonzero(): + if self._coeff_stream.is_undefined() or 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: + if self._coeff_stream != other._coeff_stream or other._coeff_stream.is_undefined(): return False elif isinstance(other._coeff_stream, Stream_exact): - if other._coeff_stream != self._coeff_stream: + if other._coeff_stream != self._coeff_stream or self._coeff_stream.is_undefined(): return False else: # both streams are inexact, perhaps they are equal by @@ -969,19 +991,26 @@ def _richcmp_(self, other, op): # perhaps their caches are different if self._coeff_stream != other._coeff_stream: return False + if self._coeff_stream.is_undefined() or other._coeff_stream.is_undefined(): + return False # undecidable otherwise prec = self.parent().options['halting_precision'] if prec is None: - raise ValueError("undecidable") + return Unknown + # raise UnknownError("undecidable") # at least one of the approximate orders is not infinity 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) + ret = (self == other) + if ret is Unknown: + return ret + return not ret + # FIXME: This should check for equality in <= and >= and other return NotImplemented return False def __hash__(self): @@ -1004,9 +1033,8 @@ 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. + When the halting precision is infinite, then any series that is + not known to be zero will be ``True``. TESTS:: @@ -1022,11 +1050,9 @@ def __bool__(self): 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 + sage: bool(M) + True + sage: M[15] 1 sage: bool(M) # optional - sage.rings.finite_rings True @@ -1034,11 +1060,9 @@ def __bool__(self): 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 + sage: bool(M) + True + sage: M[15] 1 sage: bool(M) # optional - sage.rings.finite_rings True @@ -1065,18 +1089,52 @@ def __bool__(self): sage: g.define(1 + z*g) # optional - sage.rings.finite_rings sage: bool(g) # optional - sage.rings.finite_rings True + + Comparison with finite halting precision:: + + sage: M = L(lambda n: 2*n if n < 10 else 0, valuation=0) + sage: bool(M) + True + sage: M.is_zero() + False + + sage: L.options.halting_precision = 20 + sage: bool(M) + False + sage: M.is_zero() + True + + + With finite halting precision, it can be considered to + be indistinguishable from zero until possibly enough + coefficients are computed:: + + sage: L. = LazyLaurentSeriesRing(GF(2)) + sage: L.options.halting_precision = 20 + sage: f = L(lambda n: 0, valuation=0) + sage: f.is_zero() + True + + sage: g = L(lambda n: 0 if n < 50 else 1, valuation=2) + sage: bool(g) # checks up to degree 22 = 2 + 20 + False + sage: bool(g) # checks up to degree 42 = 22 + 20 + False + sage: bool(g) # checks up to degree 62 = 42 + 20 + True + sage: L.options._reset() """ if isinstance(self._coeff_stream, Stream_zero): return False + + prec = self.parent().options['halting_precision'] + if prec is None and not self.parent().options['use_unknown']: + return True + 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_undefined(): + return True if self._coeff_stream._is_sparse: cache = self._coeff_stream._cache if any(cache[a] for a in cache): @@ -1085,15 +1143,57 @@ def __bool__(self): 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") + raise UnknownError("undecidable") + v = self._coeff_stream._approximate_order return any(self[i] for i in range(v, v + prec)) + def is_nonzero(self): + """ + Return ``True`` if ``self`` is known to be nonzero. + + EXAMPLES: + + A series that it not known to be nonzero with no halting precision:: + + sage: L. = LazyLaurentSeriesRing(GF(2)) + sage: f = L(lambda n: 0, valuation=0) + sage: f.is_nonzero() + False + sage: bool(f) + True + sage: g = L(lambda n: 0 if n < 50 else 1, valuation=2) + sage: g.is_nonzero() + False + sage: g[60] + 1 + sage: g.is_nonzero() + True + + With finite halting precision, it can be considered to + be indistinguishable from zero until possibly enough + coefficients are computed:: + + sage: L.options.halting_precision = 20 + sage: f = L(lambda n: 0, valuation=0) + sage: f.is_zero() + True + + sage: g = L(lambda n: 0 if n < 50 else 1, valuation=2) + sage: g.is_nonzero() # checks up to degree 22 = 2 + 20 + False + sage: g.is_nonzero() # checks up to degree 42 = 22 + 20 + False + sage: g.is_nonzero() # checks up to degree 62 = 42 + 20 + True + sage: L.options._reset() + """ + if self._coeff_stream.is_nonzero(): + return True + if self.parent().options['halting_precision'] is not None: + return bool(self) + return False + def define(self, s): r""" Define an equation by ``self = s``. @@ -1339,6 +1439,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") @@ -1385,7 +1503,9 @@ def _repr_(self): sage: L(lambda x: x if x > 0 else 0, valuation=-10) O(z^-3) - sage: L.undefined(valuation=0) + sage: s = L.undefined(valuation=0); s + Uninitialized Lazy Laurent Series + sage: (s + s^2).map_coefficients(lambda f: f % 3) Uninitialized Lazy Laurent Series sage: L(0) 0 @@ -1399,7 +1519,7 @@ def _repr_(self): """ if isinstance(self._coeff_stream, Stream_zero): return '0' - if isinstance(self._coeff_stream, Stream_uninitialized) and self._coeff_stream._target is None: + if self._coeff_stream.is_undefined(): return 'Uninitialized Lazy Laurent Series' return self._format_series(repr) @@ -1432,7 +1552,10 @@ def _latex_(self): sage: latex(L(lambda x: x if x > 0 else 0, valuation=-10)) O(\frac{1}{z^{3}}) - sage: latex(L.undefined(valuation=0)) + sage: s = L.undefined(valuation=0) + sage: latex(s) + \text{\texttt{Undef}} + sage: latex((s + s^2).map_coefficients(lambda f: f % 3)) \text{\texttt{Undef}} sage: latex(L(0)) 0 @@ -1449,7 +1572,7 @@ def _latex_(self): from sage.misc.latex import latex if isinstance(self._coeff_stream, Stream_zero): return latex('0') - if isinstance(self._coeff_stream, Stream_uninitialized) and self._coeff_stream._target is None: + if self._coeff_stream.is_undefined(): return latex("Undef") return self._format_series(latex) @@ -1773,9 +1896,17 @@ 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 + + sage: L.options.use_unknown = True + sage: 2 * M == 3 * M + Unknown + + sage: L.options.halting_precision = 30 + sage: 2 * M == 3 * M + False + + sage: L.options._reset() Sparse series can be multiplied with a scalar:: @@ -1837,7 +1968,6 @@ def _acted_upon_(self, scalar, self_on_left): sage: 1 * M is M True - """ # With the current design, the coercion model does not have # enough information to detect a priori that this method only @@ -3156,8 +3286,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() diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 32545f65574..6804ccb211e 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -19,6 +19,21 @@ :class:`sage.rings.padics.generic_nodes.pAdicRelaxedGeneric`, :func:`sage.rings.padics.factory.ZpER` +.. WARNING:: + + When the halting precision is infinite, the default for ``bool(f)`` + is ``True`` for any lazy series ``f`` that is not known to be zero. + This could end up resulting in infinite loops:: + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: f = L(lambda n: 0, valuation=0) + sage: 1 / f # not tested - infinite loop + +.. SEEALSO:: + + The examples of :class:`LazyLaurentSeriesRing` contain a discussion + about the different methods of comparisons the lazy series can use. + AUTHORS: - Kwankyu Lee (2019-02-24): initial version @@ -488,7 +503,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)] @@ -661,6 +676,7 @@ class options(GlobalOptions): - constant_length: 3 - display_length: 7 - halting_precision: None + - use_unknown: False sage: LLS.options.display_length 7 @@ -694,8 +710,11 @@ class options(GlobalOptions): description='the number of coefficients to display for nonzero constant series', checker=lambda x: x in ZZ and x > 0) halting_precision = dict(default=None, - description='the number of coefficients, beginning with the approximate valuation, to check in equality tests', - checker=lambda x: x is None or x in ZZ and x > 0) + description='the number of coefficients, beginning with the approximate valuation, to check in equality tests', + checker=lambda x: x is None or x in ZZ and x > 0) + use_unknown = dict(default=False, + description='whether to raise an error when a comparison is unknown', + checker=lambda x: x is True or x is False) @cached_method def one(self): @@ -1139,36 +1158,98 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: s 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) - If the series is not specified by a finite number of initial - coefficients and a constant for the remaining coefficients, then - equality checking will depend on the coefficients which have - already been computed. If this information is not enough to - check that two series are different we raise an error:: + By default, any two series ``f`` and ``g`` that are not known to + be equal are considered to be different:: + + sage: f = L(lambda n: 0, valuation=0) + sage: f == 0 + False + + .. WARNING:: + + We have imposed that ``(f == g) == not (f != g)``, and so + ``f != g`` returning ``True`` might not mean that the two + series are actually different:: + + sage: g = L.zero() + sage: f != g + True + + This can be verified by :meth:`~sage.rings.lazy_series.is_nonzero()`, + which only returns ``True`` if the series is known to be nonzero:: + + sage: (f - g).is_nonzero() + False + + The implementation of the ring can be either be a sparse or a dense one. + The default is a sparse implementation:: + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: L.is_sparse() + True + sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) + sage: L.is_sparse() + False + + We additionally provide two other methods of performing comparisons. + The first is returning an :class:`Unknown` and the second uses a check + up to a (user set) finite precision. These behaviors are set using the + options ``use_unknown`` and ``halting_precision``. In particular, + this applies to series that are not specified by a finite number + of initial coefficients and a constant for the remaining coefficients. + Equality checking will depend on the coefficients which have + already been computed. If this information is not enough to + check that two series are different, then if ``L.options.use_unknown`` + is set to ``True``, then we return an :class:`Unknown`:: + + sage: L.options.use_unknown = True sage: f = 1 / (z + z^2); f z^-1 - 1 + z - z^2 + z^3 - z^4 + z^5 + O(z^6) 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 + Unknown 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 3*z^-1 - 3 + 3*z - 3*z^2 + 3*z^3 - 3*z^4 + 3*z^5 + O(z^6) sage: f2 == f3 False + sage: f2a = f + f + sage: f2 == f2a + Unknown + sage: zf = L(lambda n: 0, valuation=0) + sage: zf == 0 + Unknown - The implementation of the ring can be either be a sparse or a dense one. - The default is a sparse implementation:: + For boolean checks, an error is raised when it is not known to be nonzero:: - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: L.is_sparse() - True - sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) - sage: L.is_sparse() + sage: bool(zf) + Traceback (most recent call last): + ... + UnknownError: undecidable + + If the halting precision is set to a finite number `p` (for unlimited + precision, it is set to ``None``), then it will check up to `p` values + from the current position:: + + sage: L.options.halting_precision = 20 + sage: f2 = f * 2 # currently no coefficients computed + sage: f3 = f * 3 # currently no coefficients computed + sage: f2 == f3 False + sage: f2a = f + f + sage: f2 == f2a + True + sage: zf = L(lambda n: 0, valuation=0) + sage: zf == 0 + True + + TESTS: + + We reset the options:: + + sage: L.options._reset() """ Element = LazyLaurentSeries From fdc7529528552566ec057f38af46720d923b59e1 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 12 Apr 2023 17:48:42 +0900 Subject: [PATCH 2/2] Alternative proposal returning None for unknown comparisons. --- src/sage/data_structures/stream.py | 2 +- src/sage/rings/lazy_series.py | 36 ++++++++++++++++-------------- src/sage/rings/lazy_series_ring.py | 26 ++++++++++----------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index f3e9d2efc39..a71a3bcd8c4 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -664,7 +664,7 @@ def __init__(self, initial_coefficients, constant=None, degree=None, order=None) if order + len(initial_coefficients) == self._degree: # Strip off the constant values at the end for w in reversed(initial_coefficients): - if w != self._constant: + if not (w == self._constant): break initial_coefficients.pop() self._degree -= 1 diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8f5ebbfde61..6a2a3b55eea 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -927,21 +927,21 @@ def _richcmp_(self, other, op): False sage: fz = L(lambda n: 0, valuation=0) - sage: L.zero() == fz - False - sage: fz == L.zero() - False + sage: (L.zero() == fz) is None + True + sage: (fz == L.zero()) is None + True With using :class:`Unknown`:: sage: L.options.use_unknown = True sage: fz = L(lambda n: 0, valuation=0) - sage: L.zero() == fz - Unknown - sage: fz == L.zero() - Unknown - sage: fz != L.zero() - Unknown + sage: (L.zero() == fz) is None + True + sage: (fz == L.zero()) is None + True + sage: (fz != L.zero()) is None + True With using finite halting precision:: @@ -997,7 +997,8 @@ def _richcmp_(self, other, op): # undecidable otherwise prec = self.parent().options['halting_precision'] if prec is None: - return Unknown + return None + #return Unknown # raise UnknownError("undecidable") # at least one of the approximate orders is not infinity m = min(self._coeff_stream._approximate_order, @@ -1006,7 +1007,7 @@ def _richcmp_(self, other, op): if op is op_NE: ret = (self == other) - if ret is Unknown: + if ret is None: return ret return not ret @@ -1144,7 +1145,8 @@ def __bool__(self): return True if prec is None: - raise UnknownError("undecidable") + return True + #raise UnknownError("undecidable") v = self._coeff_stream._approximate_order return any(self[i] for i in range(v, v + prec)) @@ -1895,12 +1897,12 @@ def _acted_upon_(self, scalar, self_on_left): Different scalars potentially give different series:: - sage: 2 * M == 3 * M - False + sage: (2 * M == 3 * M) is None + True sage: L.options.use_unknown = True - sage: 2 * M == 3 * M - Unknown + sage: (2 * M == 3 * M) is None + True sage: L.options.halting_precision = 30 sage: 2 * M == 3 * M diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 6804ccb211e..c5350cde77e 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -676,7 +676,7 @@ class options(GlobalOptions): - constant_length: 3 - display_length: 7 - halting_precision: None - - use_unknown: False + - use_unknown: True sage: LLS.options.display_length 7 @@ -712,7 +712,7 @@ class options(GlobalOptions): halting_precision = dict(default=None, description='the number of coefficients, beginning with the approximate valuation, to check in equality tests', checker=lambda x: x is None or x in ZZ and x > 0) - use_unknown = dict(default=False, + use_unknown = dict(default=True, description='whether to raise an error when a comparison is unknown', checker=lambda x: x is True or x is False) @@ -1162,8 +1162,8 @@ class LazyLaurentSeriesRing(LazySeriesRing): be equal are considered to be different:: sage: f = L(lambda n: 0, valuation=0) - sage: f == 0 - False + sage: (f == 0) is None + True .. WARNING:: @@ -1172,7 +1172,7 @@ class LazyLaurentSeriesRing(LazySeriesRing): series are actually different:: sage: g = L.zero() - sage: f != g + sage: (f != g) is None True This can be verified by :meth:`~sage.rings.lazy_series.is_nonzero()`, @@ -1207,8 +1207,8 @@ class LazyLaurentSeriesRing(LazySeriesRing): z^-1 - 1 + z - z^2 + z^3 - z^4 + z^5 + O(z^6) sage: f2 = f * 2 # currently no coefficients computed sage: f3 = f * 3 # currently no coefficients computed - sage: f2 == f3 - Unknown + sage: (f2 == f3) is None + True 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 @@ -1216,18 +1216,16 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: f2 == f3 False sage: f2a = f + f - sage: f2 == f2a - Unknown + sage: (f2 == f2a) is None + True sage: zf = L(lambda n: 0, valuation=0) - sage: zf == 0 - Unknown + sage: (zf == 0) is None + True For boolean checks, an error is raised when it is not known to be nonzero:: sage: bool(zf) - Traceback (most recent call last): - ... - UnknownError: undecidable + True If the halting precision is set to a finite number `p` (for unlimited precision, it is set to ``None``), then it will check up to `p` values