Repository: sympy/sympy

Issue:
powsimp should make `Rational**expr` canonical
While
```python
>>> powsimp(S.Half**x)
2**-x
```
the following is unchanged
```python
>>> eq = (S(2)/3)**x
>>> powsimp(eq) == eq
True
```
It could canonically be `(3/2)**-x`. This will make some distribution results be better posed as `base**(1-f)` instead of `(1/base)**(f - 1)` where `0<=f<=1`.

Discussion:

Patch:
diff --git a/sympy/core/exprtools.py b/sympy/core/exprtools.py
index ccdb10243c..3566d1372e 100644
--- a/sympy/core/exprtools.py
+++ b/sympy/core/exprtools.py
@@ -217,28 +217,19 @@ def decompose_power(expr: Expr) -> tTuple[Expr, int]:
     """
     Decompose power into symbolic base and integer exponent.
 
-    Explanation
-    ===========
-
-    This is strictly only valid if the exponent from which
-    the integer is extracted is itself an integer or the
-    base is positive. These conditions are assumed and not
-    checked here.
-
     Examples
     ========
 
     >>> from sympy.core.exprtools import decompose_power
     >>> from sympy.abc import x, y
+    >>> from sympy import exp
 
     >>> decompose_power(x)
     (x, 1)
     >>> decompose_power(x**2)
     (x, 2)
-    >>> decompose_power(x**(2*y))
-    (x**y, 2)
-    >>> decompose_power(x**(2*y/3))
-    (x**(y/3), 2)
+    >>> decompose_power(exp(2*y/3))
+    (exp(y/3), 2)
 
     """
     base, exp = expr.as_base_exp()
@@ -268,28 +259,25 @@ def decompose_power(expr: Expr) -> tTuple[Expr, int]:
 
 def decompose_power_rat(expr: Expr) -> tTuple[Expr, Rational]:
     """
-    Decompose power into symbolic base and rational exponent.
+    Decompose power into symbolic base and rational exponent;
+    if the exponent is not a Rational, then separate only the
+    integer coefficient.
 
-    """
-    base, exp = expr.as_base_exp()
+    Examples
+    ========
 
-    if exp.is_Number:
-        if exp.is_Rational:
-            e: Rational = exp  # type: ignore
-        else:
-            base, e = expr, S.One
-    else:
-        exp, tail = exp.as_coeff_Mul(rational=True)
+    >>> from sympy.core.exprtools import decompose_power_rat
+    >>> from sympy.abc import x
+    >>> from sympy import sqrt, exp
 
-        if exp is S.NegativeOne:
-            base, e = Pow(base, tail), S.NegativeOne
-        elif exp is not S.One:
-            tail = _keep_coeff(Rational(1, exp.q), tail)  # type: ignore
-            base, e = Pow(base, tail), Integer(exp.p)  # type: ignore
-        else:
-            base, e = expr, S.One
+    >>> decompose_power_rat(sqrt(x))
+    (x, 1/2)
+    >>> decompose_power_rat(exp(-3*x/2))
+    (exp(x/2), -3)
 
-    return base, e
+    """
+    _ = base, exp = expr.as_base_exp()
+    return _ if exp.is_Rational else decompose_power(expr)
 
 
 class Factors:
diff --git a/sympy/core/power.py b/sympy/core/power.py
index cbea7e25aa..e17c9bf148 100644
--- a/sympy/core/power.py
+++ b/sympy/core/power.py
@@ -965,9 +965,9 @@ def as_base_exp(self):
         Explanation
         ===========
 
-        If base is 1/Integer, then return Integer, -exp. If this extra
-        processing is not needed, the base and exp properties will
-        give the raw arguments
+        If base a Rational less than 1, then return 1/Rational, -exp.
+        If this extra processing is not needed, the base and exp
+        properties will give the raw arguments.
 
         Examples
         ========
@@ -978,12 +978,14 @@ def as_base_exp(self):
         (2, -2)
         >>> p.args
         (1/2, 2)
+        >>> p.base, p.exp
+        (1/2, 2)
 
         """
 
         b, e = self.args
-        if b.is_Rational and b.p == 1 and b.q != 1:
-            return Integer(b.q), -e
+        if b.is_Rational and b.p < b.q and b.p > 0:
+            return 1/b, -e
         return b, e
 
     def _eval_adjoint(self):
diff --git a/sympy/stats/drv_types.py b/sympy/stats/drv_types.py
index ca64c29368..e944c82eb9 100644
--- a/sympy/stats/drv_types.py
+++ b/sympy/stats/drv_types.py
@@ -179,7 +179,7 @@ def FlorySchulz(name, a):
     >>> X = FlorySchulz("x", a)
 
     >>> density(X)(z)
-    (4/5)**(z - 1)*z/25
+    (5/4)**(1 - z)*z/25
 
     >>> E(X)
     9
@@ -252,7 +252,7 @@ def Geometric(name, p):
     >>> X = Geometric("x", p)
 
     >>> density(X)(z)
-    (4/5)**(z - 1)/5
+    (5/4)**(1 - z)/5
 
     >>> E(X)
     5


