Skip to content

Commit c325311

Browse files
author
Release Manager
committed
gh-36284: improve checks We attempt to deduce automatically, when we can check coefficients without breaking implicit definitions and with as little user intervention as possible. Fixes #36253. URL: #36284 Reported by: Martin Rubey Reviewer(s): Travis Scrimshaw
2 parents 0b5fc8e + 45a96d9 commit c325311

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

src/sage/rings/lazy_series.py

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,18 +1053,27 @@ def __bool__(self):
10531053
sage: L.<z> = LazyLaurentSeriesRing(GF(2))
10541054
sage: bool(z - z)
10551055
False
1056-
sage: f = 1/(1 - z)
1057-
sage: bool(f)
1056+
sage: bool(1/(1 - z))
10581057
True
10591058
sage: M = L(lambda n: n, valuation=0); M
10601059
z + z^3 + z^5 + O(z^7)
10611060
sage: M.is_zero()
10621061
False
10631062
sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M
10641063
O(z^7)
1065-
sage: bool(M) # optional - sage.rings.finite_rings
1064+
sage: bool(M)
10661065
True
1067-
sage: M[15] # optional - sage.rings.finite_rings
1066+
1067+
With the `secure` option, we raise an error if we cannot know
1068+
whether the series is zero or not::
1069+
1070+
sage: # needs sage.rings.finite_rings
1071+
sage: L.options.secure = True
1072+
sage: bool(M)
1073+
Traceback (most recent call last):
1074+
...
1075+
ValueError: undecidable
1076+
sage: M[15]
10681077
1
10691078
sage: bool(M)
10701079
True
@@ -1073,12 +1082,15 @@ def __bool__(self):
10731082
sage: L.<z> = LazyLaurentSeriesRing(GF(2), sparse=True)
10741083
sage: M = L(lambda n: 2*n if n < 10 else 1, valuation=0); M
10751084
O(z^7)
1076-
sage: bool(M) # optional - sage.rings.finite_rings
1077-
True
1078-
sage: M[15] # optional - sage.rings.finite_rings
1085+
sage: bool(M)
1086+
Traceback (most recent call last):
1087+
...
1088+
ValueError: undecidable
1089+
sage: M[15]
10791090
1
10801091
sage: bool(M)
10811092
True
1093+
sage: L.options._reset()
10821094
10831095
Uninitialized series::
10841096
@@ -1108,12 +1120,14 @@ def __bool__(self):
11081120
11091121
Comparison with finite halting precision::
11101122
1123+
sage: # needs sage.rings.finite_rings
11111124
sage: M = L(lambda n: 2*n if n < 10 else 0, valuation=0)
11121125
sage: bool(M)
11131126
True
11141127
sage: M.is_zero()
11151128
False
11161129
1130+
sage: # needs sage.rings.finite_rings
11171131
sage: L.options.halting_precision = 20
11181132
sage: bool(M)
11191133
False
@@ -1124,12 +1138,14 @@ def __bool__(self):
11241138
be indistinguishable from zero until possibly enough
11251139
coefficients are computed::
11261140
1141+
sage: # needs sage.rings.finite_rings
11271142
sage: L.<z> = LazyLaurentSeriesRing(GF(2))
11281143
sage: L.options.halting_precision = 20
11291144
sage: f = L(lambda n: 0, valuation=0)
11301145
sage: f.is_zero()
11311146
True
11321147
1148+
sage: # needs sage.rings.finite_rings
11331149
sage: g = L(lambda n: 0 if n < 50 else 1, valuation=2)
11341150
sage: bool(g) # checks up to degree 22 = 2 + 20
11351151
False
@@ -1364,7 +1380,7 @@ def define(self, s):
13641380
sage: E = L(lambda n: s[n], valuation=0)
13651381
sage: X = L(s[1])
13661382
sage: A = L.undefined()
1367-
sage: A.define(X*E(A, check=False))
1383+
sage: A.define(X*E(A))
13681384
sage: A[:6]
13691385
[m[1],
13701386
2*m[1, 1] + m[2],
@@ -1645,8 +1661,8 @@ def _ascii_art_(self):
16451661
sage: L.options.display_length = 3
16461662
sage: ascii_art(1 / (1 - e[1]*z))
16471663
e[] + e[1]*z + e[1, 1]*z^2 + O(e[]*z^3)
1648-
sage: x = L.undefined(valuation=0) # optional - sage.combinat
1649-
sage: ascii_art(x + x^2 - 5) # optional - sage.combinat
1664+
sage: x = L.undefined(valuation=0)
1665+
sage: ascii_art(x + x^2 - 5)
16501666
Uninitialized Lazy Series
16511667
sage: L.options._reset()
16521668
"""
@@ -1669,8 +1685,8 @@ def _unicode_art_(self):
16691685
sage: L.options.display_length = 3
16701686
sage: unicode_art(1 / (1 - e[1]*z))
16711687
e[] + e[1]*z + e[1, 1]*z^2 + O(e[]*z^3)
1672-
sage: x = L.undefined(valuation=0) # optional - sage.combinat
1673-
sage: unicode_art(x + x^2 - 5) # optional - sage.combinat
1688+
sage: x = L.undefined(valuation=0)
1689+
sage: unicode_art(x + x^2 - 5)
16741690
Uninitialized Lazy Series
16751691
sage: L.options._reset()
16761692
"""
@@ -3267,10 +3283,20 @@ def __invert__(self):
32673283
Traceback (most recent call last):
32683284
...
32693285
ZeroDivisionError: cannot divide by a series of positive valuation
3286+
3287+
Check that :issue:`36253` is fixed::
3288+
3289+
sage: f = L(lambda n: n)
3290+
sage: ~f
3291+
Traceback (most recent call last):
3292+
...
3293+
ZeroDivisionError: cannot divide by a series of positive valuation
32703294
"""
32713295
P = self.parent()
32723296
coeff_stream = self._coeff_stream
3273-
if P._minimal_valuation is not None and coeff_stream._approximate_order > 0:
3297+
if (P._minimal_valuation is not None
3298+
and (coeff_stream._approximate_order > 0
3299+
or not coeff_stream.is_uninitialized() and not coeff_stream[0])):
32743300
raise ZeroDivisionError("cannot divide by a series of positive valuation")
32753301

32763302
# the inverse is exact if and only if coeff_stream corresponds to one of
@@ -3425,6 +3451,9 @@ def _div_(self, other):
34253451
ZeroDivisionError: cannot divide by 0
34263452
sage: L.options._reset()
34273453
"""
3454+
# currently __invert__ and _div_ behave differently with
3455+
# respect to division by lazy power series of positive
3456+
# valuation, so we cannot call ~other if self.is_one()
34283457
if not other:
34293458
raise ZeroDivisionError("cannot divide by 0")
34303459

@@ -3603,7 +3632,7 @@ def exp(self):
36033632
P = self.parent()
36043633
R = self.base_ring()
36053634
coeff_stream = self._coeff_stream
3606-
# TODO: coefficients should not be checked here, it prevents
3635+
# coefficients must not be checked here, it prevents
36073636
# us from using self.define in some cases!
36083637
if ((not coeff_stream.is_uninitialized())
36093638
and any(coeff_stream[i] for i in range(coeff_stream._approximate_order, 1))):
@@ -3656,7 +3685,7 @@ def log(self):
36563685
P = self.parent()
36573686
R = self.base_ring()
36583687
coeff_stream = self._coeff_stream
3659-
# TODO: coefficients should not be checked here, it prevents
3688+
# coefficients must not be checked here, it prevents
36603689
# us from using self.define in some cases!
36613690
if ((not coeff_stream.is_uninitialized())
36623691
and (any(coeff_stream[i] for i in range(coeff_stream._approximate_order, 0))
@@ -3798,7 +3827,7 @@ def _im_gens_(self, codomain, im_gens, base_map=None):
37983827

37993828
return codomain(self.map_coefficients(base_map)(im_gens[0]))
38003829

3801-
def __call__(self, g, *, check=True):
3830+
def __call__(self, g):
38023831
r"""
38033832
Return the composition of ``self`` with ``g``.
38043833
@@ -4192,18 +4221,18 @@ def __call__(self, g, *, check=True):
41924221
raise NotImplementedError("can only compose with a lazy series")
41934222

41944223
# Perhaps we just don't yet know if the valuation is positive
4195-
if check:
4196-
if g._coeff_stream._approximate_order <= 0:
4197-
if any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_order, 1)):
4198-
raise ValueError("can only compose with a positive valuation series")
4199-
g._coeff_stream._approximate_order = 1
4224+
if g._coeff_stream._approximate_order <= 0:
4225+
if (not g._coeff_stream.is_uninitialized()
4226+
and any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_order, 1))):
4227+
raise ValueError("can only compose with a positive valuation series")
4228+
g._coeff_stream._approximate_order = 1
42004229

42014230
if isinstance(g, LazyDirichletSeries):
4202-
if check:
4203-
if g._coeff_stream._approximate_order == 1:
4204-
if g._coeff_stream[1] != 0:
4205-
raise ValueError("can only compose with a positive valuation series")
4206-
g._coeff_stream._approximate_order = 2
4231+
if g._coeff_stream._approximate_order == 1:
4232+
if (not g._coeff_stream.is_uninitialized()
4233+
and g._coeff_stream[1] != 0):
4234+
raise ValueError("can only compose with a positive valuation series")
4235+
g._coeff_stream._approximate_order = 2
42074236
# we assume that the valuation of self[i](g) is at least i
42084237

42094238
def coefficient(n):
@@ -4362,6 +4391,7 @@ def revert(self):
43624391
R = P.base_ring()
43634392
# we cannot assume that the last initial coefficient
43644393
# and the constant differ, see stream.Stream_exact
4394+
# TODO: provide example or remove this claim
43654395
if (coeff_stream._degree == 1 + len(coeff_stream._initial_coefficients)
43664396
and coeff_stream._constant == -R.one()
43674397
and all(c == -R.one() for c in coeff_stream._initial_coefficients)):
@@ -4785,7 +4815,7 @@ def _im_gens_(self, codomain, im_gens, base_map=None):
47854815

47864816
return codomain(self.map_coefficients(base_map)(*im_gens))
47874817

4788-
def __call__(self, *g, check=True):
4818+
def __call__(self, *g):
47894819
r"""
47904820
Return the composition of ``self`` with ``g``.
47914821
@@ -5038,18 +5068,17 @@ def __call__(self, *g, check=True):
50385068
g = [P(h) for h in g]
50395069
R = P._internal_poly_ring.base_ring()
50405070

5041-
if check:
5042-
for h in g:
5043-
if h._coeff_stream._approximate_order == 0:
5044-
if h[0]:
5045-
raise ValueError("can only compose with a positive valuation series")
5046-
h._coeff_stream._approximate_order = 1
5071+
for h in g:
5072+
if h._coeff_stream._approximate_order == 0:
5073+
if not h._coeff_stream.is_uninitialized() and h[0]:
5074+
raise ValueError("can only compose with a positive valuation series")
5075+
h._coeff_stream._approximate_order = 1
50475076

5048-
if isinstance(h, LazyDirichletSeries):
5049-
if h._coeff_stream._approximate_order == 1:
5050-
if h._coeff_stream[1] != 0:
5051-
raise ValueError("can only compose with a positive valuation series")
5052-
h._coeff_stream._approximate_order = 2
5077+
if isinstance(h, LazyDirichletSeries):
5078+
if h._coeff_stream._approximate_order == 1:
5079+
if not h._coeff_stream.is_uninitialized() and h._coeff_stream[1] != 0:
5080+
raise ValueError("can only compose with a positive valuation series")
5081+
h._coeff_stream._approximate_order = 2
50535082

50545083
# We now have that every element of g has a _coeff_stream
50555084
sorder = self._coeff_stream._approximate_order
@@ -5782,7 +5811,7 @@ def is_unit(self):
57825811
return False
57835812
return self[0].is_unit()
57845813

5785-
def __call__(self, *args, check=True):
5814+
def __call__(self, *args):
57865815
r"""
57875816
Return the composition of ``self`` with ``g``.
57885817
@@ -5968,10 +5997,10 @@ def __call__(self, *args, check=True):
59685997
P = LazySymmetricFunctions(R)
59695998
g = P(g)
59705999

5971-
if check and not (isinstance(self._coeff_stream, Stream_exact)
5972-
and not self._coeff_stream._constant):
6000+
if not (isinstance(self._coeff_stream, Stream_exact)
6001+
and not self._coeff_stream._constant):
59736002
if g._coeff_stream._approximate_order == 0:
5974-
if g[0]:
6003+
if not g._coeff_stream.is_uninitialized() and g[0]:
59756004
raise ValueError("can only compose with a positive valuation series")
59766005
g._coeff_stream._approximate_order = 1
59776006

@@ -6254,6 +6283,7 @@ def functorial_composition(self, *args):
62546283
62556284
The symmetric function `\sum_n h_n` is a left absorbing element::
62566285
6286+
sage: # needs sage.modules
62576287
sage: H.functorial_composition(f) - H
62586288
O^7
62596289
@@ -6370,7 +6400,7 @@ def coefficient(n):
63706400
else:
63716401
raise NotImplementedError("only implemented for arity 1")
63726402

6373-
def arithmetic_product(self, *args, check=True):
6403+
def arithmetic_product(self, *args):
63746404
r"""
63756405
Return the arithmetic product of ``self`` with ``g``.
63766406
@@ -6419,9 +6449,6 @@ def arithmetic_product(self, *args, check=True):
64196449
64206450
- ``g`` -- a cycle index series having the same parent as ``self``
64216451
6422-
- ``check`` -- (default: ``True``) a Boolean which, when set
6423-
to ``False``, will cause input checks to be skipped
6424-
64256452
OUTPUT:
64266453
64276454
The arithmetic product of ``self`` with ``g``.
@@ -6565,15 +6592,11 @@ def arithmetic_product(self, *args, check=True):
65656592
and not g._coeff_stream._constant):
65666593
gs = g.symmetric_function()
65676594
c += self[0].arithmetic_product(gs)
6568-
elif check:
6569-
raise ValueError("can only take the arithmetic product with a positive valuation series")
65706595
if g[0]:
65716596
if (isinstance(self._coeff_stream, Stream_exact)
65726597
and not self._coeff_stream._constant):
65736598
fs = self.symmetric_function()
65746599
c += fs.arithmetic_product(g[0])
6575-
elif check:
6576-
raise ValueError("can only take the arithmetic product with a positive valuation series")
65776600

65786601
p = R.realization_of().p()
65796602
# TODO: does the following introduce a memory leak?
@@ -6848,7 +6871,7 @@ def __invert__(self):
68486871
return P.element_class(P, Stream_dirichlet_invert(self._coeff_stream,
68496872
P.is_sparse()))
68506873

6851-
def __call__(self, p, *, check=True):
6874+
def __call__(self, p):
68526875
r"""
68536876
Return the composition of ``self`` with a linear polynomial ``p``.
68546877

0 commit comments

Comments
 (0)