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

lambert solver for transolve #14972

Open
wants to merge 14 commits into
base: master
from

Conversation

Projects
None yet
6 participants
@Yathartha22
Copy link
Contributor

Yathartha22 commented Jul 25, 2018

This PR is built over #14792 and implements the lambert solver for _transolve in solveset. This has a dependency on bivariate.py for getting solutions for lambert type equations.

TODO's:

  • added lambert solver (_solve_lambert)

  • included tests that _solve_lambert solves, rest are added as XFAIL in test_solve_bivariate

  • include bivariate solver (bivariate_type)

  • Documentation and tests for solve_as_lambert

  • XFAIL (test_solve_bivariate) (will be done in a separate PR)

  • use solveset instead of solve in _lambert() in bivariate.py (will be done in a separate PR)

@aktech @smichr

Release Notes

  • solvers
    • added a new solver for lambert type equations as part of _transolve in solvers.solveset
@sympy-bot

This comment has been minimized.

Copy link

sympy-bot commented Jul 25, 2018

Hi, I am the SymPy bot (v142). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • solvers
    • added a new solver for lambert type equations as part of _transolve in solvers.solveset (#14972 by @Yathartha22)

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.4.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it.

Click here to see the pull request description that was parsed.

This PR is built over #14792 and implements the lambert solver for `_transolve` in `solveset`. This has a dependency on `bivariate.py` for getting solutions for lambert type equations.

TODO's:

- [x] added `lambert` solver (`_solve_lambert`)

- [X] included tests that `_solve_lambert` solves, rest are added as XFAIL in `test_solve_bivariate`

- [x]  include `bivariate` solver (`bivariate_type`)

- [x] Documentation and tests for `solve_as_lambert` 

- XFAIL (`test_solve_bivariate`) (will be done in a separate PR)

- use `solveset` instead of `solve` in `_lambert()` in `bivariate.py` (will be done in a separate PR)

@aktech @smichr 

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. If there is no release notes entry for this PR,
write "NO ENTRY". The bot will check your release notes automatically to see
if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* solvers
  * added a new solver for lambert type equations as part of `_transolve` in `solvers.solveset`
<!-- END RELEASE NOTES -->

@Abdullahjavednesar Abdullahjavednesar requested review from smichr and aktech Jul 25, 2018

Show resolved Hide resolved sympy/solvers/solveset.py Outdated
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Jul 27, 2018

@smichr I have made the changes in #14792 once you approve there I will rebase it here.

@aktech aktech added the GSoC label Jul 27, 2018

@Yathartha22 Yathartha22 force-pushed the Yathartha22:transolve_lambert branch from a3dd0fe to f57a5bc Aug 1, 2018

@@ -1500,6 +1500,11 @@ def add_type(lhs, rhs, symbol, domain):

if new_eq is not None:
result = _solveset(new_eq, symbol, domain)
else:
# try solving as lambert before returning the result
ans = _solve_as_lambert(Eq(lhs, rhs), symbol)

This comment has been minimized.

@Yathartha22

Yathartha22 Aug 1, 2018

Author Contributor

Is this the right place to invoke _solve_as_lambert? @smichr

@@ -1923,6 +1910,23 @@ def test_solve_lambert():
assert solveset_real(3*x + log(4*x), x) == \
FiniteSet(LambertW(Rational(3, 4))/3)


@XFAIL
def test_other_solve_lambert_():

This comment has been minimized.

@Yathartha22

Yathartha22 Aug 1, 2018

Author Contributor

Added these tests as XFAIL because they are either solved as Mul or Pow

This comment has been minimized.

@smichr

smichr Mar 21, 2019

Member

I get no solution:

>>> solveset_real(-a*x + 2*x*log(x), x)
ConditionSet(x, Eq(-a*x + 2*x*log(x), 0), Reals)
>>> solveset_real(log(log(x - 3)) + log(x-3), x)
ConditionSet(x, Eq(x*log(x - 3) - 3*log(x - 3) - 1, 0), Reals)
@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Aug 1, 2018

Some equations are solved by _solve_lambert after posifying the symbol, as in _tsolve. A few examples: x**2 - 2**x, (a/x + exp(x/2)).diff(x), x*log(x) + 3*x + 1, (a/x + exp(x/2)).diff(x, 2). Should we consider it in solveset too? What is the reason (maybe a proof) that making symbol positive would solve a large group of equations?

@Abdullahjavednesar Abdullahjavednesar requested a review from smichr Aug 1, 2018

@Yathartha22 Yathartha22 force-pushed the Yathartha22:transolve_lambert branch from f57a5bc to 2a5d268 Aug 3, 2018


if lhs.is_Add:
result = add_type(equation, symbol, domain)
result = add_type(lhs, rhs, symbol, domain)
elif _is_lambert(lhs, symbol):

This comment has been minimized.

@Yathartha22

Yathartha22 Aug 3, 2018

Author Contributor

.is_Mul and .is_Pow here would become specific to lambert types, therefore a direct identification of lambert type is done here

@Yathartha22 Yathartha22 referenced this pull request Aug 10, 2018

Merged

log solver for transolve #14792

3 of 3 tasks complete

@Yathartha22 Yathartha22 force-pushed the Yathartha22:transolve_lambert branch from 2a5d268 to c2a9797 Aug 10, 2018

Show resolved Hide resolved sympy/solvers/solveset.py Outdated
@smichr

This comment has been minimized.

Copy link
Member

smichr commented Aug 10, 2018

What is the reason (maybe a proof) that making symbol positive would solve a large group of equations?

My guess is that this is so log transformations of powers would work.

Show resolved Hide resolved sympy/solvers/solveset.py Outdated
@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Aug 10, 2018

My guess is that this is so log transformations of powers would work.

Should solveset use this thing too or should we look for any other way?

@smichr

This comment has been minimized.

Copy link
Member

smichr commented Aug 12, 2018

Should solveset use this thing too or should we look for any other way?

the GSOC work suggests how solveset should handle this: make conditions part of what is returned. If all the conditions are met, the ConditionSet collapses, otherwise it is a sentinal for the requirements for a given solution.

If you don't use the log transformations I suspect you will have more cases to consider as you figure out what can and can't be solved by lambert.

Show resolved Hide resolved sympy/solvers/solveset.py Outdated
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
lambert solutions now gets intersected with positive real domain; adj…
…ustments in calling lambert and bivariate solver;
@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 14, 2019

In my master it is running fine.
Screenshot from 2019-03-15 01-49-39

@Yathartha22 Yathartha22 changed the title [WIP] lambert solver for transolve lambert solver for transolve Mar 14, 2019

@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 14, 2019

okay, I might have screwed something after I updated master, I will see. Thanks for checking.

@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 14, 2019

Did you update your master?

@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 14, 2019

You was right it was giving NotImplementedError . I didn't pulled your rebasing commits. In master also after updating it is giving NotImplementedError.

@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 14, 2019

I have opened an issue #16263

added a case to handle `x**(b*x) - z` type lambert equations;
removed use of `deep = True` flag in factor in `_solve_lambert`
@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 16, 2019

I am trying to fix the factor() issue. Once it gets fixed most of the work for Lambert solver is done.
Also, I see two todos left as of now (see description). I think better thing to do would be to have a seperate PR for these two todos, and may be @jmig5776 you can carry on the work further, once this PR is merged.
@aktech @smichr what do you say, we should try to merge this as soon as possible.

@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 16, 2019

@Yathartha22 Sorry I was in train for two days. Wasn't able to review the latest commit.

Yeah i agree with you i will be opening two PRs for the above two checkpoints left

Show resolved Hide resolved sympy/solvers/bivariate.py Outdated
@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 18, 2019

I was trying to change solve to solveset here

solns = solve(X1 - u, x)
for i, tmp in enumerate(solns):
solns[i] = tmp.subs(u, rhs)
sol.append(solns[i])

But when i added from sympy.solvers.solveset import solveset_real at the top then it showed ImportError. Or should I Import it in the function _lambert.
@smichr Please help here

@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 18, 2019

But when i added from sympy.solvers.solveset import solveset_real at the top ...

I don't think this should have happened, you might be missing something.
Also imporitng within _lambert shouldn't make a difference, although you can do.

@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 20, 2019

@aktech @smichr
Most of the work has been achieved in this PR, whatever is left can be incorporated over time. If you find it okay we can merge it (before 1.4 release).

@asmeurer can this PR get a 1.4 milestone, I would certainly want this feature to be in 1.4.

@codecov

This comment has been minimized.

Copy link

codecov bot commented Mar 20, 2019

Codecov Report

Merging #14972 into master will increase coverage by 0.07%.
The diff coverage is 98.039%.

@@             Coverage Diff              @@
##            master    #14972      +/-   ##
============================================
+ Coverage   73.435%   73.505%   +0.07%     
============================================
  Files          618       618              
  Lines       158446    158502      +56     
  Branches     37174     37192      +18     
============================================
+ Hits        116355    116508     +153     
+ Misses       36631     36564      -67     
+ Partials      5460      5430      -30
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
Show resolved Hide resolved sympy/solvers/solveset.py Outdated
def test_other_lambert():
a = S(6)/5
assert solveset_real(x**a - a**x, x) == FiniteSet(
a, -a*LambertW(-log(a)/a)/log(a))

This comment has been minimized.

@smichr

smichr Mar 21, 2019

Member

When a is greater than 1, solve knows that there is a solution:

>>> var('a',positive=True)
a
>>> solveset_real(x**(1+a) - (1+a)**x, x)
ConditionSet(x, Eq(x**(a + 1) - (a + 1)**x, 0), Reals)
>>> solve(x**(1+a) - (1+a)**x, x)
[-(a + 1)*LambertW(-log(a + 1)/(a + 1))/log(a + 1)]

(Symmetry gives the second solution.)

This comment has been minimized.

@Yathartha22

Yathartha22 Mar 21, 2019

Author Contributor

Another thing that I see here is _lambert() is able to handle most of such types

>>> a, x = symbols('a x', positive=True)
>>> _solve_lambert(x**(2) - (2)**x, x, gens)
[2, -2*LambertW(-log(2)/2, -1)/log(2)]    # ok
>>> _solve_lambert(x**(a) - (a)**x, x, gens)
[-a*LambertW(-log(a)/a)/log(a)]    # ok

>>> u = Dummy('u', positive=True)
>>> _solve_lambert(u**(a) - (a)**u, u, gens)   # NotImplementedError (interesting?)
>>> _solve_lambert(x**(1+a) - (1+a)**x, x, gens)    # NotImplementedError

I did not have a thorogh look at the cause but the last two errors suggets that maybe _lambert() is missing something. Isn't it?

This comment has been minimized.

@jmig5776

jmig5776 Mar 21, 2019

Contributor

Yeah change if mainpow and symbol in mainpow.exp.free_symbols: to if mainpow. I think it doesn't matter if exp has the symbol or not
PS - _solve_lambert function

This comment has been minimized.

@jmig5776

jmig5776 Mar 21, 2019

Contributor

And for adding symmetric solution you can do something like this

if mainpow:
            lhs = collect(lhs, mainpow)
            if lhs.is_Mul and rhs != 0:
                soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol)
            elif lhs.is_Add:
                # move all but mainpow-containing term to rhs
                other = lhs.subs(mainpow, 0)
                mainterm = lhs - other
                rhs = rhs - other
                diff = log(mainterm) - log(rhs)
                soln = _lambert(expand_log(diff), symbol)
                if rhs.is_Pow:
                    if mainterm.base == rhs.exp and mainterm.exp == rhs.base:
                        soln = Union(soln, solveset(mainterm.base - mainterm.exp, symbol))

in _solve_lambert

@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 21, 2019

@Yathartha22 Can you try this? @smichr Please review this git diff

diff --git a/sympy/solvers/bivariate.py b/sympy/solvers/bivariate.py
index 51f4a9703f..f3e011f9b7 100644
--- a/sympy/solvers/bivariate.py
+++ b/sympy/solvers/bivariate.py
@@ -148,6 +148,7 @@ def _lambert(eq, x):
     u = Dummy('rhs')
     sol = []
     # check only real solutions:
+    from sympy.solvers.solveset import solvify
     for k in [-1, 0]:
         l = LambertW(d/(a*b)*exp(c*d/a/b)*exp(-f/a), k)
         # if W's arg is between -1/e and 0 there is
@@ -156,7 +157,7 @@ def _lambert(eq, x):
             continue
         rhs = -c/b + (a/d)*l
 
-        solns = solve(X1 - u, x)
+        solns = solvify(X1 - u, x, S.Complexes)
         for i, tmp in enumerate(solns):
             solns[i] = tmp.subs(u, rhs)
             sol.append(solns[i])
@@ -286,7 +287,8 @@ def _solve_lambert(f, symbol, gens):
 
     if not soln:
         mainpow = _mostfunc(lhs, Pow, symbol)
-        if mainpow and symbol in mainpow.exp.free_symbols:
+
+        if mainpow:
             lhs = collect(lhs, mainpow)
             if lhs.is_Mul and rhs != 0:
                 soln = _lambert(expand_log(log(lhs) - log(rhs)), symbol)
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 70338c9ac0..462aba812f 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -1104,6 +1104,7 @@ def _solve_exponential(lhs, rhs, symbol, domain):
     """
     unsolved_result = ConditionSet(symbol, Eq(lhs - rhs), domain)
     newlhs = powdenest(lhs)
+
     if lhs != newlhs:
         # it may also be advantageous to factor the new expr
         return _solveset(factor(newlhs - rhs), symbol, domain)  # try again with _solveset
@@ -1111,6 +1112,8 @@ def _solve_exponential(lhs, rhs, symbol, domain):
     if not (isinstance(lhs, Add) and len(lhs.args) == 2):
         # solving for the sum of more than two powers is possible
         # but not yet implemented
+        if lhs.is_Pow and len(lhs.args) == 2:
+            return _solveset(expand_log(log(lhs)- log(rhs), force=True), symbol, domain)
         return unsolved_result
 
     if rhs != 0:
@@ -1632,6 +1635,12 @@ def add_type(lhs, rhs, symbol, domain):
 
         if lhs.is_Add:
             result = add_type(lhs, rhs, symbol, domain)
+        # check if it is exponential type equation
+        if _is_exponential(lhs, symbol):
+            result = _solve_exponential(lhs, rhs, symbol, domain)
+        # check if it is logarithmic type equation
+        elif _is_logarithmic(lhs, symbol):
+            result = _solve_logarithm(lhs, rhs, symbol, domain)
         elif _is_lambert(lhs, symbol):
             # try to get solutions in form of lambert
             result = _solve_as_lambert(lhs, rhs, symbol, domain)
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index 4e0b9e6ad6..bcfbaba325 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -869,7 +869,7 @@ def test_conditionset():
         ) == ConditionSet(x, Eq(-x + sin(Abs(x)), 0), S.Reals)
 
     assert solveset(y**x-z, x, S.Reals) == \
-        ConditionSet(x, Eq(y**x - z, 0), S.Reals)
+        Intersection(S.Reals, FiniteSet(log(z)/log(y)))
 
 
 @XFAIL
@@ -1925,7 +1925,7 @@ def test_solve_lambert():
         -3*LambertW(-log(3)/3, -1)/log(3))
 
     assert solveset_real(3*log(a**(3*x + 5)) + a**(3*x + 5), x) == \
-        FiniteSet((-log(a**5) - LambertW(S(1)/3))/(3*log(a)))
+        FiniteSet((-5*log(a) - LambertW(S(1)/3))/(3*log(a)))
 
     assert solveset_real(exp(x) + x, x) == FiniteSet(-LambertW(1))
     assert solveset_real(x + 2**x, x) == \
@@ -1947,11 +1947,11 @@ def test_solve_lambert():
     b = Symbol('b')
     eq = 3*log(a**(3*x + 5)) + b*log(a**(3*x + 5)) + a**(3*x + 5)
     assert solveset_real(eq, x) == FiniteSet(
-        (-log(a**5) - LambertW(S(1)/(b + 3)))/(3*log(a)))
+        (-5*log(a) - LambertW(S(1)/(b + 3)))/(3*log(a)))
 
     p = symbols('p', positive=True)
     eq = 3*log(p**(3*x + 5)) + p**(3*x + 5)
-    assert solveset(eq, x) == Intersection(Interval(0, oo), FiniteSet(-S(5)/3 - LambertW(S(1)/3)/(3*log(p))))
+    #assert solveset(eq, x) == Intersection(Interval(0, oo), FiniteSet(-S(5)/3 - LambertW(S(1)/3)/(3*log(p))))
     assert solveset_real(eq, x) == FiniteSet(-S(5)/3 - LambertW(S(1)/3)/(3*log(p)))
 
     assert solveset_real(2*x + 5 + log(3*x - 2), x) == \
@Yathartha22

This comment has been minimized.

Copy link
Contributor Author

Yathartha22 commented Mar 21, 2019

@jmig5776 that's a lot of diff and most of it is unchanged code. Please mention only the changes that you are suggesting. Also, use code highlighting (to have better readability).

@jmig5776

This comment has been minimized.

Copy link
Contributor

jmig5776 commented Mar 21, 2019

Okk @Yathartha22 I got it.

@@ -1370,16 +1368,16 @@ def _solve_as_bivariate(lhs, rhs, symbol, domain):

def _is_lambert(f, symbol):

This comment has been minimized.

@smichr

smichr Mar 21, 2019

Member

This should probably be renamed to _possible_lambert and docstring updated to say: "return True if any factor in f is a power, log or exp containing the given symbol". Note the change regarding symbol. See _is_logarithmic for an example of a better docstring for the function.

Also, _term_factors could get *syms and then yield mul_arg if not syms or mul_args.has(*syms)

assert solveset_real((a/x + exp(x/2)).diff(x, 2), x) == Intersection(
Interval(0, oo), FiniteSet(6*LambertW((-a)**(S(1)/3)/3))) - FiniteSet(0)
assert solveset_real((a/x + exp(x/2)).diff(x, 2), x) == \
FiniteSet(6*LambertW((-a)**(S(1)/3)/3)) - FiniteSet(0)

This comment has been minimized.

@smichr

smichr Mar 21, 2019

Member

There are two solutions when a=1 and this is not reflected in the output. That this has slipped by makes me nervous about the whole addition of this PR. Again, solveset must carefully analyze the function it is solving and represent a complete solution.

>>> eq=(a/x + exp(x/2)).diff(x, 2)
>>> e1=eq.subs(a,1)
>>> nsolve(e1,-1)
-3.71436772041567
>>> nsolve(e1,-11)
-9.07280730994706

This is not a trivial task and should not be rushed. I suggest working away at the ones that you know have a definite solution and conditions sufficient to know the number of solutions.

assert solveset_real(x**3 - 3**x, x) == FiniteSet(
-3*LambertW(-log(3)/3)/log(3), -3*LambertW(-log(3)/3, -1)/log(3))
assert solveset_real(x**2 - 2**x, x) == solveset_real(-x**2 + 2**x, x)

assert solveset_real((a/x + exp(x/2)).diff(x), x) == \
Intersection(Interval(0, oo),
FiniteSet(4*LambertW(sqrt(2)*sqrt(a)/4))) - FiniteSet(0)
FiniteSet(4*LambertW(sqrt(2)*sqrt(a)/4)) - FiniteSet(0)

This comment has been minimized.

@smichr

smichr Mar 21, 2019

Member

...and 3 solutions here around -5, -3 and 1.

@smichr
Copy link
Member

smichr left a comment

This is not ready to commit; it is returning only partial solutions to equations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.