Test patch:
diff --git a/sympy/core/tests/test_power.py b/sympy/core/tests/test_power.py
index f3fcb8a1e3..e0c4b6e2ae 100644
--- a/sympy/core/tests/test_power.py
+++ b/sympy/core/tests/test_power.py
@@ -16,7 +16,7 @@
 from sympy.core.power import power, integer_nthroot
 from sympy.testing.pytest import warns, _both_exp_pow
 from sympy.utilities.exceptions import SymPyDeprecationWarning
-
+from sympy.abc import a, b, c, x, y
 
 def test_rational():
     a = Rational(1, 5)
@@ -47,14 +47,12 @@ def feq(a, b):
 
 
 def test_expand():
-    x = Symbol('x')
     assert (2**(-1 - x)).expand() == S.Half*2**(-x)
 
 
 def test_issue_3449():
     #test if powers are simplified correctly
     #see also issue 3995
-    x = Symbol('x')
     assert ((x**Rational(1, 3))**Rational(2)) == x**Rational(2, 3)
     assert (
         (x**Rational(3))**Rational(2, 5)) == (x**Rational(3))**Rational(2, 5)
@@ -168,8 +166,6 @@ def eqn(num, den, pow):
     eq = eqn(nneg, dneg, -pow)
     assert eq.is_Pow and eq.as_numer_denom() == ((-dneg)**pow, (-nneg)**pow)
 
-    x = Symbol('x')
-    y = Symbol('y')
     assert ((1/(1 + x/3))**(-S.One)).as_numer_denom() == (3 + x, 3)
     notp = Symbol('notp', positive=False)  # not positive does not imply real
     b = ((1 + x/notp)**-2)
@@ -197,7 +193,6 @@ def eqn(num, den, pow):
 
 
 def test_Pow_Expr_args():
-    x = Symbol('x')
     bases = [Basic(), Poly(x, x), FiniteSet(x)]
     for base in bases:
         # The cache can mess with the stacklevel test
@@ -207,8 +202,6 @@ def test_Pow_Expr_args():
 
 def test_Pow_signs():
     """Cf. issues 4595 and 5250"""
-    x = Symbol('x')
-    y = Symbol('y')
     n = Symbol('n', even=True)
     assert (3 - y)**2 != (y - 3)**2
     assert (3 - y)**n != (y - 3)**n
@@ -262,8 +255,6 @@ def test_power_rewrite_exp():
 
 
 def test_zero():
-    x = Symbol('x')
-    y = Symbol('y')
     assert 0**x != 0
     assert 0**(2*x) == 0**x
     assert 0**(1.0*x) == 0**x
@@ -283,17 +274,20 @@ def test_zero():
     assert Float(0)**-oo is zoo
 
 def test_pow_as_base_exp():
-    x = Symbol('x')
     assert (S.Infinity**(2 - x)).as_base_exp() == (S.Infinity, 2 - x)
     assert (S.Infinity**(x - 2)).as_base_exp() == (S.Infinity, x - 2)
     p = S.Half**x
     assert p.base, p.exp == p.as_base_exp() == (S(2), -x)
+    p = (S(3)/2)**x
+    assert p.base, p.exp == p.as_base_exp() == (3*S.Half, x)
+    p = (S(2)/3)**x
+    assert p.as_base_exp() == (S(3)/2, -x)
+    assert p.base, p.exp == (S(2)/3, x)
     # issue 8344:
     assert Pow(1, 2, evaluate=False).as_base_exp() == (S.One, S(2))
 
 
 def test_nseries():
-    x = Symbol('x')
     assert sqrt(I*x - 1)._eval_nseries(x, 4, None, 1) == I + x/2 + I*x**2/8 - x**3/16 + O(x**4)
     assert sqrt(I*x - 1)._eval_nseries(x, 4, None, -1) == -I - x/2 - I*x**2/8 + x**3/16 + O(x**4)
     assert cbrt(I*x - 1)._eval_nseries(x, 4, None, 1) == (-1)**(S(1)/3) - (-1)**(S(5)/6)*x/3 + \
@@ -315,8 +309,6 @@ def test_nseries():
 
 
 def test_issue_6100_12942_4473():
-    x = Symbol('x')
-    y = Symbol('y')
     assert x**1.0 != x
     assert x != x**1.0
     assert True != x**1.0
@@ -346,15 +338,11 @@ def test_issue_6208():
 
 
 def test_issue_6990():
-    x = Symbol('x')
-    a = Symbol('a')
-    b = Symbol('b')
     assert (sqrt(a + b*x + x**2)).series(x, 0, 3).removeO() == \
         sqrt(a)*x**2*(1/(2*a) - b**2/(8*a**2)) + sqrt(a) + b*x/(2*sqrt(a))
 
 
 def test_issue_6068():
-    x = Symbol('x')
     assert sqrt(sin(x)).series(x, 0, 7) == \
         sqrt(x) - x**Rational(5, 2)/12 + x**Rational(9, 2)/1440 - \
         x**Rational(13, 2)/24192 + O(x**7)
@@ -369,19 +357,15 @@ def test_issue_6068():
 
 
 def test_issue_6782():
