I want to integrate a function that takes array arguments using an efficient (vectorised/parallelised) method.
I can get the desired output using unwanted loops in the code as in the following (reduced complexity) example:
import numpy as np
from scipy import integrate
c = np.array([1, 2])
r = np.array([2, 1])
def fun(p, c):
p1 = np.array(np.zeros(c.shape))
mask = np.array(np.sqrt(np.pi/(2*c)) < 1)
p1[mask] = np.arccos(np.sqrt(np.pi/(2*c[mask])))
d = np.zeros(c.shape)
mask = np.abs(p) <= p1
d[mask] = 1/(np.pi/(2*c[mask]**2) + np.cos(p))
mask = np.logical_and(np.abs(p) > p1, np.abs(p) <= np.pi/2)
d[mask] = 1/(np.pi/(2*c[mask]**2) +
((np.cos(p1[mask]) - np.cos(p))/2))
return(d)
def intgd(p, r, c):
A = np.ones((np.size(r), np.size(c)))
s = np.sin(r) - np.sin(p)
A[s != 0] = np.sin(c[s != 0]*s[s != 0])/(c[s != 0]*s[s != 0])
return 1/fun(p, c)**2*(A**2)
res = np.zeros((np.size(r), np.size(c)))
for ii in range(0, np.size(r)):
for jj in range(0, np.size(c)):
res[ii, jj], err = integrate.quad(intgd, -np.pi/2, np.pi/2,
epsabs=1e-10, limit=100,
args=(r[ii], c[jj]))
However, my real function needs to handle much larger array inputs, which leads to unacceptably lengthy calculation duration.
I have tried variations along the following, and gained the knowledge that (as noted in comments on this question), the vec_func=True
option for scipy.integrate.quadrature
does not in fact enable vector-valued arguments to be passed as parameters into the function being integrated. [Aside: this makes it quite different to the MATLAB integral
function, for which the option ArrayValued, true
does enable that functionality, which results in much faster, apparently parallelised, evaluation of the integral.]
import numpy as np
from scipy import integrate
c = np.array([1, 2], ndmin=2)
r = np.array([2, 1])
r = r[:, np.newaxis]
def fun(p, c):
p1 = np.zeros(c.shape)
mask = np.array(np.sqrt(np.pi/(2*c)) < 1, ndmin=2)
p1[mask] = np.arccos(np.sqrt(np.pi/(2*c[mask])))
d = np.zeros(c.shape)
mask = np.abs(p) <= p1
d[mask] = 1/(np.pi/(2*c[mask]**2) + np.cos(p))
mask = np.logical_and(np.abs(p) > p1, np.abs(p) <= np.pi/2)
d[mask] = 1/(np.pi/(2*c[mask]**2) +
((np.cos(p1[mask]) - np.cos(p))/2))
return(d)
def intgd(p, r, c):
A = np.ones((np.size(r), np.size(c)))
c_bcr = np.broadcast_to(c, (np.size(r), np.size(c)))
r_bcc = np.broadcast_to(r, (np.size(r), np.size(c)))
s = np.sin(r_bcc) - np.sin(p)
A[s != 0] = np.sin(c_bcr[s != 0]*s[s != 0])/(c_bcr[s != 0]*s[s != 0])
return 1/fun(p, c)**2*(A**2)
res, err = integrate.quadrature(intgd, -np.pi/2, np.pi/2,
args=(r, c), tol=1e-10, vec_func=True)
How can I use Scipy to integrate array-argument functions without resorting to loops?