This is the same solution as defined here but applied to some function and compared with interp2d
available in Scipy. We use numba library to make the interpolation function even faster than Scipy implementation.
import numpy as np
from scipy.interpolate import interp2d
import matplotlib.pyplot as plt
from numba import jit, prange
@jit(nopython=True, fastmath=True, nogil=True, cache=True, parallel=True)
def bilinear_interpolation(x_in, y_in, f_in, x_out, y_out):
f_out = np.zeros((y_out.size, x_out.size))
for i in prange(f_out.shape[1]):
idx = np.searchsorted(x_in, x_out[i])
x1 = x_in[idx-1]
x2 = x_in[idx]
x = x_out[i]
for j in prange(f_out.shape[0]):
idy = np.searchsorted(y_in, y_out[j])
y1 = y_in[idy-1]
y2 = y_in[idy]
y = y_out[j]
f11 = f_in[idy-1, idx-1]
f21 = f_in[idy-1, idx]
f12 = f_in[idy, idx-1]
f22 = f_in[idy, idx]
f_out[j, i] = ((f11 * (x2 - x) * (y2 - y) +
f21 * (x - x1) * (y2 - y) +
f12 * (x2 - x) * (y - y1) +
f22 * (x - x1) * (y - y1)) /
((x2 - x1) * (y2 - y1)))
return f_out
We make it quite a large interpolation array to assess the performance of each method.
The sample function is,

x = np.linspace(0, 4, 13)
y = np.array([0, 2, 3, 3.5, 3.75, 3.875, 3.9375, 4])
X, Y = np.meshgrid(x, y)
Z = np.sin(np.pi*X/2) * np.exp(Y/2)
x2 = np.linspace(0, 4, 1000)
y2 = np.linspace(0, 4, 1000)
Z2 = bilinear_interpolation(x, y, Z, x2, y2)
fun = interp2d(x, y, Z, kind='linear')
Z3 = fun(x2, y2)
fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(10, 6))
ax[0].pcolormesh(X, Y, Z, shading='auto')
ax[0].set_title("Original function")
X2, Y2 = np.meshgrid(x2, y2)
ax[1].pcolormesh(X2, Y2, Z2, shading='auto')
ax[1].set_title("bilinear interpolation")
ax[2].pcolormesh(X2, Y2, Z3, shading='auto')
ax[2].set_title("Scipy bilinear function")
plt.show()

Performance Test
Python without numba library
bilinear_interpolation
function, in this case, is the same as numba
version except that we change prange
with python normal range
in the for loop, and remove function decorator jit
%timeit bilinear_interpolation(x, y, Z, x2, y2)
Gives 7.15 s ± 107 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Python with numba numba
%timeit bilinear_interpolation(x, y, Z, x2, y2)
Gives 2.65 ms ± 70.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Scipy implementation
%%timeit
f = interp2d(x, y, Z, kind='linear')
Z2 = f(x2, y2)
Gives 6.63 ms ± 145 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Performance tests are performed on 'Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz'