Repository: sympy/sympy
Pull request: solveset modifications for use of Piecewise
<!-- 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://github.com/blog/1506-closing-issues-via-pull-requests .-->
fixes #14223 
fixes #10158
fixes #14238
closes #13913 as an alternative
do not address #10532 which is a WIP at #10751

#### Brief description of what is fixed or changed


Rather than modifying sovleset to recognize functions which can be written in terms of Piecewise, the `f` passed to `solveset` is now rewritten unconditionally in terms of Piecewise. In addition:

- Piecewise needed modification to pay attention to the given domain when computing the sets in which the expressions are valid
- solving of Relational (Eq and otherwise) was moved to `_solveset` since solution of Piecewise which uses `_solveset` can entail solving for expressions that are Relational (Eq and otherwise)
- the solution of 0 is excluded from `_invert` if it is not in the domain

#### Other comments

There is an asymmetry in how `solveset_real` and `invert_real` behave that I suspect exists because unlike `solveset`, the name `invert` was already taken by `sympy.polys.polytools.invert`. Whereas `solveset_real` doesn't accept a domain, `invert_real` does and simply passes it along to `_invert` so if one uses a domain other than S.Reals it doesn't matter if `invert_complex` (alias for `_invert`) or `invert_real` is called with that domain.

- [ ] check that the solution of Abs(Max(Abs(x-1),2-x))-3 is {-1, 4} and guard all Abs from replacements or perhaps only do so if the folded expression's symbol-dependent part is Abs. Maybe this for guarding Abs:

```
    mask = [Tuple(*i) for i in zip(ordered(f.atoms(Abs)), numbered_symbols('a', cls=Dummy)))]
    for i in range(len(mask)):
        for j in range(i + 1, len(mask)):
            mask[j] = mask[j].replace(*mask[i])
    for o, n in mask:
        f = f.replace(o, n)
    f = f.rewrite(Piecewise) # everything that's not in Abs
    for o, n in reversed(mask):
        # everything *in* abs
        f.replace(n, o.args[0].func(o.args[0].rewrite(Piecewise)))
    f = piecewise_fold(f)
```

Problem statement:
solve(x*Max(x, 15) - 10, x) doesn't work
`solve(x*Max(x, 15) - 10, x)` gives NotImplementedError. `solveset` can't solve it either. 

I think to solve equations like this, one should replace the Max with each argument and solve independently, then backtrack and remove any solutions where the Max argument is not actually the maximum. For instance, in this case, one would solve `x**2 - 10` and `x*15 - 10`. The first one gives `[sqrt(10), -sqrt(10)]`, but both of those solutions are invalid because they aren't the maximum in `Max(x, 15)`. The second gives `2/3`, which is the correct solution. 


solveset needs to clip solution with constraining set
When a Piecewise is being solved by solveset, a check is not made that the solution is within the conditions under which the expression is constrained. This leads to spurious solutions as shown below. The solution should be clipped by the constraining set:

```diff
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 1ce7e20..0595502 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -797,7 +797,7 @@ def _solveset(f, symbol, domain, _check=False):
                 in_set = in_set.as_set()
             if in_set.is_Interval:
                 dom -= in_set
-            solns = solver(expr, symbol, in_set)
+            solns = solver(expr, symbol, in_set) & in_set
             result += solns
     else:
         lhs, rhs_s = inverter(f, 0, symbol)
```

current behavior

```python
>>> solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x,S.Reals)
{-1, 0, 1}
```
after fix
```python
>>> solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x,S.Reals)
{-1, 1}
```

On review of the situation, there should be no need for clipping the result since the domain passed to `solver` was already identified as `in_set`. Perhaps the following is better:

```diff
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 1ce7e20..ca3f6ad 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -848,13 +848,20 @@ def _solveset(f, symbol, domain, _check=False):
             # -- leave it alone
             return result

-        # whittle away all but the symbol-containing core
-        # to use this for testing
-        fx = orig_f.as_independent(symbol, as_Add=True)[1]
-        fx = fx.as_independent(symbol, as_Add=False)[1]
+        ### XXX improve this. result & domain should just work
+        # but consider the current state where an ImageSet doesn't
+        # know how to intersect with Complexes:
+        # >>> solveset(exp(x) - 1, x)
+        # Intersection(ImageSet(Lambda(_n, 2*_n*I*pi), S.Integers), Complexes(S.Reals x S.Reals, False))
+        if isinstance(result, (Interval, FiniteSet)):
+            result = result & domain

         if isinstance(result, FiniteSet):
             # check the result for invalid solutions
+            # whittle away all but the symbol-containing core
+            # to use this for testing
+            fx = orig_f.as_independent(symbol, as_Add=True)[1]
+            fx = fx.as_independent(symbol, as_Add=False)[1]
             result = FiniteSet(*[s for s in result
                       if isinstance(s, RootOf)
                       or domain_check(fx, symbol, s)])
```

