Repository: sympy/sympy
Pull request: Added test for simplified Piecewise is missing conditions
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
Fixes #21481

#### Brief description of what is fixed or changed


#### Other comments


#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

* physics.units
  * Corrected a semantical error in the conversion between volt and statvolt which
    reported the volt as being larger than the statvolt.

or if no release note(s) should be included use:

NO ENTRY

See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
information on how to write release notes. The bot will check your release
notes automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
NO ENTRY
<!-- END RELEASE NOTES -->


Problem statement:
simplified Piecewise is missing conditions
I have been trying to write a model for the size of symbolic powers.
I define the Piecewise expression as follows for power `b**e` with
the expressions of the Piecewise giving a value that is representative
of the region in which the size of the power is found (e.g. 1/2 if the
value of the power is between 0 and 1):

```python
var('b e')
A=Piecewise((1, Eq(b, 1) | Eq(e, 0) | (Eq(b, -1) & Eq(Mod(e, 2), 0))),
(0, Eq(b, 0) & (e > 0)), (-1, Eq(b, -1) & Eq(Mod(e, 2), 1)),
(Piecewise((2, ((b > 1) & (e > 0)) | ((b > 0) & (b < 1) & (e < 0)) |
((e >= 2) & (b < -1) & Eq(Mod(e, 2), 0)) | ((e <= -2) & (b > -1) & (b
< 0) & Eq(Mod(e, 2), 0))), (S.Half, ((b > 1) & (e < 0)) | ((b > 0) & (e >
0) & (b < 1)) | ((e <= -2) & (b < -1) & Eq(Mod(e, 2), 0)) | ((e >= 2)
& (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))), (-S.Half, Eq(Mod(e, 2), 1) &
(((e <= -1) & (b < -1)) | ((e >= 1) & (b > -1) & (b < 0)))), (-2, ((e
>= 1) & (b < -1) & Eq(Mod(e, 2), 1)) | ((e <= -1) & (b > -1) & (b < 0)
& Eq(Mod(e, 2), 1)))), Eq(im(b), 0) & Eq(im(e), 0)))
```
As a test of folding and simplification I create the following alternative forms for A
```python
B=piecewise_fold(A)
sa=A.simplify()
sb=B.simplify()
```
I then test the forms over a range of `b` and `e` values:
```python
v = Tuple(-2, -1, -0.5, 0, 0.5, 1, 2)
for i in v:
 for j in v:
  r = {b:i,e:j}
  ok=[k.xreplace(r) for k in (A,B,sa,sb)]
  if len(set(ok))!=1:print('ab %s %s'%(r,ok))
```
Although `A` and `B` agree, the simplified forms of each do not and I get these results
```python
s {b: -2, e: -1} [-1/2, -1/2, -1/2, nan]
s {b: -2, e: 1} [-2, -2, -2, nan]
s {b: -1/2, e: 1} [-1/2, -1/2, -1/2, nan]
```
The `nan` indicates that the conditions for the values of `b` and `e` that were passed are not found in the Piecewise expression so a default `nan` is returned. I confirmed that the value returned by `sa` agrees with the value from `A`. I have not yet tracked down the source of the discrepancy from `sb` in which are missing the cases for -2 and -1/2:
```python
Piecewise(
    (1, Eq(b, 1) | Eq(e, 0) | (Eq(b, -1) & Eq(Mod(e, 2), 0))),
    (0, Eq(b, 0) & (e > 0)),
    (-1, Eq(b, -1) & Eq(Mod(e, 2), 1)),
    (2, Eq(im(b), 0) & Eq(im(e), 0) & ((b > 0) | (b > 1)) & ((b > 0) | (e > 0)) & ((b > 1) | (b < 1)) & ((b > 1) | (e < 0)) & ((e > 0) | (b < 1)) & ((e > 0) | (e < 0))),
    (1/2, Eq(im(b), 0) & Eq(im(e), 0) & ((b > 0) | (b > 1)) & ((b > 0) | (e < 0)) & ((b > 1) | (e > 0)) & ((b > 1) | (b < 1)) & ((e > 0) | (e < 0)) & ((b < 1) | (e <0))))
```

Hints from issue discussion:
It looks like it is the Mod that is causing the trouble:
```python
>>> p
((e >= 1) & (b < -1) & Eq(Mod(e, 2), 1)) | ((e <= -1) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 1))
>>> p.simplify()
(b < -1) & Eq(Mod(e, 2), 1)
>>> p.subs(Eq(Mod(e, 2), 1),True)
((e >= 1) & (b < -1)) | ((e <= -1) & (b > -1) & (b < 0))
>>> _.simplify()
((e >= 1) | (e <= -1)) & ((e >= 1) | (b > -1)) & ((e >= 1) | (b < 0)) & ((e <= -1) | (b < -1)) & ((b > -1) | (b < -1)) & ((b < -1) | (b < 0))
```
This seems to have been fixed in the current master.

```
pprint(sb)
⎧ 1                                                            for (b = -1 ∧ e
⎪                                                                             
⎪ 0                                                                         fo
⎪                                                                             
⎪ -1                                                                    for b 
⎪                                                                             
⎨ 2        for im(b) = 0 ∧ im(e) = 0 ∧ ((b > 1 ∧ e > 0) ∨ (e mod 2 = 0 ∧ e ≥ 2
⎪                                                                             
⎪1/2       for im(b) = 0 ∧ im(e) = 0 ∧ ((b > 1 ∧ e < 0) ∨ (e mod 2 = 0 ∧ e ≤ -
⎪                                                                             
⎪-1/2  for im(b) = 0 ∧ im(e) = 0 ∧ e mod 2 = 1 ∧ (e ≥ 1 ∨ e ≤ -1) ∧ (e ≥ 1 ∨ b
⎪                                                                             
⎩ -2   for im(b) = 0 ∧ im(e) = 0 ∧ e mod 2 = 1 ∧ (e ≥ 1 ∨ e ≤ -1) ∧ (e ≥ 1 ∨ b
```
(and some more lines...)

