Repository: sympy/sympy

Issue:
Fix issue in Piecewise simplification
There is an issue introduced in #15898 that is fixed in #15929 that should make into 1.4 I will try to provide a PR to the 1.4 branch this weekend. (I probably won't have time to refactor #15929 based on the suggestions and that may possibly not count as minor fixes to introduce at this stage...)

Unfortunately, I didn't notice the release plans until today.

Discussion:

Patch:
diff --git a/sympy/functions/elementary/piecewise.py b/sympy/functions/elementary/piecewise.py
index 2c34fb863b..edd4dabd59 100644
--- a/sympy/functions/elementary/piecewise.py
+++ b/sympy/functions/elementary/piecewise.py
@@ -3,7 +3,6 @@
 from sympy.core import Basic, S, Function, diff, Tuple, Dummy, Symbol
 from sympy.core.basic import as_Basic
 from sympy.core.compatibility import range
-from sympy.core.function import UndefinedFunction
 from sympy.core.numbers import Rational, NumberSymbol
 from sympy.core.relational import (Equality, Unequality, Relational,
     _canonical)
@@ -325,6 +324,9 @@ def doit(self, **hints):
     def _eval_simplify(self, ratio, measure, rational, inverse):
         args = [a._eval_simplify(ratio, measure, rational, inverse)
             for a in self.args]
+        _blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and (
+            getattr(e.rhs, '_diff_wrt', None) or
+            isinstance(e.rhs, (Rational, NumberSymbol)))
         for i, (expr, cond) in enumerate(args):
             # try to simplify conditions and the expression for
             # equalities that are part of the condition, e.g.
@@ -342,23 +344,18 @@ def _eval_simplify(self, ratio, measure, rational, inverse):
                 for j, e in enumerate(eqs):
                     # these blessed lhs objects behave like Symbols
                     # and the rhs are simple replacements for the "symbols"
-                    if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \
-                        isinstance(e.rhs,
-                            (Rational, NumberSymbol,
-                            Symbol, UndefinedFunction)):
+                    if _blessed(e):
                         expr = expr.subs(*e.args)
                         eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]]
                         other = [ei.subs(*e.args) for ei in other]
                 cond = And(*(eqs + other))
                 args[i] = args[i].func(expr, cond)
-        # See if expressions valid for a single point happens to evaluate
+        # See if expressions valid for an Equal expression happens to evaluate
         # to the same function as in the next piecewise segment, see:
         # https://github.com/sympy/sympy/issues/8458
         prevexpr = None
         for i, (expr, cond) in reversed(list(enumerate(args))):
             if prevexpr is not None:
-                _prevexpr = prevexpr
-                _expr = expr
                 if isinstance(cond, And):
                     eqs, other = sift(cond.args,
                         lambda i: isinstance(i, Equality), binary=True)
@@ -366,20 +363,24 @@ def _eval_simplify(self, ratio, measure, rational, inverse):
                     eqs, other = [cond], []
                 else:
                     eqs = other = []
-                if eqs:
+                _prevexpr = prevexpr
+                _expr = expr
+                if eqs and not other:
                     eqs = list(ordered(eqs))
-                    for j, e in enumerate(eqs):
+                    for e in eqs:
                         # these blessed lhs objects behave like Symbols
                         # and the rhs are simple replacements for the "symbols"
-                        if isinstance(e.lhs, (Symbol, UndefinedFunction)) and \
-                            isinstance(e.rhs,
-                                (Rational, NumberSymbol,
-                                Symbol, UndefinedFunction)):
+                        if _blessed(e):
                             _prevexpr = _prevexpr.subs(*e.args)
                             _expr = _expr.subs(*e.args)
+                # Did it evaluate to the same?
                 if _prevexpr == _expr:
+                    # Set the expression for the Not equal section to the same
+                    # as the next. These will be merged when creating the new
+                    # Piecewise
                     args[i] = args[i].func(args[i+1][0], cond)
                 else:
+                    # Update the expression that we compare against
                     prevexpr = expr
             else:
                 prevexpr = expr


Test patch:
diff --git a/sympy/functions/elementary/tests/test_piecewise.py b/sympy/functions/elementary/tests/test_piecewise.py
index e80325eff4..36b3462d1b 100644
--- a/sympy/functions/elementary/tests/test_piecewise.py
+++ b/sympy/functions/elementary/tests/test_piecewise.py
@@ -3,7 +3,7 @@
     Integral, integrate, Interval, lambdify, log, Max, Min, oo, Or, pi,
     Piecewise, piecewise_fold, Rational, solve, symbols, transpose,
     cos, sin, exp, Abs, Ne, Not, Symbol, S, sqrt, Tuple, zoo,
-    factor_terms, DiracDelta, Heaviside, Add, Mul, factorial)
+    factor_terms, DiracDelta, Heaviside, Add, Mul, factorial, Ge)
 from sympy.printing import srepr
 from sympy.utilities.pytest import raises, slow
 
@@ -433,6 +433,18 @@ def test_piecewise_simplify():
             (2*x, And(Eq(a, 0), Eq(y, 0))),
             (2, And(Eq(a, 1), Eq(y, 0))),
             (0, True))
+    args = (2, And(Eq(x, 2), Ge(y ,0))), (x, True)
+    assert Piecewise(*args).simplify() == Piecewise(*args)
+    args = (1, Eq(x, 0)), (sin(x)/x, True)
+    assert Piecewise(*args).simplify() == Piecewise(*args)
+    assert Piecewise((2 + y, And(Eq(x, 2), Eq(y, 0))), (x, True)
+        ).simplify() == x
+    # check that x or f(x) are recognized as being Symbol-like for lhs
+    args = Tuple((1, Eq(x, 0)), (sin(x) + 1 + x, True))
+    ans = x + sin(x) + 1
+    f = Function('f')
+    assert Piecewise(*args).simplify() == ans
+    assert Piecewise(*args.subs(x, f(x))).simplify() == ans.subs(x, f(x))
 
 
 def test_piecewise_solve():