recursion error when doing subs with Piecewise
Ignore the semantics...I've tried to pare it down to a minimal working example.
```
>>> var('r')
>>> eq=Abs(r + Abs(r + 1))
>>> a=Abs(r + 1)
>>> eq.subs(a, Piecewise((0, r > 0), (-r+1, True)))
Abs(r + Piecewise((0, r > 0), (-r + 1, True)))

>>> var('r',real=True)
>>> p = Piecewise((0, r > 0), (-r+1, True))
>>> eq = Abs(1+abs(r+1))
>>> a = Abs(r + 1)
>>> eq.subs(a, p)
Piecewise((0, r > 0), (-r + 1, True)) + 1

>>> eq=Abs(r + Abs(r + 1))
>>> eq.subs(a, p)
...
RecursionError: maximum recursion depth exceeded while calling a Python object
```

Hints from issue discussion:
Perhaps rewriting as a Piecewise and resubmitting to `solve` rather than handling the solution in-place would be a way to go? The `piecewise_fold`would then take care of multiple Max/Min in the expression

`x*Max(x, 15) - Max(x, 10)` -> `Piecewise ((x**2, x>15), (15, True)) - Piecewise ((x, x>10), (10, True))` for which sympy says the solution is 15.

Yep, this seems to work

```
In [4]: solve(x*Piecewise((x, x >=15), (15, x < 15)) - 10, x)
Out[4]: [2/3]
```

So I guess the algorithm I hinted at is already implemented for Piecewise. You don't need to fold to solve multiple Piecewise, by the way, just check every combination of Piecewise args. 

It looks like Max.rewrite(Piecewise) isn't implemented. 

By the way, this is from http://stackoverflow.com/q/33803806/161801.

> You don't need to fold to solve multiple Piecewise, by the way, just check every combination of Piecewise args

That is now what `piecewise_fold` does.
I will take this up.
@smichr I tried this with the modifications in PR #13913 and it gives the correct result.
```
>>> solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x , S.Reals)  
{-1, 1}
```
@Yathartha22 , how about adding the diff as a PR to @ishanaj 's #13913?

@ishanaj , how about the following?

```
solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x , Interval(0,2))
```

The 2nd diff that I showed above should really be applied. It would be a good exercise of collaboration if @Yathartha22 made that PR to yours. I will leave another note on your PR regarding your work on Min/Max and Piecewise rewriting.
 another change in behaviour i think:
before
```
>>> solveset(Piecewise((-x,x<0),(4,True)),x ,S.Reals)
{0}
```
after
```
>>> solveset(Piecewise((-x,x<0),(4,True)),x ,S.Reals)
EmptySet()
```

>  how about adding the diff as a PR to @ishanaj 's #13913?

That will be a good idea, because this issue seems in accordance with the PR.
```
>>> solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x , Interval(0,2))
{-1, 1}    #as per PR #13913
```
>It would be a good exercise of collaboration if @Yathartha22 made that PR to yours.

Yeah, this seems cool!!!
That result should not include that -1...I think that is the part the @Yathartha22 could work out.

```
>>> solveset((Abs(x+Min(x,2))-2).rewrite(Piecewise),x , Interval(0,2))
{-1, 1}    #as per PR #13913
```

Uggh. Forget the diff and just see why this fails:

```
>>> solveset(x,x,Interval(2,3))
{0}
```
>  just see why this fails:

