In mathematics, linear interpolation is a method of curve fitting using linear polynomials to construct new data points within the range of a discrete set of known data points.[1]

In [1]:
%matplotlib inline
%watermark -dtvmp numpy,matplotlib,pandas,cython

import matplotlib.pylab as plt
import lerp
from lerp import *
2017-10-12 22:57:48

CPython 3.6.2
IPython 6.1.0

numpy 1.13.1
matplotlib 2.0.2
pandas 0.20.3
cython 0.26.1

compiler   : GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)
system     : Darwin
release    : 15.6.0
machine    : x86_64
processor  : i386
CPU cores  : 2
interpreter: 64bit
In [2]:
lerp.options.display.max_rows = 15

Usage

BreakPoints

In [3]:
A = BreakPoints(d=[1.040, 1.051, 1.057, 1.063, 1.064, 1.067, 1.068, 1.068, 1.068, 1.066, 1.064,
                1.060, 1.056, 1.050, 1.042, 1.032], label="Ballistic coefficient", unit="G1")

Display in the notebook, integer above the value are helps for indexing purpose

In [4]:
A
Out[4]:

BreakPoints: Ballistic coefficient [G1]

012345678910111213...15
1.041.0511.0571.0631.0641.0671.0681.0681.0681.0661.0641.061.0561.05...1.032

In [5]:
A[6]
Out[5]:
1.0680000000000001

Get a plot with the plot() method

In [6]:
A.plot()
Out[6]:
[<matplotlib.lines.Line2D at 0x181b8a0400>]
../_images/_src_lerp_11_1.png

Not that fancy… you can set the ggplot style

In [7]:
plt.style.use('ggplot')
A.plot()
Out[7]:
[<matplotlib.lines.Line2D at 0x181ba223c8>]
../_images/_src_lerp_13_1.png

If you don’t like ggplot, choose one of the seaborn styles

In [8]:
import matplotlib.gridspec as gridspec
import matplotlib as mpl
styles = [_s for _s in plt.style.available if 'seaborn' in _s]
n = np.ceil(np.sqrt(len(styles))).astype(np.int)

gs = gridspec.GridSpec(n, n)

plt.figure(figsize=(24,18))

for i, s in enumerate(styles):
    mpl.rcParams.update(mpl.rcParamsDefault)
    plt.style.use(s)
    plt.subplot(gs[i])
    A.plot()
    (A / 1.01).plot()
    (A / 1.02).plot()
    plt.title(s)

plt.tight_layout()
../_images/_src_lerp_15_0.png

mesh2d

From Ballistic coefficient article in Wikipedia.

Doppler radar measurement results for a lathe turned monolithic solid .50 BMG very-low-drag bullet (Lost River J40 13.0 millimetres (0.510 in), 50.1 grams (773 gr) monolithic solid bullet / twist rate 1:380 millimetres (15 in)) look like this:

In [9]:
BC = mesh2d(x=np.arange(500,2100,100), x_label="Range", x_unit="m",
            d=[1.040, 1.051, 1.057, 1.063, 1.064, 1.067, 1.068, 1.068, 1.068, 1.066, 1.064,
               1.060, 1.056, 1.050, 1.042, 1.032], label="Ballistic coefficient", unit="G1")

Display in the jupyter notebooks / ipython

In [10]:
BC
Out[10]:

mesh2d

012345678910111213...15
Range [m]500600700800900100011001200130014001500160017001800...2000
Ballistic coefficient [G1]1.041.0511.0571.0631.0641.0671.0681.0681.0681.0661.0641.061.0561.05...1.032

Interpolation

In [11]:
BC(501)
Out[11]:
1.04011
In [12]:
BC([501, 609, 2500])
Out[12]:
array([ 1.04011,  1.05154,  0.982  ])

Default : values are extrapolated

In [13]:
BC.interpolate([501, 609, 2500])
Out[13]:
array([ 1.04011,  1.05154,  1.032  ])

