Repository: sympy/sympy

Issue:
Regression in Piecewise result
I might be doing something wrong, but with the latest `master` branch I'm getting a different result than previously.

The example with more details is available at https://splines.readthedocs.io/en/0.1.0/euclidean/catmull-rom-uniform.html#Cardinal-Functions.

Here's just a small reproducer (which could for sure be made even more minimal):

```python
import sympy as sp

t = sp.symbols('t')
i, j, k = sp.symbols('i j k', integer=True)
w = sp.Function('w')
C0k = sp.Sum(
        sp.Product(
            sp.Piecewise((1, sp.Eq(j, 0)), ((t / j) + 1, True)),
            (j, i - k, i)) * w(t + i),
        (i, 0, k))
B0 = sp.Piecewise((0, t < i), (1, t < i + 1), (0, True))
B1 = (t - i) * B0 + (i + 2 - t) * B0.subs(i, i + 1)
B2 = (t - i) / 2 * B1 + (i + 3 - t) / 2 * B1.subs(i, i + 1)
w2 = B2.subs(i, -1)
C01 = C0k.subs(k, 1).replace(w, lambda x: w2.subs(t, x)).doit().simplify()
sp.printing.pprint(C01)
```

### SymPy 1.9 (and before)

```
⎧        0          for t < -2
⎪                             
⎪               2             
⎪(t + 1)⋅(t + 2)              
⎪────────────────   for t < -1
⎪       2                     
⎪                             
⎪     3      2                
⎪  3⋅t    5⋅t                 
⎪- ──── - ──── + 1  for t < 0 
⎪   2      2                  
⎨                             
⎪    3      2                 
⎪ 3⋅t    5⋅t                  
⎪ ──── - ──── + 1   for t < 1 
⎪  2      2                   
⎪                             
⎪               2             
⎪(1 - t)⋅(t - 2)              
⎪────────────────   for t < 2 
⎪       2                     
⎪                             
⎩        0          otherwise
```

### `master` branch since 34289ce4d92cc9424b05a01b7fa74d156853c392

```
⎧                  0                         for t < -2    
⎪                                                          
⎪                          2                               
⎪           (t + 1)⋅(t + 2)                                
⎪           ────────────────                 for t < -1    
⎪                  2                                       
⎪                                                          
⎪                                 2                        
⎪   3      2   t   (1 - t)⋅(t + 1)    1                    
⎪- t  - 2⋅t  - ─ + ──────────────── + ─  for t ≤ -2 ∨ t < 0
⎪              2          2           2                    
⎪                                                          
⎪                      2                                   
⎨               1 - 4⋅t                      for t ≤ -1    
⎪                                                          
⎪                        2                                 
⎪  3      2   t   (t - 1) ⋅(t + 1)   1                     
⎪ t  - 2⋅t  + ─ + ──────────────── + ─       for t < 1     
⎪             2          2           2                     
⎪                                                          
⎪                          2                               
⎪           (1 - t)⋅(t - 2)                                
⎪           ────────────────                 for t < 2     
⎪                  2                                       
⎪                                                          
⎪                  0                         otherwise     
⎩                                                          
```

Not only is this different, but the ranges also look kinda dubious, don't they?

