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
log solver for transolve #14792
log solver for transolve #14792
Conversation
If the other branch is very stable, this is ok...but I have often run into rebase issues when doing this. |
I guess it won't be stable, it will have few commits more. I will give it a try though otherwise I will wait for main branch to get merge and add a new PR. |
e0cfba3
to
b28e36c
Compare
sympy/solvers/solveset.py
Outdated
new_f = new_lhs - rhs | ||
|
||
result = S.EmptySet | ||
solutions = _solveset(new_f, symbol, domain) |
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.
I believe you do not want to do this. You want to do the solution and domain checking all in one place, not duplicate it in every helper for every type of equation.
b28e36c
to
fe25af3
Compare
sympy/solvers/solveset.py
Outdated
return new_f | ||
|
||
|
||
def _is_logarithmic(f, symbol): |
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.
Will be adding documentation in the next commit.
Logic has been changed to check each term of the expression of log form rather than previously used logcombine method:
- using logcombine can be risky as it may manipulate the equation like
log(x) - log(2*x)
would reduce tolog(1/2)
and the routine would returnFalse
as there will be no symbol.
sympy/solvers/solveset.py
Outdated
for expr_arg in expr_args: | ||
if check_log(expr_arg, symbol): | ||
return True | ||
if isinstance(expr_arg, Pow): |
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.
This is for expressions like log(x)**2
.
sympy/solvers/solveset.py
Outdated
@@ -975,6 +982,438 @@ def _solveset(f, symbol, domain, _check=False): | |||
if isinstance(s, RootOf) | |||
or domain_check(fx, symbol, s)]) | |||
|
|||
if domain.is_subset(S.Reals) and _is_logarithmic(f, symbol): |
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.
This check is done so as to remove unwanted solutions form the original result.
This method is implemented rather than using checksol
because there are some unwanted solution that would still creep into the actual result.
Take for example log(3*x) - log(-x + 1) - log(4*x + 1)
, the result in general is -1/2 and 1/2
but the negative solution is only valid if we consier complex valued logairthm but since we are concerned with the Real domain as of now only natural logairthm will work which is undefined for negative as well as for 0
therefore -1/2
can't be the solution in real doamin.
If the result is verified using checksol
the subsitution would cause the cancelling of I*pi
and hence it would return True
, making -1/2
as the solution
In this method each solution is tried in each term of the expression, if its result is not in a real form then it is not the solution (This is specifically beneficial for checking for logarithmic expressions).
fe25af3
to
4576b31
Compare
sympy/solvers/solveset.py
Outdated
return result | ||
|
||
|
||
def log_singularities(f, symbol, result, domain): |
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.
Will be adding docstring in the next commit.
@smichr @aktech I have started a discussion to arrive at a conclusion for logarithmic conclusion. Have a look. |
…ns; improved `_solve_log` as per the documentation given in `_transolve`
… determine singularities in real domain
4576b31
to
afc5951
Compare
|
sympy/solvers/solveset.py
Outdated
Returns | ||
======= | ||
|
||
An improved equation containg a single instance of log. |
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.
->containing (sp)
sympy/solvers/solveset.py
Outdated
return True | ||
|
||
expr_args = _term_factors(f) | ||
for expr_arg in expr_args: |
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.
suggest combining into for expr_arg in _term_factors(f):
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.
Just used to improve readability. One could at once get what is returned from _term_factors
. Should I change it?
sympy/solvers/solveset.py
Outdated
|
||
expr_args = _term_factors(f) | ||
for expr_arg in expr_args: | ||
if check_log(expr_arg, symbol): |
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.
is the helper necessary? if isinstance(expr_arg, log) and symbol in expr_arg.free_symbols:
will be less than 72 characters.
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.
oh...you use it again below. Then how about prefacing with
base = expr_arg.base if isinstance(expr_arg, Pow) else expr_arg
if isinstance(base, log) and symbol in base.free_symbols:
return True
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.
This can be done. Thanks!
sympy/solvers/solveset.py
Outdated
|
||
def _is_logarithmic(f, symbol): | ||
r""" | ||
Helper to check if the given equation is logarithmic or not. |
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.
Like exponential equations, you should describe the definition of being logarithmic.
My main concern is that if there are conditions that are neither false nor true -- just undecided -- I would like to see the solution and the conditions come back, not a generic |
This will handle the recursion error: old = [log(a), log(-b)]
new = list(expand_log, old))
if new == old:
return unsolved_result
return _solveset(new[0] - new[1], symbol, domain) Nothing in solveset is ready to handle Piecwise solutions. It expects sets to be returned. That being the case, I don't know how the solution with conditions can be returned. You might as well just return functions (instead of solutions) from the helpers. In this case, One possible use of conditions, however, would be to return EmptySet if the conditions are False. |
IMO we can do this in two ways:
for equation
Returning |
In either case it seems the appropriate answer is EmptySet. e.g. that there is no answer in a domain doesn't mean there isn't an answer. If the user gives a domain and we know the answer is not in that domain then the empty set is appropriate:
Regarding the condition set return value, I think you are right. What about:
|
Will |
…orrect in solve_exponential
sympy/solvers/solveset.py
Outdated
|
||
return unsolved_result | ||
return ConditionSet(symbol, conditions, solutions) |
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.
Just one query about this scenario. Forcing the equation to be solved prompts solveset
to return equation as an intersection with the domain.
For eg: if I force the following equation to get solved
>>> a, b, x, y = symbols('a b x y')
>>> solveset(a**x - b**(x + 1), x, S.Reals)
Intersection(Reals, {log(b)/(log(a) - log(b))})
This is something similar to what we get in logarithmic eqautions
. So is returning this kind of ConditionSet
still appropriate or should we try force
solving the equation (to get something as above)?
If this is the case then passing domain to the helpers is not necessary. Once can helper-solve in the complex domain and |
But still, we will be needing to pass the "domain" parameter in |
@Yathartha22 I am fine with merging this as long as @smichr doesn't have any concerns. |
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 83b6ffb..1626d8d 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -1113,10 +1113,9 @@ def _solve_exponential(lhs, rhs, symbol, domain):
Eq(im(b_exp), 0)
)
- if conditions is S.true:
- return _solveset(expand_log(log(a)) - expand_log(log(-b)), symbol, domain)
-
- return unsolved_result
+ neweq = expand_log(log(a), force=True) - expand_log(log(-b), force=True)
+ soln = _solveset(neweq, symbol, domain)
+ return ConditionSet(symbol, conditions, soln)
def _is_exponential(f, symbol):
@@ -1528,9 +1527,6 @@ def add_type(lhs, rhs, symbol, domain):
else:
result = rhs_s
- if isinstance(result, ConditionSet):
- result = unsolved_result
-
return result
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 37c9166..a6879cf 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -1867,8 +1867,10 @@ def test_exponential_symbols():
(x - z)/log(2)) - FiniteSet(0)
a, b, x, y = symbols('a b x y')
+ assert solveset(a**x - b**x, x) == ConditionSet(
+ x, (a > 0) & (b > 0) & Eq(im(x), 0), {0})
assert solveset_real(a**x - b**x, x) == ConditionSet(
- x, Eq(a**x - b**x, 0), S.Reals)
+ x, (a > 0) & (b > 0), {0})
@XFAIL
The docstrings needs a touch up if you use this diff to reflect that ConditionSet may be returned if there are conditions that must be met for the solution to be valid. It seems a little ill-defined right now as to who decides there is no solution, and whether the difference between 'can't solve' and 'no solution' is clearly being made. If used, a few tests need editing, too, to put the constant on the rhs of the Eq:
|
Maybe we define the " conditions=True
if domain.is_subset(Reals):
conditions = And(
a_base > 0,
b_base > 0,
Eq(im(a_exp), 0),
Eq(im(b_exp), 0)) and Note: Even though doing this we will be getting the solutions in Complex domain as well (but they won't be general ones). I would rather make a separate PR for solving exponential equations in complex domain (genral solutions)
I guess |
@smichr I have already made the changes in the latest commit below this comment. I guess the branch you are using is not updated. |
This will be the behaviour >>> f = a**x - b**x
>>> solveset(f, x, S.Reals)
ConditionSet(x, (a > 0) & (b > 0), {0})
>>> solveset(f, x, S.Complexes)
{0}
>>> a, b = symbols('a b', positive=True)
>>> solveset(f, x, S.Reals)
{0} |
There are still assumptions when the domain is complex:
We should require that a and b be nonzero and that a not equal b: |
Oh yes I missed this for complex domain. Thanks! |
Please sharpen a pencil and check out the assertions in the following code comments: diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 5ac6781..15f4849 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -13,7 +13,7 @@
from __future__ import print_function, division
from sympy.core.sympify import sympify
-from sympy.core import (S, Pow, Dummy, pi, Expr, Wild, Mul, Equality,
+from sympy.core import (S, Pow, Dummy, pi, Expr, Wild, Mul,
Add)
from sympy.core.containers import Tuple
from sympy.core.facts import InconsistentAssumptions
@@ -27,10 +27,11 @@
from sympy.functions import (log, Abs, tan, cot, sin, cos, sec, csc, exp,
acos, asin, acsc, asec, arg,
piecewise_fold, Piecewise)
+from sympy.functions.elementary.complexes import sign, im
from sympy.functions.elementary.trigonometric import (TrigonometricFunction,
HyperbolicFunction)
from sympy.functions.elementary.miscellaneous import real_root
-from sympy.logic.boolalg import And
+from sympy.logic.boolalg import And, ITE
from sympy.sets import (FiniteSet, EmptySet, imageset, Interval, Intersection,
Union, ConditionSet, ImageSet, Complement, Contains)
from sympy.sets.sets import Set
@@ -246,10 +247,14 @@ def _invert_real(f, g_ys, symbol):
s, b = integer_log(rhs, base)
if b:
return _invert_real(expo, FiniteSet(s), symbol)
- elif rhs is S.One:
- # special case: 0**x - 1
- return (expo, FiniteSet(0))
- return (expo, S.EmptySet)
+ elif base.is_zero:
+ one = Eq(rhs, 1)
+ if one == True:
+ # special case: 0**x - 1
+ return (expo, FiniteSet(0))
+ elif one == False:
+ return (expo, S.EmptySet)
+ return (f, g_ys)
if isinstance(f, TrigonometricFunction):
@@ -877,7 +882,7 @@ def _solveset(f, symbol, domain, _check=False):
inverter = lambda f, rhs, symbol: _invert(f, rhs, symbol, domain)
result = EmptySet()
-
+ print(f)
if f.expand().is_zero:
return domain
elif not f.has(symbol):
@@ -1104,27 +1109,64 @@ def _solve_exponential(lhs, rhs, symbol, domain):
return unsolved_result
a, b = list(ordered(lhs.args))
- a_term = a.as_independent(symbol)[1]
- b_term = b.as_independent(symbol)[1]
+ a_co, a_term = a.as_independent(symbol)
+ b_co, b_term = b.as_independent(symbol)
+
+ # if we don't know if the equation really is two args then
+ # we can't return a solution as though it were and there
+ # is no way to represent a Piecewise solution in solveset
+ # so we have to return an unsolved result
+ if And(Ne(a_co, 0), Ne(b_co, 0)) != True:
+ # indeterminate:
+ # if a==0 or c==0, the solution of a*b**f(x) + c*d**g(x)
+ # is empty set if d or b (respectively) is not zero else
+ # if a==0 and d==0 then g(x)=1 or if c==0 and b==0 then
+ # f(x) == 1; if a and c are 0 the solution is the domain
+ return unsolved_result
a_base, a_exp = a_term.base, a_term.exp
b_base, b_exp = b_term.base, b_term.exp
- from sympy.functions.elementary.complexes import im
+ if ITE(
+ Eq(a_base, b_base),
+ Ne(a_exp, b_exp),
+ ITE(
+ Eq(a_exp, b_exp),
+ Ne(a_base, b_base),
+ Ne(a, b))) != True:
+ # indeterminate:
+ # if bases are equal there are two possible solutions:
+ # if(b=d=B) then a*b**f(x) + c*d**g(x)
+ # is a*B**f(x) + c*B**g(x); if the exponents are the same, f=g=h, then
+ # the solution is given by B**h(x)*(a + c); if B is zero then the solution
+ # is h(x) != 0 else the solution is the empty set.
+ # If the exponents are the same, there are 3 possible solutions
+ # if f=g=h, then we have a*b**h(x)+c*d**h(x). The case of b==d
+ # was handled above and there are two cases: h(x) != 0 or the empty set.
+ # If b!=d then log(a) + h(x)*log(b) - log(-c) - h(x)*log(d)
+ # which gives log(a/-c) + h(x)*log(b/d) and h(x) = -log(a/-c)/log(b/d)
+ return unsolved_result
+ L, R = map(lambda i: expand_log(log(i), force=True), (a, -b))
if domain.is_subset(S.Reals):
conditions = And(
+ Ne(sign(a_co), sign(b_co)),
a_base > 0,
b_base > 0,
Eq(im(a_exp), 0),
- Eq(im(b_exp), 0))
+ Eq(im(b_exp), 0),
+ )
+ solutions = _solveset(L - R, symbol,
+ # when domain is Reals, conditions will take care
+ # of keeping the solution Real, otherwise we
+ # need the clipped solution from solveset
+ S.Complexes if domain is S.Reals else domain)
else:
conditions = And(
Ne(a_base, 0),
- Ne(b_base, 0))
-
- log_type_equation = expand_log(log(a), force=True) - expand_log(log(-b), force=True)
- solutions = _solveset(log_type_equation, symbol, domain)
+ Ne(b_base, 0),
+ )
+ solutions = _solveset(L - R, symbol, domain)
return ConditionSet(symbol, conditions, solutions)
@@ -1884,7 +1926,7 @@ def linear_eq_to_matrix(equations, *symbols):
for equation in equations:
f = sympify(equation)
- if isinstance(f, Equality):
+ if isinstance(f, Eq):
f = f.lhs - f.rhs
# Extract coeff of symbols |
The comments are not "production ready" but I have to leave this to you while I end my own week's break and finish things delayed. |
I opened an issue for the mishandling of a single exponential term, so that doesn't have to be fixed. I think this routine is giving an answer in cases where there is not enough information to return a Set solution, but that can be fixed later. The main thing is that the structure is in place. I think the GSOC has a category for "future improvements". So there's no reason to delay this PR. Congrats. I will be checking during breaks today to see if there is anything else you need reviewed. |
Well, thanks @smichr for the merge and all the reviews :). I was actually working on
For points
Next aim would be adding a lambert solver #14972. I will rebase it with the current master. |
This PR is an extension to #14736 and is built over its branch.
The PR aims to implement
log solver
as part oftransolve
.TODO's:
Documentation for
_solve_
and_is_
routines for logTests for the helpers
Improvement in
_solve_
routine to handle corner cases.Release Notes
_transolve
insolvers.solveset