I'm going to try to combine the __call__
of 2 rbfi
into one call.
In [80]: from scipy.interpolate import Rbf
Make a sample as illustrated in the docs:
In [81]: x, y, z, d = np.random.rand(4, 50)
In [82]: rbfi0 = Rbf(x, y, z, d)
In [83]: xi = yi = zi = np.linspace(0, 1, 20)
In [84]: di0 = rbfi0(xi, yi, zi)
In [85]: di0
Out[85]:
array([ 0.26614249, 0.07429816, -0.01512205, 0.05134466, 0.24213774,
0.41653342, 0.45280185, 0.34763177, 0.17681661, 0.07186139,
0.16299749, 0.40416788, 0.641642 , 0.78828711, 0.79709639,
0.6530432 , 0.42473033, 0.24155719, 0.17008326, 0.179932 ])
Make a second sample:
In [86]: x, y, z, d = np.random.rand(4, 50)
In [87]: rbfi1 = Rbf(x, y, z, d)
In [88]: di1 = rbfi1(xi, yi, zi)
In [89]: di1
Out[89]:
array([ 0.38975158, 0.39887118, 0.42430634, 0.48554998, 0.59403568,
0.71745345, 0.77483525, 0.70657269, 0.53545478, 0.34931526,
0.28960157, 0.45825098, 0.7538652 , 0.99950089, 1.14749381,
1.19019632, 1.12736371, 1.00558691, 0.87811695, 0.77231634])
Look at the key attributes of the rbfi
:
In [90]: rbfi0.nodes
Out[90]:
array([ -13.02451018, -3.05675802, 8.54073071, -81.47163716,
-5.74247623, 118.70153224, -1.39117053, -3.37170396,
....
-10.08326243, 8.9995743 , 3.83357612, -4.59815344,
-25.09981508, -2.8753806 , -0.63932038, 76.59402274,
0.26222997, -30.35280108])
In [91]: rbfi0.nodes.shape
Out[91]: (50,)
In [92]: rbfi1.nodes.shape
Out[92]: (50,)
In [93]: rbfi0.xi.shape
Out[93]: (3, 50)
In [94]: rbfi1.xi.shape
Out[94]: (3, 50)
Construct the variables in the __call__
:
In [95]: xa = np.asarray([a.flatten() for a in [xi,yi,zi]], dtype=np.float_)
In [96]: xa.shape
Out[96]: (3, 20)
In [97]: r0 = rbfi0._call_norm(xa, rbfi0.xi)
In [98]: r1 = rbfi1._call_norm(xa, rbfi1.xi)
In [99]: r0.shape
Out[99]: (20, 50)
In [100]: r1.shape
Out[100]: (20, 50)
Compute the norm
for both rbfi
with one call - by concatenating the xi
arrays:
In [102]: r01 = rbfi0._call_norm(xa, np.concatenate((rbfi0.xi, rbfi1.xi),axis=1))
In [103]: r01.shape
Out[103]: (20, 100)
In [104]: np.allclose(r0, r01[:,:50])
Out[104]: True
In [105]: np.allclose(r1, r01[:,50:])
Now do the same for the nodes' and
dot`:
In [110]: res01 = np.dot(rbfi0._function(r01), np.concatenate((rbfi0.nodes, rbfi1.nodes)))
In [111]: res01.shape
Out[111]: (20,)
Oops. We want two sets of 20; this fits it against all 100 nodes at once. I need to do some reshaping.
In [133]: r01.shape
Out[133]: (20, 100)
In [134]: r01 = r01.reshape(20,2,50)
In [135]: nodes01 = np.concatenate((rbfi0.nodes, rbfi1.nodes))
In [136]: nodes01.shape
Out[136]: (100,)
In [137]: nodes01 = nodes01.reshape(2,50) # should have just stacked them
The _function
callables for the 2 rbfi
differ, so I have use them separately:
In [138]: fr01 = [rbfi0._function(r01[:,0,:]), rbfi1._function(r01[:,1,:])]
In [139]: fr01[0].shape
Out[139]: (20, 50)
With more samples this list would be constructed with a list comprehension.
In [140]: fr01 = np.stack(fr01, axis=1)
In [141]: fr01.shape
Out[141]: (20, 2, 50)
Now I can do the np.dot
for the combined rbfi
:
In [142]: res01 = np.einsum('ijk,jk->ij', fr01, nodes01)
In [143]: res01.shape
Out[143]: (20, 2)
In [144]: np.allclose(res0, res01[:,0])
Out[144]: True
In [145]: np.allclose(res1, res01[:,1])
Out[145]: True
In [149]: di01 = np.stack([rbfi0(xi, yi, zi), rbfi1(xi, yi, zi)],axis=1)
In [150]: di01.shape
Out[150]: (20, 2)
In [151]: np.allclose(di01, res01)
So I've managed to replace the In [149]
iteration with a In [138]
one. I don't know if that's a time savings or not. It may depend on how costly the _function
call is compared to the rest the rbfi.__call__
.
In my example
In [131]: rbfi0._function
Out[131]: <bound method Rbf._h_multiquadric of <scipy.interpolate.rbf.Rbf object at 0xab002fac>>
I don't know if your parameters, function='linear', smooth=0
make a difference. If the respective _function
attributes are the same, then I could replace iteration with a
rbfi0._function(r01).reshape(20,2,50)
That gives an idea of how you might speed up the iteration of the rbfi
, and maybe even replace it with a 'vector' operation.
It looks like, for the default function, the difference is only in the epsilon
value:
In [156]: rbfi0._function??
Signature: rbfi0._function(r)
Source:
def _h_multiquadric(self, r):
return np.sqrt((1.0/self.epsilon*r)**2 + 1)
File: /usr/local/lib/python3.5/dist-packages/scipy/interpolate/rbf.py
Type: method
In [157]: rbfi0.epsilon
Out[157]: 0.25663331561494024
In [158]: rbfi1.epsilon
Out[158]: 0.26163317529091562