Strange behaviour in scipy.solve_ivp when using an implicit method - python

I recently ran into a question about integration and encountered a strange bug. I attempt a very simple problem using solve_ivp:
from scipy.integrate import solve_ivp
import numpy as np
def f(y, t):
return y
y0 = [1,1,1,1]
method = 'RK23'
s = solve_ivp(f, (0,1), y0, method=method, t_eval=np.linspace(0,1))
And it works fine. When I change to method='BDF' or method='Radau' I get an error:
Traceback (most recent call last):
File "<ipython-input-222-f11c4406e92c>", line 10, in <module>
s = solve_ivp(f, (0,1), y0, method=method, t_eval=np.linspace(0,1))
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\ivp.py", line 455, in solve_ivp
solver = method(fun, t0, y0, tf, vectorized=vectorized, **options)
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\radau.py", line 299, in __init__
self.jac, self.J = self._validate_jac(jac, jac_sparsity)
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\radau.py", line 345, in _validate_jac
J = jac_wrapped(t0, y0, self.f)
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\radau.py", line 343, in jac_wrapped
sparsity)
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\common.py", line 307, in num_jac
return _dense_num_jac(fun, t, y, f, h, factor, y_scale)
File "C:\ProgramData\Anaconda3\lib\site-packages\scipy\integrate\_ivp\common.py", line 318, in _dense_num_jac
diff = f_new - f[:, None]
IndexError: too many indices for array
I also get an error with method = 'LSODA', although different (i.e. all implicit integrators). I do not get an error with any of the explicit integrators.
I tried this in spyder with scipy version 1.0.0 and in google colab (scipy version 1.1.0), with the same results.
Is this a bug or am I missing some argument I need for implicit integrators??

It appears that the Radau and BDF methods do not handle single-valued RHS functions. Making the function f above output a 1-D list solves your issue. Additionally, as mentioned by Weckesser in the comments, solve_ivp expects the RHS to be f(t, y) and not f(y, t).
Like this
def f(t, y):
return [y]

Related

I am attempting to integrate a sine wave. I am getting a valueError: invalid callable given

import numpy as np
import scipy.integrate as integrate
time = np.arange(0.0, 1, 0.0001)
test = np.sin(time)
test2 = integrate.quad(test,0,0.01)
I set up time with np.arange
I then create the sin function
I then attempt to integrate the function
Traceback:
Traceback (most recent call last):
File "/home/andrew/PycharmProjects/memresistor/test.py", line 8, in <module>
test = integrate.quad(test,0,0.01)
File "/home/andrew/PycharmProjects/memresistor/venv/lib/python3.8/site-packages/scipy/integrate/_quadpack_py.py", line 351, in quad
retval = _quad(func, a, b, args, full_output, epsabs, epsrel, limit,
File "/home/andrew/PycharmProjects/memresistor/venv/lib/python3.8/site-packages/scipy/integrate/_quadpack_py.py", line 463, in _quad
return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
ValueError: invalid callable given
I think this is the misunderstanding: np.sin is a function. np.sin() is not. When the () is added to the end, the function is called and will be evaluated, thus no longer being a function. Does this work for you?
import numpy as np
import scipy.integrate as integrate
test = integrate.quad(np.sin, 0, 1)

How to curve_fit an implicit scalar function using SciPy?

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).

Is there a way for scipy.integrate.quad to accept arrays in args?

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.)

Python - How can I fit a curve to a function that contains a numerically calculated integral?