The regression was introduced in #22286 by @smichr.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v162). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* functions
  * bug fixed for simplification of Piecewise expressions ([#22751](https://github.com/sympy/sympy/pull/22751) by [@smichr](https://github.com/smichr))

  * condition of Piecewise which are univariate are made more canonical (redundant conditions removed) ([#22751](https://github.com/sympy/sympy/pull/22751) by [@smichr](https://github.com/smichr))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.10.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    <!-- 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. -->


    #### 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.

    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 -->
    * functions
        * bug fixed for simplification of Piecewise expressions
        * condition of Piecewise which are univariate are made more canonical (redundant conditions removed)
    <!-- END RELEASE NOTES -->


</details><p>


Patch:
diff --git a/sympy/functions/elementary/piecewise.py b/sympy/functions/elementary/piecewise.py
index b38944ff71..b81722e9f6 100644
--- a/sympy/functions/elementary/piecewise.py
+++ b/sympy/functions/elementary/piecewise.py
@@ -9,6 +9,7 @@
 from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not,
     true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and)
 from sympy.polys.polyutils import illegal
+from sympy.sets.sets import Interval
 from sympy.utilities.iterables import uniq, sift, common_prefix
 from sympy.utilities.misc import filldedent, func_name
 
@@ -1214,57 +1215,89 @@ def _clip(A, B, k):
 
 def piecewise_simplify_arguments(expr, **kwargs):
     from sympy.simplify.simplify import simplify
-    args = []
+
+    # simplify conditions
     f1 = expr.args[0].cond.free_symbols
+    args = None
     if len(f1) == 1 and not expr.atoms(Eq):
         x = f1.pop()
         # this won't return intervals involving Eq
         # and it won't handle symbols treated as
         # booleans
         ok, abe_ = expr._intervals(x, err_on_Eq=True)
+        def include(c, x, a):
+            "return True if c.subs(x, a) is True, else False"
+            try:
+                return c.subs(x, a) == True
+            except TypeError:
+                return False
         if ok:
             args = []
+            covered = S.EmptySet
             for a, b, e, i in abe_:
                 c = expr.args[i].cond
-                if a is S.NegativeInfinity:
-                    if b is S.Infinity:
+                incl_a = include(c, x, a)
+                incl_b = include(c, x, b)
+                iv = Interval(a, b, not incl_a, not incl_b)
+                cset = iv - covered
+                if not cset:
+                    continue
+                if incl_a and incl_b:
+                    if a.is_infinite and b.is_infinite:
                         c = S.true
+                    elif b.is_infinite:
+                        c = (x >= a)
+                    elif a in covered or a.is_infinite:
+                        c = (x <= b)
+                    else:
+                        c = And(a <= x, x <= b)
+                elif incl_a:
+                    if a in covered or a.is_infinite:
+                        c = (x < b)
                     else:
-                        if c.subs(x, b) == True:
-                            c = (x <= b)
-                        else:
-                            c = (x < b)
-                else:
-                    incl_a = (c.subs(x, a) == True)
-                    incl_b = (c.subs(x, b) == True)
-                    if incl_a and incl_b:
-                        if b.is_infinite:
-                            c = (x >= a)
-                        else:
-                            c = And(a <= x, x <= b)
-                    elif incl_a:
                         c = And(a <= x, x < b)
-                    elif incl_b:
-                        if b.is_infinite:
-                            c = (x > a)
-                        else:
-                            c = And(a < x, x <= b)
+                elif incl_b:
+                    if b.is_infinite:
+                        c = (x > a)
+                    else:
+                        c = (x <= b)
+                else:
+                    if a in covered:
+                        c = (x < b)
                     else:
                         c = And(a < x, x < b)
+                covered |= iv
+                if a is S.NegativeInfinity and incl_a:
+                    covered |= {S.NegativeInfinity}
+                if b is S.Infinity and incl_b:
+                    covered |= {S.Infinity}
                 args.append((e, c))
-            args.append((Undefined, True))
-            expr = Piecewise(*args)
-    for e, c in expr.args:
+            if not S.Reals.is_subset(covered):
+                args.append((Undefined, True))
+    if args is None:
+        args = list(expr.args)
+        for i in range(len(args)):
+            e, c  = args[i]
+            if isinstance(c, Basic):
+                c = simplify(c, **kwargs)
+            args[i] = (e, c)
+
+    # simplify expressions
+    doit = kwargs.pop('doit', None)
+    for i in range(len(args)):
+        e, c  = args[i]
         if isinstance(e, Basic):
-            doit = kwargs.pop('doit', None)
             # Skip doit to avoid growth at every call for some integrals
             # and sums, see sympy/sympy#17165
             newe = simplify(e, doit=False, **kwargs)
-            if newe != expr:
+            if newe != e:
                 e = newe
-        if isinstance(c, Basic):
-            c = simplify(c, doit=doit, **kwargs)
-        args.append((e, c))
+        args[i] = (e, c)
+
+    # restore kwargs flag
+    if doit is not None:
+        kwargs['doit'] = doit
+
     return Piecewise(*args)
 
 


Test patch:
diff --git a/sympy/functions/elementary/tests/test_piecewise.py b/sympy/functions/elementary/tests/test_piecewise.py
index ae9038c319..dcff2263f4 100644
--- a/sympy/functions/elementary/tests/test_piecewise.py
+++ b/sympy/functions/elementary/tests/test_piecewise.py
@@ -2,6 +2,7 @@
 from sympy.core.add import Add
 from sympy.core.basic import Basic
 from sympy.core.containers import Tuple
+from sympy.core.expr import unchanged
 from sympy.core.function import (Function, diff, expand)
 from sympy.core.mul import Mul
 from sympy.core.numbers import (Float, I, Rational, oo, pi, zoo)
@@ -12,22 +13,20 @@
 from sympy.functions.elementary.complexes import (Abs, adjoint, arg, conjugate, im, re, transpose)
 from sympy.functions.elementary.exponential import (exp, log)
 from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt)
-from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold)
+from sympy.functions.elementary.piecewise import (Piecewise,
+    piecewise_fold, Undefined, ExprCondPair)
 from sympy.functions.elementary.trigonometric import (cos, sin)
 from sympy.functions.special.delta_functions import (DiracDelta, Heaviside)
 from sympy.functions.special.tensor_functions import KroneckerDelta
 from sympy.integrals.integrals import (Integral, integrate)
 from sympy.logic.boolalg import (And, ITE, Not, Or)
 from sympy.matrices.expressions.matexpr import MatrixSymbol
+from sympy.printing import srepr
 from sympy.sets.contains import Contains
 from sympy.sets.sets import Interval
 from sympy.solvers.solvers import solve
-from sympy.utilities.lambdify import lambdify
-from sympy.core.expr import unchanged
-from sympy.functions.elementary.piecewise import Undefined, ExprCondPair
-from sympy.printing import srepr
 from sympy.testing.pytest import raises, slow
-from sympy.simplify import simplify
+from sympy.utilities.lambdify import lambdify
 
 a, b, c, d, x, y = symbols('a:d, x, y')
 z = symbols('z', nonzero=True)
@@ -480,6 +479,56 @@ def test_piecewise_simplify():
     expr = Piecewise((-d + 2*n, Eq(1/t, 1)), (t**(1 - 4*n)*t**(4*n - 1)*(-d + 2*n), True))
     assert expr.simplify() == -d + 2*n
 
+    # issue 22747
+    p = Piecewise((0, (t < -2) & (t < -1) & (t < 0)), ((t/2 + 1)*(t +
+        1)*(t + 2), (t < -1) & (t < 0)), ((S.Half - t/2)*(1 - t)*(t + 1),
+        (t < -2) & (t < -1) & (t < 1)), ((t + 1)*(-t*(t/2 + 1) + (S.Half
+        - t/2)*(1 - t)), (t < -2) & (t < -1) & (t < 0) & (t < 1)), ((t +
+        1)*((S.Half - t/2)*(1 - t) + (t/2 + 1)*(t + 2)), (t < -1) & (t <
+        1)), ((t + 1)*(-t*(t/2 + 1) + (S.Half - t/2)*(1 - t)), (t < -1) &
+        (t < 0) & (t < 1)), (0, (t < -2) & (t < -1)), ((t/2 + 1)*(t +
+        1)*(t + 2), t < -1), ((t + 1)*(-t*(t/2 + 1) + (S.Half - t/2)*(t +
+        1)), (t < 0) & ((t < -2) | (t < 0))), ((S.Half - t/2)*(1 - t)*(t
+        + 1), (t < 1) & ((t < -2) | (t < 1))), (0, True)) + Piecewise((0,
+        (t < -1) & (t < 0) & (t < 1)), ((1 - t)*(t/2 + S.Half)*(t + 1),
+        (t < 0) & (t < 1)), ((1 - t)*(1 - t/2)*(2 - t), (t < -1) & (t <
+        0) & (t < 2)), ((1 - t)*((1 - t)*(t/2 + S.Half) + (1 - t/2)*(2 -
+        t)), (t < -1) & (t < 0) & (t < 1) & (t < 2)), ((1 - t)*((1 -
+        t/2)*(2 - t) + (t/2 + S.Half)*(t + 1)), (t < 0) & (t < 2)), ((1 -
+        t)*((1 - t)*(t/2 + S.Half) + (1 - t/2)*(2 - t)), (t < 0) & (t <
+        1) & (t < 2)), (0, (t < -1) & (t < 0)), ((1 - t)*(t/2 +
+        S.Half)*(t + 1), t < 0), ((1 - t)*(t*(1 - t/2) + (1 - t)*(t/2 +
+        S.Half)), (t < 1) & ((t < -1) | (t < 1))), ((1 - t)*(1 - t/2)*(2
+        - t), (t < 2) & ((t < -1) | (t < 2))), (0, True))
+    assert p.simplify() == Piecewise(
+        (0, t < -2), ((t + 1)*(t + 2)**2/2, t < -1), (-3*t**3/2
+        - 5*t**2/2 + 1, t < 0), (3*t**3/2 - 5*t**2/2 + 1, t < 1), ((1 -
+        t)*(t - 2)**2/2, t < 2), (0, True))
+
+    # coverage
+    nan = Undefined
+    covered = Piecewise((1, x > 3), (2, x < 2), (3, x > 1))
+    assert covered.simplify().args  == covered.args
+    assert Piecewise((1, x < 2), (2, x < 1), (3, True)).simplify(
+        ) == Piecewise((1, x < 2), (3, True))
+    assert Piecewise((1, x > 2)).simplify() == Piecewise((1, x > 2),
+        (nan, True))
+    assert Piecewise((1, (x >= 2) & (x < oo))
+        ).simplify() == Piecewise((1, (x >= 2) & (x < oo)), (nan, True))
+    assert Piecewise((1, x < 2), (2, (x > 1) & (x < 3)), (3, True)
+        ). simplify() == Piecewise((1, x < 2), (2, x < 3), (3, True))
+    assert Piecewise((1, x < 2), (2, (x <= 3) & (x > 1)), (3, True)
+        ).simplify() == Piecewise((1, x < 2), (2, x <= 3), (3, True))
+    assert Piecewise((1, x < 2), (2, (x > 2) & (x < 3)), (3, True)
+        ).simplify() == Piecewise((1, x < 2), (2, (x > 2) & (x < 3)),
+        (3, True))
+    assert Piecewise((1, x < 2), (2, (x >= 1) & (x <= 3)), (3, True)
+        ).simplify() == Piecewise((1, x < 2), (2, x <= 3), (3, True))
+    assert Piecewise((1, x < 1), (2, (x >= 2) & (x <= 3)), (3, True)
+        ).simplify() == Piecewise((1, x < 1), (2, (x >= 2) & (x <= 3)),
+        (3, True))
+
+
 def test_piecewise_solve():
     abs2 = Piecewise((-x, x <= 0), (x, x > 0))
     f = abs2.subs(x, x - 2)
@@ -1402,7 +1451,7 @@ def test_issue_20360():
     n = symbols("n", integer=True)
     lam = pi * (n - S.Half)
     eq = integrate(exp(lam * tau), (tau, 0, t))
-    assert simplify(eq) == (2*exp(pi*t*(2*n - 1)/2) - 2)/(pi*(2*n - 1))
+    assert eq.simplify() == (2*exp(pi*t*(2*n - 1)/2) - 2)/(pi*(2*n - 1))
 
 
 def test_piecewise_eval():