-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Creating symbolic nsolve expression. #968
Conversation
SymPy Bot Summary: There were test failures. @Krastanov: Please fix the test failures. Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYqIEKDA Interpreter: /usr/bin/python (2.7.1-final-0) Automatic review by SymPy Bot. |
try: | ||
name = kwargs.pop('imp_function') | ||
except KeyError: | ||
raise TypeError('To create a symbolic nsolve function you must' |
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.
Why not make a default name? Anyway, I don't like the except KeyError
. It's better to wrap this around an if block (i.e., if 'imp_function' in kwargs
). But if you use a default name, you can just send it as the second argument to pop.
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.
That was changed to a default name. It's nicer, thanks.
The test still fails. |
F = lambdify((x1, x2), f.T, modules='mpmath') | ||
for x0 in [(-1, 1), (1, -2), (4, 4), (-4, -4)]: | ||
# The next line crashes because I try to sympify a matrix... | ||
x = nsolve(f, (x1, x2), x0, tol=1.e-8) |
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.
It seems that it crashes here because now I try to sympify the args. Should I just make an exception for matrices? Is there a smarter solution?
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.
Yeah, I think for now you can just make an exception. Matrix should be the only SymPy class that will give you problems with this.
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.
Ok, I'll do that. And I'll check if there is a problem with Expr*Matrix (in the tests).
Implemented functions are apparently not callable, which is a good thing, because the order of the arguments would be arbitrary (at least in terms of this method). But the way that they print does not make this clear. |
I did not understand you last post. In And yes, currently the arguments for the nsolve expression have an arbitrary order, but this is not a problem because they are already applied. I mean that |
The workaround for Matrix is done. |
SymPy Bot Summary: All tests have passed. Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY-5AKDA Interpreter: /usr/bin/python (2.7.1-final-0) Automatic review by SymPy Bot. |
Now it is ready for a review. |
# TODO: Ugly workaround for sympification of a matrix | ||
# and the lack of free_symbols property. | ||
# TODO: Same problem with lists. | ||
f = Tuple(*tuple(f)) if isinstance(f, (Matrix, list)) else sympify(f) | ||
fargs = args[1] |
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.
When reviewing please check those lines.
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.
You mean the Matrix bit?
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.
Yes, the workaround: lines 1933..1936
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.
Looks OK to me, though Matrix.free_symbols could be added if that would help.
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.
The sympification will still be a problem.
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.
It's fine. It will be fixed by issue 1015.
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.
Can you provide a link to the issue? I found only 388. 1015 seems to be about limits.
Is the code here good for inclusion or should the workaround be changed?
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 mean pull 1015. I did not understand at first. I suppose that this may wait for 1015 then.
@asmeurer, can this go in as I would like to use it in plotting examples. |
SymPy Bot Summary: There were merge conflicts; could not test the branch. @Krastanov: Please rebase or merge your branch with master. See the report for a list of the merge conflicts. Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY1tcPDA Interpreter: /usr/local/bin/python2.5 (2.5.0-final-0) Automatic review by SymPy Bot. |
@@ -1961,6 +1996,15 @@ def nsolve(*args, **kwargs): | |||
x = findroot(f, x0, J=J, **kwargs) | |||
return x | |||
|
|||
def _nsolve_implemented_function(f, fargs, x0, **kwargs): | |||
#TODO the order of the substitutions is random |
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 should probably be fixed. The order of the args will be completely random and might not even be the same for two runs of the same expression.
When the equation in nsolve contains too many free symbols the nsolve is not evaluated but it's placed in a lambda within an implemented_funtion. That implemented_function is called on the free symbols and the resulting expression is returned. Examples ======== >>> r = nsolve(tanh(y*x)-x, x, 1, imp_function='root_of_equ') >>> r root_of_equ(y) >>> r.evalf(subs={y : 0.9}) 0 >>> r.evalf(subs={y : 1.1}) 0.502940574944642 Problems ======== evalf() does not play well with sympy.mpmath.matrices.matrices.matrix. The tests for this are commented out. An issue was created for the problem: issue 2994 In [14]: r2.evalf(subs={y : 1.2}) Out[14]: root2(y) # not as expected In [15]: r2._imp_(1.2) Out[15]: # as expected [ 1.2] [-1.2] In [16]: type(_) Out[16]: sympy.mpmath.matrices.matrices.matrix * Default choice of name for the nsolve expression. When you call nsolve with too many free symbols it will create an implemented_function with a default name if you do not supply one. In [1]: nsolve(x-y,x,0) Out[1]: nsolve_root(y) In [2]: _.subs({y:1}) Out[2]: nsolve_root(1) In [3]: _.evalf() Out[3]: 1.0000000000000 * Workaround for the creation of nsolve symbolic expression from Matrix. Two problems were present: - sympify does not support Matrix - Matrix and list do not have free_symbols Both of those are casted to Tuples as a workaround: f = Tuple(*tuple(f)) if isinstance(f, (Matrix, list)) else sympify(f) * Exchange wrongly used atoms(Symbol) in nsolve with free_symbols. nsolve permits the user to not specify the variable to solve for. To find the right variable it's poping one from atoms(Symbol). This will cause an error if solving for an Integral for examples. Still nsolving for expressions of that type is imposible because lambdify does not support them.
When creating a symbolic nsolve one gets a random ordering for the arguments. This is generally not a problem as the function object is already called with the arguments in the correct order and the user is expected to use subs on the resulting expression. However if the user wants to use the .func argument there is no easy way to get the argument order. This commit fixes this by permitting the user to specify the desired order.
@asmeurer, I am sorry for taking so long to address your remarks on this PR. You had already reviewed the workaround that I use in order to work with You asked for an option to enforce the order of the arguments for the generated function. This was added. Test are running now. If they pass, can this go in? The most widely used cases (1D problems) work ok. The multidimensional cases work thanks to the workaround. |
SymPy Bot Summary: ✳️ All tests have passed. Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY5Y0YDA Interpreter: /usr/bin/python (2.7.2-final-0) Automatic review by SymPy Bot. |
Hmm... I'm not sure I understand what's going on here. The concept of "symbolic nsolve" is rather obviously self-contradicting and I get the feeling that you're gluing some unrelated functionality onto nsolve(). |
@ronan, It is possible that my proposition is not idiomatic in sympy, if you want for example the function: Obviously if this PR is accepted, the docstring should be made clearer |
Actually, we can't solve x = tan(a_x), so, a fortiori, we can't define the function. Also, I'm not even sure what function you mean: is "solution of tan(a_x)-x" supposed to be the set of solutions, a particular solution specified somehow, something else? |
@Krastanov be careful about user names. @ ronan pinged some other guy. You wanted @rlamy. @rlamy I think he means strictly in numerical terms (because this is nsolve). So it probably means just one solution, whichever one nsolve finds with the given starting value. @Krastanov correct me if I am wrong. |
@rlamy, my example was not the best one. I give a more detailed examples and justification here. TL;DR @asmeurer is correct, and this will return only the first solution found by nsolve. Examples why one would want this functionality:(in some form, not necessarily part of nsolve) Here is a more canonical example: "solution to x=tanh(a*x) as a function of a" This one is very important for solving some toy models in the physics of phase transitions. For example a user will want to plot the phase diagram for a simple Ising model. To do this he will need to evaluate these solutions many times. Presumably he will be aware of the fact that there are 3 (or 1) solutions to this equation and will set his starting seed accordingly. Back to the example of "x=tan(a*x)". This again is a classical example from physics. It gives you the characteristics of a wave on a massive spring attached to a mass (or in acoustics it can be a toy model for a flute). If I want to know the frequency as a function of the mass you need to evaluate it. Why it is natural to have this in nsolve:Well... Give me a more intuitive way to write this: Obviously the title is unimportant, but I wanted to give a complete example. The only way to do this in sympy at the moment is:
|
@rlamy, maybe it will be clearer if I say that I "fake" lazy evaluation in python. |
@rlamy, You raised the question about "defining what a solution means". By "solution" I mean the output of nsolve for sensible numerical seed. Obviously if the initial guess is not sensible the results will be useless. However in this case nsolve should not have been used to start with. |
It seems that what you want is |
To me it seems uglier to mix sympy expressions and python expressions. What is the use of a symbolic library if you have to use How do you integrate the curve that I just plotted in the previous example?
Disclaimer: the limits for the integral are close because of numerical instability. I suppose that this can be fixed with a better choice of kwargs. For me it is still very unclear what the issue is. Could you try to clearly state why this should not be part of |
@rlamy btw if you think that If this is the case you are pointing to a deficiency in |
I think what we need, perhaps, is a Table(expr, (x, xmin, xmax, step)) |
@smichr, I like your proposition very much (there are minor details on which I disagree, but we can discuss them when we start working on this). Just for reference - see also InterpolatedFunction from Mathematica. However I believe that here is a deeper issue that sparked my disagreement with @rlamy. |
# TODO: evalf does not play well with mpmath matrix. | ||
# For the moment I'm only testing _imp_. | ||
#assert r2.evalf(subs={y : 1.2})[0] == 1.2 | ||
assert r2._imp_(1.2)[0] == 1.2 |
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 there a reason that implemented_function doesn't have a __call__
method instead of having to use _imp_
?
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.
Maybe I misunderstood the question but the __call__
method is necessary for the creation of "applied function expressions".
f = implemented_function(blah blah)
f is an instance of Function
f(x) uses the f.__call__ method to create the expression f(x)
f(x).evalf() uses the f._imp_ method
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 was just wondering why you were writing assert r2._imp_(1.2)[0]
instead of r2(1.2)[0]
.
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.
r2
is an expression, so it is not callable. I tried r2.evalf()
but evalf
does not work when the answer is an mpmath matrix. r2._imp_
returns the _imp_
of r2.func
so there may be a deeper problem here:
f(x).func == f # this is ok
f(x)._imp_ == f._imp_ # this does not make sense as you already remarked
Anyway, this seems to be a bug in something around the Function
classes, not the behavior of nsolve
. I should update the test so it uses the stricter semantics.
What type of set would you return and in what case(s)? |
@Krastanov: it's basically the nature of nsolve() that it returns a single solution. In the case of x = tan(a*x), I don't see how it's even possible for it to return the full infinite set of solutions. The real issue is that you're making nsolve() a lot more complicated by allowing it to return completely different types depending on circumstances. That kind of behaviour is an accident waiting to happen in users' code and is hard to memorise. |
@smichr, @rlamy, check Mathematica's documentation for NSolve returning set of solutions. example: @rlamy, |
@rlamy, I filed an issue (more of a feature request). If you are not persuaded by my arguments and if the others agree with you we can just close the request and move the discussion to the issue tracker. http://code.google.com/p/sympy/issues/detail?id=3259 I still believe that the approach in this PR is the best proposed so far for solving the problem, however it will be counterproductive if we just argue over the semantics. @rlamy, You have not answered my last question about your view on the possibility to have |
I agree that this functionality would be useful to have. Whether it should be part of nsolve or some other functionality I don't care. To put an argument forward for "doing things symbolically" as opposed to "just using lambdas", would it be possible to let an implemented function take in symbolic arguments, and remaining unevaluated until a numerical argument (or evalf) is called? In other words, it would just be sugar for creating a custom Function with |
@asmeurer. The discussion with @rlamy made me think about something like that. Maybe a It would work something like that:
There are many issues to be ironed out with such approach, especially when we have so many sympy functions that guess, for example, the free symbols. |
Also, it should be considered which approach is more idiomatic for sympy. It is a beautiful feature, but lazy evaluation is not part of python. |
I am closing this. There is an issue for the subject: http://code.google.com/p/sympy/issues/detail?id=3276 |
When the equation in nsolve contains too many free symbols the nsolve is
not evaluated but it's placed in a lambda within an implemented_funtion.
That implemented_function is called on the free symbols and the resulting
expression is returned.
Examples
Problems
evalf() does not play well with sympy.mpmath.matrices.matrices.matrix.
The tests for this are commented out.
An issue was created for the problem: issue 2994
In [14]: r2.evalf(subs={y : 1.2})
Out[14]: root2(y) # not as expected
In [15]: r2.imp(1.2)
Out[15]: # as expected
[ 1.2]
[-1.2]
In [16]: type(_)
Out[16]: sympy.mpmath.matrices.matrices.matrix