Repository: sympy/sympy
Pull request: Series/Limits: Fixed leading term method inconsistency for expressions involving variables in powers 
<!-- 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. -->

Fixes #23231 
#### Brief description of what is fixed or changed
 
The block we are getting rid of which is  as follows .
```
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -1792,11 +1792,7 @@ def _eval_as_leading_term(self, x, logx=None, cdir=0):
             raise PoleError("Cannot expand %s around 0" % (self))
         elif e.has(x):
             lt = exp(e * log(b))
-            try:
-                lt = lt.as_leading_term(x, logx=logx, cdir=cdir)
-            except PoleError:
-                pass
-            return lt
+            return lt.as_leading_term(x, logx=logx, cdir=cdir)
```

This block was responsible for handling the following cases (cases where expressions have args involving variables in exponents/powers for eg `x**x` , `(2**x + x**(1/x))` ). Hence getting rid of this block will spoil almost all cases of limits and order of such type as something like  `(x**(1/x)).as_leading_term(x)` would give a `NotImplementedError/PoleError` .
```
# On master
>>> (x**(1/x)).as_leading_term(x)
exp(log(x)/x)
>>> (2**(1/x)).as_leading_term(x)
exp(log(2)/x)
>>> ((1/x)**(1/x)).as_leading_term(x)
exp(log(1/x)/x)
```

What would be potentially spoiled here .
```
# results after making the change
>>> O(x**x, (x, oo))      # on master O(exp(x*log(x)), (x, oo))
O((1/x)**x, (x, oo))
>>> O(x**x + 2**x, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(1, (x, oo))
>>> O(x**x + 1/x**2, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(x**(-2), (x, oo))
>>>
>>> O(x**x + x**2, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(x**2, (x, oo))
```

Hence now this case needs to expressed in the leading term method of `exponential.py` . Order for such cases (variables present in exponents) was under tested(just one trivial case i guess) or rather not tested at all , hence I have addressed a test for this too .

#### 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 -->
* series
  * Fixed leading term method inconsistency for expressions involving variables in powers.
<!-- END RELEASE NOTES -->


Problem statement:
Sympy giving the wrong solution 
Hello, 

While doing an example with 
```
f(x) =( 2^(1/x) -2^(-1/x))/( 2^(1/x) +2^(-1/x))
```
Where you want to see the left end-behavior and the right end-behavior for f(1/x). Now left end-behavior is -1 and right end-behavior is 1 but according to SymPy its results is equal to 1. 

```
julia> using PythonCall
 
julia> sympy = pyimport("sympy")
Python module: <module 'sympy' from '/Users/verzani/.julia/environments/v1.7/.CondaPkg/env/lib/python3.10/site-packages/sympy/__init__.py'>
 
julia> sympy.limit((2^x - 2^(-x))/(2^x + 2^(-x)), x, -sympy.oo)^C
 
julia> x = sympy.symbols("x")
Python Symbol: x
 
julia> sympy.limit((2^x - 2^(-x))/(2^x + 2^(-x)), x, -sympy.oo)
Python One: 1
 
julia> sympy.__version__
Python str: '1.10'
```

Any reason behind this error?

Hints from issue discussion:
This looks like a regression, it was working correctly in e.g. 1.7.1
```
I can confirm this is an error 
>>> f = (2**x - 2**(-x))/(2**x + 2**(-x))
>>>
>>> limit(f, x, -oo)
1   # expected -1 
```
Bisected to bf1eb95c20d7b74e5bd30bfe03b17745f09faf23 from #21589.

CC @0sidharth @jksuom 
It seems that `Pow._eval_as_leading_term` masks a `PoleError` that should be seen by `Limit.doit`. Something like this might resolve the issue:
```
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -1792,11 +1792,7 @@ def _eval_as_leading_term(self, x, logx=None, cdir=0):
             raise PoleError("Cannot expand %s around 0" % (self))
         elif e.has(x):
             lt = exp(e * log(b))
-            try:
-                lt = lt.as_leading_term(x, logx=logx, cdir=cdir)
-            except PoleError:
-                pass
-            return lt
+            return lt.as_leading_term(x, logx=logx, cdir=cdir)
         else:
             from sympy.functions.elementary.complexes import im
             f = b.as_leading_term(x, logx=logx, cdir=cdir)
```
I had just started solving this just recently and realized you provided the diff just now, so maybe I'll confirm and verify any mistake (test failures due to this) after the change. 

**EDIT1:** I realize what was going wrong here and this change seems to address the issue correctly .Although I don't see any serious errors (just 1 overall which is a trivial case in `test_order.py` and can be addressed) , I presume this was added to address some case in `order.py` , would need some observation to see if something is going wrong.

