Skip to content
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

Merged
merged 34 commits into from Jul 17, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
059b5ae
initial commit for transolved and exponential solver
Yathartha22 May 23, 2018
f7c38cb
added tests for exponential solver
Yathartha22 May 23, 2018
885923b
added docstring for exponential solver
Yathartha22 May 23, 2018
fd0a4f6
Merge branch 'master' of https://github.com/sympy/sympy into transolv…
Yathartha22 May 23, 2018
f8bc1ad
added documentation for transolve
Yathartha22 May 24, 2018
9dfaf58
minor changes in code and tests.
Yathartha22 May 24, 2018
a2305da
minor changes
Yathartha22 May 25, 2018
d2f067c
improved documentation and imported tests from solve.
Yathartha22 May 31, 2018
217b11f
minor documentation fixes
Yathartha22 Jun 2, 2018
c38ea87
added proof of correctness and minor changes in documentation
Yathartha22 Jun 5, 2018
197355d
imported tests for log and lambert form solve, minor documentation fi…
Yathartha22 Jun 8, 2018
5be292f
improvements for XFAILS to pass
Yathartha22 Jun 8, 2018
ffef477
updated and improved documentation for sphinx
Yathartha22 Jun 10, 2018
a7574b0
removed repeated test
Yathartha22 Jun 11, 2018
648da0e
minor documentation fixes
Yathartha22 Jun 12, 2018
f8597b5
documentation fxes according to pep8; made `_check_expo` to work for …
Yathartha22 Jun 13, 2018
a45df5c
documentation fixes for: `transolve`, `check_expo` and `expo_solver`;…
Yathartha22 Jun 18, 2018
3e0972c
improved case for handling more than 2 terms in `expo_solver`
Yathartha22 Jun 21, 2018
2309ce7
minor documentation changes
Yathartha22 Jun 21, 2018
5e89449
removed unnecessay lines from documentation
Yathartha22 Jun 21, 2018
f2e6ab0
do test before working with args
smichr Jun 21, 2018
89ce4c9
added test for `expo_solver` to return `None`; used top import for Ad…
Yathartha22 Jun 21, 2018
e2f12ac
removed `pow_type()` as this case is handled in `solveset`; removed l…
Yathartha22 Jun 26, 2018
154ee3b
minor document changes
Yathartha22 Jun 28, 2018
8345107
documentation imporovements
Yathartha22 Jul 3, 2018
c0bc441
improved `Philosophy behind the module`; changed the naming conventio…
Yathartha22 Jul 4, 2018
6ddc927
minor documnetation changes
Yathartha22 Jul 4, 2018
de66d1e
Improved documentation for "How to add new class of equations"
Yathartha22 Jul 5, 2018
cca2d14
added `make_expr_args()` to get all the terms of the expressions
Yathartha22 Jul 7, 2018
e49647e
renamed `make_expr_args` to `term_factors()` and improved its documen…
Yathartha22 Jul 8, 2018
e329518
made `term_factors()` an iterator; added some examples in its docstring
Yathartha22 Jul 9, 2018
90efc7a
removed unused list in `term_factors()`; improved doctests for the same
Yathartha22 Jul 9, 2018
e51734a
made `term_factors()` private; minor documentation changes; renamed l…
Yathartha22 Jul 10, 2018
254f45f
removed repeated test and minor change in documentation
Yathartha22 Jul 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
97 changes: 93 additions & 4 deletions sympy/solvers/solveset.py
Expand Up @@ -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,
Copy link
Member

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

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)
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -978,6 +985,88 @@ def _solveset(f, symbol, domain, _check=False):
return result


def _expo_solver(f, symbol):
"""
Helper function for solving exponential equations.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make it: Helper function for solving exponential equations (supported type).


This function solves exponential equation by using logarithms.
Exponents are converted to a more general form of logs which can further be
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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 2*pi*I in SymPy.


return (lhs - rhs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant parenthesis around



def _check_expo(f, symbol):
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 _transolve's documentation you have written it can solve Exponential equations which is not correct, based on this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why does it only looks for exponential with two exponent terms?

Exponents with one term is handled by _invert_real, more than two terms are not supported (unless they can be reduced like 4**(x + 1) + 4**(x + 2) + 4**(x - 1) - 3**(x + 2) - 3**(x + 3), this will be factored and handled in is_Mul in _transolve), therfore _check_expo only looks for two terms.
Should I make for n terms??

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made changes in _check_expo to work for n arguments.

"""
try:
a, b = ordered(f.args)
Copy link
Member

Choose a reason for hiding this comment

The 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]
Copy link
Member

Choose a reason for hiding this comment

The 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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make it private

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean making it _transolve ??

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

"""Helper for solving transcendental equations."""

if 'tsolve_saw' not in flags:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flags.setdefault('tsolve_saw', [])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or all in one:

if f in flags.setdefault('tsolve_saw', []):
    return...
flags['tsolve_saw'].append(f)

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A call to invert_complex(..., domain) will handle which inverter to call (like calling to solveset will just work when you pass a domain).

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also write this as:

unsolved_result = ConditionSet(symbol, Eq(f, 0), domain)
result = unsolved_result

and use the unsolved_result in Line 1330, rather than duplicating the same thing.


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

Expand Down
59 changes: 57 additions & 2 deletions sympy/solvers/tests/test_solveset.py
Expand Up @@ -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():
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to return a simplified result for such equations.
eg: 2**x - 32 currently gives {log(32)/log(2)}, should rather be {5}.
Is it a good idea to use simplify() here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw simplify can be a very expensive function call and is generally avoided in library code. In the old solve there is a flag to enable or disable simplify.

Is it possible instead to invert powers using a two argument log, like log(32, 2)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asmeurer it seems that two argument log does not work well with imageset. I tried

>>> g = FiniteSet(32)
>>> imageset(Lambda(n, log(n, 2)), g)
{log(32)/log(2)}

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))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equation can be handled by solve_decomposition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Poly used here?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.
Can we have a check of such case just to increase the coverage?

if isinstance(f, Poly):
            f = f.as_expr()


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