not a Python function
sorry if i'm being dumb about this, but functions with multiple input arguments don't seem to be recognized as functions:
julia> using PyCall
julia> @pyimport scipy.optimize as so
julia> so.curve_fit((x,m,b)->m*x+b, 1:10, 2:2:20)
ERROR: PyError (:PyObject_Call) <type 'exceptions.TypeError'>
TypeError('<PyCall.jlwrap (anonymous function)> is not a Python function',)
File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 604, in curve_fit
args, varargs, varkw, defaults = _getargspec(f)
File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/site-packages/scipy/_lib/_util.py", line 289, in getargspec_no_self
argspec = inspect.getargspec(func)
File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/inspect.py", line 816, in getargspec
raise TypeError('{!r} is not a Python function'.format(func))
[inlined code] from /home/arthurb/.julia/v0.4/PyCall/src/exception.jl:81
in pycall at /home/arthurb/.julia/v0.4/PyCall/src/PyCall.jl:356
in call at /home/arthurb/.julia/v0.4/PyCall/src/PyCall.jl:372
julia> so.newton(x -> cos(x) - x, 1)
0.7390851332151607
You're doing it fine, it's a bug. optimize is trying to count how many arguments the function has, assuming it's a regular Python function, but PyCall is not producing native Python functions. It's not trivial to fix. In the meantime, this works:
using PyCall
funner3 = pyeval("""lambda f: lambda a, b, c: f(a, b, c)""")
@pyimport scipy.optimize as so
so.curve_fit(funner3((x,m,b)->m*x+b), 1:10, 2:2:20)
Looking at the inspect source code in Python, it seems like we need a couple of things to make this work:
- The Julia function-wrapper type should be a subtype of
types.FunctionType - As discussed on julia-users, have the PyCall function wrapper add a
func_code(or__code__in Py3) attribute that returns an object with at least some of the attributes of the Pythonfunc_code, e.g.co_argcount(the number of arguments),co_filename(the file where it is defined),co_firstlineno(the line number),co_name(the name of the function). - Implement those attributes by looping over the method list and picking some reasonable definition, e.g. defining
co_argcountto be the maximum number of arguments over all methods. (This is a little bit ambiguous because Julia method overloading semantics are so different from Python.) ** In particular,inspect.getargsneeds theco_argcountandco_varnamesandco_flags, andco_codeshould be an empty list. ** It also needsfunc_codeto be a subtype oftypes.CodeType.
All possible in principle, although we may have to throw an error if the method overloading makes the answers too ambiguous, but a nontrivial amount of work to implement.
thanks for the workaround. that's all in need for now.
Hello
Is there any update regarding this? I don't have the know-how to implement what @stevengj. I could try if somebody could point to how to start working on this but I understand that may itself be similar to the amount of work needed to fix it. Just wanted to know if there is an update because I learned that LsqFit.jl is not very reliable, so only reliable easy-to-use curve fitting method is provided by scipy.optimize.curve_fit at the moment.
The workaround is to wrap the Julia function in a Python lambda as noted above by @cstjean.
We actually do this internally in some cases with PyCall.jlfun2pyfun, so you could do the same:
so = pyimport("scipy.optimize")
f = PyCall.jlfun2pyfun((x,m,b)->m*x+b)
so.curve_fit(f, 1:10, 2:2:20)