拉格朗日插值¶
Ⅰ. 作用 –> Ⅱ. 原理 –> Ⅲ. 余项 –> Ⅳ. 示例 –> Ⅴ. Python 代码 –> Ⅵ. 背景
Ⅰ. 作用¶
顾名思义,既然是插值,那就可以补充数据,在旧数据之间插入新值. 拉格朗日插值得到的多项式当然也可用于预测,但我强烈不建议你去预测那些遥远的节点,一来,变数太大不够准确,二来,插值构造的多项式阶数过高,他们说,这会产生龙格现象.
Ⅱ. 原理¶
已知从小到大排序的唯一点列,统计上亦称“观测”,observation:
定义拉格朗日插值基函数:
定义拉格朗日插值多项式:
阶数:
新的插值节点:
Ⅲ. 余项¶
插值多项式毕竟不是本源函数(一般现实里本源函数也是未知),因此,拉格朗日插值多项式就存在误差,亦称余项.
假定人类已知性质良好的本源函数:
则,拉格朗日插值多项式余项(截断误差)为:
Ⅳ. 示例¶
已知观测:
则二次拉格朗日插值多项式为:
插值:
预测遥远的节点:
注意到,由已知点列构造的拉格朗日插值多项式,对于新的插值节点 12,距离自变量 1, 2, 3 整体较远,代入得到的函数值 936 非常大. 用插值多项式预测遥远的节点,变数极大,结果是不稳定的.
Ⅴ. 代码¶
1'''
2# System --> Windows & Python3.8.0
3# File ----> Lagrange.py
4# Author --> Illusionna
5# Create --> 2023/11/02 15:53:15
6'''
7# -*- Encoding: UTF-8 -*-
8
9
10import copy
11import numpy as np
12from typing import Literal
13from sympy import (symbols, expand)
14
15
16class LAGRANGE:
17 """
18 --------------------------------------------------
19 类: 拉格朗日插值多项式.
20 obj = LAGRANGE(X:list, Y:list)
21 --------------------------------------------------
22 函数:
23 1. obj.Interpolate(x:float) -> float
24 x 为插值节点.
25 2. obj.Show(precision:int=12, mode='definition') -> None
26 precision 为显示定义式的多项式系数的精度;
27 mode 为多项式显示的模式 --> 'simplify' & 'definition'
28 --------------------------------------------------
29 """
30 def __init__(
31 self,
32 *args,
33 X:list,
34 Y:list,
35 **kwargs
36 ) -> None:
37 self.X = X
38 self.Y = Y
39 self.__base = LAGRANGE.__BaseCoefficients(self)
40
41 def Interpolate(self, x:float) -> float:
42 """
43 拉格朗日插值.
44 """
45 result = 0
46 val = x
47 for i in range(0, len(self.X), 1):
48 temp = list(
49 map(lambda x: val - x, self.X)
50 )
51 temp.pop(i)
52 numerator = np.array(temp).prod()
53 del temp
54 # --------------------------------------
55 """
56 如果想获取更精确的插值,解锁如下注释...
57 """
58 # temp = list(
59 # map(lambda x: self.X[i] - x, self.X)
60 # )
61 # temp.remove(0)
62 # denominator = np.array(temp).prod()
63 # del temp
64 """
65 用如下注释顶替 result 输出结果...
66 """
67 # result = result + (self.Y[i] * numerator / denominator)
68 # --------------------------------------
69 result = result + self.__base[i]*numerator
70 return result
71
72 def Show(
73 self,
74 precision:int=12,
75 mode:Literal['definition', 'simplify']='definition'
76 ) -> None:
77 """
78 控制台显示拉格朗日多项式.
79 """
80 if mode == 'definition':
81 showString = '\033[036mL(x)\033[0m = '
82 for i in range(0, len(self.__base), 1):
83 coef = self.__base[i]
84 string = LAGRANGE.__PolynomialString(self.X, i, 'definition')
85 temp = f'\033[033m%.{precision}f\033[0m{string} \033[031m+\033[0m ' % coef
86 showString = showString + temp
87 showString = showString[:-13]
88 del temp
89 print(showString)
90 elif mode == 'simplify':
91 showString = ''
92 for i in range(0, len(self.__base), 1):
93 coef = self.__base[i]
94 string = LAGRANGE.__PolynomialString(self.X, i, 'simplify')
95 string = string[:-1]
96 temp = f'%.{precision}f*{string}+' % coef
97 showString = showString + temp
98 showString = showString[:-1]
99 temp = str(expand(showString))
100 expression= 'L(x) = '
101 expression = expression + temp
102 del temp
103 print(expression)
104 else:
105 print('Error...')
106 exit(0)
107
108 def __BaseCoefficients(self) -> list:
109 coefficientsVector = []
110 for i in range(0, len(self.Y), 1):
111 y = self.Y[i]
112 temp = list(
113 map(lambda x: self.X[i] - x, self.X)
114 )
115 temp.remove(0)
116 denominator = np.array(temp).prod()
117 coefficientsVector.append(y / denominator)
118 del temp
119 return coefficientsVector
120
121 def __PolynomialString(vector:list, i:int, mode:str) -> str:
122 temp = copy.deepcopy(vector)
123 temp.pop(i)
124 string = ''
125 if mode == 'definition':
126 for j in range(0, len(temp), 1):
127 value = temp[j]
128 if value > 0:
129 string = string + f'(x-{value})'
130 elif value < 0:
131 string = string + f'(x+{abs(value)})'
132 elif value == 0:
133 string = string + '(x)'
134 del temp
135 return string
136 else:
137 for j in range(0, len(temp), 1):
138 value = temp[j]
139 if value > 0:
140 string = string + f'(x-{value})*'
141 elif value < 0:
142 string = string + f'(x+{abs(value)})*'
143 elif value == 0:
144 string = string + '(x-0)*'
145 del temp
146 return string
147
148
149if __name__ == '__main__':
150 """
151 以 y = (x^4)*(e^x) 为例.
152 查看 LAGRANGE 类文档
153 >>> print(LAGRANGE.__doc__)
154 """
155 # 测试拉格朗日插值类效果.
156 print('\033[H\033[J', end='')
157 print(LAGRANGE.__doc__)
158
159 # ----------------------------------------
160 # 插值核心代码.
161 X = [-7, -6.2, -5.4, -4.6, -3.8, -3]
162 Y = [2.18, 2.99, 3.84, 4.50, 4.66, 4.03]
163 obj = LAGRANGE(X=X, Y=Y)
164 value = obj.Interpolate(-5)
165 # ----------------------------------------
166
167 print(f'当 x = -5, 插值 L(x) = {value}')
168 print('\n插值结果定义式:')
169 obj.Show(precision=7, mode='definition')
170 print('\n插值结果化简式')
171 obj.Show(mode='simplify')
172 print('')
173
174 # ----------------------------------------
175
176 import matplotlib.pyplot as plt
177
178 x = np.linspace(-7, -1, 20)
179 y1 = x**4 * np.exp(x)
180 y2 = []
181 for i in range(0, len(x), 1):
182 y2.append(obj.Interpolate(x[i]))
183
184 observation = plt.plot(X, Y, 'bo')
185 interpolation = plt.plot(x, y2, 'r*')
186 function = plt.plot(x, y1, 'g-')
187
188 plt.title('Lagrange Interpolation')
189 plt.legend(['observation', 'interpolation', 'function: $y=x^4e^x$'])
190 plt.show()
插值结果:
Ⅵ. 背景¶
【背景根据历史虚构】 |
在 16 世纪,由于欧洲航海事业的发展,先驱们就必须掌握“观天象”这一本领,自然就离不开天文学的引导.
十七世纪新纪元的伊始之年,步 Giordano Bruno 火刑之后尘,欧洲的人们逐渐接受《日心说》思想.
假设在 17 世纪,人类通过某些特殊手段获取到太阳和地球的部分数据,包括距离和引力,但由于马虎,忘了记载数量级、量纲单位 :)>
距离(Distance) |
引力(Gravity) |
|---|---|
1.46 |
3.7160276576 |
1.47 |
3.6656414249 |
1.48 |
3.6162730803 |
1.50 |
3.5204820244 |
1.51 |
3.4740075238 |
1.52 |
3.4284472624 |
人们知道一年有 365 天、四个季度,地球围绕太阳椭圆公转,因此,地日距离自然不同,表格中六个观测的距离是地表最强肉眼观察大师第谷所得,(六个引力值是未来的笔者我所提供).
某一天,第谷又观察到一个新的地日距离 1.49 无单位,人们想知道当天的引力值,但未来的我因网络信号不佳,提供的引力值传输到数据链路层被吃掉了.
焦头烂额之际,主人公 Joseph-Louis Lagrange 粉墨登场,说:“我会”,于是他创造性地给出拉格朗日插值法,熟练 Lagrange.py 代码,在 1.48 和 1.50 之间插入 1.49 的函数值,终端返回:
至此,人们的问题得以解决.
但不久之后,质疑的声音在人群中嘹响. 譬如,未来的我,我觉得拉格朗日的结果不一定对,他方法得到的结果应该有很大偏差. 所以,这个偏差究竟该怎么断定?
这个时候,我们就需要这六个观察对应的本源函数,用绝对真值和估计插值比对,看看误差大小是否在我们可控范围之内.
纵使世间万象万千,好在稍后的牛顿发现了现象背后的本源函数,即万有引力定律:
通过表格已知的数据,把分子作为整体消去后,我们得到距离为 1.49 时的万有引力:
通过本源函数计算的引力值和拉格朗日插值得到的引力值,一直到小数点后 10 位才发生分歧(不同电子设备计算的值可能略有不同,此处只表意),面对如此穷极至小的误差,我拜倒在拉格朗日的膝底.
从此,笔者我质疑的声音消失了.
当然,消去分子整体后,重新确定万有引力函数的常数项系数,上述过程,通过拉格朗日插值多项式余项估算也可以的: