Repository: sympy/sympy
Pull request: 21942: remove match from solve (master)
<!-- 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 #21942
#### Brief description of what is fixed or changed


#### 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 -->
* solvers
  * use of `match` was removed to make solving more reliable
<!-- END RELEASE NOTES -->


Problem statement:
Algebraic re-arrangement of exponential equations
Sympy version: 1.7.1
Environment: Google Colab
Python version: 3.7

I am trying to use Sympy 1.7.1 in a google Colab environment running python 3.7 to automate some tedious algebra in macroeconomic models, and I'm running into an issue when trying to re-arrange equations of the form:
```
mc = (a * r ** (1 - p) + (1 - a) * w ** (1 - p)) ** (1 / (1 - p))
```
With parameters 0 < a < 1, and p > 0, p != 1 given, and w, r > 0. I would like to be able to re-arrange this expression to obtain functions r(mc, w) or w(mc, r). In the case of r, my expected output is:
 ```
r = ((1 / a) * (mc ** (1 - p) - (1 - a) * w ** (1 - p))) ** (1 / (1 - p))
```

To solve this equation I am trying the following code:

```
import sympy as sp

a, p, mc, r, w = sp.symbols(['a', 'p', 'mc', 'r', 'w'], real=True, nonnegative=True)
eq = mc - (a * r ** (1 - p) + (1 - a) * w ** (1 - p)) ** (1 / (1 - p))
sp.solve(eq, r)
```

Which results in the following error and trace-back: 

```
TypeError                                 Traceback (most recent call last)
<ipython-input-15-a95f68afce93> in <module>()
----> 1 sp.solve(eq, r)

/usr/local/lib/python3.7/dist-packages/sympy/solvers/solvers.py in _solve(f, *symbols, **flags)
   1458                 d = dgen.pop()
   1459                 w = Wild('g')
-> 1460                 gen = f_num.match(D.xreplace({d: w}))[w]
   1461                 spart = gen.as_independent(symbol)[1].as_base_exp()[0]
   1462                 if spart == symbol:

TypeError: 'NoneType' object is not subscriptable
```

When i define a new parameter q = (1 - p), however, so that:
```
mc = (a * r ** q + (1 - a) * w ** q) ** (1 / q)
```

Sympy.solve finds expressions for r or w without any problem. Indeed, it seems only the outside exponent, (1 / (1 - p)) is important to change: the system is able to solve `mc = (a * r ** (1 - p) + (1 - a) * w ** (1 - p)) ** (1 / p)` without any problem. Evidently, that (1 - p) in the denominator of the exponent is causing the problem, but I don't know enough about how the solve function works to understand why that would be the case.

Are there best practices for writing functions of exponents, such as the one I have presented here, in order to facilitate the solve function? 

Thanks for any help in advance! This is my first time posting an issue on github, so I apologize if I did it wrong.

Hints from issue discussion:

Commit message: Initial fix for `matches`