I have the following code:
import numpy as np
import scipy.integrate as spi
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import math as mh
def GUFunction(z, Omega_Lambda):
integral = spi.quad(lambda zvar: AuxIntegrandum(zvar, Omega_Lambda), 0.0, z)[0]
DL = (1+z) * c/H0 * integral *1000000
return (5*(mh.log(DL,10)-1))
def AuxIntegrandum(z, Omega_Lambda):
Omega_m = 1 - Omega_Lambda
return 1 / mh.sqrt(Omega_m*(1+z)**3 + Omega_Lambda)
def DataFit(filename):
print curve_fit(GUFunction, ComputeData(filename)[0], ComputeData(filename)[1])
DataFit("data.dat")
data.dat has z values in the first column and GUF(z) values in the second column.
Upon executing this code, the compiler tells me that comparing an array to a value (+inf or -inf) is ambiguous.
I think this refers to the integration boundaries, where it looks to see if I want to integrate to infinity. For some reason it apparently puts all z-values from the data file into the integration boundary.
Is there some trick I don't know about which allows you to fit a curve to a numerically integrated function?
Here's the exact error:
Traceback (most recent call last):
File "plot.py", line 83, in <module>
DataFit("data.dat")
File "plot.py", line 67, in DataFit
print curve_fit(GUFunction, ComputeData(filename)[0], ComputeData(filename)[1])
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 736, in curve_fit
res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 377, in leastsq
shape, dtype = _check_func('leastsq', 'func', func, x0, args, n)
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 26, in _check_func
res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 454, in func_wrapped
return func(xdata, *params) - ydata
File "plot.py", line 57, in GUFunction
integral = spi.quad(lambda zvar: AuxIntegrandum(zvar, Omega_Lambda), 0.0, z)[0]
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/integrate/quadpack.py", line 323, in quad
points)
File "/home/joshua/anaconda2/lib/python2.7/site-packages/scipy/integrate/quadpack.py", line 372, in _quad
if (b != Inf and a != -Inf):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Short answer: curve_fit tries to evaluate the target function on an array of xdata, but quad cannot accept a vector argument. You need to define your target function via e.g. a list comprehension over an input array.
Let's cook up a minimum reproducible example:
In [33]: xdata = np.linspace(0, 3, 11)
In [34]: ydata = xdata**3
In [35]: def integr(x):
...: return quad(lambda t: t**2, 0, x)[0]
...:
In [36]: def func(x, a):
...: return integr(x) * a
...:
In [37]: curve_fit(func, xdata, ydata)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-37-4660c65f85a2> in <module>()
----> 1 curve_fit(func, xdata, ydata)
[... removed for clarity ...]
~/virtualenvs/py35/lib/python3.5/site-packages/scipy/integrate/quadpack.py in _quad(func, a, b, args, full_output, epsabs, epsrel, limit, points)
370 def _quad(func,a,b,args,full_output,epsabs,epsrel,limit,points):
371 infbounds = 0
--> 372 if (b != Inf and a != -Inf):
373 pass # standard integration
374 elif (b == Inf and a != -Inf):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Which is precisely the error you're seeing. Ok, the error comes from quad, which tries to evaluate func(xdata, a), which boils down to integr(xdata) and that does not work. (How I found it out? I put import pdb; pdf.set_trace() inside the func function and poked around in the debugger).
Then, let's make the target function handle array arguments:
In [38]: def func2(x, a):
...: return np.asarray([integr(xx) for xx in x]) * a
...:
In [39]: curve_fit(func2, xdata, ydata)
Out[39]: (array([ 3.]), array([[ 3.44663413e-32]]))

scipy curve_fit doesn't like math module

While trying to create an example with scipy.optimize curve_fit I found that scipy seems to be incompatible with Python's math module. While function f1 works fine, f2 throws an error message.
from scipy.optimize import curve_fit
from math import sin, pi, log, exp, floor, fabs, pow
x_axis = np.asarray([pi * i / 6 for i in range(-6, 7)])
y_axis = np.asarray([sin(i) for i in x_axis])
def f1(x, m, n):
return m * x + n
coeff1, mat = curve_fit(f1, x_axis, y_axis)
print(coeff1)
def f2(x, m, n):
return m * sin(x) + n
coeff2, mat = curve_fit(f2, x_axis, y_axis)
print(coeff2)
The full traceback is
Traceback (most recent call last):
File "/Documents/Programming/Eclipse/PythonDevFiles/so_test.py", line 49, in <module>
coeff2, mat = curve_fit(f2, x_axis, y_axis)
File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/minpack.py", line 742, in curve_fit
res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/minpack.py", line 377, in leastsq
shape, dtype = _check_func('leastsq', 'func', func, x0, args, n)
File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/minpack.py", line 26, in _check_func
res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
File "/usr/local/lib/python3.5/dist-packages/scipy/optimize/minpack.py", line 454, in func_wrapped
return func(xdata, *params) - ydata
File "/Documents/Programming/Eclipse/PythonDevFiles/so_test.py", line 47, in f2
return m * sin(x) + n
TypeError: only length-1 arrays can be converted to Python scalars
The error message appears with lists and numpy arrays as input alike. It affects all math functions, I tested (see functions in import) and must have something to do with, how the math module manipulates input data. This is most obvious with pow() function - if I don't import this function from math, curve_fit works properly with pow().
The obvious question - why does this happen and how can math functions be used with curve_fit?
P.S.: Please don't discuss, that one shouldn't fit the sample data with a linear fit. This was just chosen to illustrate the problem.
Be careful with numpy-arrays, operations working on arrays and operations working on scalars!
Scipy optimize assumes the input (initial-point) to be a 1d-array and often things go wrong in other cases (a list for example becomes an array and if you assumed to work on lists, things go havoc; those kind of problems are common here on StackOverflow and debugging is not that easy to do by the eye; code-interaction helps!).
import numpy as np
import math
x = np.ones(1)
np.sin(x)
> array([0.84147098])
math.sin(x)
> 0.8414709848078965 # this only works as numpy has dedicated support
# as indicated by the error-msg below!
x = np.ones(2)
np.sin(x)
> array([0.84147098, 0.84147098])
math.sin(x)
> TypeError: only size-1 arrays can be converted to Python scalars
To be honest: this is part of a very basic understanding of numpy and should be understood when using scipy's somewhat sensitive functions.

Categories

Resources