This is due to [this](https://github.com/sympy/sympy/blob/master/sympy/solvers/solveset.py#L108). The condition evaluates to `True`, since `x1==f_x`. Either we fix it here or have another check before returning the solution in `_solveset`
Alternate to `x_1==f_x`, we should use `(x1 == f_x and f_x != x)`.

Commit message: additional test for non-Expr SymPy object

Patch to review:
diff --git a/sympy/functions/elementary/complexes.py b/sympy/functions/elementary/complexes.py
index f3cc98a232f1..77bc6da274f0 100644
--- a/sympy/functions/elementary/complexes.py
+++ b/sympy/functions/elementary/complexes.py
@@ -499,7 +499,7 @@ def eval(cls, arg):
                 return arg2
         # reject result if all new conjugates are just wrappers around
         # an expression that was already in the arg
-        conj = arg.conjugate()
+        conj = signsimp(arg.conjugate(), evaluate=False)
         new_conj = conj.atoms(conjugate) - arg.atoms(conjugate)
         if new_conj and all(arg.has(i.args[0]) for i in new_conj):
             return
diff --git a/sympy/functions/elementary/piecewise.py b/sympy/functions/elementary/piecewise.py
index 7cb3cec8262e..19dd5190a128 100644
--- a/sympy/functions/elementary/piecewise.py
+++ b/sympy/functions/elementary/piecewise.py
@@ -857,27 +857,32 @@ def __eval_cond(cls, cond):
             except TypeError:
                 pass
 
-    def as_expr_set_pairs(self):
+    def as_expr_set_pairs(self, domain=S.Reals):
         """Return tuples for each argument of self that give
-        the expression and the interval in which it is valid.
+        the expression and the interval in which it is valid
+        which is contained within the given domain.
         If a condition cannot be converted to a set, an error
         will be raised. The variable of the conditions is
         assumed to be real; sets of real values are returned.
 
         Examples
         ========
-        >>> from sympy import Piecewise
+        >>> from sympy import Piecewise, Interval
         >>> from sympy.abc import x
-        >>> Piecewise(
+        >>> p = Piecewise(
         ...     (1, x < 2),
         ...     (2,(x > 0) & (x < 4)),
-        ...     (3, True)).as_expr_set_pairs()
+        ...     (3, True))
+        >>> p.as_expr_set_pairs()
         [(1, Interval.open(-oo, 2)),
          (2, Interval.Ropen(2, 4)),
          (3, Interval(4, oo))]
+        >>> p.as_expr_set_pairs(Interval(0, 3))
+        [(1, Interval.Ropen(0, 2)),
+         (2, Interval(2, 3)), (3, EmptySet())]
         """
         exp_sets = []
-        U = S.Reals
+        U = domain
         for expr, cond in self.args:
             cond_int = U.intersect(cond.as_set())
             U = U - cond_int
diff --git a/sympy/functions/elementary/tests/test_complexes.py b/sympy/functions/elementary/tests/test_complexes.py
index 60127d5b5e57..866846d1d81a 100644
--- a/sympy/functions/elementary/tests/test_complexes.py
+++ b/sympy/functions/elementary/tests/test_complexes.py
@@ -877,3 +877,9 @@ def test_issue_14216():
     A = MatrixSymbol("A", 2, 2)
     assert unpolarify(A[0, 0]) == A[0, 0]
     assert unpolarify(A[0, 0]*A[1, 0]) == A[0, 0]*A[1, 0]
+
+
+def test_issue_14238():
+    # doesn't cause recursion error
+    r = Symbol('r', real=True)
+    assert Abs(r + Piecewise((0, r > 0), (1 - r, True)))
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 1ce7e20f5c4f..dec2bbd8f348 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -11,19 +11,22 @@
 
 from sympy.core.sympify import sympify
 from sympy.core import S, Pow, Dummy, pi, Expr, Wild, Mul, Equality
+from sympy.core.containers import Tuple
+from sympy.core.facts import InconsistentAssumptions
 from sympy.core.numbers import I, Number, Rational, oo
-from sympy.core.function import (Lambda, expand_complex, AppliedUndef)
+from sympy.core.function import (Lambda, expand_complex, AppliedUndef, Function)
 from sympy.core.relational import Eq
 from sympy.core.symbol import Symbol
 from sympy.simplify.simplify import simplify, fraction, trigsimp
 from sympy.functions import (log, Abs, tan, cot, sin, cos, sec, csc, exp,
                              acos, asin, acsc, asec, arg,
-                             piecewise_fold)
+                             piecewise_fold, Piecewise)
 from sympy.functions.elementary.trigonometric import (TrigonometricFunction,
                                                       HyperbolicFunction)
-from sympy.functions.elementary.miscellaneous import real_root
+from sympy.functions.elementary.miscellaneous import real_root, Application
 from sympy.sets import (FiniteSet, EmptySet, imageset, Interval, Intersection,
                         Union, ConditionSet, ImageSet, Complement)
+from sympy.sets.sets import Set
 from sympy.matrices import Matrix
 from sympy.polys import (roots, Poly, degree, together, PolynomialError,
                          RootOf)
