In the mathematical field of numerical analysis, monotone cubic interpolation is a variant of cubic interpolation that preserves monotonicity of the data set being interpolated.
Monotonicity is preserved by linear interpolation but not guaranteed by cubic interpolation.
Monotone interpolation can be accomplished using cubic Hermite spline with the tangents modified to ensure the monotonicity of the resulting Hermite spline.
An algorithm is also available for monotone quintic Hermite interpolation.
There are several ways of selecting interpolating tangents for each data point. This section will outline the use of the Fritsch–Carlson method. Note that only one pass of the algorithm is required.
Let the data points be indexed in sorted order for .
for .
for .
If and have opposite signs, set ..
If either or is negative, then the input data points are not strictly monotone, and is a local extremum. In such cases, piecewise monotone curves can still be generated by choosing if or if , although strict monotonicity is not possible globally..
, or
and rescale the tangents via,
.
After the preprocessing above, evaluation of the interpolated spline is equivalent to a cubic Hermite spline, using the data , , and for .
To evaluate at , find the index in the sequence where , lies between , and , that is: . Calculate
then the interpolated value is
where are the basis functions for the cubic Hermite spline.
The following Python implementation takes a data set and produces a monotone cubic spline interpolant function:
"""Monotone Cubic Spline Interpolation."""defcreate_interpolant(xs,ys):n=len(xs)ifn!=len(ys):raiseValueError("xs and ys must have the same length.")ifn==0:returnlambdax:(0.0,0.0)ifn==1:value=float(ys[0])returnlambdax:(value,0.0)# Sort xs and ys togethersorted_pairs=sorted(zip(xs,ys),key=lambdap:p[0])xs=[float(x)forx,_insorted_pairs]ys=[float(y)for_,yinsorted_pairs]# Compute consecutive differences and slopesdxs=[xs[i+1]-xs[i]foriinrange(n-1)]dys=[ys[i+1]-ys[i]foriinrange(n-1)]ms=[dy/dxfordx,dyinzip(dxs,dys)]# Compute first-degree coefficients (c1s)c1s=[ms[0]]foriinrange(len(ms)-1):m,m_next=ms[i],ms[i+1]ifm*m_next<=0:c1s.append(0.0)else:dx,dx_next=dxs[i],dxs[i+1]common=dx+dx_nextc1s.append(3*common/((common+dx_next)/m+(common+dx)/m_next))c1s.append(ms[-1])# Compute second- and third-degree coefficients (c2s, c3s)c2s,c3s=[],[]foriinrange(len(c1s)-1):c1,m=c1s[i],ms[i]inv_dx=1/dxs[i]common=c1+c1s[i+1]-2*mc2s.append((m-c1-common)*inv_dx)c3s.append(common*inv_dx*inv_dx)definterpolant(x):# Clamp x to rangeifx<=xs[0]:i=0elifx>=xs[-1]:i=n-2else:# Binary search for intervallow,high=0,n-2whilelow<=high:mid=(low+high)//2ifxs[mid]<x:low=mid+1else:high=mid-1i=max(0,high)dx=x-xs[i]val=ys[i]+dx*(c1s[i]+dx*(c2s[i]+dx*c3s[i]))dval=c1s[i]+dx*(2*c2s[i]+dx*3*c3s[i])returnval,dvalreturninterpolant# Example usageif__name__=="__main__":X=[0,1,2,3,4]Y=[0,1,4,9,16]spline=create_interpolant(X,Y)print("# Data")print("x\tf(x)")forx,yinzip(X,Y):print(f"{x:.6f}\t{y:.6f}")print("\n# Interpolated values")print("x\tP(x)\tdP(x)/dx")M=25foriinrange(M+1):x=X[0]+(X[-1]-X[0])*i/Mp,dp=spline(x)print(f"{x:.6f}\t{p:.6f}\t{dp:.6f}")