I'm a bit of a newbie in Python.
I'm writing a little piece of code in order to find the minimum of a function:
import os,sys,matplotlib,pylab
import numpy as np
from scipy.optimize import fmin
par = [2., 0.5, 0.008]
x1 = 0.4
f2_2 = lambda x, param: param[0] * x**2 + param[1] * x + param[2]
xmin = fmin(f2_2,x1,args = (par))
print xmin
it should be very simple, however I am getting this error:
"Traceback (most recent call last):
File "prova.fmin.py", line 9, in <module>
xmin = fmin(f2_2,x1,args = (par))
File "/usr/lib/python2.7/dist-packages/scipy/optimize/optimize.py", line 257, in fmin
fsim[0] = func(x0)
File "/usr/lib/python2.7/dist-packages/scipy/optimize/optimize.py", line 176, in function_wrapper
return function(x, *args)
TypeError: <lambda>() takes exactly 2 arguments (4 given)"
Could someone help me in understanding this please?
I just tried this out. Looks like you need to say (par,) and not just (par). Note that (par,) is a tuple, with the variable par as a single element, whereas (par) just evaluates to par: no tuple. The "args" keyword of fmin expects to find a tuple, not par, which in this case is a list.
Edit:
Well, actually, it would seem that args doesn't mind receiving a list either. But then, inside of fmin, when the function f2_2 is called, args is unpacked, meaning its contents are now passed as arguments to f2_2. This means that f2_2 ends up getting four arguments, viz. x, 2, 0.5 and 0.008 in this case, as opposed to getting just the two arguments x and [2, 0.5, 0.008].
You need to define the lambda function to accept more arguments, like this:
f2_2 = lambda x, *param: param[0] * x**2 + param[1] * x + param[2]
Related
Is it possible to concatenate scipy.optimize.curve_fit with scipy.optimize.bisect (or fsolve, or whatever) for implicit scalar functions?
In practice, have a look at this Python code where I try to define an implicit function and pass it to curve_fit to obtain the best fit for a parameter:
import numpy as np
import scipy.optimize as opt
import scipy.special as spc
# Estimate of initial parameter (not really important for this example)
fact, _, _, _ = spc.airy(-1.0188)
par0 = -np.log(2.0*fact*(18**(1.0/3.0))*np.pi*1e-6)
# Definition of an implicit parametric function f(c,t;b)=0
def func_impl(c, t, p) :
return ( c - ((t**3)/9.0) / ( np.log(t*(c**(1.0/3.0))) + p ) )
# definition of the function I believe should be passed to curve_fit
def func_egg(t, p) :
x_st, _ = opt.bisect( lambda x : func_impl(x, t, p), a=0.01, b=0.3 )
return x_st
# Some data points
t_data = np.deg2rad(np.array([95.0, 69.1, 38.8, 14.7]))
c_data = np.array([0.25, 0.10, 0.05, 0.01])
# Call to curve_fit
popt, pcov = opt.curve_fit(func_egg, t_data, c_data, p0=par0)
b = popt[0]
Now, I am aware of all the things that may go wrong when trying to automatically find roots (although bisection should be stable, provided there's a root between a and b); however, the error I get seems to concern the dimensionality of the output of func_impl:
Traceback (most recent call last):
File "example_fit.py", line 23, in <module>
popt, pcov = opt.curve_fit(func_egg, t_data, c_data, p0=par0)
File "/usr/local/lib/python3.7/site-packages/scipy/optimize/minpack.py", line 752, in curve_fit
res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
File "/usr/local/lib/python3.7/site-packages/scipy/optimize/minpack.py", line 383, in leastsq
shape, dtype = _check_func('leastsq', 'func', func, x0, args, n)
File "/usr/local/lib/python3.7/site-packages/scipy/optimize/minpack.py", line 26, in _check_func
res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
File "/usr/local/lib/python3.7/site-packages/scipy/optimize/minpack.py", line 458, in func_wrapped
return func(xdata, *params) - ydata
File "example_fit.py", line 15, in func_egg
x_st, _ = opt.bisect( lambda x : func_impl(x, t, p), a=0.01, b=0.3 )
File "/usr/local/lib/python3.7/site-packages/scipy/optimize/zeros.py", line 550, in bisect
r = _zeros._bisect(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
File "example_fit.py", line 15, in <lambda>
x_st, _ = opt.bisect( lambda x : func_impl(x, t, p), a=0.01, b=0.3 )
File "example_fit.py", line 11, in func_impl
return ( c - ((t**3)/9.0) / ( np.log(t*(c**(1.0/3.0))) + p ) )
TypeError: only size-1 arrays can be converted to Python scalars
My guess is that curve_fit basically treats the output of the input function as a vector having the same dimensionality of the input data; I thought I could easily work around this by 'vectorizing' the implicit function, or func_egg, although it does not seem as trivial as I thought.
Am I missing something?
Is there a simple workaround?
I guess I end up answering my own question. I hope this could be useful to others.
Let's first choose a simpler implicit function, in this case, f(c,t;b)=c-b*t^3 (the reason will be clarified later):
import numpy as np
import scipy.optimize as opt
import scipy.special as spc
import matplotlib.pyplot as plt
# Definition of an implicit parametric function f(c,t;b)=0
def func_impl(c, t, p) :
return (c-p*t**3)
Let's vectorize it:
v_func_impl = np.vectorize(func_impl)
The same script as the one in the question, but now (1) func_egg is vectorized, and (2) I use newton instead of bisect (I found it easier to provide x0 instead of [a,b]):
# Definition of the function I believe should be passed to curve_fit
def func_egg(t, p) :
x_st = opt.newton( lambda x : func_impl(x, t, p), x0=0.05 )
return x_st
v_func_egg = np.vectorize(func_egg)
# Some data points
t_data = np.deg2rad(np.array([127.0, 95.0, 69.1, 38.8]))
c_data = np.array([0.6, 0.25, 0.10, 0.05])
# Call to curve_fit
par0 = 0.05
popt, pcov = opt.curve_fit(v_func_egg, t_data, c_data, p0=par0)
b = popt[0]
Now it works!
plt.plot(t_data, c_data)
plt.plot(np.linspace(0.5, 2.5), b*np.linspace(0.5, 2.5)**3)
plt.show()
So, in essence:
In order to concatenate scipy curve-fitting and root-finding one needs to ensure that each function is vectorized (or can deal with numpy arrays as input and output).
Make sure that your function is not 'too ugly', otherwise even if the concatenation works the root-finding procedure itself may not be able to find a result (this goes into numerical mathematics; I should have checked the regularity of my original function).
I have a function:
def xx(th, T, B):
f = integrate.quad(xint, 0, np.inf, args = (th, T, B))[0]
a = v(th)*f
return a
where xint is a function of functions of p, th, T, B. All the preceding functions work well; xx(th, T, B) should then be integrated over th and the other variables are single numbers.
When I run this I get: TypeError: only size-1 arrays can be converted to Python scalars because th is an array rather than a single number.
I've tried using lambda functions and also dblquad to do both the integrals in the same calculation but nothing has been working. Bearing in mind the limits thus trying to avoid a for loop, is there any way of getting integrate.quad to accept an array argument?
Traceback when run:
run file.py
Traceback (most recent call last):
File "file.py", line 280, in <module>
file()
File "file.py", line 240, in sctif
axes[0, 2].plot(th, xx(th,10, 10))
File "file.py", line 166, in xx
f, _ = integrate.quad(xint, 0, 100,args = (th,T,B))
File "/home/caitlin/anaconda3/lib/python3.7/site-packages/scipy/integrate/quadpack.py", line 352, in quad
points)
File "/home/caitlin/anaconda3/lib/python3.7/site-packages/scipy/integrate/quadpack.py", line 463, in _quad
return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
TypeError: only size-1 arrays can be converted to Python scalars`
I never really got the usefulness of the args option. IMHO, the code becomes clearer if you define a function that accepts only one argument, perhaps by wrapping:
th = 2.0
T = 1.0
B = 3.0
def xint(x):
return th * x ** B / T
f, _ = integrate.quad(xint, 0, np.inf)
Now, if one of th, T, B is a vector, your function is vector-valued, and you cannot use quad anymore. I'd look into quad_vec or quadpy.quad. (quadpy is a project of mine.)
I am using python ODE to solve Van der Pol equation. What I want to do is to find the number of times the ODE's derivatives is called during one invocation of integrate(t) method. The solver I am using is isoda. I am trying to find the number of times the derivatives is called during one invocation without the jacbian function and the number of times the jacobian function is called when I include the jacobian function.
def modified_vanderpol_integrator_demo(dt_min=1e-3, dt_max=10., mu=100.):
# define a class to store the number of calls
class F:
def __init__(self):
self.callsydot = 0
self.callsjac = 0
def ydot(self,t, y, mu):
self.callsydot += 1
x, v = y[0], y[1]
return array([v, mu*(1- x*x)*v - x])
def jac(self, t, y, mu):
self.callsjac += 1
return array([[0, 1], [2*mu*v*x - 1, mu*(1-x*x)]])
Dt = linspace(dt_min, dt_max, 1000)
Num_of_times1 = []
Num_of_times2 = []
for dt in Dt:
xinitial = array([1.0, 2.0]) # initial value
f1 = F()
r1 = ode(f1.ydot)
r1.set_integrator('lsoda')
r1.set_initial_value(xinitial)
r1.set_f_params(mu)
r1.integrate(dt)
Num_of_times1.append(f1.callsydot)
f2 = F()
r2 = ode(f2.ydot, f2.jac)
r2.set_integrator('lsoda', with_jacobian=True)
r2.set_initial_value(xinitial)
r2.set_f_params(mu)
r2.integrate(dt)
Num_of_times2.append(f2.callsjac)
plt.plot(Dt, Num_of_times1)
plt.plot(Dt, Num_of_times2)
plt.show()
When I run this script, I got the message
create_cb_arglist: Failed to build argument list (siz) with enough arguments (tot-opt) required by user-supplied function (siz,tot,opt=2,3,0).
Traceback (most recent call last):
File "stiffequations.py", line 118, in <module>
modified_vanderpol_integrator_demo(dt_min=1e-3, dt_max=10., mu=100.)
File "stiffequations.py", line 111, in modified_vanderpol_integrator_demo
r2.integrate(dt)
File "/usr/local/lib/python2.7/dist-packages/scipy/integrate/_ode.py", line 394, in integrate
self.f_params, self.jac_params)
File "/usr/local/lib/python2.7/dist-packages/scipy/integrate/_ode.py", line 1193, in run
y1, t, istate = self.runner(*args)
lsoda.error: failed in processing argument list for call-back jac.
Why this happens and how to fix it?
Thanks.
I have found out what is wrong with my script. I forget to set the parameters in the jacobian equation.
r2.set_jac_params(mu)
I'm trying to run the code below, where Bwavelength, throughput and newflux are lists.
def ABconversion(Bwavelength, throughput):
ABconstant=[]
c=3e18
i=0
for i in range(0, len(Bwavelength)):
ABconstant.append(((3e18/((Bwavelength[i])**2))*throughput[i]))
i+=1
print len(Bwavelength), len(ABconstant), ABconstant
a=Bwavelength[0]
b=Bwavelength[-1]
h=((b-a)/len(Bwavelength))
ABflux = numpy.trapz(Bwavelength, ABconstant, h)
return ABflux
def ABmagnitude(newflux, ABflux):
ABmagarray=[]
for i in range(len(newflux)):
ABmag = -2.5*log10((newflux[i])/ABflux) - 48.6
ABmagarray.append(ABmag)
return ABmagarray
ABflux1 = ABconversion(Bwavelength, throughput)
print ABflux1
ABmagarray = ABmagnitude(z, ABflux1)
print epoch, ABmagarray
z is defined earlier in the file and is also a list.
However, when I run this I get the message:
Traceback (most recent call last):
File "Rewrite17.11.2014.py", line 196, in <module>
ABflux1 = ABconversion(Bwavelength, throughput)
File "Rewrite17.11.2014.py", line 186, in ABconversion
ABflux = numpy.trapz(Bwavelength, ABconstant, h)
File "C:\Python27\lib\site-packages\numpy\lib\function_base.py, line 3234, in trapz
ret = add.reduce(d * (y[slice1]+y[slice2]/2.0, axis)
ValueError: Operands could not be broadcast together with shapes (0,) (444,)
I don't quite understand the error (I'm fairly new to programming), but I think it means the two "shapes" don't have the same dimensions. I'm not sure why this is.
Thanks in advance.
According to this documentation the parameters to trapz(y, x, dx, axis) are:
y - Array like - input array to integrate.
x - Optional array - If x is None, then spacing between all y elements is dx.
dx - Optional scalar - If x is None, spacing given by dx is assumed. Default is 1.
axis - Optional Int - specify the axis.
So you shouldn't specify both x and dx - one of them should be None.
Perhaps this is what you want: trapz(Bwavelength, None, h).
See this answer for more details on the error message and NumPy's "braodcasting rule".
Replace:
numpy.trapz(Bwavelength, ABconstant, h)
with:
numpy.trapz(np.array(Bwavelength)[:,np.newaxis], ABconstant, h)
I was trying to fit a specific function with scipy and I got weird results. I decided to test something I know the answer to so I created this:
from scipy.optimize import curve_fit as cf
import numpy as np
import random
def func(x,a):
return a+X
X =[]
for i in range (10):
V = random.random()
X.append(i+3 + V/10)
print cf(func, np.array(range(10)),np.array(X))
I expected to get something around 3, nevertheless, here the output:
(array([ -2.18158824e-12]), inf)
As a side note, I tried to see what I send something to func and I got this:
print func(np.array(range(10)),3)
Traceback (most recent call last):
File "/tmp/py1759O-P", line 16, in <module>
print func(np.array(range(10)),3)
File "/tmp/py1759O-P", line 6, in func
return a+X
TypeError: unsupported operand type(s) for +: 'int' and 'list
What am I doing wrong?
Don't use x and X as variable names when they carry such different meanings (or perhaps you didn't know Python is case sensitive?):
def func(x,a):
return a+X
X =[]
x is a numpy array, X is a list, and a is a scalar parameter value.
a+X results in an error since you can not add a scalar to a list.
In func, the argument is x, but X is used in the body of the function.
Here's a modified version of your code. It uses a few more features of numpy (e.g. np.random.random() instead of random.random()).
from scipy.optimize import curve_fit as cf
import numpy as np
def func(x, a):
return a + x
n = 10
xdata = np.arange(n)
ydata = func(xdata, 3) + np.random.random(n) / 10
print cf(func, xdata, ydata)
The output is
(array([ 3.04734293]), array([[ 8.19208558e-05]]))