@@ -32,12 +35,54 @@
 from sympy.solvers.polysys import solve_poly_system
 from sympy.solvers.inequalities import solve_univariate_inequality
 from sympy.utilities import filldedent
+from sympy.utilities.iterables import numbered_symbols
 from sympy.calculus.util import periodicity, continuous_domain
 from sympy.core.compatibility import ordered, default_sort_key, is_sequence
 
 from types import GeneratorType
 
 
+def _masked(f, *atoms):
+    """Return ``f``, with all objects given by ``atoms`` replaced with
+    Dummy symbols, ``d``, and the list of replacements, ``(d, e)``,
+    where ``e`` is an object of type given by ``atoms`` in which
+    any other instances of atoms have been recursively replaced with
+    Dummy symbols, too. The tuples are ordered so that if they are
+    applied in sequence, the orgin ``f`` will be restored.
+
+    Examples
+    ========
+
+    >>> from sympy import cos
+    >>> from sympy.abc import x
+    >>> from sympy.solvers.solveset import _masked
+
+    >>> f = cos(cos(x) + 1)
+    >>> f, reps = _masked(cos(1 + cos(x)), cos)
+    >>> f
+    _a1
+    >>> reps
+    [(_a1, cos(_a0 + 1)), (_a0, cos(x))]
+    >>> for d, e in reps:
+    ...     f = f.xreplace({d: e})
+    >>> f
+    cos(cos(x) + 1)
+    """
+    sym = numbered_symbols('a', cls=Dummy)
+    mask = []
+    for a in ordered(f.atoms(*atoms)):
+        for i in mask:
+            a = a.replace(*i)
+        mask.append((a, next(sym)))
+    mask = list(reversed(mask))
+    for i, (o, n) in enumerate(mask):
+        f = f.replace(o, n)
+        mask[i] = (n, o)
+    if mask and f == mask[0][1]:
+        f = mask[0][0]
+    return f, mask
+
+
 def _invert(f_x, y, x, domain=S.Complexes):
     r"""
     Reduce the complex valued equation ``f(x) = y`` to a set of equations
@@ -105,7 +150,7 @@ def _invert(f_x, y, x, domain=S.Complexes):
     else:
         x1, s = _invert_complex(f_x, FiniteSet(y), x)
 
-    if not isinstance(s, FiniteSet) or x1 == f_x or x1 != x:
+    if not isinstance(s, FiniteSet) or x1 != x:
         return x1, s
 
     return x1, s.intersection(domain)
@@ -757,15 +802,9 @@ def _solveset(f, symbol, domain, _check=False):
                               S.NegativeInfinity]):
             f = a/m + h  # XXX condition `m != 0` should be added to soln
 
-    f = piecewise_fold(f)
-
     # assign the solvers to use
     solver = lambda f, x, domain=domain: _solveset(f, x, domain)
-    if domain.is_subset(S.Reals):
-        inverter_func = invert_real
-    else:
-        inverter_func = invert_complex
-    inverter = lambda f, rhs, symbol: inverter_func(f, rhs, symbol, domain)
+    inverter = lambda f, rhs, symbol: _invert(f, rhs, symbol, domain)
 
     result = EmptySet()
 
@@ -789,16 +828,29 @@ def _solveset(f, symbol, domain, _check=False):
         a = f.args[0]
         result = solveset_real(a > 0, symbol)
     elif f.is_Piecewise:
-        dom = domain
         result = EmptySet()
-        expr_set_pairs = f.as_expr_set_pairs()
+        # the conditions can only be solved on the real domain
+        expr_set_pairs = f.as_expr_set_pairs(domain & S.Reals)
         for (expr, in_set) in expr_set_pairs:
             if in_set.is_Relational:
                 in_set = in_set.as_set()
-            if in_set.is_Interval:
-                dom -= in_set
             solns = solver(expr, symbol, in_set)
             result += solns
+    elif isinstance(f, Eq):
+        from sympy.core import Add
+        result = solver(Add(f.lhs, - f.rhs, evaluate=False), symbol, domain)
+    elif f.is_Relational:
+        if not domain.is_subset(S.Reals):
+            raise NotImplementedError(filldedent('''
+                Inequalities in the complex domain are
+                not supported. Try the real domain by
+                setting domain=S.Reals'''))
+        try:
+            result = solve_univariate_inequality(
+            f, symbol, domain=domain, relational=False)
+        except NotImplementedError:
+            result = ConditionSet(symbol, f, domain)
+        return result
     else:
         lhs, rhs_s = inverter(f, 0, symbol)
         if lhs == symbol:
@@ -961,6 +1013,7 @@ def solveset(f, symbol=None, domain=S.Complexes):
 
     """
     f = sympify(f)