Interpolation is performed and boundaries values are kept

In [14]:
BC.options
Out[14]:
{'extrapolate': True, 'step': False}
In [15]:
BC.options['extrapolate']= False

This can be controled though the dict key extrapolate in options or interpolate method.

In [16]:
BC([501, 609, 2500])
Out[16]:
array([ 1.04011,  1.05154,  1.032  ])
In [17]:
BC.max()
Out[17]:
1.0680000000000001
In [18]:
BC.max(argwhere=True)
Out[18]:
(1100, 1.0680000000000001)

Plot as steps

I like the color from vega

In [19]:
from cycler import cycler
category20 = cycler('color', ['#1f77b4', '#aec7e8', '#ff7f0e',
                              '#ffbb78', '#2ca02c', '#98df8a',
                              '#d62728', '#ff9896', '#9467bd',
                              '#c5b0d5', '#8c564b', '#c49c94',
                              '#e377c2', '#f7b6d2', '#7f7f7f',
                              '#c7c7c7', '#bcbd22', '#dbdb8d',
                              '#17becf', '#9edae5'])

plt.style.use('seaborn-darkgrid')
plt.rc('axes', prop_cycle=category20)
In [20]:
plt.figure(figsize=(10,10))

BC.plot(label="Interpolation between points")
BC.steps.plot(label="Steps")
BC.steps.plot(where="pre", label="Steps, pre")

plt.graphpaper(dx=200, dy=0.005)
plt.legend(bbox_to_anchor=(1.05, 0.5), loc='center left', facecolor="white", frameon=False)
plt.tight_layout()
../_images/_src_lerp_36_0.png

Slicing

In [21]:
BC[2:4]
Out[21]:

mesh2d

01
Range [m]700800
Label [unit]1.0571.063

Breakpoints strictly monotone, reverse order has no effect

In [22]:
BC[::-1]
Out[22]:

mesh2d

012345678910111213...15
Range [m]500600700800900100011001200130014001500160017001800...2000
Label [unit]1.041.0511.0571.0631.0641.0671.0681.0681.0681.0661.0641.061.0561.05...1.032

In [23]:
BC[6]
Out[23]:
(1100, 1.0680000000000001)
In [24]:
BC.x
Out[24]:

BreakPoints: Range [m]

012345678910111213...15
500600700800900100011001200130014001500160017001800...2000

In [25]:
BC(np.arange(500, 550, 10))
Out[25]:
array([ 1.04  ,  1.0411,  1.0422,  1.0433,  1.0444])
In [26]:
BC.resample(np.arange(500, 2000, 200))
Out[26]:

mesh2d

01234567
Range [m]50070090011001300150017001900
Ballistic coefficient [G1]1.041.0571.0641.0681.0681.0641.0561.042

In [27]:
BC.steps(BC.x)
Out[27]:
array([ 1.04 ,  1.051,  1.057,  1.063,  1.064,  1.067,  1.068,  1.068,
        1.068,  1.066,  1.064,  1.06 ,  1.056,  1.05 ,  1.042,  1.032])
In [28]:
BC.__dict__
Out[28]:
{'_d': array([ 1.04 ,  1.051,  1.057,  1.063,  1.064,  1.067,  1.068,  1.068,
         1.068,  1.066,  1.064,  1.06 ,  1.056,  1.05 ,  1.042,  1.032]),
 '_options': {'extrapolate': False, 'step': False},
 '_steps': x = BreakPoints(d=[ 500,  600,  700,  800,  900, 1000, 1100, 1200, 1300, 1400,
              1500, 1600, 1700, 1800, 1900, 2000], label="Range", unit="m")
 d = array([ 1.04 ,  1.051,  1.057,  1.063,  1.064,  1.067,  1.068,  1.068,
         1.068,  1.066,  1.064,  1.06 ,  1.056,  1.05 ,  1.042,  1.032]),
 '_x': BreakPoints(d=[ 500,  600,  700,  800,  900, 1000, 1100, 1200, 1300, 1400,
              1500, 1600, 1700, 1800, 1900, 2000], label="Range", unit="m"),
 'label': 'Ballistic coefficient',
 'unit': 'G1'}

