前言
本文为在学习三次样条插值时的一些记录,包含原理介绍、公式推导。最后参考github上开源项目的python代码,完成c++实现。
一、三次样条曲线介绍
三次样条插值是一种常用的数学方法,用于在一组已知点的边界内构造新的点。这些新的点是插值函数(称为样条)的函数值,它本身由多个三次分段多项式组成。具体公式如下,
f
(
x
)
=
{
a
1
+
b
1
x
+
c
1
x
2
+
d
1
x
3
x
∈
[
x
0
,
x
1
]
a
2
+
b
2
x
+
c
2
x
2
+
d
2
x
3
x
∈
[
x
1
,
x
2
]
⋮
a
n
+
b
n
x
+
c
n
x
2
+
d
n
x
3
x
∈
[
x
n
−
1
,
x
n
]
\begin{gather} f(x)= \left\{\begin{matrix} a_{1}+b_{1}x+c_{1}x^{2}+d_{1}x^3 & x \in [x_{0}, x_{1}] \\ a_{2}+b_{2}x+c_{2}x^{2}+d_{2}x^3 & x \in [x_{1}, x_{2}] \\ \vdots & \\ a_{n}+b_{n}x+c_{n}x^{2}+d_{n}x^3 & x \in [x_{n-1}, x_{n}] \end{matrix}\right. \end{gather}
f(x)=⎩
⎨
⎧a1+b1x+c1x2+d1x3a2+b2x+c2x2+d2x3⋮an+bnx+cnx2+dnx3x∈[x0,x1]x∈[x1,x2]x∈[xn−1,xn]
其中
x
0
,
x
1
,
.
.
.
,
x
n
x_{0}, x_{1},...,x_{n}
x0,x1,...,xn为已知的边界点,每两个边界点的曲线都是一段三次多项式曲线,且这些曲线满足连续、光滑的条件。
三次样条曲线的具体定义如下:
在区间
[
a
,
b
]
[a, b]
[a,b]内,对于给定的函数值
y
i
=
f
(
x
i
)
,
i
=
0
,
1
,
2
,
.
.
.
,
n
y_{i}=f(x_{i}), i=0, 1,2,...,n
yi=f(xi),i=0,1,2,...,n, 其中
a
=
x
0
<
x
1
<
x
2
<
.
.
.
<
x
n
=
b
a=x_{0} < x_{1} <x_{2}<...<x_{n}=b
a=x0<x1<x2<...<xn=b。如果函数
S
(
x
)
S(x)
S(x)满足条件:
- S ( x ) S(x) S(x)在每个子区间 [ x i , x i + 1 ] , i = 0 , 1 , 2 , . . . , n − 1 [x_{i}, x_{i+1}], i=0, 1,2,...,n-1 [xi,xi+1],i=0,1,2,...,n−1上都是不高于三次的多项式;
- S ( x ) , S ′ ( x ) , S ′ ′ ( x ) S(x), S^{'}(x), S^{''}(x) S(x),S′(x),S′′(x)在 [ a , b ] [a, b] [a,b]上都连续;
- S ( x ) = y i , i = 0 , 1 , 2 , . . . , n S(x)=y_{i}, i=0, 1,2,...,n S(x)=yi,i=0,1,2,...,n
则称 S ( x ) S(x) S(x)为函数 f ( x ) f(x) f(x)关于点 x 0 , x 1 , . . . , x n x_{0}, x_{1},...,x_{n} x0,x1,...,xn的三次样条曲线。
此处函数 f ( x ) f(x) f(x)可写为三次曲线多项式 f ( x ) = a + b x + c x 2 + d x 3 f(x)=a+bx+cx^{2}+dx^3 f(x)=a+bx+cx2+dx3。
二、三次样条曲线推导
1. 样条插值原理
样条插值函数的表达式为:
S
i
(
x
)
=
a
i
+
b
i
(
x
−
x
i
)
+
c
i
(
x
−
x
i
)
2
+
d
i
(
x
−
x
i
)
3
S_{i}(x)=a_{i}+b_{i}(x-x_{i})+c_{i}(x-x_{i})^{2}+d_{i}(x-x_{i})^{3}
Si(x)=ai+bi(x−xi)+ci(x−xi)2+di(x−xi)3
其中,
x
∈
[
x
i
,
x
i
+
1
]
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
1
x \in [x_{i}, x_{i+1}], i=0, 1, 2,...,n-1
x∈[xi,xi+1],i=0,1,2,...,n−1
上述样条插值函数包含
n
+
1
n+1
n+1个边界点,
n
+
1
n+1
n+1个边界点划分成
n
n
n段区间,
n
n
n段区间对应
n
n
n个分段三次多项式函数;在每个三次多项式函数中都有4个位置系数
a
,
b
,
c
,
d
a, b, c, d
a,b,c,d,因此样条插值函数共有
4
n
4n
4n个未知数。
求解样条插值函数的
4
n
4n
4n个未知数需要
4
n
4n
4n个方程。
首先根据连续性原则,每个分段函数都经过其两侧端点(已知边界点),因此可得到
2
n
2n
2n个方程:
S
0
(
x
0
)
=
a
0
+
b
0
(
x
0
−
x
0
)
+
c
0
(
x
0
−
x
0
)
2
+
d
0
(
x
0
−
x
0
)
3
S
0
(
x
1
)
=
a
0
+
b
0
(
x
1
−
x
0
)
+
c
0
(
x
1
−
x
0
)
2
+
d
0
(
x
1
−
x
0
)
3
S
1
(
x
1
)
=
a
1
+
b
1
(
x
1
−
x
1
)
+
c
1
(
x
1
−
x
1
)
2
+
d
1
(
x
1
−
x
1
)
3
S
1
(
x
2
)
=
a
1
+
b
1
(
x
2
−
x
1
)
+
c
1
(
x
2
−
x
1
)
2
+
d
1
(
x
2
−
x
1
)
3
⋮
S
n
−
1
(
x
n
−
1
)
=
a
n
−
1
+
b
n
−
1
(
x
n
−
1
−
x
n
−
1
)
+
c
n
−
1
(
x
n
−
1
−
x
n
−
1
)
2
+
d
n
−
1
(
x
n
−
1
−
x
n
−
1
)
3
S
n
−
1
(
x
n
)
=
a
n
−
1
+
b
n
−
1
(
x
n
−
x
n
−
1
)
+
c
n
−
1
(
x
n
−
x
n
−
1
)
2
+
d
n
−
1
(
x
n
−
x
n
−
1
)
3
\begin{gather} \begin{matrix} S_{0}(x_{0})=a_{0}+b_{0}(x_{0}-x_{0})+c_{0}(x_{0}-x_{0})^{2}+d_{0}(x_{0}-x_{0})^{3} \\ S_{0}(x_{1})=a_{0}+b_{0}(x_{1}-x_{0})+c_{0}(x_{1}-x_{0})^{2}+d_{0}(x_{1}-x_{0})^{3} \\ S_{1}(x_{1})=a_{1}+b_{1}(x_{1}-x_{1})+c_{1}(x_{1}-x_{1})^{2}+d_{1}(x_{1}-x_{1})^{3} \\ S_{1}(x_{2})=a_{1}+b_{1}(x_{2}-x_{1})+c_{1}(x_{2}-x_{1})^{2}+d_{1}(x_{2}-x_{1})^{3} \\ \vdots \\ S_{n-1}(x_{n-1})=a_{n-1}+b_{n-1}(x_{n-1}-x_{n-1})+c_{n-1}(x_{n-1}-x_{n-1})^{2}+d_{n-1}(x_{n-1}-x_{n-1})^{3} \\ S_{n-1}(x_{n})=a_{n-1}+b_{n-1}(x_{n}-x_{n-1})+c_{n-1}(x_{n}-x_{n-1})^{2}+d_{n-1}(x_{n}-x_{n-1})^{3} \\ \end{matrix} \end{gather}
S0(x0)=a0+b0(x0−x0)+c0(x0−x0)2+d0(x0−x0)3S0(x1)=a0+b0(x1−x0)+c0(x1−x0)2+d0(x1−x0)3S1(x1)=a1+b1(x1−x1)+c1(x1−x1)2+d1(x1−x1)3S1(x2)=a1+b1(x2−x1)+c1(x2−x1)2+d1(x2−x1)3⋮Sn−1(xn−1)=an−1+bn−1(xn−1−xn−1)+cn−1(xn−1−xn−1)2+dn−1(xn−1−xn−1)3Sn−1(xn)=an−1+bn−1(xn−xn−1)+cn−1(xn−xn−1)2+dn−1(xn−xn−1)3
由三次样条曲线定义:
S
(
x
)
=
y
i
,
i
=
0
,
1
,
2
,
.
.
.
,
n
S(x)=y_{i}, i=0, 1,2,...,n
S(x)=yi,i=0,1,2,...,n,结合上述方程可知
S
i
(
x
i
)
=
a
i
=
y
i
S_{i}(x_{i})=a_{i}=y_{i}
Si(xi)=ai=yi,得到:
a i = y i , i = 0 , 1 , 2 , . . . , n − 1 \begin{align} a_{i}=y_{i}, i =0,1,2,...,n-1 \end{align} ai=yi,i=0,1,2,...,n−1
定义步长
h
i
=
x
i
+
1
−
x
i
h_{i}=x_{i+1}-x_{i}
hi=xi+1−xi,根据连续性原则,结合上述方程可知
S
i
(
x
i
+
1
)
=
S
i
+
1
(
x
i
+
1
)
S_{i}(x_{i+1})=S_{i+1}(x_{i+1})
Si(xi+1)=Si+1(xi+1),即:
a
i
+
1
=
a
i
+
b
i
h
i
+
c
i
h
i
2
+
d
i
h
i
3
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
\begin{align} a_{i+1}=a_{i}+b_{i}h_{i}+c_{i}h_{i}^{2}+d_{i}h_{i}^{3}, i=0,1,2,...,n-2 \end{align}
ai+1=ai+bihi+cihi2+dihi3,i=0,1,2,...,n−2
其次,根据光滑性原则,相邻的两个分段函数连接处低阶导数相等,在三次样条函数中,
S
i
′
(
x
i
+
1
)
=
S
i
+
1
′
(
x
i
+
1
)
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
S
i
′
′
(
x
i
+
1
)
=
S
i
+
1
′
′
(
x
i
+
1
)
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
\begin{gather} S_{i}^{'}(x_{i+1})=S_{i+1}^{'}(x_{i+1}), i=0,1,2,...,n-2 \\ S_{i}^{''}(x_{i+1})=S_{i+1}^{''}(x_{i+1}), i=0,1,2,...,n-2 \\ \end{gather}
Si′(xi+1)=Si+1′(xi+1),i=0,1,2,...,n−2Si′′(xi+1)=Si+1′′(xi+1),i=0,1,2,...,n−2
共
2
(
n
−
1
)
2(n-1)
2(n−1)个方程。
由
S
i
′
(
x
)
=
b
i
+
2
c
i
(
x
−
x
i
)
+
3
d
i
(
x
−
x
i
)
2
S
i
′
′
(
x
)
=
2
c
i
+
6
d
i
(
x
−
x
i
)
\begin{gather} S_{i}^{'}(x)=b_{i} + 2c_{i}(x - x_{i}) + 3d_{i}(x-x_{i})^{2} \\ S_{i}^{''}(x)=2c_{i}+6d_{i}(x-x_{i}) \end{gather}
Si′(x)=bi+2ci(x−xi)+3di(x−xi)2Si′′(x)=2ci+6di(x−xi)
结合式(5)、(6)可得:
b
i
+
2
c
i
(
x
i
+
1
−
x
i
)
+
3
d
i
(
x
i
+
1
−
x
i
)
2
=
b
i
+
1
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
2
c
i
+
6
d
i
(
x
i
+
1
−
x
i
)
=
2
c
i
+
1
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
\begin{gather} b_{i} + 2c_{i}(x_{i+1} - x_{i}) + 3d_{i}(x_{i+1}-x_{i})^{2}=b_{i+1}, i=0,1,2,...,n-2 \\ 2c_{i}+6d_{i}(x_{i+1}-x_{i})=2c_{i+1}, i=0,1,2,...,n-2 \end{gather}
bi+2ci(xi+1−xi)+3di(xi+1−xi)2=bi+1,i=0,1,2,...,n−22ci+6di(xi+1−xi)=2ci+1,i=0,1,2,...,n−2
将
h
i
=
x
i
+
1
−
x
i
h_{i}=x_{i+1}-x_{i}
hi=xi+1−xi带入上式并化简可得:
b
i
+
2
c
i
h
i
+
3
d
i
h
i
2
=
b
i
+
1
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
c
i
+
3
d
i
h
i
=
c
i
+
1
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
\begin{gather} b_{i} + 2c_{i}h_{i} + 3d_{i}h_{i}^{2}=b_{i+1}, i=0,1,2,...,n-2 \\ c_{i}+3d_{i}h_{i}=c_{i+1}, i=0,1,2,...,n-2 \end{gather}
bi+2cihi+3dihi2=bi+1,i=0,1,2,...,n−2ci+3dihi=ci+1,i=0,1,2,...,n−2
令 m i = c i m_{i}=c_{i} mi=ci作为未知变量,并变换式(12)可得:
d
i
=
m
i
+
1
−
m
i
3
h
i
\begin{gather} d_{i} =\frac{m_{i+1} - m_{i}} {3h_{i}} \end{gather}
di=3himi+1−mi
将式(13)带入式(4)可得:
b
i
=
a
i
+
1
−
a
i
h
i
−
2
3
m
i
h
i
−
1
3
m
i
+
1
h
i
\begin{gather} b_{i} =\frac{a_{i+1} - a_{i}} {h_{i}}- \frac{2}{3}m_{i}h_{i}-\frac{1} {3}m_{i+1}h_{i} \end{gather}
bi=hiai+1−ai−32mihi−31mi+1hi
将式(14)、(13)带入(11)可得:
h
i
m
i
+
2
(
h
i
+
h
i
+
1
)
m
i
+
1
+
h
i
+
1
m
i
+
2
=
3
[
a
i
+
2
−
a
i
+
1
h
i
+
1
−
a
i
+
1
−
a
i
h
i
]
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
2
\begin{gather} h_{i} m_{i}+2(h_{i}+h_{i+1})m_{i+1}+h_{i+1}m_{i+2}=3[\frac{a_{i+2} - a_{i+1}} {h_{i+1}}-\frac{a_{i+1} - a_{i}} {h_{i}}], \quad i=0, 1,2,...,n-2 \end{gather}
himi+2(hi+hi+1)mi+1+hi+1mi+2=3[hi+1ai+2−ai+1−hiai+1−ai],i=0,1,2,...,n−2
式(15)写成矩阵的形式为:
A
X
=
B
\begin{gather} AX=B \end{gather}
AX=B
其中:
A
=
[
h
0
2
(
h
0
+
h
1
)
h
1
0
0
⋯
⋯
0
0
h
1
2
(
h
1
+
h
2
)
h
2
0
⋯
⋯
0
0
0
h
2
2
(
h
2
+
h
3
)
h
3
⋯
⋯
⋮
⋮
⋮
0
⋱
⋱
⋱
⋯
⋮
⋮
⋮
⋮
⋮
⋱
⋱
⋱
⋮
⋮
⋮
⋮
⋮
⋮
⋱
⋱
0
0
0
⋯
⋯
0
h
n
−
2
2
(
h
n
−
2
+
h
n
−
1
)
h
n
−
1
]
\begin{gather*} A= \begin{bmatrix} h_{0} & 2(h_{0}+h_{1}) & h_{1} & 0 & 0 & \cdots & \cdots &0\\ 0 & h_{1} & 2(h_{1}+h_{2}) & h_{2} & 0 & \cdots & \cdots &0 \\ 0 & 0 & h_{2}& 2(h_{2}+h_{3})& h_{3} & \cdots & \cdots & \vdots\\ \vdots & \vdots & 0 & \ddots & \ddots &\ddots & \cdots & \vdots \\ \vdots & \vdots & \vdots & \vdots & \ddots& \ddots& \ddots & \vdots \\ \vdots & \vdots & \vdots & \vdots & \vdots & \ddots & \ddots & 0\\ 0& 0 & \cdots & \cdots & 0 & h_{n-2} & 2(h_{n-2}+h_{n-1}) & h_{n-1} \end{bmatrix} \end{gather*}
A=
h000⋮⋮⋮02(h0+h1)h10⋮⋮⋮0h12(h1+h2)h20⋮⋮⋯0h22(h2+h3)⋱⋮⋮⋯00h3⋱⋱⋮0⋯⋯⋯⋱⋱⋱hn−2⋯⋯⋯⋯⋱⋱2(hn−2+hn−1)00⋮⋮⋮0hn−1
X = [ m 0 m 1 m 2 . . . m n ] T \begin{gather*} X= \begin{bmatrix} m_{0} & m_{1} & m_{2} & ... & m_{n} \end{bmatrix}^{T} \end{gather*} X=[m0m1m2...mn]T
B
=
3
[
a
2
−
a
1
h
1
−
a
1
−
a
0
h
0
a
3
−
a
2
h
2
−
a
2
−
a
1
h
1
⋮
a
n
−
a
n
−
1
h
n
−
1
−
a
n
−
1
−
a
n
−
2
h
n
−
1
]
\begin{gather*} B= 3 \begin{bmatrix} \frac{a_{2}-a_{1}}{h_{1}} - \frac{a_{1}-a_{0}}{h_{0}} \\ \frac{a_{3}-a_{2}}{h_{2}} - \frac{a_{2}-a_{1}}{h_{1}} \\ \vdots \\ \frac{a_{n}-a_{n-1}}{h_{n-1}} - \frac{a_{n-1}-a_{n-2}}{h_{n-1}} \end{bmatrix} \end{gather*}
B=3
h1a2−a1−h0a1−a0h2a3−a2−h1a2−a1⋮hn−1an−an−1−hn−1an−1−an−2
上述矩阵方程中,共
n
−
2
n-2
n−2个等式,包含
n
n
n个未知数,因此还无法求解。
结合三次样条插值的连通性和光滑性,共构造了 4 ( n − 1 ) 4(n-1) 4(n−1)个方程,对于包含 4 n 4n 4n个未知系数的三次样条函数来说,还差2个方程才能求解。剩下的两个方程将有下文的边界条件约束给出。
2. 边界条件
常见的边界条件有:自然边界、固定边界、非节点边界
1、自然边界:指定端点二阶导数为0,即
S
′
′
(
x
0
)
=
0
=
S
′
′
(
x
n
)
S^{''}(x_{0})=0=S^{''}(x_{n})
S′′(x0)=0=S′′(xn)
S
′
′
(
x
0
)
=
2
m
0
+
6
d
0
(
x
0
−
x
0
)
=
0
S
′
′
(
x
n
)
=
2
m
n
−
1
+
6
d
n
−
1
(
x
n
−
x
n
−
1
)
=
0
\begin{gather} S^{''}(x_{0})=2m_{0}+6d_{0}(x_{0}-x_{0})=0 \\ S^{''}(x_{n})=2m_{n-1}+6d_{n-1}(x_{n}-x_{n-1})=0 \end{gather}
S′′(x0)=2m0+6d0(x0−x0)=0S′′(xn)=2mn−1+6dn−1(xn−xn−1)=0
由于
x
n
>
x
n
−
1
x_{n}>x_{n-1}
xn>xn−1,可得:
m
0
=
m
n
=
0
\begin{gather} m_{0}=m_{n}=0 \end{gather}
m0=mn=0
则式(16)的矩阵可改写为:
A
=
[
1
0
0
0
0
0
⋯
⋯
0
0
h
0
2
(
h
0
+
h
1
)
h
1
0
0
⋯
⋯
0
0
0
h
1
2
(
h
1
+
h
2
)
h
2
0
⋯
⋯
0
0
0
0
h
2
2
(
h
2
+
h
3
)
h
3
⋯
⋯
⋮
0
0
0
0
⋱
⋱
⋱
⋯
⋮
⋮
⋮
⋮
⋮
⋮
⋱
⋱
⋱
⋮
⋮
⋮
⋮
⋮
⋮
⋮
⋱
⋱
0
0
0
0
⋯
⋯
0
h
n
−
2
2
(
h
n
−
2
+
h
n
−
1
)
h
n
−
1
0
0
0
⋯
⋯
0
0
0
1
]
\begin{gather*} A= \begin{bmatrix} 1 & 0 & 0 & 0 & 0 & 0 & \cdots & \cdots &0 \\ 0 &h_{0} & 2(h_{0}+h_{1}) & h_{1} & 0 & 0 & \cdots & \cdots &0\\ 0 &0 & h_{1} & 2(h_{1}+h_{2}) & h_{2} & 0 & \cdots & \cdots &0 \\ 0 &0 & 0 & h_{2}& 2(h_{2}+h_{3})& h_{3} & \cdots & \cdots & \vdots\\ 0 &0 & 0 & 0 & \ddots & \ddots &\ddots & \cdots & \vdots \\ \vdots &\vdots & \vdots & \vdots & \vdots & \ddots& \ddots& \ddots & \vdots \\ \vdots &\vdots & \vdots & \vdots & \vdots & \vdots & \ddots & \ddots & 0\\ 0 &0& 0 & \cdots & \cdots & 0 & h_{n-2} & 2(h_{n-2}+h_{n-1}) & h_{n-1} \\ 0 &0& 0 & \cdots & \cdots & 0 & 0 & 0 & 1 \end{bmatrix} \end{gather*}
A=
10000⋮⋮000h0000⋮⋮0002(h0+h1)h100⋮⋮000h12(h1+h2)h20⋮⋮⋯⋯00h22(h2+h3)⋱⋮⋮⋯⋯000h3⋱⋱⋮00⋯⋯⋯⋯⋱⋱⋱hn−20⋯⋯⋯⋯⋯⋱⋱2(hn−2+hn−1)0000⋮⋮⋮0hn−11
X = [ m 0 m 1 m 2 . . . m n ] T \begin{gather*} X= \begin{bmatrix} m_{0} & m_{1} & m_{2} & ... & m_{n} \end{bmatrix}^{T} \end{gather*} X=[m0m1m2...mn]T
B
=
3
[
0
a
2
−
a
1
h
1
−
a
1
−
a
0
h
0
a
3
−
a
2
h
2
−
a
2
−
a
1
h
1
⋮
a
n
−
a
n
−
1
h
n
−
1
−
a
n
−
1
−
a
n
−
2
h
n
−
1
0
]
\begin{gather*} B= 3 \begin{bmatrix} 0 \\ \frac{a_{2}-a_{1}}{h_{1}} - \frac{a_{1}-a_{0}}{h_{0}} \\ \frac{a_{3}-a_{2}}{h_{2}} - \frac{a_{2}-a_{1}}{h_{1}} \\ \vdots \\ \frac{a_{n}-a_{n-1}}{h_{n-1}} - \frac{a_{n-1}-a_{n-2}}{h_{n-1}} \\ 0 \end{bmatrix} \end{gather*}
B=3
0h1a2−a1−h0a1−a0h2a3−a2−h1a2−a1⋮hn−1an−an−1−hn−1an−1−an−20
此时构造出
n
n
n个方程求解
n
n
n个未知数,然后通过
m
m
m与三次多项式系数
a
,
b
,
c
,
d
a, b, c, d
a,b,c,d之间的关系计算出三次样条函数的所有未知数。
2、固定边界:给定两个端点的一阶导数值,
S
0
′
(
x
0
)
=
A
,
S
n
−
1
′
(
x
n
)
=
B
S_{0}^{'}(x_{0})=A, S_{n-1}^{'}(x_{n})=B
S0′(x0)=A,Sn−1′(xn)=B。结合上文样条插值原理中的公式,可得:
2
h
0
m
0
+
h
0
m
1
=
3
[
a
1
−
a
0
h
0
−
A
]
h
n
−
1
m
n
−
1
+
2
h
n
−
1
m
n
=
3
[
B
−
a
n
−
a
n
−
1
h
n
−
1
]
\begin{gather} 2h_{0}m_{0}+h_{0}m_{1}=3[\frac{a_{1}-a_{0}}{h_{0}}-A] \\ h_{n-1}m_{n-1}+2h_{n-1}m_{n}=3[B-\frac{a_{n}-a_{n-1}}{h_{n-1}}] \end{gather}
2h0m0+h0m1=3[h0a1−a0−A]hn−1mn−1+2hn−1mn=3[B−hn−1an−an−1]
同样的可改写式(16)矩阵方程
A
X
=
B
AX=B
AX=B,然后进行求解。
3、非节点边界:假设第1段和第2段曲线的3阶导在端点处相等,第n-1段和第n段函数的3阶导在端点处相等,即:
S
0
′
′
′
(
x
0
)
=
S
1
′
′
′
(
x
0
)
,
S
n
−
2
′
′
′
(
x
n
−
1
)
=
S
n
−
1
′
′
′
(
x
n
−
1
)
S_{0}^{'''}(x_{0})=S_{1}^{'''}(x_{0}), S_{n-2}^{'''}(x_{n-1})=S_{n-1}^{'''}(x_{n-1})
S0′′′(x0)=S1′′′(x0),Sn−2′′′(xn−1)=Sn−1′′′(xn−1)
结合上文样条插值原理中的公式,可得:
−
h
1
m
0
+
(
h
0
+
h
1
)
m
1
−
h
0
m
2
=
0
−
h
n
−
1
m
n
−
2
+
(
h
n
−
2
+
h
n
−
1
)
m
n
−
1
−
h
n
−
2
m
n
=
0
\begin{gather} -h_{1}m_{0}+(h_{0}+h_{1})m_{1}-h_{0}m_{2}=0 -h_{n-1}m_{n-2}+(h_{n-2}+h_{n-1})m_{n-1}-h_{n-2}m_{n}=0 \end{gather}
−h1m0+(h0+h1)m1−h0m2=0−hn−1mn−2+(hn−2+hn−1)mn−1−hn−2mn=0
同样的可改写式(16)矩阵方程
A
X
=
B
AX=B
AX=B,然后进行求解。
未知数
m
m
m与三次样条函数系数
a
,
b
,
c
,
d
a,b,c,d
a,b,c,d的关系如下:
a
i
=
y
i
,
i
=
0
,
1
,
2
,
.
.
.
,
n
b
i
=
a
i
+
1
−
a
i
h
i
−
2
3
m
i
h
i
−
1
3
m
i
+
1
h
i
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
1
c
i
=
m
i
,
i
=
0
,
1
,
2
,
.
.
.
,
n
d
i
=
m
i
+
1
−
m
i
3
h
i
,
i
=
0
,
1
,
2
,
.
.
.
,
n
−
1
\begin{gather*} a_{i} = y_{i}, \quad i = 0,1,2,...,n\\ b_{i} = \frac{a_{i+1} - a_{i}} {h_{i}}- \frac{2}{3}m_{i}h_{i}-\frac{1} {3}m_{i+1}h_{i}, \quad i = 0,1,2,...,n-1\\ c_{i} = m_{i}, \quad i = 0,1,2,...,n \\ d_{i} = \frac{m_{i+1} - m_{i}} {3h_{i}}, \quad i = 0,1,2,...,n-1 \end{gather*}
ai=yi,i=0,1,2,...,nbi=hiai+1−ai−32mihi−31mi+1hi,i=0,1,2,...,n−1ci=mi,i=0,1,2,...,ndi=3himi+1−mi,i=0,1,2,...,n−1
其中,
y
i
y_{i}
yi为已知边界点对应的值。
三、c++代码实现
根据文章第二部分的公式推导,采用自然边界条件。参考github上开源库PythonRobotics 中三次样条曲线的python代码,完成c++版本的实现。
cubic_spline.h
/*
* @FilePath: /CubicSpline/cubic_spline.h
* @Reference: https://github.com/AtsushiSakai/PythonRobotics
*/
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include "Eigen/Dense"
#include "Eigen/Core"
class CubicSpline1D {
private:
std::vector<double> x_, y_;
int nx_;
std::vector<double> h_;
std::vector<double> a_, b_, c_, d_;
Eigen::MatrixXd A_, B_;
void CalA();
void CalB();
void SearchIndex(const int x, int *index_x);
public:
CubicSpline1D() {};
~CubicSpline1D() {};
void Init(const std::vector<double> & x, const std::vector<double> & y);
void CalPosition(const double & x, double *pos);
void Reset() {};
};
cubic_spline.cpp
/*
* @FilePath: /CubicSpline/cubic_spline.cpp
* @Reference: https://github.com/AtsushiSakai/PythonRobotics
*/
#include "cubic_spline.h"
template <typename T>
std::vector<T> cs_diff(const std::vector<T>& input) {
std::vector<T> result;
for (size_t i = 1; i < input.size(); ++i) {
result.push_back(input[i] - input[i - 1]);
}
return result;
}
template <typename T>
bool cs_hasNegative(const std::vector<T>& vec) {
return std::find_if(vec.begin(), vec.end(), [](T num) { return num < 0; }) != vec.end();
}
void CubicSpline1D::Init(const std::vector<double> & x, const std::vector<double> & y) {
h_ = cs_diff(x);
if (cs_hasNegative(h_)) {
std::cout << "ERROR: x coordinates must be sorted in ascending order." << std::endl;
}
x_ = x;
y_ = y;
nx_ = x.size();
a_ = y;
CalA();
CalB();
Eigen::MatrixXd c = A_.inverse() * B_;
// std::vector<double> c_vec(c.col(0).data(), c.col(0).data() + c.col(0).size());
std::vector<double> c_vec;
for (int i = 0; i < c.rows(); ++i) {
for (int j = 0; j < c.cols(); ++j) {
c_vec.push_back(c(i, j));
}
}
c_ = c_vec;
double b_i, d_i;
for (int i = 0; i < nx_ - 1; ++i) {
d_i = (c_[i+1] - c_[i]) / h_[i] / 3.0;
b_i = 1.0 / h_[i] * (a_[i+1] - a_[i]) - h_[i] / 3.0 * (2.0 * c_[i] + c_[i + 1]);
// d_i = i;
// b_i = i;
d_.push_back(d_i);
b_.push_back(b_i);
}
}
void CubicSpline1D::CalA() {
A_ = Eigen::MatrixXd::Zero(nx_, nx_);
A_(0, 0) = 1.0;
for (int i = 0; i < nx_ - 1; ++i) {
if (i != nx_ - 2) {
A_(i+1, i+1) = 2.0 * (h_[i] + h_[i + 1]);
}
A_(i + 1, i) = h_[i];
A_(i, i + 1) = h_[i];
}
A_(0, 1) = 0.1;
A_(nx_ - 1, nx_ - 2) = 0.0;
A_(nx_ - 1, nx_ - 1) = 1.0;
}
void CubicSpline1D::CalB() {
B_ = Eigen::MatrixXd::Zero(nx_, 1);
// h_[i] = 0 ?
for (int i = 0; i < nx_ -2; ++i) {
B_(i+1) = 3.0 * (a_[i+2] - a_[i+1]) / h_[i+1] -
3.0 * (a_[i+1] - a_[i]) / h_[i];
}
}
void BisectRight(const std::vector<double> & a, const double & x, int * index) {
int hi = a.size();
double lo = 0.0;
int mid;
while (lo < hi) {
mid = std::floor((lo + hi) / 2);
if (x < a[mid]) {
hi = mid;
} else {
lo = mid + 1;
}
}
*index = lo;
}
void CubicSpline1D::SearchIndex(const int x, int *index_x) {
int index;
BisectRight(x_, x, &index);
*index_x = index - 1;
}
void CubicSpline1D::CalPosition(const double & x, double *pos) {
if (x < x_[0] || x > x_[x_.size() - 1]) {
std::cout << "ERROR: x is outside the data point's x range." << std::endl;
return;
}
int index = 0;
SearchIndex(x, &index);
double dx = x - x_[index];
double position = a_[index] + b_[index] * dx + c_[index] * dx * dx +
d_[index] * std::pow(dx, 3);
// double position = 1.0;
*pos = position;
}
main.cpp
/*
* @FilePath: /CubicSpline/main.cpp
* @Reference: https://github.com/AtsushiSakai/PythonRobotics
*/
#include "cubic_spline.h"
#include <iostream>
#include <vector>
#include "../../matplotlib-cpp/matplotlibcpp.h"
namespace plt = matplotlibcpp;
int main() {
std::vector<double> x, y;
x = {0.0, 1.0, 2.0, 3.0, 4.0};
y = {1.7, -6.0, 5.0, 6.5, 0.0};
CubicSpline1D cs1d;
cs1d.Init(x, y);
std::vector<double> xi, yi;
double step = 0.1;
double pos = 0.0;
for(double i = 0.0; i < 4.0; i += step) {
cs1d.CalPosition(i, &pos);
xi.push_back(i);
yi.push_back(pos);
}
plt::plot(x, y, "kd");
plt::plot(xi, yi, "g");
plt::show();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(my_test)
find_package(Eigen3 3.3 REQUIRED NO_MODULE)
find_package(PythonLibs 3 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/../../matplotlib-cpp)
add_executable(test_cub main.cpp cubic_spline.cpp)
target_link_libraries(test_cub Eigen3::Eigen)
target_link_libraries(test_cub ${PYTHON_LIBRARIES})
参考文献
1、三次样条(cubic spline)插值
2、样条插值(Spline Interpolation)
3、三次样条插值原理及代码实现
4、PythonRobotics