到目前为止,描述的两种插值方法拉格朗日多项式和正向差分会形成高阶的多项式,但一般情况下,选择阶数的值等于小于数据点的数目更合适。除了考虑计算的复杂程度外,高阶多项式还会导致给定数据点之间出现不需要的极大值和极小值。所以另一种类型的插值方法以“分段”的方式从点到点拟合低阶多项式。多项式的阶数可以由我们自己决定。如果多项式被选择为线性,则数据点只是由直线连接,在每个直线相互连接的点上都有“角”。在工程应用中,如果有许多数据点紧密地聚集在一起,那么线性函数的不连续性质可能不是问题。
最常用的分段多项式方法是在相邻点之间拟合三次函数,这种方法能够保持结点处的二阶导数连续性。这种三次多项式通常被称为“样条函数”,因为插值函数可以被视为一个柔性弹性梁(或样条),最初是直的,通过所需的点(xi, yi), i = 0,1,…,产生变形。
一般来说,如果有np点,则需要n个三次样条函数(其中n = np−1),可以写成这样的形式
4n个未知系数Aji可以由以下4n个条件确定:
给与下面四个点,使用三次样条函数去计算x=1.3时的y值
在这个例子中,将有三个三次样条分布在四个坐标上。我们假设在x = 0.0和x = 2.3处的二阶导数为零,因此在x = 1.0和x = 1.5处还有两个二阶导数。
从上面方程可以写成下面的形式
需要的正向差分项求出在下表中
替换之后,得到方程为
很容易解出得到
这样,全部的二阶导就都知道了,分别为f‘’(0) = 0, f’’(1) = −3.2364, f’’(1.5) = 6.2185 and f’’(2.3) = 0,
因此从上面的式子可以求得三次样条函数,展示在下图中
在x = 1.3处进行插值,它位于三次样条函数f2(x)的1.0到1.5范围之间,设置i=2,由上面的方程得到
因此
得到
其中有一个主程序,和两个子程序,分别为天际线矩阵的乔列斯基分解子程序sparin,和一个逆向迭代求解的子程序spabac。详情可见以天际线存储矩阵的乔列斯基分解
#三次样条函数的插值求解
import B
import numpy as np1
np=5;xi=6.5
diffx=np1.zeros((np-1),dtype=np1.float)
diffy=np1.zeros((np-1),dtype=np1.float)
kdiag=np1.zeros((np-2),dtype=np1.int64)
kv=np1.zeros((2*(np-2)-1))
rhs=np1.zeros((np),dtype=np1.float)
x=np1.zeros((np))
y=np1.zeros((np))
x[0:np]=(0.0,2.5,5.0,8.0,10.0)
y[0:np]=(0.0,-0.004538,-0.005,0.0,0.004352)
print('三次样条函数的插值求解')
print('数据点',' x y')
for i in range(1,np+1):
print('{:13.4e}'.format(x[i-1]),end='')
print('{:13.4e}'.format(y[i-1]))
for i in range(1,np):
diffx[i-1]=x[i]-x[i-1]
diffy[i-1]=y[i]-y[i-1]
for i in range(1,np-1):
kdiag[i-1]=2*i-1
kv[kdiag[i-1]-1]=2.0*(diffx[i-1]+diffx[i])
rhs[i-1]=6.0*(diffy[i]/diffx[i]-diffy[i-1]/diffx[i-1])
for i in range(1,np-2):
kv[2*i-1]=diffx[i]
B.sparin(kv,kdiag)
B.spabac(kv,rhs,kdiag)
print('插值点',' x y')
for i in range(1,np):
if xi<x[i]:
yi=(rhs[i-2]*(x[i]-xi)**3+rhs[i-1]*(xi-x[i-1])**3)/(6.0*diffx[i-1])+(y[i-1]/diffx[i-1]-\
rhs[i-2]*diffx[i-1]/6.0)*(x[i]-xi)+(y[i]/diffx[i-1]-rhs[i-1]*diffx[i-1]/6.0)*(xi-x[i-1])
break
print('{:13.4e}'.format(xi),end='')
print('{:13.4e}'.format(yi))
sparin
def sparin(kv,kdiag):
#对称天际线矩阵的乔列斯基分解
n=kdiag.shape[0]
kv[0]=kv[0]**0.5
for i in range(2,n+1):
ki=kdiag[i-1]-i
l=kdiag[i-2]-ki+1
for j in range(int(l),i+1):
x=np.float64(kv[ki+j-1])
kj=kdiag[j-1]-j
if j!=1:
ll=kdiag[j-2]-kj+1
ll=max(l,ll)
if ll!=j:
m=j-1
for k in range(int(ll),m+1):
x=x-np.float64(kv[ki+k-1]*kv[kj+k-1])
kv[ki+j-1]=x/kv[kj+j-1]
kv[ki+i-1]=x**0.5
spabac
def spabac(kv,loads,kdiag):
#天际线矩阵的乔列斯基前后迭代
n=kdiag.shape[0]
loads[0]=loads[0]/kv[0]
for i in range(2,n+1):
ki=kdiag[i-1]-i
l=kdiag[i-2]-ki+1
x=loads[i-1]
if l!=i:
m=i-1
for j in range(int(l),m+1):
x=x-kv[ki+j-1]*loads[j-1]
loads[i-1]=x/kv[ki+i-1]
for it in range(2,n+1):
i=n+2-it
ki=kdiag[i-1]-i
x=loads[i-1]/kv[ki+i-1]
loads[i-1]=x
l=kdiag[i-2]-ki+1
if l!=i:
m=i-1
for k in range(int(l),int(m+1)):
loads[k-1]=loads[k-1]-x*kv[ki+k-1]
loads[0]=loads[0]/kv[0]