.polyfit(): how to get a polymesh object from discrete values

In [29]:
BC.plot("+")
BC.polyfit(degree=2).plot(xlim=[500,2000])
BC.polyfit(degree=4).plot(xlim=[500,2000], ylim=[1.02, 1.08])
../_images/_src_lerp_48_0.png
In [30]:
BC.polyfit(degree=4)
Out[30]:
-3.5896443597682554e-14·x⁴ + 1.820225053584262e-10·x³ - 3.821239680082758e-07·x² + 0.00037528910653332015·x + 0.9278994701119253
In [31]:
from sklearn.metrics import r2_score

for i in range(10):
    print(i, r2_score(BC.d, BC.polyfit(degree=i)(BC.x).d))
0 0.0
1 0.0698263920953
2 0.988101760851
3 0.988408030976
4 0.997727098729
5 0.998238919776
6 0.998565636158
7 0.998569159428
8 0.998610724807
9 0.998652377691

.add()

In [32]:
# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]
test2 = mesh2d(x, y)
x2 = np.arange(0,60)
test = mesh2d(x2, np.sin(x2), x_label="Mon label", unit="%")
In [33]:
(test + test2)
Out[33]:

mesh2d

012345678910111213...59
Label [unit]012345678910111213...59
Label [%]5.06.841470984817.909297426832.141120008063.243197504694.041075725345.72058450187.656986598728.989358246629.412118485249.4559788891110.000009793411.46342708213.4201670368...59.6367380071

In [34]:
test[:4]
Out[34]:

mesh2d

0123
Mon label [unit]0123
Label [unit]0.00.8414709848080.9092974268260.14112000806

In [35]:
test[-3:] + test[:4]
Out[35]:

mesh2d

0123456
Label [unit]0123575859
Label [unit]-31.2961851364-29.8980062588-29.2734719239-29.4849414499-40.90429585-41.115765376-42.2400774357

In [36]:
(test + test2).plot()
Out[36]:
[<matplotlib.lines.Line2D at 0x181d938240>]
../_images/_src_lerp_56_1.png
In [37]:
test.steps.plot()
Out[37]:
[<matplotlib.lines.Line2D at 0x181c046630>]
../_images/_src_lerp_57_1.png
In [38]:
test
Out[38]:

mesh2d

012345678910111213...59
Mon label [unit]012345678910111213...59
Label [%]0.00.8414709848080.9092974268260.14112000806-0.756802495308-0.958924274663-0.2794154981990.6569865987190.9893582466230.412118485242-0.544021110889-0.999990206551-0.5365729180.420167036827...0.636738007139

In [39]:
test.resample(np.linspace(0,60,100))
Out[39]:

mesh2d

012345678910111213...99
Mon label [unit]0.00.6060606060611.212121212121.818181818182.424242424243.03030303033.636363636364.242424242424.848484848485.454545454556.060606060616.666666666677.272727272737.87878787879...60.0
Label [%]0.00.5099824150350.8558584119030.8969653464590.583403976440.113910235231-0.430285221356-0.805801714546-0.92829976264-0.650056648998-0.2226638559610.3448525664130.7476334117840.94907077415...0.280603366194

In [40]:
plt.figure(figsize=(12,7))
test.plot()
newX = np.linspace(-10,100,200)
test.resample(newX).plot('.', c=category20.by_key()['color'][6], lw=0.1)
plt.ylim(-5, 2)
Out[40]:
(-5, 2)
../_images/_src_lerp_60_1.png

Not implemented

In [41]:
test + test2.steps
Out[41]:

mesh2d