Patch to review:
diff --git a/sympy/core/add.py b/sympy/core/add.py
index 6b308069878a..0d3f574837ce 100644
--- a/sympy/core/add.py
+++ b/sympy/core/add.py
@@ -518,8 +518,8 @@ def _combine_inverse(lhs, rhs):
         Returns lhs - rhs, but treats oo like a symbol so oo - oo
         returns 0, instead of a nan.
         """
-        from sympy.simplify.simplify import signsimp
         from sympy.core.symbol import Dummy
+        from sympy.simplify.simplify import signsimp
         inf = (S.Infinity, S.NegativeInfinity)
         if lhs.has(*inf) or rhs.has(*inf):
             oo = Dummy('oo')
@@ -527,14 +527,16 @@ def _combine_inverse(lhs, rhs):
                 S.Infinity: oo,
                 S.NegativeInfinity: -oo}
             ireps = {v: k for k, v in reps.items()}
-            eq = signsimp(lhs.xreplace(reps) - rhs.xreplace(reps))
+            eq = lhs.xreplace(reps) - rhs.xreplace(reps)
             if eq.has(oo):
                 eq = eq.replace(
                     lambda x: x.is_Pow and x.base is oo,
                     lambda x: x.base)
             return eq.xreplace(ireps)
         else:
-            return signsimp(lhs - rhs)
+            rv = lhs - rhs
+            srv = signsimp(rv)
+            return srv if srv.is_Number else rv
 
     @cacheit
     def as_two_terms(self):
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
index 1b3fc566ccf1..57a2175412ec 100644
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -1224,7 +1224,9 @@ def check(l, r):
             if len(b) != blen:
                 lhs = Mul(*[k**v for k, v in a.items()]).xreplace(i_)
                 rhs = Mul(*[k**v for k, v in b.items()]).xreplace(i_)
-        return signsimp(lhs/rhs)
+        rv = lhs/rhs
+        srv = signsimp(rv)
+        return srv if srv.is_Number else rv
 
     def as_powers_dict(self):
         d = defaultdict(int)
diff --git a/sympy/core/tests/test_match.py b/sympy/core/tests/test_match.py
index 62c06c9f965a..c88cbb6b912e 100644
--- a/sympy/core/tests/test_match.py
+++ b/sympy/core/tests/test_match.py
@@ -1,5 +1,5 @@
 from sympy import (abc, Add, cos, collect, Derivative, diff, exp, Float, Function,
-    I, Integer, log, Mul, oo, Poly, Rational, S, sin, sqrt, Symbol, symbols,
+    I, Integer, log, Mul, oo, Poly, Rational, S, signsimp, sin, sqrt, Symbol, symbols,
     Wild, pi, meijerg, Sum
 )
 
@@ -509,7 +509,7 @@ def test_issue_3883():
     a, b, c = symbols('a b c', cls=Wild, exclude=(gamma,))
 
     assert f.match(a * log(gamma) + b * gamma + c) == \
-        {a: Rational(-1, 2), b: -(mu - x)**2/2, c: log(2*pi)/2}
+        {a: Rational(-1, 2), b: -(-mu + x)**2/2, c: log(2*pi)/2}
     assert f.expand().collect(gamma).match(a * log(gamma) + b * gamma + c) == \
         {a: Rational(-1, 2), b: (-(x - mu)**2/2).expand(), c: (log(2*pi)/2).expand()}
     g1 = Wild('g1', exclude=[gamma])
@@ -647,7 +647,7 @@ def test_issue_4319():
     ans = {S.Zero, y - S.One/8}
 
     def ok(pat):
-        assert set(p.match(pat).values()) == ans
+        assert set(p.match(pat).values()) == ans, set(p.match(pat).values())
 
     ok(Wild("coeff", exclude=[x])*x + Wild("rest"))
     ok(Wild("w", exclude=[x])*x + Wild("rest"))
@@ -717,6 +717,18 @@ def test_match_issue_17397():
     assert r == {a3: x - 4 + 4/x, b3: 1 - 2/x, c3: x - 4}
 
 
+def test_match_issue_21942():
+    a, r, w = symbols('a, r, w', nonnegative=True)
+    p = symbols('p', positive=True)
+    g_ = Wild('g')
+    pattern = g_ ** (1 / (1 - p))
+    eq = (a * r ** (1 - p) + w ** (1 - p) * (1 - a)) ** (1 / (1 - p))
+    m = {g_: a * r ** (1 - p) + w ** (1 - p) * (1 - a)}
+    assert pattern.matches(eq) == m
+    assert (-pattern).matches(-eq) == m
+    assert pattern.matches(signsimp(eq)) is None
+
+
 def test_match_terms():
     X, Y = map(Wild, "XY")
     x, y, z = symbols('x y z')
diff --git a/sympy/solvers/solvers.py b/sympy/solvers/solvers.py
index e2f69271119a..4459d1417f9d 100644
--- a/sympy/solvers/solvers.py
+++ b/sympy/solvers/solvers.py
@@ -18,7 +18,7 @@
     default_sort_key)
 from sympy.core.sympify import sympify
 from sympy.core import (S, Add, Symbol, Equality, Dummy, Expr, Mul,
-    Pow, Unequality, Wild)
+    Pow, Unequality)
 from sympy.core.exprtools import factor_terms
 from sympy.core.function import (expand_mul, expand_log,
                           Derivative, AppliedUndef, UndefinedFunction, nfloat,
@@ -1447,17 +1447,12 @@ def _solve(f, *symbols, **flags):
             return [sol]
 
         poly = None
-        # check for a single non-symbol generator
-        dums = f_num.atoms(Dummy)
-        D = f_num.replace(
-            lambda i: isinstance(i, Add) and symbol in i.free_symbols,
-            lambda i: Dummy())
-        if not D.is_Dummy:
-            dgen = D.atoms(Dummy) - dums
-            if len(dgen) == 1:
-                d = dgen.pop()
-                w = Wild('g')
-                gen = f_num.match(D.xreplace({d: w}))[w]
+        # check for a single Add generator
+        if not f_num.is_Add:
+            add_args = [i for i in f_num.atoms(Add)
+                if symbol in i.free_symbols]
+            if len(add_args) == 1:
+                gen = add_args[0]
                 spart = gen.as_independent(symbol)[1].as_base_exp()[0]
                 if spart == symbol:
                     try:
diff --git a/sympy/solvers/tests/test_solvers.py b/sympy/solvers/tests/test_solvers.py
index c2100197c640..21ffe5d65521 100644
--- a/sympy/solvers/tests/test_solvers.py
+++ b/sympy/solvers/tests/test_solvers.py
@@ -20,7 +20,7 @@
 from sympy.testing.pytest import slow, XFAIL, SKIP, raises
 from sympy.testing.randtest import verify_numerically as tn
 
-from sympy.abc import a, b, c, d, k, h, p, x, y, z, t, q, m, R
+from sympy.abc import a, b, c, d, e, k, h, p, x, y, z, t, q, m, R
 
 
 def NS(e, n=15, **options):
@@ -2390,3 +2390,9 @@ def test_issue_17454():
 def test_issue_21852():
     solution = [21 - 21*sqrt(2)/2]
     assert solve(2*x + sqrt(2*x**2) - 21) == solution
+
+
+def test_issue_21942():
+    eq = -d + (a*c**(1 - e) + b**(1 - e)*(1 - a))**(1/(1 - e))
+    sol = solve(eq, c, simplify=False, check=False)
+    assert sol == [(b/b**e - b/(a*b**e) + d**(1 - e)/a)**(1/(1 - e))]


Code review comments:
diff_hunk: @@ -518,23 +518,25 @@ def _combine_inverse(lhs, rhs):
         Returns lhs - rhs, but treats oo like a symbol so oo - oo
         returns 0, instead of a nan.
         """