+    symbol = sympify(symbol)
 
     if f is S.true:
         return domain
@@ -969,10 +1022,26 @@ def solveset(f, symbol=None, domain=S.Complexes):
         return S.EmptySet
 
     if not isinstance(f, (Expr, Number)):
-        raise ValueError("%s is not a valid SymPy expression" % (f))
+        raise ValueError("%s is not a valid SymPy expression" % f)
+
+    if not isinstance(symbol, Expr) and  symbol is not None:
+        raise ValueError("%s is not a valid SymPy symbol" % symbol)
+
+    if not isinstance(domain, Set):
+        raise ValueError("%s is not a valid domain" %(domain))
 
     free_symbols = f.free_symbols
 
+    if symbol is None and not free_symbols:
+        b = Eq(f, 0)
+        if b is S.true:
+            return domain
+        elif b is S.false:
+            return S.EmptySet
+        else:
+            raise NotImplementedError(filldedent('''
+                relationship between value and 0 is unknown: %s''' % b))
+
     if symbol is None:
         if len(free_symbols) == 1:
             symbol = free_symbols.pop()
@@ -985,31 +1054,29 @@ def solveset(f, symbol=None, domain=S.Complexes):
         # the xreplace will be needed if a ConditionSet is returned
         return solveset(f[0], s[0], domain).xreplace(swap)
 
-    elif not free_symbols:
-        b = Eq(f, 0)
-        if b is S.true:
-            return domain
-        elif b is S.false:
-            return S.EmptySet
-        else:
-            raise NotImplementedError(filldedent('''
-                relationship between value and 0 is unknown: %s''' % b))
-
-    if isinstance(f, Eq):
-        from sympy.core import Add
-        f = Add(f.lhs, - f.rhs, evaluate=False)
-    elif f.is_Relational:
-        if not domain.is_subset(S.Reals):
-            raise NotImplementedError(filldedent('''
-                Inequalities in the complex domain are
-                not supported. Try the real domain by
-                setting domain=S.Reals'''))
-        try:
-            result = solve_univariate_inequality(
-            f, symbol, domain=domain, relational=False)
-        except NotImplementedError:
-            result = ConditionSet(symbol, f, domain)
-        return result
+    if domain.is_subset(S.Reals):
+        if not symbol.is_real:
+            assumptions = symbol.assumptions0
+            assumptions['real'] = True
+            try:
+                r = Dummy('r', **assumptions)
+                return solveset(f.xreplace({symbol: r}), r, domain
+                    ).xreplace({r: symbol})
+            except InconsistentAssumptions:
+                pass
+    # Abs has its own handling method which avoids the
+    # rewriting property that the first piece of abs(x)
+    # is for x >= 0 and the 2nd piece for x < 0 -- solutions
+    # can look better if the 2nd condition is x <= 0. Since
+    # the solution is a set, duplication of results is not
+    # an issue, e.g. {y, -y} when y is 0 will be {0}
+    f, mask = _masked(f, Abs)
+    f = f.rewrite(Piecewise) # everything that's not an Abs
+    for d, e in mask:
+        # everything *in* an Abs
+        e = e.func(e.args[0].rewrite(Piecewise))
+        f = f.xreplace({d: e})
+    f = piecewise_fold(f)
 
     return _solveset(f, symbol, domain, _check=True)
 
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 03eb18c3c5e5..607cb85b09ee 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -9,7 +9,7 @@
 from sympy.functions.elementary.exponential import (LambertW, exp, log)
 from sympy.functions.elementary.hyperbolic import (HyperbolicFunction,
     atanh, sinh, tanh)