012345678910111213...59
Label [unit]012345678910111213...59
Label [%]5.06.841470984817.909297426832.141120008063.243197504694.041075725345.72058450187.656986598728.989358246629.412118485249.4559788891110.000009793411.46342708213.4201670368...59.6367380071

In [42]:
test.steps([-10, 2.3, 3, 3.1, 59, 58.9, 90])
Out[42]:
array([ -8.41470985,   0.6788442 ,   0.14112001,   0.05132776,
         0.63673801,   0.67235147, -10.40343586])
In [43]:
test.steps
Out[43]:

mesh2d

012345678910111213...59
Mon label [unit]012345678910111213...59
Label [%]0.00.8414709848080.9092974268260.14112000806-0.756802495308-0.958924274663-0.2794154981990.6569865987190.9893582466230.412118485242-0.544021110889-0.999990206551-0.5365729180.420167036827...0.636738007139

In [44]:
test.diff(n=1)
Out[44]:

mesh2d

012345678910111213...58
Mon label [unit]012345678910111213...58
Label [unit]0.8414709848080.0678264420178-0.768177418766-0.897922503368-0.2021217793550.6795087764640.9364020969180.332371647905-0.577239761382-0.956139596131-0.4559690956610.463417288550.9567399548270.570440318868...-0.356134640945

In [45]:
#plt.plot(test.X, test.Y, c="b")
newX = np.arange(-10, 10, 0.001)
plt.plot(newX, test(newX), ":r")
plt.plot(newX, test.steps(newX), "-g")
Out[45]:
[<matplotlib.lines.Line2D at 0x181ba962e8>]
../_images/_src_lerp_66_1.png
In [46]:
test.steps(newX)
Out[46]:
array([-8.41470985, -8.41386838, -8.41302691, ..., -0.54115269,
       -0.54210883, -0.54306497])
In [47]:
import random
from numba import jit

N = 200
myMesh = mesh2d(np.arange(N)*5, [random.uniform(2.5, 10.0) for i in range(N)], x_label="MON Label")
In [48]:
@jit
def _extrapolate(self, X):
    """
    """
    if X <= self.x[0]:
        res = self.d[0] + (X - self.x[0]) *\
            (self.d[1] - self.d[0]) / (self.x[1] - self.x[0])
    elif X >= self.x[-1]:
        res = self.d[-1] + (x - self.x[-1]) *\
            (self.d[-1] - self.d[-2]) / (self.x[-1] - self.x[-2])
    else:
        res = np.interp(X, self.x, self.d)
    return res
In [49]:
%timeit myMesh.extrapolate(255)
21.7 µs ± 915 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [50]:
%timeit _extrapolate(myMesh, 255)
The slowest run took 5.50 times longer than the fastest. This could mean that an intermediate result is being cached.
94.3 µs ± 87.9 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [51]:
np.diff(test2.d) / np.diff(test2.x)
Out[51]:

BreakPoints: Label [unit]

0123
1.0-5.02.01.0

%timeit myMesh.resample(np.arange(-1000, 2000))%timeit np.interp(np.arange(-1000, 2000), myMesh.x, myMesh.d)%timeit np.interp(range(3000), myMesh.x, myMesh.d)%timeit myMesh(np.arange(-10000, 200000))%timeit [myMesh(x) for x in np.arange(-10000, 200000)]

mesh3d

In [52]:
m3d = mesh3d(x=np.arange(10), x_label="X", x_unit="X unit",
             y=np.arange(10), y_label="Y", y_unit="Y unit",
             d=np.random.random((10,10)), label="data", unit="data unit")
In [53]:
lerp.options.display.max_rows = 15
In [54]:
m3d
Out[54]:

mesh3d: data [data unit]

