I added some parameters for the dimensions; from your choice of n_x = n_y = n_p = 2
, the dependencies were not that clear.
from scipy.interpolate import interp1d, interp2d, dfitpack
n_x = 30
n_y = 40
n_p = 50
T = np.random.random((n_p, n_x, n_y)) * 100
p = np.random.random(n_p)
p[np.argmin(p)] = 0
p[np.argmax(p)] = 1
p_target = np.random.random((n_x, n_y))
T_new = np.empty(p_target.shape)
for ix, iy in np.ndindex(p_target.shape):
f = interp1d(p, T[:, ix, iy])
T_new[ix, iy] = f(p_target[ix, iy])
Than a word to your modelling. If I understood correctly you want temperature_xy = fun_xy(pressure)
, a separate function for each coordinate on your spatial grid.
Another option might be to include the spatial components in a combined function temperature_xy = fun(pressure, x, y)
. For the second approach have look at scipy.interpolate.griddata.
You can rearrange the first approach to make it work with interp2d()
.
For this the first dimension is the pressure x=pressure
and the second dimension represents the combined spatial dimensions y=product(x, y)
.
To make this behave as n_x * n_y
independent interpolations of the pressure values, I just use the same dummy values 0, 1, 2... for the spatial components both when creating the interpolation and when evaluating it.
Because the evaluation of interp2d()
normaly only works on grid coordinates,
I used the method provided by user6655984 to evaluate the function only on a specific set of points.
def evaluate_interp2d(f, x, y):
"""https://stackoverflow.com/a/47233198/7570817"""
return dfitpack.bispeu(f.tck[0], f.tck[1], f.tck[2], f.tck[3], f.tck[4], x, y)[0]
f2 = interp2d(x=p, y=np.arange(n_x*n_y), z=T.reshape(n_p, n_x*n_y).T)
T_new2 = evaluate_interp2d(f=f2, x=p_target.ravel(), y=np.arange(n_x*n_y))
T_new2 = T_new2.reshape(n_x, n_y)
print(np.allclose(T_new, T_new2))
# True
With those settings I get a time improvement of almost 10x
. But if you use even larger values, like n_x=n_y=1000
the memory usage of this custom interp2d approach grows too large and you iterative approach wins.
# np=50
# nx*ny 1e2 1e4 1e5 1e6
# interp1d 0.0056s 0.3420s 3.4133s 33.390s
# interp2d 0.0004s 0.0388s 2.0954s 191.66s
With this knowledge you could loop over a big 1000x1000
grid and process 100x100
pieces sequentially, then you would end up at around 3sec instead of 30sec.
def interpolate2d_flat(p, p_target_flat, T_flat):
n_p, n_xy = T_flat.shape
f2 = interp2d(x=p, y=np.arange(n_xy), z=T_flat.T)
return evaluate_interp2d(f=f2, x=p_target_flat, y=np.arange(n_xy))
n_splits = n_x * n_y // 1000 # So each patch has size n_p*1000, can be changed
# Flatten and split the spatial dimensions
T_flat_s = np.array_split(T.reshape(n_p, n_x*n_y), n_splits, axis=1)
p_target_flat_s = np.array_split(p_target.ravel(), n_splits, axis=0)
# Loop over the patches
T_new_flat = np.concatenate([interpolate2d_flat(p=p, p_target_flat=ptf, T_flat=Tf)
for (ptf, Tf) in zip(p_target_flat_s, T_flat_s)])
T_new2 = T_new_flat.reshape(n_x, n_y)