-from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.miscellaneous import sqrt, Min, Max
 from sympy.functions.elementary.piecewise import Piecewise
 from sympy.functions.elementary.trigonometric import (
     TrigonometricFunction, acos, acot, acsc, asec, asin, atan, atan2,
@@ -56,6 +56,9 @@ def test_invert_real():
     def ireal(x, s=S.Reals):
         return Intersection(s, x)
 
+    # issue 14223
+    assert invert_real(x, 0, x, Interval(1, 2)) == (x, S.EmptySet)
+
     minus_n = Intersection(Interval(-oo, 0), FiniteSet(-n))
     plus_n = Intersection(Interval(0, oo), FiniteSet(n))
     assert solveset(abs(x) - n, x, S.Reals) == Union(minus_n, plus_n)
@@ -189,10 +192,12 @@ def test_domain_check():
     assert domain_check(x, x, oo) is False
     assert domain_check(0, x, oo) is False
 
+
 def test_issue_11536():
     assert solveset(0**x - 100, x, S.Reals) == S.EmptySet
     assert solveset(0**x - 1, x, S.Reals) == FiniteSet(0)
 
+
 def test_is_function_class_equation():
     from sympy.abc import x, a
     assert _is_function_class_equation(TrigonometricFunction,
@@ -263,6 +268,10 @@ def test_garbage_input():
     raises(ValueError, lambda: solveset_complex([x], x))
     assert solveset_complex(x, pi) == S.EmptySet
 
+    raises(ValueError, lambda: solveset((x, y), x))
+    raises(ValueError, lambda: solveset(x + 1, S.Reals))
+    raises(ValueError, lambda: solveset(x + 1, x, 2))
+
 
 def test_solve_mul():
     assert solveset_real((a*x + b)*(exp(x) - 3), x) == \
@@ -278,7 +287,9 @@ def test_solve_invert():
     assert solveset_real(3**(x + 2), x) == FiniteSet()
     assert solveset_real(3**(2 - x), x) == FiniteSet()
 
-    assert solveset_real(y - b*exp(a/x), x) == Intersection(S.Reals, FiniteSet(a/log(y/b)))
+    assert solveset_real(y - b*exp(a/x), x) == Intersection(
+        S.Reals, FiniteSet(a/log(y/b)))
+
     # issue 4504
     assert solveset_real(2**x - 10, x) == FiniteSet(log(10)/log(2))
 
@@ -652,7 +663,7 @@ def test_atan2():
     assert solveset_real(atan2(x, 2) - pi/3, x) == FiniteSet(2*sqrt(3))
 
 
-def test_piecewise():
+def test_piecewise_solveset():
     eq = Piecewise((x - 2, Gt(x, 2)), (2 - x, True)) - 3
     assert set(solveset_real(eq, x)) == set(FiniteSet(-1, 5))
 
@@ -793,10 +804,11 @@ def test_solve_trig():
                   9*sqrt(57))**(S(1)/3))/(3*(67 + 9*sqrt(57))**(S(1)/6))) +
                   2*pi), S.Integers))
 
-    assert solveset_real(2*tan(x)*sin(x) + 1, x) == \
-        Union(ImageSet(Lambda(n, 2*n*pi + 2*atan(sqrt(4
-                + sqrt(17)))), S.Integers), ImageSet(Lambda(n, 2*n*pi
-                - 2*atan(sqrt(4 + sqrt(17))) + 2*pi), S.Integers))
+    assert solveset_real(2*tan(x)*sin(x) + 1, x) == Union(
+        ImageSet(Lambda(n, 2*n*pi + atan(sqrt(2)*sqrt(-1 + sqrt(17))/
+            (-sqrt(17) + 1)) + pi), S.Integers),
+        ImageSet(Lambda(n, 2*n*pi - atan(sqrt(2)*sqrt(-1 + sqrt(17))/
+            (-sqrt(17) + 1)) + pi), S.Integers))
 
     assert solveset_real(cos(2*x)*cos(4*x) - 1, x) == \
                             ImageSet(Lambda(n, n*pi), S.Integers)
@@ -958,17 +970,17 @@ def test_conditionset():
     assert solveset(Eq(sin(x)**2 + cos(x)**2, 1), x, domain=S.Reals) == \
         ConditionSet(x, True, S.Reals)
 
-    assert solveset(Eq(x**2 + x*sin(x), 1), x, domain=S.Reals) == \
-        ConditionSet(x, Eq(x*(x + sin(x)) - 1, 0), S.Reals)
+    assert solveset(Eq(x**2 + x*sin(x), 1), x, domain=S.Reals
+        ) == ConditionSet(x, Eq(x*(x + sin(x)) - 1, 0), S.Reals)
 
-    assert solveset(Eq(sin(Abs(x)), x), x, domain=S.Reals) == \
-        ConditionSet(x, Eq(-x + sin(Abs(x)), 0), Interval(-oo, oo))
+    assert solveset(Eq(-I*(exp(I*x) - exp(-I*x))/2, 1), x
+        ) == imageset(Lambda(n, 2*n*pi + pi/2), S.Integers)
 
