Repository: sympy/sympy
Pull request: making invert_real to work for 0**x + a type equations
This PR tries to fix [issue#11536](https://github.com/sympy/sympy/issues/11536). 

The approach I applied solves the issue but other `test cases` fail, and I am not able to figure it out what might have caused the failure. 
ping @kshitij10496 


Problem statement:
Solution of 0**x + a should be EmptySet
There is some discrepancy in the behaviour of `0**x` and the solving technique for the following equations in solveset:

``` python
In []: solveset(0**x - 1, x, S.Reals)
Out[]: {0} # correct result

In []: solveset(0**x - 100, x, S.Reals)
Out[]: {0} # should be EmptySet 

In []: solveset(0**x + 100, x, S.Reals)
Out[]: {0} # should be EmptySet

In [17]: solve(0**x - 100, x) # raises NotImplementedError
```

Hints from issue discussion:
For

``` python
solve(0**x - 100, x)
```

SymPy 1.0 returns

``` python
ConditionSet(x, Eq(0**x - 100, 0), Complexes((-oo, oo) x (-oo, oo), False))
```

Which version of SymPy are you using?

I agree with you @kshitij10496. Should it be fixed? If so I would like to work on it.
> Should it be fixed? If so I would like to work on it.

Yes, it needs to be fixed. 

The function `0**x` is a transcendental function.
For now, I would suggest we prevent `solveset` from returning an incorrect solution by returning a `ConditionSet` object instead. 
However, this case needs to be addressed in the future while implementing `transolve`. 
@nikoskaragiannakis I think you might have confused between `solve` and `solveset` here.
In spite of that, I agree with you that `solveset` works fine in the `S.Complexes` domain.
However, the issue here is that an incorrect solution is returned in case of real domain.

```python
In []: solve(0**x - 100, x) # raises NotImplementedError

In []: solveset(0**x - 100, x)
Out[]: ConditionSet(x, Eq(0**x - 100, 0), Complexes((-oo, oo) x (-oo, oo), False)) # correct answer in Complex domain

In []: solveset(0**x - 100, x, S.Reals)
Out[]: {0} # wrong result in the Real domain
```
@kshitij10496 I could fix this to return `ConditionSet` for time being.
Right now I am trying to study `solve's` transcendental eq solver. Also looking forward to contribute in `transolve` in future.
@kshitij10496 I was thinking to use `checksol` to check whether the the result satisfies the given eq `f`, but many tests were failing, I implemented it in line 692 of `solveset` module.
```
chk = checksol(f, symbol, rhs_s.args[0])
            if not chk:
                result = ConditionSet(symbol, f, domain)
```
I guess its not the right place to use `checksol` or is there any other way to implement.
What do you suggest?
Checking for the solution inside `_solveset` won't exactly address this behavior.
I would suggest you fix this by adding checks inside `invert_real` function.
These are indeterminate forms : 

```
In [1]: 0**0
Out[1]: 1 # must be nan

In [2]: oo/oo
Out[2]: nan

In [3]: 0/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-3-6549dea6d1ae> in <module>()
----> 1 0/0

ZeroDivisionError: division by zero # must be nan

In [4]: 0*oo
Out[4]: nan

In [5]: oo-oo
Out[5]: nan

In [6]: 1**oo
Out[6]: nan

In [7]: oo**0
Out[7]: 1 # must be nan

```

So [0**x  - 1 = 0 for x ](http://www.wolframalpha.com/input/?i=0**x++-+1+%3D+0+for+x) should return no solution. Right ? 
We can have a look [here](https://math.stackexchange.com/questions/11150/zero-to-the-zero-power-is-00-1)
@jksuom @asmeurer 
As @Shekharrajak mentioned above,`0**0` returns `1`.
Is this by design or a bug?

Commit message: re-implemented the logic

Patch to review:
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 7d5dc1254a81..84d8c415c590 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -117,7 +117,6 @@ def invert_real(f_x, y, x, domain=S.Reals):
     """
     return _invert(f_x, y, x, domain)
 
-
 def _invert_real(f, g_ys, symbol):
     """Helper function for _invert."""
 
@@ -188,8 +187,13 @@ def _invert_real(f, g_ys, symbol):
                 return _invert_real(base, res, symbol)
 
         if not base_has_sym:
-            return _invert_real(expo,
-                imageset(Lambda(n, log(n)/log(base)), g_ys), symbol)
+            if base is not S.Zero:
+                return _invert_real(expo,
+                    imageset(Lambda(n, log(n)/log(base)), g_ys), symbol)
+            if base is S.Zero and g_ys.args[0] is S.One:
+                #special case: 0**x - 1
+                return (expo, FiniteSet(0))
+
 
     if isinstance(f, TrigonometricFunction):
         if isinstance(g_ys, FiniteSet):
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index bdbd25c216c4..3ec3adc7e71b 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -177,6 +177,9 @@ 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) == ConditionSet(x, Eq(0**x - 100, 0), S.Reals)
+    assert solveset(0**x - 1, x, S.Reals) == FiniteSet(0)
 
 def test_is_function_class_equation():
     from sympy.abc import x, a


Code review comments:
diff_hunk: @@ -188,8 +187,13 @@ def _invert_real(f, g_ys, symbol):
                 return _invert_real(base, res, symbol)
 
         if not base_has_sym:
-            return _invert_real(expo,
-                imageset(Lambda(n, log(n)/log(base)), g_ys), symbol)
+            if base is not S.Zero:
+                return _invert_real(expo,
+                    imageset(Lambda(n, log(n)/log(base)), g_ys), symbol)
+            if base is S.Zero and g_ys.args[0] is S.One:
path: sympy/solvers/solveset.py
text: @user1:
`if ...` -> `elif g_ys.args[0] is S.One:`

@user1:
I'm not sure what `g_ys` is -- a Tuple? Would it ever have args[0] be 1 but other terms also present? 1 in position other than 0?

@author:
> I'm not sure what g_ys is

`g_ys` is actually a `FiniteSet` that is returned by every recursive call.

> Would it ever have args[0] be 1 but other terms also present?

I don't think there will be more than one args in `g_ys` because it is a sub-condition of `if f.is_Pow` which will always have  single element in finiteset.

> 1 in position other than 0?

I guess this is a very special case `1` will be always present in position 0