Y [Y unit]
0123456789
0123456789
X [X unit]000.4348703522360.2314824468550.3235545908070.1945131886690.5487933454420.961498709940.8449782977640.02284771724790.8975816143850.374790493167
110.4374022098260.5875968266780.7919359008570.7972500851030.1723538167110.3079225722350.8923144388450.03892615412750.1522855937130.668586250984
220.2395664234570.1177572228280.07781585200510.4085870806390.5759142820620.6154222734310.07942692165710.2415200403660.5764749492690.428839200808
330.4018804555570.3231992738720.4575624724670.01415681546290.2271490730910.4658866279680.1034954343750.4327888533570.5431426977290.624016873678
440.9970104423680.5807246327790.789205537660.4330539092050.8991673566630.1366015516710.3216710881690.07197360982030.8736548257560.0908232066389
550.8392831645460.09831051349670.3366236802540.9221243395530.0002319079311590.2994445188680.145499822560.9418612672250.08528973826060.806634210453
660.3290428695330.7330840982550.365610234920.8376373392750.3580716770980.8197521147670.8359151370570.6124848463970.6655165933920.917622397535
770.3215202243230.8822928717230.1314719340250.06389565408530.05754847916850.3135983245980.9326791298660.5820865158520.2719568177730.322686998655
880.6873388665960.3423671979370.006679487637910.8784360839130.1036691001420.780763603560.7949912643180.1001039684890.4988820065350.515767614048
990.6623847942990.9844406541630.9944696036720.6118679049810.8523811067350.8466792302440.6897093618670.1365978048090.3760009544990.960696980125

In [55]:
m3d(4.5, 5.7)
Out[55]:
0.22891672933562421

Interpolation

In [56]:
m3d(3.5, 5.6)
Out[56]:
0.24804759269057369

Call method default : extrapolation above boundaries

In [57]:
m3d(9.1,9.4)
Out[57]:
1.2617807437078306

As far mesh2d, can be set with options attributes or method call .interpolate

In [58]:
m3d.options
Out[58]:
{'extrapolate': True}
In [59]:
m3d.interpolate(9.1,9.4)
Out[59]:
0.9606969801252196
In [60]:
m3d(x=3.5)
Out[60]:

mesh2d

0123456789
Y [Y unit]0123456789
Label [unit]0.6994454489620.4519619533250.6233840050640.2236053623340.5631582148770.3012440898190.2125832612720.2523812315880.7083987617430.357420040158

Slicing

In [61]:
m3d[3:6]
Out[61]:

mesh3d: data [data unit]

Y [Y unit]
0123456789
0123456789
X [X unit]030.4018804555570.3231992738720.4575624724670.01415681546290.2271490730910.4658866279680.1034954343750.4327888533570.5431426977290.624016873678
140.9970104423680.5807246327790.789205537660.4330539092050.8991673566630.1366015516710.3216710881690.07197360982030.8736548257560.0908232066389
250.8392831645460.09831051349670.3366236802540.9221243395530.0002319079311590.2994445188680.145499822560.9418612672250.08528973826060.806634210453

Garbage after that point

In [62]:
from scipy import misc
from scipy import ndimage
# http://www.ndt.net/article/wcndt00/papers/idn360/idn360.htm

from PIL import Image
from urllib.request import urlopen
import io

URL = 'https://www.researchgate.net/profile/Robert_Dinnebier/publication/268693474/figure/fig5/AS:268859169046548@1441112429791/Log-log-plot-of-ionic-conductivity-times-temperature-versus-frequency-measured-at.png'

with urlopen(URL) as url:
    im = misc.imread(io.BytesIO(url.read()))

