三点三次自然样条插值¶
Ⅰ. 背景 –> Ⅱ. 原理 –> Ⅲ. 示例 –> Ⅳ. C 代码与 Python Scipy 库对比
Ⅰ. 背景¶
前面三次埃尔米特插值中,提到了无人车拐弯,要求旋转相等平缓,样条(Spline)源自于工程领域,工人为了用一条平滑的曲线穿过固定的节点,使用弹性的木条自然安置在这些点列上. 如下图所示.
Ⅱ. 原理¶
对于自变量从小到大排序的点列:
在每个节点区间 内,有不超过三次多项式:
定义样条插值多项式:
这个结合构成的大函数节点处函数值与真实观测值相等,且二阶可导:
简言之,就是一个每个小区间上都是不超过三次的多项式分段组合构成的大函数,且工程师们希望节点交界处尽量光滑,要求二阶可导. 至于多分段样条插值的原理,公式排版起来极其复杂,参考 USTC 的三弯矩方程求解算法.
http://staff.ustc.edu.cn/~lgliu/Courses/GAMES102_2020/documents/GAMES102-suppl-2-CubicSpline.pdf
这里为了简化数学模型,方便理解,Illusionna 重定义三点三次自然样条插值.
其中,小函数 和
都是三次多项式,满足:
这里的“自然”插值,转化成数学语言,即端节点处二阶导数恒为零:
Ⅲ. 示例¶
已知三个节点,计算三次自然样条插值多项式【** 类似 Python 的可迭代对象拆包】.
设小函数:
由于:
消元:
又因为:
所以求一阶导数和二阶导数,化简:
并且,因为是自然样条插值:
所以:
联立方程组 ()(
)(
) 整理得到:
Ⅳ. C 代码与 Scipy 对比¶
C 代码算法实现原理建议先阅读:
<Natural Cubic Spline Algorithm【右击,在新的标签页中打开链接】>
<NaturalCubicSpline.tex【点击下载 .tex 源文件】>
C 代码只实现求解多分段样条插值多项式系数,并没有插值函数,若需要则先编译成动态链接库供 Python 调用进行插值.
1/*
2System --> Linux & gcc8.1.0
3File ----> NaturalCubicSpline.c
4Author --> Illusionna
5Create --> 2024/2/21 22:16:30
6'''
7-*- Encoding: UTF-8 -*-
8*/
9
10// 生成 Python 调用需要的动态链接库: gcc --share -o spline.dll NaturalCubicSpline.c
11
12# include <stdio.h>
13# include <malloc.h>
14
15double ** Spline(double * X, double * Y, int n){
16 int i, j;
17 n--;
18 double stepArray[n], alphaArray[n], lArray[n+1], muArray[n+1], zArray[n+1], cArray[n+1], bArray[n], dArray[n];
19 // Step 1.
20 for (i=0; i<n; ++i){
21 stepArray[i] = X[i+1] - X[i];
22 }
23 // Step 2.
24 for (i=1; i<n; ++i){
25 alphaArray[i] = (3 * (Y[i+1] - Y[i]) / stepArray[i]) - (3 * (Y[i] - Y[i-1]) / stepArray[i-1]);
26 }
27 // Step 3.
28 lArray[0] = 1;
29 muArray[0] = 0;
30 zArray[0] = 0;
31 // Step 4.
32 for (i=1; i<n; ++i){
33 lArray[i] = 2 * (X[i+1] - X[i-1]) - stepArray[i-1] * muArray[i-1];
34 muArray[i] = stepArray[i] / lArray[i];
35 zArray[i] = (alphaArray[i] - stepArray[i-1] * zArray[i-1]) / lArray[i];
36 }
37 // Step 5.
38 lArray[n] = 1;
39 zArray[n] = 0;
40 cArray[n] = 0;
41 // Step 6.
42 for (j=n-1; j>=0; --j){
43 cArray[j] = zArray[j] - muArray[j] * cArray[j+1];
44 bArray[j] = ((Y[j+1] - Y[j]) / stepArray[j]) - (stepArray[j] * (cArray[j+1] + 2 * cArray[j]) / 3);
45 dArray[j] = (cArray[j+1] - cArray[j]) / (3 * stepArray[j]);
46 }
47 // Information.
48 printf("\033[036mThe coefficients of each interval cubic polynomial:\033[0m\n");
49 printf("S(x)=a+b(x-xk)+c(x-xk)^2+d(x-xk)^3, xk < x < x_{k+1}\n");
50 printf("%5s %9s %8s %8s %8s\n", "xk", "a", "b", "c", "d");
51 for (i=0; i<n; ++i){
52 printf("%.5f %9.5f %8.5f %9.5f %9.5f\n", X[i], Y[i], bArray[i], cArray[i], dArray[i]);
53 }
54 double ** result;
55 int columns = 5;
56 result = (double**)malloc(n * sizeof(double*));
57 for (i=0; i<n; ++i){
58 result[i] = (double *)malloc(columns * sizeof(double));
59 result[i][0] = X[i];
60 result[i][1] = Y[i];
61 result[i][2] = bArray[i];
62 result[i][3] = cArray[i];
63 result[i][4] = dArray[i];
64 }
65 return result;
66}
67
68/*
69main() 为测试代码.
70*/
71
72// int main(){
73// double X[] = {1, 2, 3};
74// double Y[] = {2, 3, 5};
75// printf("\033[H\033[J");
76// int n = sizeof(X) / sizeof(X[0]);
77// double **cs;
78// cs = Spline(X, Y, n);
79// printf("\033[033mTest for getting the return value (2-dimension array pointer)\033[0m.\n");
80// for (int i=0; i<n-1; ++i){
81// for (int j=0; j<5; ++j){
82// printf("%f ", cs[i][j]);
83// }
84// }
85// return 0;
86// }
1'''
2# System --> Windows & Python3.8.0
3# File ----> NaturalCubicSpline.py
4# Author --> Illusionna
5# Create --> 2024/2/22 21:08:56
6'''
7# -*- Encoding: UTF-8 -*-
8
9
10import os
11import ctypes # 先编译生成动态链接库: gcc --share -o spline.dll NaturalCubicSpline.c
12import numpy as np
13from bisect import bisect_left
14import matplotlib.pyplot as plt
15from scipy.interpolate import CubicSpline # 与官方标准库比对测试用.
16
17def cls() -> None:
18 os.system('cls')
19 global root
20 root = os.getcwd()
21cls()
22
23
24class NATURAL_CUBIC_SPLINE:
25 def __init__(self, X:list, Y:list) -> None:
26 self.__X = X
27 self.__Y = Y
28 self.__pos = False
29 if len(X) == len(Y):
30 lib = ctypes.CDLL(root + './spline.dll')
31 self.__Spline = lib.__getattr__('Spline')
32 self.__Spline.restype = ctypes.POINTER(ctypes.POINTER(ctypes.c_double))
33 self.__Spline.argtypes = [
34 ctypes.POINTER(ctypes.c_double),
35 ctypes.POINTER(ctypes.c_double),
36 ctypes.c_int
37 ]
38 else:
39 assert print(f'\033[031m数组 X 长度: {len(X)}, 数组 Y 长度 {len(Y)}, 长度不一致.\033[0m')
40
41 def Coefficients(self) -> list:
42 ptr = ctypes.c_double * len(self.__X)
43 a = self.__Spline(
44 ptr(* self.__X),
45 ptr(* self.__Y),
46 len(self.__X)
47 )
48 self.__coefficients = [
49 [a[i][j] for j in range(0, 5, 1)]
50 for i in range(0, len(self.__X)-1, 1)
51 ]
52 self.__pos = True
53 return self.__coefficients
54
55 def Interpolate(self, x:float) -> 'function':
56 def Parameters(period:int) -> tuple:
57 xk = self.__coefficients[period][0]
58 a = self.__coefficients[period][1]
59 b = self.__coefficients[period][2]
60 c = self.__coefficients[period][3]
61 d = self.__coefficients[period][4]
62 return (xk, a, b, c, d)
63
64 def Calculate() -> 'function':
65 idx = bisect_left(self.__X, x)
66 n = len(self.__X)
67 if (idx == 0) | (idx == 1):
68 (xk, a, b, c, d) = Parameters(0)
69 elif (idx == n) | (idx == n-1):
70 (xk, a, b, c, d) = Parameters(-1)
71 else:
72 (xk, a, b, c, d) = Parameters(idx-1)
73 y = lambda x: a + b*(x-xk) + c*(x-xk)**2 + d*(x-xk)**3
74 return y(x)
75
76 if self.__pos == True:
77 return Calculate()
78 else:
79 self.Coefficients()
80 return Calculate()
81
82
83def Test1() -> None:
84 X = [1, 2, 3, 7.23]
85 Y = [2, 3, 5, -1.75]
86 nodes:list = [1.25, 1.5, 1.75, 2.25, 2.5, 2.75, 4, 5, 6] # 待预测节点横坐标向量.
87 # ---------------------------------------------------------------------------------
88 obj = NATURAL_CUBIC_SPLINE(
89 X = X,
90 Y = Y
91 )
92 # print(obj.Coefficients()) # 打印样条插值多项式系数.
93 f = lambda x: obj.Interpolate(x)
94 print(f'\033[036mIllusionna 三次自然样条插值\033[0m预测结果:\n{list(map(f, nodes))}')
95 # ---------------------------------------------------------------------------------
96 cs = CubicSpline(
97 x = X,
98 y = Y
99 )
100 print(f'\033[033mScipy 库三次样条插值\033[0m预测结果:\n{cs(nodes).tolist()}')
101
102def Test2() -> None:
103 X = np.linspace(0, 20, 100)
104 Y = np.sin(X)
105 nodes:list = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 16, 18, 19] # 待预测节点横坐标向量.
106 # ---------------------------------------------------------------------------------
107 obj = NATURAL_CUBIC_SPLINE(
108 X = X,
109 Y = Y
110 )
111 f = lambda x: obj.Interpolate(x)
112 print(f'\033[036mIllusionna 三次自然样条插值\033[0m预测结果:\n{list(map(f, nodes))}')
113 # ---------------------------------------------------------------------------------
114 cs = CubicSpline(
115 x = X,
116 y = Y
117 )
118 print(f'\033[033mScipy 库三次样条插值\033[0m预测结果:\n{cs(nodes).tolist()}')
119 # ---------------------------------------------------------------------------------
120 plt.scatter(X, Y, s=10)
121 plt.plot(nodes, list(map(f, nodes)), 'r-')
122 plt.plot(nodes, cs(nodes).tolist(), 'g--')
123 plt.legend(['Observations', 'Illusionna predicts nodes', 'Scipy predicts nodes'])
124 plt.show()
125
126
127if __name__ == '__main__':
128 Test1()
129 # Test2()