-    assert solveset(Eq(-I*(exp(I*x) - exp(-I*x))/2, 1), x) == \
-        imageset(Lambda(n, 2*n*pi + pi/2), S.Integers)
+    assert solveset(x + sin(x) > 1, x, domain=S.Reals
+        ) == ConditionSet(x, x + sin(x) > 1, S.Reals)
 
-    assert solveset(x + sin(x) > 1, x, domain=S.Reals) == \
-        ConditionSet(x, x + sin(x) > 1, S.Reals)
+    assert solveset(Eq(sin(Abs(x)), x), x, domain=S.Reals
+        ) == ConditionSet(x, Eq(-x + sin(Abs(x)), 0), S.Reals)
 
 
 @XFAIL
@@ -988,9 +1000,8 @@ def test_solveset_domain():
 def test_improve_coverage():
     from sympy.solvers.solveset import _has_rational_power
     x = Symbol('x')
-    y = exp(x+1/x**2)
-    solution = solveset(y**2+y, x, S.Reals)
-    unsolved_object = ConditionSet(x, Eq((exp((x**3 + 1)/x**2) + 1)*exp((x**3 + 1)/x**2), 0), S.Reals)
+    solution = solveset(exp(x) + sin(x), x, S.Reals)
+    unsolved_object = ConditionSet(x, Eq(exp(x) + sin(x), 0), S.Reals)
     assert solution == unsolved_object
 
     assert _has_rational_power(sin(x)*exp(x) + 1, x) == (False, S.One)
@@ -1676,6 +1687,7 @@ def test__is_finite_with_finite_vars():
         Dummy(), Dummy(real=True), Dummy(complex=True)))
     assert f(1/Dummy(real=False)) is True  # b/c it's finite but not 0
 
+
 def test_issue_13550():
     assert solveset(x**2 - 2*x - 15, symbol = x, domain = Interval(-oo, 0)) == FiniteSet(-3)
 
@@ -1683,3 +1695,35 @@ def test_issue_13550():
 def test_issue_13849():
     t = symbols('t')
     assert nonlinsolve((t*(sqrt(5) + sqrt(2)) - sqrt(2), t), t) == EmptySet()
+
+
+def test_issue_14223():
+    x = Symbol('x')
+    assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x,
+        S.Reals) == FiniteSet(-1, 1)
+    assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x,
+        Interval(0, 2)) == FiniteSet(1)
+
+
+def test_issue_10158():
+    x = Symbol('x')
+    dom = S.Reals
+    assert solveset(x*Max(x, 15) - 10, x, dom) == FiniteSet(2/S(3))
+    assert solveset(x*Min(x, 15) - 10, x, dom) == FiniteSet(-sqrt(10), sqrt(10))
+    assert solveset(Max(Abs(x - 3) - 1, x + 2) - 3, x, dom) == FiniteSet(-1, 1)
+    assert solveset(Abs(x - 1) - Abs(y), x, dom) == FiniteSet(-Abs(y) + 1, Abs(y) + 1)
+    assert solveset(Abs(x + 4*Abs(x + 1)), x, dom) == FiniteSet(-4/S(3), -4/S(5))
+    dom = S.Complexes
+    assert solveset(x*Max(x, 15) - 10, x, dom) == FiniteSet(2/S(3))
+    assert solveset(x*Min(x, 15) - 10, x, dom) == FiniteSet(
+        -sqrt(10), sqrt(10))
+    # the following gets rewritten to
+    # Piecewise((x - 1, x + 2 >= Abs(x - 3) - 1), (Abs(x - 3) - 4, True))
+    # and the Abs in the relational is solved as though x were real so
+    # should there be a check in Piecewise for Abs in conditions when the
+    # domain is complex?
+    assert solveset(Max(Abs(x - 3) - 1, x + 2) - 3, x, dom) == FiniteSet(-1, 1)
+    raises(ValueError, lambda: solveset(Abs(x - 1) - Abs(y), x, dom))
+    raises(ValueError, lambda: solveset(Abs(x + 4*Abs(x + 1)), x, dom))
+    assert solveset(2*Abs(x + Abs(x + Max(3, x))) - 2, x, S.Reals
+        ) == FiniteSet(-1, -2)


Code review comments:
diff_hunk: @@ -1676,10 +1687,43 @@ def test__is_finite_with_finite_vars():
         Dummy(), Dummy(real=True), Dummy(complex=True)))
     assert f(1/Dummy(real=False)) is True  # b/c it's finite but not 0
 