# face = misc.imread(f)
In [63]:
plt.imshow(im, cmap=plt.cm.gray, vmin=30, vmax=200)
plt.axis('off')
# plt.contour(im, [50, 200])
Out[63]:
(-0.5, 388.5, 259.5, -0.5)
../_images/_src_lerp_95_1.png
In [64]:
sx = ndimage.sobel(im, axis=0, mode='constant')
sy = ndimage.sobel(im, axis=1, mode='constant')
sob = np.hypot(sx, sy)
In [65]:
sx = ndimage.sobel(im, axis=0, mode='constant')
import numpy as np import matplotlib.pyplot as plt from scipy import interpolate x = np.array([[0.12, 0.11, 0.1, 0.09, 0.08], [0.13, 0.12, 0.11, 0.1, 0.09], [0.15, 0.14, 0.12, 0.11, 0.1], [0.17, 0.15, 0.14, 0.12, 0.11], [0.19, 0.17, 0.16, 0.14, 0.12], [0.22, 0.19, 0.17, 0.15, 0.13], [0.24, 0.22, 0.19, 0.16, 0.14], [0.27, 0.24, 0.21, 0.18, 0.15], [0.29, 0.26, 0.22, 0.19, 0.16]]) y = np.array([[71.64, 78.52, 84.91, 89.35, 97.58], [66.28, 73.67, 79.87, 85.36, 93.24], [61.48, 69.31, 75.36, 81.87, 89.35], [57.61, 65.75, 71.7, 79.1, 86.13], [55.12, 63.34, 69.32, 77.29, 83.88], [54.58, 62.54, 68.7, 76.72, 82.92], [56.58, 63.87, 70.3, 77.69, 83.53], [61.67, 67.79, 74.41, 80.43, 85.86], [70.08, 74.62, 80.93, 85.06, 89.84]]) plt.figure(figsize = (9, 9)) plt.subplot(111) for i in range(5): x_val = np.linspace(x[0, i], x[-1, i], 100) x_int = np.interp(x_val, x[:, i], y[:, i]) tck = interpolate.splrep(x[:, i], y[:, i], k = 2, s = 4) y_int = interpolate.splev(x_val, tck, der = 0) plt.plot(x[:, i], y[:, i], linestyle = '', marker = 'o') plt.plot(x_val, y_int, linestyle = ':', linewidth = 0.25, color = 'black') plt.xlabel('X') plt.ylabel('Y') plt.show()from scipy.interpolate import dfitpack, fitpackdef extrapolate(self, X, Y): if X <= self.X[0]: iX = 0 elif X >= self.X[-1]: iX = -2 else: iX = np.searchsorted(self.X, X) - 1 if Y <= self.Y[0]: iY = 0 elif Y >= self.Y[-1]: iY = -2 else: iY = np.searchsorted(self.Y, Y) - 1 Z1 = self.W[iX, iY] + (self.W[iX, iY+1] - self.W[iX, iY]) * \ (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY]) Z2 = self.W[iX+1, iY] + (self.W[iX+1, iY+1] - self.W[iX+1, iY]) * \ (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY]) return Z1 + (Z2 - Z1) * (X - self.X[iX]) / (self.X[iX+1] - self.X[iX])def interpolate(self, X, Y): if X <= self.X[0]: return np.interp(Y, self.Y, self.W[0]) elif X >= self.X[-1]: return np.interp(Y, self.Y, self.W[-1]) else: iX = np.searchsorted(self.X, X) - 1 if Y <= self.Y[0]: return np.interp(X, self.X, self.W[:,0]) elif Y >= self.Y[-1]: return np.interp(X, self.X, self.W[:,-1]) else: iY = np.searchsorted(self.Y, Y) - 1 Z1 = self.W[iX, iY] + (self.W[iX, iY+1] - self.W[iX, iY]) * \ (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY]) Z2 = self.W[iX+1, iY] + (self.W[iX+1, iY+1] - self.W[iX+1, iY]) * \ (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY]) return Z1 + (Z2 - Z1) * (X - self.X[iX]) / (self.X[iX+1] - self.X[iX])%timeit interpolate(m3d, 15,1)np.diff(m3d.W[1:3,2:4], axis=1)%timeit np.diff(m3d.W[1:3,2:4], axis=1)%timeit m3d.W[1:3,3] - m3d.W[1:3,2]np.diff(m3d.W[1:3,2:4], axis=0)m3d = mesh3d(np.arange(100),np.arange(50),np.random.random((50,100)))def extrapolate(self, X, Y): iX = 0 iY = 0 if X <= self.X[0]: iX = 0 elif X >= self.X[-1]: iX = -2 else: iX = np.where(self.X < X)[0][-1] if Y <= self.Y[0]: iY = 0 elif Y >= self.Y[-1]: iY = -2 else: iY = np.where(self.Y < Y)[0][-1] Z1 = self.W[iY, iX] + (self.W[iY, iX+1] - self.W[iY, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) Z2 = self.W[iY+1, iX] + (self.W[iY+1, iX+1] - self.W[iY+1, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) return Z1 + (Z2 - Z1) * (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY]) def extrapolate1(self, _X, _Y): xNew = np.searchsorted(self.X, _X).clip(1, len(self.X)-1).astype(int) yNew = np.searchsorted(self.Y, _Y).clip(1, len(self.Y)-1).astype(int) # 4. Calculate the slope of regions that each X value falls in. xLo, xHi = xNew - 1, xNew yLo, yHi = yNew - 1, yNew x11 = self.X[xLo] x12 = self.X[xHi] y11 = self.Y[yLo] y21 = self.Y[yHi] w11 = self.W[:,xLo] w12 = self.W[:,xHi] # Note that the following two expressions rely on the specifics of the # broadcasting semantics. xSlope = (w12 - w11) / (x12 - x11) # 5. Calculate the actual value for each entry in X. yNew = xSlope * (_X - x11) + w11 w11 = yNew[yLo] w21 = yNew[yHi] ySlope = (w21 - w11) / (y21 - y11) yNew = ySlope * (_Y - y11) + w11 return np.array(yNew) def extrapolate2(self, X, Y): iX = -2 if X >= self.X[-1] else \ np.searchsorted(self.X, X).clip(1, len(self.X)-1).astype(int) - 1 iY = -2 if Y >= self.Y[-1] else \ np.searchsorted(self.Y, Y).clip(1, len(self.Y)-1).astype(int) - 1 Z1 = self.W[iY, iX] + (self.W[iY, iX+1] - self.W[iY, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) Z2 = self.W[iY+1, iX] + (self.W[iY+1, iX+1] - self.W[iY+1, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) return Z1 + (Z2 - Z1) * (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY])def extrapolate3(self, X, Y): gc.disable() st = time.time() if X <= self.X[0]: iX = 0 elif X >= self.X[-1]: iX = -2 else: iX = np.searchsorted(self.X, X) - 1 if Y <= self.Y[0]: iY = 0 elif Y >= self.Y[-1]: iY = -2 else: iY = np.searchsorted(self.Y, Y) - 1 Z1 = self.W[iY, iX] + (self.W[iY, iX+1] - self.W[iY, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) Z2 = self.W[iY+1, iX] + (self.W[iY+1, iX+1] - self.W[iY+1, iX]) * \ (X - self.X[iX]) / (self.X[iX+1] - self.X[iX]) return Z1 + (Z2 - Z1) * (Y - self.Y[iY]) / (self.Y[iY+1] - self.Y[iY])New meshfrom functools import singledispatchclass newmesh(np.ndarray): def __new__(cls, data=None, label=None, unit=None): # We first cast to be our class type # np.asfarray([], dtype='float64') @singledispatch def myArray(o): if o is None: o = [] # Will call directly __array_finalize__ obj = np.asarray(o).flatten().view(cls) obj.label = label obj.unit = unit return obj @myArray.register(mesh1d) def _(o): if label is not None: o.label = label if unit is not None: o.unit = unit return o return myArray(data) # see InfoArray.__array_finalize__ for comments def __array_finalize__(self, obj): """ :type obj: object """ self.unit = getattr(obj, 'unit', None) self.label = getattr(obj, 'label', None) @property def d(self): return self[0] @d.setter def d(self, obj): self[0] = objtest = newmesh([1, 3, 4])test.d = 5test.flags

[1] lerp article on Wikipedia