Repository: sympy/sympy

Issue:
Piecewise simplification of and/or with re/im
Not sure what's going on here but this Piecewise is simplifying when it should not:
```julia
In [30]: z = Symbol('z')                                                                                                                       

In [31]: Piecewise((1, Or(Eq(im(z), 0), Gt(re(z), 0))), (2, True), evaluate=False)                                                             
Out[31]: 
⎧1  for im(z) = 0 ∨ re(z) > 0
⎨                            
⎩2          otherwise        

In [32]: _.doit()                                                                                                                              
Out[32]: 1
```

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v147). 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
  * `atan2(0, x)` is now correct even if `x` is complex. ([#17278](https://github.com/sympy/sympy/pull/17278) by [@oscargus](https://github.com/oscargus))

  * `Piecewise` conditions with `re` or `im` are no longer broken. ([#17278](https://github.com/sympy/sympy/pull/17278) by [@oscargus](https://github.com/oscargus))

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

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. <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://github.com/blog/1506-closing-issues-via-pull-requests . Please also
    write a comment on that issue linking back to this pull request once it is
    open. -->
    Fixes #16417
    Fixes https://github.com/sympy/sympy/issues/17141#issuecomment-508767611
    Closes #17155 

    #### Brief description of what is fixed or changed
    atan2(0, x) is now properly rewritten, assuming that x may be complex. It is also automatically evaluated to a Piecewise (unless x is extended_real and known to be non-zero). Thanks to @jksuom for suggesting the "correct" value in #17155 .

    Now, there is no recursion error in `simplify((2**acos(I+1)**2).rewrite('log'))` (and probably many other similar expressions).

    #### Other comments
    As part of it I had to fix #16417.

    Also, I realized that `imaginary` is a rather useless property as it cannot be zero. Hence, one more or less must use `I*r` where `r = Symbol('r', real=True)` to be able to e.g. plot something along the imaginary axes.

    #### Release Notes

    <!-- Write the release notes for this release below. 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
       * `atan2(0, x)` is now correct even if `x` is complex.
       * `Piecewise` conditions with `re` or `im` are no longer broken.
    <!-- END RELEASE NOTES -->


</details><p>


Patch:
diff --git a/sympy/functions/elementary/piecewise.py b/sympy/functions/elementary/piecewise.py
index 7503f6fc5b..f459c874da 100644
--- a/sympy/functions/elementary/piecewise.py
+++ b/sympy/functions/elementary/piecewise.py
@@ -160,6 +160,7 @@ def eval(cls, *_args):
         If there is a single arg with a True condition, its
         corresponding expression will be returned.
         """
+        from sympy.functions.elementary.complexes import im, re
 
         if not _args:
             return Undefined
@@ -172,11 +173,12 @@ def eval(cls, *_args):
         # make conditions canonical
         args = []
         for e, c in _args:
-            if not c.is_Atom and not isinstance(c, Relational):
+            if (not c.is_Atom and not isinstance(c, Relational) and
+                not c.has(im, re)):
                 free = c.free_symbols
                 if len(free) == 1:
                     funcs = [i for i in c.atoms(Function)
-                        if not isinstance(i, Boolean)]
+                             if not isinstance(i, Boolean)]
                     if len(funcs) == 1 and len(
                             c.xreplace({list(funcs)[0]: Dummy()}
                             ).free_symbols) == 1:
diff --git a/sympy/functions/elementary/trigonometric.py b/sympy/functions/elementary/trigonometric.py
index 635e7f278d..d0be6e6a7d 100644
--- a/sympy/functions/elementary/trigonometric.py
+++ b/sympy/functions/elementary/trigonometric.py
@@ -2981,7 +2981,7 @@ class atan2(InverseTrigonometricFunction):
 
     >>> from sympy import atan
     >>> atan2(y, x).rewrite(atan)
-    Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), (pi, x < 0), (0, x > 0), (nan, True))
+    Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), (pi, re(x) < 0), (0, Ne(x, 0)), (nan, True))
 
     but note that this form is undefined on the negative real axis.
 
@@ -3029,8 +3029,13 @@ def eval(cls, y, x):
                     return -S.Pi/2
                 elif y.is_zero:
                     return S.NaN
-        if y.is_zero and x.is_extended_real and fuzzy_not(x.is_zero):
-            return S.Pi * (S.One - Heaviside(x))
+        if y.is_zero:
+            if x.is_extended_nonzero:
+                return S.Pi * (S.One - Heaviside(x))
+            if x.is_number:
+                return Piecewise((S.Pi, re(x) < 0),
+                                 (0, Ne(x, 0)),
+                                 (S.NaN, True))
         if x.is_number and y.is_number:
             return -S.ImaginaryUnit*log(
                 (x + S.ImaginaryUnit*y)/sqrt(x**2 + y**2))
@@ -3039,16 +3044,19 @@ def _eval_rewrite_as_log(self, y, x, **kwargs):
         return -S.ImaginaryUnit*log((x + S.ImaginaryUnit*y) / sqrt(x**2 + y**2))
 
     def _eval_rewrite_as_atan(self, y, x, **kwargs):
-        return Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), (pi, x < 0), (0, x > 0), (S.NaN, True))
+        from sympy import re
+        return Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)),
+                         (pi, re(x) < 0),
+                         (0, Ne(x, 0)),
+                         (S.NaN, True))
 
     def _eval_rewrite_as_arg(self, y, x, **kwargs):
         from sympy import arg
         if x.is_extended_real and y.is_extended_real:
             return arg(x + y*S.ImaginaryUnit)
-        I = S.ImaginaryUnit
-        n = x + I*y
+        n = x + S.ImaginaryUnit*y
         d = x**2 + y**2
-        return arg(n/sqrt(d)) - I*log(abs(n)/sqrt(abs(d)))
+        return arg(n/sqrt(d)) - S.ImaginaryUnit*log(abs(n)/sqrt(abs(d)))
 
     def _eval_is_extended_real(self):
         return self.args[0].is_extended_real and self.args[1].is_extended_real


Test patch:
diff --git a/sympy/core/tests/test_power.py b/sympy/core/tests/test_power.py
index bfd4f05902..6ff37fa122 100644
--- a/sympy/core/tests/test_power.py
+++ b/sympy/core/tests/test_power.py
@@ -362,7 +362,8 @@ def test_issue_7638():
     e = 1/(1 - sqrt(2))
     assert sqrt(e) == I/sqrt(-1 + sqrt(2))
     assert e**-S.Half == -I*sqrt(-1 + sqrt(2))
-    assert sqrt((cos(1)**2 + sin(1)**2 - 1)**(3 + I)).exp == S.Half
+    assert sqrt((cos(1)**2 + sin(1)**2 - 1)**(3 + I)).exp in [S.Half,
+                                                              S(3)/2 + I/2]
     assert sqrt(r**(4/S(3))) != r**(2/S(3))
     assert sqrt((p + I)**(4/S(3))) == (p + I)**(2/S(3))
     assert sqrt((p - p**2*I)**2) == p - p**2*I
diff --git a/sympy/functions/elementary/tests/test_piecewise.py b/sympy/functions/elementary/tests/test_piecewise.py
index b7114ce870..3c9841d82c 100644
--- a/sympy/functions/elementary/tests/test_piecewise.py
+++ b/sympy/functions/elementary/tests/test_piecewise.py
@@ -1228,6 +1228,39 @@ def test_issue_8458():
     assert p3.simplify() == Piecewise((0, Eq(x, -1)), (sin(x), True))
 
 
+def test_issue_16417():
+    from sympy import im, re, Gt
+    z = Symbol('z')
+    assert unchanged(Piecewise, (1, Or(Eq(im(z), 0), Gt(re(z), 0))), (2, True))
+
+    x = Symbol('x')
+    assert unchanged(Piecewise, (S.Pi, re(x) < 0),
+                 (0, Or(re(x) > 0, Ne(im(x), 0))),
+                 (S.NaN, True))
+    r = Symbol('r', real=True)
+    p = Piecewise((S.Pi, re(r) < 0),
+                 (0, Or(re(r) > 0, Ne(im(r), 0))),
+                 (S.NaN, True))
+    assert p == Piecewise((S.Pi, r < 0),
+                 (0, r > 0),
+                 (S.NaN, True), evaluate=False)
+    # Does not work since imaginary != 0...
+    #i = Symbol('i', imaginary=True)
+    #p = Piecewise((S.Pi, re(i) < 0),
+    #              (0, Or(re(i) > 0, Ne(im(i), 0))),
+    #              (S.NaN, True))
+    #assert p == Piecewise((0, Ne(im(i), 0)),
+    #                      (S.NaN, True), evaluate=False)
+    i = I*r
+    p = Piecewise((S.Pi, re(i) < 0),
+                  (0, Or(re(i) > 0, Ne(im(i), 0))),
+                  (S.NaN, True))
+    assert p == Piecewise((0, Ne(im(i), 0)),
+                          (S.NaN, True), evaluate=False)
+    assert p == Piecewise((0, Ne(r, 0)),
+                          (S.NaN, True), evaluate=False)
+
+
 def test_eval_rewrite_as_KroneckerDelta():
     x, y, z, n, t, m = symbols('x y z n t m')
     K = KroneckerDelta
diff --git a/sympy/functions/elementary/tests/test_trigonometric.py b/sympy/functions/elementary/tests/test_trigonometric.py
index e16312521d..d952cf28d4 100644
--- a/sympy/functions/elementary/tests/test_trigonometric.py
+++ b/sympy/functions/elementary/tests/test_trigonometric.py
@@ -1039,9 +1039,8 @@ def test_atan2():
     assert atan2(y, -oo)==  2*pi*Heaviside(re(y)) - pi
 
     assert atan2(y, x).rewrite(log) == -I*log((x + I*y)/sqrt(x**2 + y**2))
-    assert atan2(0, 0).rewrite(atan) == S.NaN
+    assert atan2(0, 0) == S.NaN
     w = Symbol('w')
-    assert atan2(0, w).rewrite(atan) == Piecewise((pi, w < 0), (0, w > 0), (S.NaN, True))
 
     ex = atan2(y, x) - arg(x + I*y)
     assert ex.subs({x:2, y:3}).rewrite(arg) == 0
@@ -1056,6 +1055,18 @@ def test_atan2():
     assert rewrite == -I*log(abs(I*i + r)/sqrt(abs(i**2 + r**2))) + arg((I*i + r)/sqrt(i**2 + r**2))
     assert (e - rewrite).subs(reps).equals(0)
 
+    assert atan2(0, x).rewrite(atan) == Piecewise((pi, re(x) < 0),
+                                            (0, Ne(x, 0)),
+                                            (nan, True))
+    assert atan2(0, r).rewrite(atan) == Piecewise((pi, r < 0), (0, Ne(r, 0)), (S.NaN, True))
+    assert atan2(0, i),rewrite(atan) == 0
+    assert atan2(0, r + i).rewrite(atan) == Piecewise((pi, r < 0), (0, True))
+
+    assert atan2(y, x).rewrite(atan) == Piecewise(
+            (2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)),
+            (pi, re(x) < 0),
+            (0, (re(x) > 0) | Ne(im(x), 0)),
+            (nan, True))
     assert conjugate(atan2(x, y)) == atan2(conjugate(x), conjugate(y))
 
     assert diff(atan2(y, x), x) == -y/(x**2 + y**2)
diff --git a/sympy/simplify/tests/test_simplify.py b/sympy/simplify/tests/test_simplify.py
index 0b29cabf9f..943874736b 100644
--- a/sympy/simplify/tests/test_simplify.py
+++ b/sympy/simplify/tests/test_simplify.py
@@ -853,6 +853,13 @@ def test_issue_7971():
     assert simplify(z) is S.Zero
 
 
+@slow
+def test_issue_17141_slow():
+    # Should not give RecursionError
+    assert simplify((2**acos(I+1)**2).rewrite('log')) == 2**((pi + 2*I*log(-1 +
+                   sqrt(1 - 2*I) + I))**2/4)
+
+
 def test_issue_17141():
     # Check that there is no RecursionError
     assert simplify(x**(1 / acos(I))) == x**(2/(pi - 2*I*log(1 + sqrt(2))))
@@ -862,12 +869,6 @@ def test_issue_17141():
     p = 2**acos(I+1)**2
     assert simplify(p) == p
 
-    # However, for a complex number it still happens
-    if PY3:
-        raises(RecursionError, lambda: simplify((2**acos(I+1)**2).rewrite('log')))
-    else:
-        raises(RuntimeError, lambda: simplify((2**acos(I+1)**2).rewrite('log')))
-
 
 def test_simplify_kroneckerdelta():
     i, j = symbols("i j")