-    x = Symbol('x')
     assert sqrt(sin(x**3)).series(x, 0, 7) == x**Rational(3, 2) + O(x**7)
     assert sqrt(sin(x**4)).series(x, 0, 3) == x**2 + O(x**3)
 
 
 def test_issue_6653():
-    x = Symbol('x')
     assert (1 / sqrt(1 + sin(x**2))).series(x, 0, 3) == 1 - x**2/2 + O(x**3)
 
 
 def test_issue_6429():
-    x = Symbol('x')
-    c = Symbol('c')
     f = (c**2 + x)**(0.5)
     assert f.series(x, x0=0, n=1) == (c**2)**0.5 + O(x)
     assert f.taylor_term(0, x) == (c**2)**0.5
@@ -510,7 +494,6 @@ def test_better_sqrt():
 
 
 def test_issue_2993():
-    x = Symbol('x')
     assert str((2.3*x - 4)**0.3) == '1.5157165665104*(0.575*x - 1)**0.3'
     assert str((2.3*x + 4)**0.3) == '1.5157165665104*(0.575*x + 1)**0.3'
     assert str((-2.3*x + 4)**0.3) == '1.5157165665104*(1 - 0.575*x)**0.3'
@@ -594,7 +577,6 @@ def test_issue_18762():
 
 
 def test_issue_21860():
-    x = Symbol('x')
     e = 3*2**Rational(66666666667,200000000000)*3**Rational(16666666667,50000000000)*x**Rational(66666666667, 200000000000)
     ans = Mul(Rational(3, 2),
               Pow(Integer(2), Rational(33333333333, 100000000000)),
@@ -603,7 +585,6 @@ def test_issue_21860():
 
 
 def test_issue_21647():
-    x = Symbol('x')
     e = log((Integer(567)/500)**(811*(Integer(567)/500)**x/100))
     ans = log(Mul(Rational(64701150190720499096094005280169087619821081527,
                            76293945312500000000000000000000000000000000000),
@@ -615,7 +596,6 @@ def test_issue_21647():
 
 
 def test_issue_21762():
-    x = Symbol('x')
     e = (x**2 + 6)**(Integer(33333333333333333)/50000000000000000)
     ans = Mul(Rational(5, 4),
               Pow(Integer(2), Rational(16666666666666667, 25000000000000000)),
@@ -665,3 +645,8 @@ def test_powers_of_I():
         1, sqrt(I), I, sqrt(I)**3, -1, -sqrt(I), -I, -sqrt(I)**3,
         1, sqrt(I), I, sqrt(I)**3, -1]
     assert sqrt(I)**(S(9)/2) == -I**(S(1)/4)
+
+
+def test_issue_23918():
+    b = S(2)/3
+    assert (b**x).as_base_exp() == (1/b, -x)
diff --git a/sympy/stats/tests/test_discrete_rv.py b/sympy/stats/tests/test_discrete_rv.py
index b011872abb..98303a460d 100644
--- a/sympy/stats/tests/test_discrete_rv.py
+++ b/sympy/stats/tests/test_discrete_rv.py
@@ -292,9 +292,6 @@ def test_product_spaces():
     assert str(P(X1 + X2 < 3).rewrite(Sum)) == (
         "Sum(Piecewise((1/(4*2**n), n >= -1), (0, True)), (n, -oo, -1))/3")
     assert str(P(X1 + X2 > 3).rewrite(Sum)) == (
-        "Sum(Piecewise((2**(X2 - n - 2)*(2/3)**(X2 - 1)/6, "
-        "X2 - n <= 2), (0, True)), (X2, 1, oo), (n, 1, oo))")
-    assert str(P(X1 + X2 > 3).rewrite(Sum)) == (
-        "Sum(Piecewise((2**(X2 - n - 2)*(2/3)**(X2 - 1)/6, "
-        "X2 - n <= 2), (0, True)), (X2, 1, oo), (n, 1, oo))")
+        'Sum(Piecewise((2**(X2 - n - 2)*(3/2)**(1 - X2)/6, '
+        'X2 - n <= 2), (0, True)), (X2, 1, oo), (n, 1, oo))')
     assert P(Eq(X1 + X2, 3)) == Rational(1, 12)
diff --git a/sympy/stats/tests/test_joint_rv.py b/sympy/stats/tests/test_joint_rv.py
index aa7d41eb06..1f4aeae7fd 100644
--- a/sympy/stats/tests/test_joint_rv.py
+++ b/sympy/stats/tests/test_joint_rv.py
@@ -34,6 +34,7 @@
 from sympy.abc import x, y
 
 
+
 def test_Normal():
     m = Normal('A', [1, 2], [[1, 0], [0, 1]])
     A = MultivariateNormal('A', [1, 2], [[1, 0], [0, 1]])
@@ -297,6 +298,7 @@ def test_expectation():
     m = Normal('A', [x, y], [[1, 0], [0, 1]])
     assert simplify(E(m[1])) == y
 
+
 @XFAIL
 def test_joint_vector_expectation():
     m = Normal('A', [x, y], [[1, 0], [0, 1]])