-        from sympy.simplify.simplify import signsimp
         from sympy.core.symbol import Dummy
+        from sympy.simplify.simplify import signsimp
         inf = (S.Infinity, S.NegativeInfinity)
         if lhs.has(*inf) or rhs.has(*inf):
             oo = Dummy('oo')
             reps = {
                 S.Infinity: oo,
                 S.NegativeInfinity: -oo}
             ireps = {v: k for k, v in reps.items()}
-            eq = signsimp(lhs.xreplace(reps) - rhs.xreplace(reps))
+            eq = lhs.xreplace(reps) - rhs.xreplace(reps)
             if eq.has(oo):
                 eq = eq.replace(
                     lambda x: x.is_Pow and x.base is oo,
                     lambda x: x.base)
             return eq.xreplace(ireps)
         else:
-            return signsimp(lhs - rhs)
+            rv = lhs - rhs
+            srv = signsimp(rv)
+            return srv if srv.is_Number else rv
path: sympy/core/add.py
text: @author:
```suggestion
        srv = signsimp(rv)
        return srv if srv.is_Number else rv
```

diff_hunk: @@ -518,23 +518,25 @@ def _combine_inverse(lhs, rhs):
         Returns lhs - rhs, but treats oo like a symbol so oo - oo
         returns 0, instead of a nan.
         """
-        from sympy.simplify.simplify import signsimp
         from sympy.core.symbol import Dummy
+        from sympy.simplify.simplify import signsimp
         inf = (S.Infinity, S.NegativeInfinity)
         if lhs.has(*inf) or rhs.has(*inf):
             oo = Dummy('oo')
             reps = {
                 S.Infinity: oo,
                 S.NegativeInfinity: -oo}
             ireps = {v: k for k, v in reps.items()}
-            eq = signsimp(lhs.xreplace(reps) - rhs.xreplace(reps))
+            eq = lhs.xreplace(reps) - rhs.xreplace(reps)
             if eq.has(oo):
                 eq = eq.replace(
                     lambda x: x.is_Pow and x.base is oo,
                     lambda x: x.base)
             return eq.xreplace(ireps)
path: sympy/core/add.py
text: @author:
```suggestion
            rv = eq.xreplace(ireps)
```