**EDIT2:** Okay I now know which cases were dependent on the current implementation . Exponential of the form `x**x` and `x**y` (multivariate version) is being spoiled here . Though there aren't many tests to catch this in test_order.py
```
# results after making the change
>>> O(x**x, (x, oo))      # on master O(exp(x*log(x)), (x, oo))
O((1/x)**x, (x, oo))
>>> O(x**x + 2**x, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(1, (x, oo))
>>> O(x**x + 1/x**2, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(x**(-2), (x, oo))
>>>
>>> O(x**x + x**2, (x, oo))   # on master O(exp(x*log(x)), (x, oo))
O(x**2, (x, oo))
```
Will try to check what's going wrong here sometime soon !

Commit message: Added a case in leading term method of exponential.py

Patch to review:
diff --git a/sympy/core/power.py b/sympy/core/power.py
index c85a1fcee2c1..8be33df01ad4 100644
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -1793,11 +1793,7 @@ def _eval_as_leading_term(self, x, logx=None, cdir=0):
             raise PoleError("Cannot expand %s around 0" % (self))
         elif e.has(x):
             lt = exp(e * log(b))
-            try:
-                lt = lt.as_leading_term(x, logx=logx, cdir=cdir)
-            except PoleError:
-                pass
-            return lt
+            return lt.as_leading_term(x, logx=logx, cdir=cdir)
         else:
             from sympy.functions.elementary.complexes import im
             f = b.as_leading_term(x, logx=logx, cdir=cdir)
diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py
index a3994b3a04f7..39fdd8490777 100644
--- a/sympy/functions/elementary/exponential.py
+++ b/sympy/functions/elementary/exponential.py
@@ -541,6 +541,8 @@ def _eval_as_leading_term(self, x, logx=None, cdir=0):
             # Check out function: test_issue_18473() in test_exponential.py and
             # test_limits.py for more information.
             return exp(arg0)
+        if isinstance(arg.as_numer_denom()[0], log):
+            return self
         if arg0 is S.NaN:
             arg0 = arg.limit(x, 0)
         if arg0.is_infinite is False:
diff --git a/sympy/functions/elementary/tests/test_exponential.py b/sympy/functions/elementary/tests/test_exponential.py
index 9b5383429818..c6ded13d480a 100644
--- a/sympy/functions/elementary/tests/test_exponential.py
+++ b/sympy/functions/elementary/tests/test_exponential.py
@@ -210,6 +210,12 @@ def test_exp_leading_term():
     # raises(NotImplementedError, lambda: exp(x + 1/x).as_leading_term(x))
 
 
+def test_issue_23231():
+    assert (x**(1/x)).as_leading_term(x) == exp(log(x)/x)
+    assert (2**(1/x)).as_leading_term(x) == exp(log(2)/x)
+    assert ((1/x)**(1/x)).as_leading_term(x) == exp(log(1/x)/x)
+
+
 @_both_exp_pow
 def test_exp_taylor_term():
     x = symbols('x')
diff --git a/sympy/series/tests/test_limits.py b/sympy/series/tests/test_limits.py
index d3d0ee0e10b9..6f7de1e35707 100644
--- a/sympy/series/tests/test_limits.py
+++ b/sympy/series/tests/test_limits.py
@@ -1102,3 +1102,8 @@ def test_issue_21785():
 
 def test_issue_22181():
     assert limit((-1)**x * 2**(-x), x, oo) == 0
+
+
+def test_issue_23231():
+    f = (2**x - 2**(-x))/(2**x + 2**(-x))
+    assert limit(f, x, -oo) == -1
diff --git a/sympy/series/tests/test_order.py b/sympy/series/tests/test_order.py
index 18e7bd1adf78..132f35af8fc0 100644
--- a/sympy/series/tests/test_order.py
+++ b/sympy/series/tests/test_order.py
@@ -460,5 +460,14 @@ def test_issue_22165():
     assert O(log(x)).contains(2)
 
 
+def test_issue_23231():
+    # This test checks Order for expressions having
+    # arguments containing variables in exponents/powers.
+    assert O(x**x + 2**x, (x, oo)) == O(exp(x*log(x)), (x, oo))
+    assert O(x**x + x**2, (x, oo)) == O(exp(x*log(x)), (x, oo))
+    assert O(x**x + 1/x**2, (x, oo)) == O(exp(x*log(x)), (x, oo))
+    assert O(2**x + 3**x , (x, oo)) == O(exp(x*log(3)), (x, oo))
+
+
 def test_issue_9917():
     assert O(x*sin(x) + 1, (x, oo)) == O(x, (x, oo))


Code review comments:
diff_hunk: @@ -541,6 +541,8 @@ def _eval_as_leading_term(self, x, logx=None, cdir=0):
             # Check out function: test_issue_18473() in test_exponential.py and
             # test_limits.py for more information.
             return exp(arg0)
+        if isinstance(arg.as_numer_denom()[0], log):
path: sympy/functions/elementary/exponential.py
text: @user1:
It seems that this will only work in very special cases not including this:
```
>>> O(x**(2*x), (x, oo))
O(exp(x*log(1/x)), (x, oo))
```


@author:
True I am fixing this , i realized this will become a bit specialized . Hence I turned this into draft . Working on this