New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
transcendental equation solver for solveset: transolve #14736
Changes from 4 commits
059b5ae
f7c38cb
885923b
fd0a4f6
f8bc1ad
9dfaf58
a2305da
d2f067c
217b11f
c38ea87
197355d
5be292f
ffef477
a7574b0
648da0e
f8597b5
a45df5c
3e0972c
2309ce7
5e89449
f2e6ab0
89ce4c9
e2f12ac
154ee3b
8345107
c0bc441
6ddc927
de66d1e
cca2d14
e49647e
e329518
90efc7a
e51734a
254f45f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,10 +14,12 @@ | |
from sympy.core.containers import Tuple | ||
from sympy.core.facts import InconsistentAssumptions | ||
from sympy.core.numbers import I, Number, Rational, oo | ||
from sympy.core.function import (Lambda, expand_complex, AppliedUndef, Function) | ||
from sympy.core.function import (Lambda, expand_complex, AppliedUndef, Function, | ||
expand_log) | ||
from sympy.core.relational import Eq | ||
from sympy.core.symbol import Symbol | ||
from sympy.simplify.simplify import simplify, fraction, trigsimp | ||
from sympy.simplify import powdenest | ||
from sympy.functions import (log, Abs, tan, cot, sin, cos, sec, csc, exp, | ||
acos, asin, acsc, asec, arg, | ||
piecewise_fold, Piecewise) | ||
|
@@ -30,9 +32,9 @@ | |
from sympy.sets.sets import Set | ||
from sympy.matrices import Matrix | ||
from sympy.polys import (roots, Poly, degree, together, PolynomialError, | ||
RootOf) | ||
RootOf, factor) | ||
from sympy.solvers.solvers import (checksol, denoms, unrad, | ||
_simple_dens, recast_to_symbols) | ||
_simple_dens, recast_to_symbols, _ispow) | ||
from sympy.solvers.polysys import solve_poly_system | ||
from sympy.solvers.inequalities import solve_univariate_inequality | ||
from sympy.utilities import filldedent | ||
|
@@ -943,7 +945,12 @@ def _solveset(f, symbol, domain, _check=False): | |
elif equation.has(Abs): | ||
result += _solve_abs(f, symbol, domain) | ||
else: | ||
result += _solve_as_rational(equation, symbol, domain) | ||
new_result = _solve_as_rational(equation, symbol, domain) | ||
if isinstance(new_result, ConditionSet): | ||
# may be a transcendental type equation | ||
result += transolve(equation, symbol, domain) | ||
else: | ||
result += new_result | ||
else: | ||
result += solver(equation, symbol) | ||
|
||
|
@@ -978,6 +985,88 @@ def _solveset(f, symbol, domain, _check=False): | |
return result | ||
|
||
|
||
def _expo_solver(f, symbol): | ||
""" | ||
Helper function for solving exponential equations. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will make it: |
||
|
||
This function solves exponential equation by using logarithms. | ||
Exponents are converted to a more general form of logs which can further be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...which can be solved by solveset. |
||
solved by passing to _solveset. | ||
It deals with equations of type `a*f(x) + b*g(x)`, where f(x) and g(x) | ||
are power terms. | ||
|
||
eg: 3**(2*x) - 2**(x + 3) can be transformed to a better log form, | ||
2*x*log(3) - (x+3)*log(2), which is easily solvable. | ||
""" | ||
|
||
a, b = ordered(f.args) | ||
|
||
lhs = a | ||
rhs = -b | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove empty line, (as it is not separating a big enough block of code) |
||
lhs = expand_log(log(lhs), force=True) | ||
rhs = expand_log(log(rhs), force=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. force=True does operations that are not mathematically correct for all values. Is it possible for this function to return wrong results because of this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, log(x*y) will expand into log(x)+log(y) but that is not always true. e.g. if x == y == -1, the former is 0 while the latter is |
||
|
||
return (lhs - rhs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. redundant parenthesis around |
||
|
||
|
||
def _check_expo(f, symbol): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add tests for this function |
||
""" | ||
Helper to check whether an equation is exponential or not. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line just says that it checks if its exponential or not but the description below says it checks only two exponent terms, this is not consistent and misleading. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also why does it only looks for exponential with two exponent terms? In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Exponents with one term is handled by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which one do you think is better and why and how would you convey this information to a developer? Think about these things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have made changes in |
||
""" | ||
try: | ||
a, b = ordered(f.args) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use meaningful variable names. |
||
except ValueError: | ||
return False | ||
|
||
ad = a.as_independent(symbol)[1] | ||
bd = b.as_independent(symbol)[1] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
|
||
return any(_ispow(i) for i in (ad, bd)) | ||
|
||
|
||
def transolve(f, symbol, domain, **flags): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please make it private There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean making it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
"""Helper for solving transcendental equations.""" | ||
|
||
if 'tsolve_saw' not in flags: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or all in one:
|
||
flags['tsolve_saw'] = [] | ||
if f in flags['tsolve_saw']: | ||
return ConditionSet(symbol, Eq(f, 0), domain) | ||
else: | ||
flags['tsolve_saw'].append(f) | ||
|
||
inverter = invert_real if domain.is_subset(S.Reals) else invert_complex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we can do that. Will add a comment so as to avoid confusion. |
||
lhs, rhs_s = inverter(f, 0, symbol, domain) | ||
|
||
result = ConditionSet(symbol, Eq(f, 0), domain) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also write this as:
and use the |
||
|
||
if lhs.is_Add: | ||
# do we need a loop for rhs? | ||
# Can't determine a case as of now. | ||
rhs = rhs_s.args[0] | ||
equation = factor(powdenest(lhs-rhs)) | ||
|
||
if equation.is_Mul: | ||
result = _solveset(equation, symbol, domain) | ||
|
||
# check if it is exponential type equation | ||
elif _check_expo(equation, symbol): | ||
new_f = _expo_solver(equation, symbol) | ||
result = _solveset(new_f, symbol, domain) | ||
|
||
if isinstance(result, ConditionSet): | ||
result = ConditionSet(symbol, Eq(f, 0), domain) | ||
|
||
else: | ||
result = transolve(f, symbol, domain, **flags) | ||
|
||
if lhs.is_Pow: | ||
new_f = _expo_solver(lhs, symbol) | ||
result = _solveset(new_f, symbol, domain) | ||
|
||
return result | ||
|
||
|
||
def solveset(f, symbol=None, domain=S.Complexes): | ||
r"""Solves a given inequality or equation with set as output | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1613,9 +1613,9 @@ def test_issue_10555(): | |
f = Function('f') | ||
g = Function('g') | ||
assert solveset(f(x) - pi/2, x, S.Reals) == \ | ||
ConditionSet(x, Eq(2*f(x) - pi, 0), S.Reals) | ||
ConditionSet(x, Eq(f(x) - pi/2, 0), S.Reals) | ||
assert solveset(f(g(x)) - pi/2, g(x), S.Reals) == \ | ||
ConditionSet(g(x), Eq(2*f(g(x)) - pi, 0), S.Reals) | ||
ConditionSet(g(x), Eq(f(g(x)) - pi/2, 0), S.Reals) | ||
|
||
|
||
def test_issue_8715(): | ||
|
@@ -1749,3 +1749,58 @@ def test_issue_14454(): | |
number = CRootOf(x**4 + x - 1, 2) | ||
raises(ValueError, lambda: invert_real(number, 0, x, S.Reals)) | ||
assert invert_real(x**2, number, x, S.Reals) # no error | ||
|
||
|
||
def test_transolve(): | ||
from sympy.abc import x, y | ||
from sympy.simplify import simplify | ||
|
||
#### exponential equations #### | ||
|
||
e1 = 3**(2*x) - 2**(x + 3) | ||
e2 = 4**(5 - 9*x) - 8**(2 - x) | ||
e3 = 2**x + 4**x | ||
e4 = exp(log(5)*x) - 2**x | ||
e5 = x**(y*z) - x # issue 10864 | ||
e6 = 5**(x/2) - 2**(x/3) | ||
e7 = 2**(x) + 4**(x) + 8**(x) - 84 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parenthesis around x is redundant |
||
e8 = 4**(x+1) + 4**(x+2) + 4**(x-1) - 3**(x+2) -3**(x+3) | ||
|
||
f1 = exp(x/y)*exp(-z/y) - 2 | ||
f2 = x**x | ||
|
||
assert solveset(e1, x, S.Reals) == FiniteSet(-3*log(2)/(-2*log(3) + log(2))) | ||
assert simplify(solveset(e2, x, S.Reals)) == FiniteSet(4/S(15)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to return a simplified result for such equations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Raw Is it possible instead to invert powers using a two argument log, like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @asmeurer it seems that two argument log does not work well with
|
||
assert solveset(e3, x, S.Reals) == S.EmptySet | ||
assert solveset(e4, x, S.Reals) == FiniteSet(0) | ||
assert solveset(e5, x, S.Reals) == FiniteSet(1) | ||
assert solveset(e6, x, S.Reals) == FiniteSet(0) | ||
assert simplify(solveset(e7, x, S.Reals)) == FiniteSet(2) | ||
assert solveset(e8, x, S.Reals) == FiniteSet(2) | ||
assert solveset(f1, x, S.Reals) == Intersection(S.Reals, FiniteSet(y*log(2*exp(z/y)))) | ||
assert solveset(f2, x, S.Reals) == S.EmptySet | ||
|
||
|
||
def test_expo_conditionset(): | ||
from sympy.abc import x, y | ||
|
||
f1 = (exp(x) + 1)**x - 2 | ||
f2 = (x + 2)**y*x - 3 | ||
|
||
assert solveset(f1, x, S.Reals) == ConditionSet(x, Eq(log(x) - log(-exp(x) - 1), 0), S.Reals) | ||
assert solveset(f2, x ,S.Reals) == ConditionSet(x, Eq(x*(x + 2)**y - 3, 0), S.Reals) | ||
|
||
@XFAIL | ||
def test_expo_fail(): | ||
from sympy.abc import x, y, z, a, b | ||
|
||
assert solveset(z**x - y, x, S.Reals) == Intersection(S.Reals, FiniteSet(log(y)/log(z))) | ||
assert solveset(y - a*x**b, x) == FiniteSet((y/a)**(1/b)) | ||
assert solveset(Poly(exp(x) + exp(-x) - 4)) == FiniteSet(log(-sqrt(3) + 2), log(sqrt(3) + 2)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Poly should be removed as there is no condition in solveset that would extract expression from Poly as it does in solve. I might have missed it.
|
||
|
||
w = symbols('w', integer=True) | ||
f1 = 2*x**w - 4*y**w | ||
f2 = (x/y)**w - 2 | ||
ans1 = solveset(f1, w, S.Reals) | ||
ans2 = solveset(f2, w, S.Reals) | ||
assert ans1 == ans2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove unused import
Function