and
```
In [7]: v = Tuple(-2, -1, -0.5, 0, 0.5, 1, 2)
   ...: for i in v:
   ...:  for j in v:
   ...:   r = {b:i,e:j}
   ...:   ok=[k.xreplace(r) for k in (A,B,sa,sb)]
   ...:   if len(set(ok))!=1:print('ab %s %s'%(r,ok))
   ...:   

In [8]: 
```

Commit message: empty commit

Patch to review:
diff --git a/sympy/functions/elementary/tests/test_piecewise.py b/sympy/functions/elementary/tests/test_piecewise.py
index 958c671c470f..74618bfa59a9 100644
--- a/sympy/functions/elementary/tests/test_piecewise.py
+++ b/sympy/functions/elementary/tests/test_piecewise.py
@@ -5,6 +5,7 @@
 from sympy.core.expr import unchanged
 from sympy.core.function import (Function, diff, expand)
 from sympy.core.mul import Mul
+from sympy.core.mod import Mod
 from sympy.core.numbers import (Float, I, Rational, oo, pi, zoo)
 from sympy.core.relational import (Eq, Ge, Gt, Ne)
 from sympy.core.singleton import S
@@ -1339,6 +1340,31 @@ def test_issue_14787():
     f = Piecewise((x, x < 1), ((S(58) / 7), True))
     assert str(f.evalf()) == "Piecewise((x, x < 1), (8.28571428571429, True))"
 
+def test_issue_21481():
+    b, e = symbols('b e')
+    A = Piecewise((1, Eq(b, 1) | Eq(e, 0) | (Eq(b, -1) & Eq(Mod(e, 2), 0))),
+                    (0, Eq(b, 0) & (e > 0)), (-1, Eq(b, -1) & Eq(Mod(e, 2), 1)),
+                    (Piecewise((2, ((b > 1) & (e > 0)) | ((b > 0) & (b < 1) & (e < 0)) |
+                            ((e >= 2) & (b < -1) & Eq(Mod(e, 2), 0)) |
+                            ((e <= -2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))),
+                            (S.Half, ((b > 1) & (e < 0)) | ((b > 0) & (e > 0) & (b < 1)) |
+                            ((e <= -2) & (b < -1) & Eq(Mod(e, 2), 0)) |
+                            ((e >= 2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))),
+                            (-S.Half, Eq(Mod(e, 2), 1) & (((e <= -1) & (b < -1)) |
+                            ((e >= 1) & (b > -1) & (b < 0)))),
+                                (-2, ((e >= 1) & (b < -1) & Eq(Mod(e, 2), 1)) |
+                                ((e <= -1) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 1)))),
+                    Eq(im(b), 0) & Eq(im(e), 0)))
+    B = piecewise_fold(A)
+    sa = A.simplify()
+    sb = B.simplify()
+    v = Tuple(-2, -1, -0.5, 0, 0.5, 1, 2)
+    for i in v:
+        for j in v:
+            r = {b:i, e:j}
+            ok = [k.xreplace(r) for k in (A, B, sa, sb)]
+            assert len(set(ok)) == 1
+
 
 def test_issue_8458():
     x, y = symbols('x y')


Code review comments:
diff_hunk: @@ -1339,6 +1340,31 @@ def test_issue_14787():
     f = Piecewise((x, x < 1), ((S(58) / 7), True))
     assert str(f.evalf()) == "Piecewise((x, x < 1), (8.28571428571429, True))"
 
+def test_issue_21481():
+    b, e = symbols('b e')
+    A = Piecewise((1, Eq(b, 1) | Eq(e, 0) | (Eq(b, -1) & Eq(Mod(e, 2), 0))),
+                    (0, Eq(b, 0) & (e > 0)), (-1, Eq(b, -1) & Eq(Mod(e, 2), 1)),
+                    (Piecewise((2, ((b > 1) & (e > 0)) | ((b > 0) & (b < 1) & (e < 0)) |
+                            ((e >= 2) & (b < -1) & Eq(Mod(e, 2), 0)) |
+                            ((e <= -2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))),
+                            (S.Half, ((b > 1) & (e < 0)) | ((b > 0) & (e > 0) & (b < 1)) |
+                            ((e <= -2) & (b < -1) & Eq(Mod(e, 2), 0)) |
+                            ((e >= 2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))),
+                            (-S.Half, Eq(Mod(e, 2), 1) & (((e <= -1) & (b < -1)) |
+                            ((e >= 1) & (b > -1) & (b < 0)))),
+                                (-2, ((e >= 1) & (b < -1) & Eq(Mod(e, 2), 1)) |
+                                ((e <= -1) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 1)))),
+                    Eq(im(b), 0) & Eq(im(e), 0)))
+    B = piecewise_fold(A)
+    sa = A.simplify()
+    sb = B.simplify()
+    v = Tuple(-2, -1, -0.5, 0, 0.5, 1, 2)
path: sympy/functions/elementary/tests/test_piecewise.py
text: @user1:
Why do you have to use `Tuple` instead of tuple? And is it intended to use `0.5` instead of `S.Half`?

@author:
Not a specific idea, but Tuple would be better I thought.

@author:
should I replace it ?

@user1:
It is better to use python iterables because `Tuple` is quite redundant.

@user1:
And also I suggest to use `S.Half` instead of floats.