+
 def test_issue_13550():
     assert solveset(x**2 - 2*x - 15, symbol = x, domain = Interval(-oo, 0)) == FiniteSet(-3)
 
 
 def test_issue_13849():
     t = symbols('t')
     assert nonlinsolve((t*(sqrt(5) + sqrt(2)) - sqrt(2), t), t) == EmptySet()
+
+
+def test_issue_14223():
+    x = Symbol('x')
+    assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x,
+        S.Reals) == FiniteSet(-1, 1)
+    assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x,
+        Interval(0, 2)) == FiniteSet(1)
+
+
+def test_issue_10158():
+    x = Symbol('x')
+    dom = S.Reals
+    assert solveset(x*Max(x, 15) - 10, x, dom) == FiniteSet(2/S(3))
+    assert solveset(x*Min(x, 15) - 10, x, dom) == FiniteSet(-sqrt(10), sqrt(10))
+    assert solveset(Max(Abs(x - 3) - 1, x + 2) - 3, x, dom) == FiniteSet(-1, 1)
+    assert solveset(Abs(x - 1) - Abs(y), x, dom) == FiniteSet(-Abs(y) + 1, Abs(y) + 1)
+    assert solveset(Abs(x + 4*Abs(x + 1)), x, dom) == FiniteSet(-4/S(3), -4/S(5))
+    dom = S.Complexes
+    assert solveset(x*Max(x, 15) - 10, x, dom) == FiniteSet(2/S(3))
+    assert solveset(x*Min(x, 15) - 10, x, dom) == FiniteSet(
+        -sqrt(10), sqrt(10))
+    # the following gets rewritten to
path: sympy/solvers/tests/test_solveset.py
text: @author:
@user1 , do you have any ideas about this situation? If the domain is complex then a bare Abs raises an error about not being inverted in the real domain. But if it appears in the condition of a Piecewise it is solved as though its arg were real.

Also `Max(x, y)` is rewritten assuming that the args are real even though it is not defined for complex values. I'm not sure if that is consistent with `Abs(x)` *not* being rewritten unless x is real (but Abs *is* defined for complex values).

@user1:
>  do you have any ideas about this situation?

What I figured out @author is that this equation gets converted to `Piecewise((x - 1, x + 2 >= Abs(x - 3) - 1), (Abs(x - 3) - 4, True))`, which further divides into `[(x - 1, Interval(0, oo)), (Abs(x - 3) - 4, Interval.open(-oo, 0))]`, now since both these `expr` are in the real domain, for the case of `Abs` _invert_real is called giving the result as {-1}. There is no `ValueError` because `_invert_real` solves it, while in cases for bare Abs in complex domain, there is no implementation in `_invert_complex` therfore they will always go to `_solve_abs` and raise error.

@user1:
> Also Max(x, y) is rewritten assuming that the args are real even though it is not defined for complex values. I'm not sure if that is consistent with Abs(x) not being rewritten unless x is real (but Abs is defined for complex values).

Yes, max or min is rewritten irrespective the args are real or complex [see here](https://github.com/sympy/sympy/blob/master/sympy/functions/elementary/miscellaneous.py#L750), but in case of `Abs` it checks for args to be real, then only rewrites. [see here](https://github.com/sympy/sympy/blob/master/sympy/functions/elementary/complexes.py#L586)

Two things need to be done:

- Restrict rewriting of Min/Max only to real, (can be done in this PR itself)

- Also, Abs should be made to be able to rewrite in complex domain. Atleast solveset should solve Abs in complex domain (should be solved in another PR)

If you agree with the above I would like to make the changes

@author:
It could be just as simple as only solving Piecewise if the conditions don't involve relationals when the variable is not real:
```
>>> solveset(x<1,x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\leslie\sympy\sympy\solvers\solveset.py", line 1006, in solveset

    setting domain=S.Reals'''))
NotImplementedError:
Inequalities in the complex domain are not supported. Try the real
domain by setting domain=S.Reals
>>> solveset(Eq(x,1),x)
{1}
>>> solveset(Eq(x,I),x)
{I}
```

@user1:
> It could be just as simple as only solving Piecewise if the conditions don't involve relationals when the variable is not real:

Rather than raising error for Piecewise let `_solve_abs` handle it. Committed in your repo.

@author:
> If you agree with the above I would like to make the changes

Ok, I will raise an error if an attempt to solve a Piecewise with non-Equality conditions in the complex domain is made. Then this can be committed and your changes can be a separate PR. How does that sound?

@user1:
Sounds great!