一、前言
本文主要讨论定积分的数值解,即数值积分。为什么需要数值积分:假设在区间上可积,为其原函数;现实中的多数情况是:
- 原函数无法用初等函数表示,即找不到原函数;
- 原函数可以找到,但是求解过程太复杂太困难了。
上面两种情况其实非常常见,因此我们就需要用数值积分!找原定积分的"近似解";只要精度够高,近似解完全可以替代真实解。
数值积分的方法可以分为两大类:拉格朗日型(单步法)、高斯型(多步法)。
- 拉格朗日型:梯形求积、辛普森求积、牛顿-科茨求积、复化求积、龙贝格求积;
- 高斯型:高斯-勒让德求积、高斯-拉盖尔求积、高斯-埃尔米特求积。
所有数值积分的核心思想:用一个积分易求的多项式去近似替代被积函数,然后对这个多项式再求定积分。 这种思想是基于插值理论。
本文所有提到的方法,完整matlab程序出处在这里。
二、拉格朗日型数值积分
拉格朗日型数值积分,是采用各阶的拉格朗日插值多项式来近似被积函数的。所以,这种方法要想提高精度,只能增多插值节点(或者将积分区间划分的份数增多)。和插值理论一样,插值节点可以是随意的!一般我们为了方便编程才选择插值节点是各个等分点。
拉格朗日型数值积分可以再细分为两大类:1. 基础方法;2. 进阶方法。基础方法:有多少插值节点,就用几阶多项式去插值近似;进阶方法:分段线性插值,即不管插值节点再多,相邻2点间用线性拉格朗日插值代替!总体用分段函数近似即可。
- 基础方法:梯形求积公式、辛普森求积公式、牛顿-科茨求积公式。
- 进阶方法:复化梯形公式、复化辛普森公式、复化梯形公式加密、龙贝格公式。
2.1 基础方法
(1)梯形求积公式:积分区间,2个插值节点为(头和尾):
用2点一阶线性拉格朗日插值多项式代替被积函数并带入原式可得:
(2) 辛普森求积公式:将积分区间二等分,取;3个插值节点为:
用3点二次拉格朗日插值多项式代替被积函数并带入原式可得:
(3) 牛顿-科茨求积公式:将积分区间n等分,取;共有n+1个插值节点。注意一点:第一个节点是,是区间左端点;最后一个节点是,是区间的右端点。
公式说明:i是节点的下标,范围是,上式连乘中i的变化是从0增加到n!一共n次循环,每次i都不能等于一个值!上式中求其实是一个很长的公式!用语言不好形容,直接看下面的程序:
% n点牛顿-科茨求积公式
% 思路: 先将[a,b]区间n等分, 然后n+1个点用n+1点(n次)拉格朗日插值
% 缺点: 高阶拉格朗日插值对于复杂弯曲函数龙贝格现象严重!不要插过6阶!
% 以f(x) = 4/(1+x^2)函数为例
clear; clc;
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
n = double(input('将积分区域几等分(几阶拉格朗日插值多项式):'));
% 算当前n等分牛顿-科茨系数Cn
Cn = zeros(1,n+1);
syms t;
% s=sym(1)符号变量类型的1, 主要是为了一开始的循环相乘
for i = 0:n
s = sym(1);
for num = 0:n
if num ~= i
s = s*(t-num);
end
end
Cn(i+1) = (-1)^(n-i)/( n*factorial(i)*factorial(n-i) )*int(s,t,0,n)
end
syms x;
f = 4/(1+x^2); % 每次修改这里的函数
R = int(f,x,low,up); % 真实结果
x = low:(up-low)/n:up; % n等分后每个点的x值
fx = double(subs(f)); % n等分后每个x点对应的函数值
% 近似结果为:
NC = (up-low)*sum(Cn.*fx);
fprintf('牛顿-科茨求积公式近似结果为: %.7f\n',NC);
fprintf('真实结果为: %f\n',R);
2.2 进阶方法
(1)复化梯形公式:区间划分和牛顿-科茨一样,只是复化梯形采用的是分段线性插值,即相邻两插值节点用线性函数代替,总体用分段函数近似即可。插值节点为:
n是区间等分数,h为每一段长。近似计算公式为:
复化梯形公式是精度优秀方法中最简单、最万能、最推荐使用的方法。
% n点复化求积公式——万能!
% 思路: 先将[a,b]区间n等分, 然后每两点间用线性拉格朗日插值——分段线性插值而已。
% 优势: 避免高阶牛顿-科茨(高阶)出现龙贝格现象, 因此等分越多越好!
% 以f(x) = 4/(1+x^2)函数为例
clear; clc;
syms x;
f = 4/(1+x^2); % 每次这里修改不同函数即可
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
n = double(input('将积分区间几等分:'));
% 真实定积分的结果:
R = int(f,x,low,up);
xnum = low:(up-low)/n:up;
fmiddle = zeros(1,n-1); % 对中间那些结果的记录
for num = 1:n-1
x = low + num*(up-low)/n;
fmiddle(num) = double(subs(f));
end
x = up;
fup = double(subs(f));
x = low;
flow = double(subs(f));
% 复化梯形公式近似结果:
FT = (up-low)/(2*n)*( flow + 2*sum(fmiddle) + fup );
fprintf('复化梯形求积公式近似结果为: %.7f\n',FT);
fprintf('真实结果为: %f\n',R);
(2)复化辛普森公式:用两种理解方式:
- 将区间分成偶数份,非重叠的相邻3点间用一个二次拉格朗日插值代替;
- 将区间分成n等分,再在每一份中插入一个中心节点;这样每一份其实里面就有3个点。
无论用哪种方式,都是分段2次多项式插值而已。本文推荐采用第一种方式:将区间[a,b]等分为偶数份n = 2m,每个小区间长度h = (b-a)/(2m),插值节点为:
每3个非重叠相邻点用二次拉格朗日插值代替即可的最后公式为:
公式若不太清楚关系可直接看程序:
% n点复化辛普森公式——万能!比复化梯形更准!
% 思路: 先将[a,b]区间n等分, 然后每两点间再插入一个中心节点!每一份3个点做3点(2阶)拉格朗日插值
% 优势: 避免高阶牛顿-科茨(高阶)出现龙贝格现象, 因此等分越多越好!
% 以f(x) = 4/(1+x^2)函数为例
clear; clc;
syms x;
f = 4/(1+x^2); % 每次这里修改不同函数即可
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
n = double(input('将积分区间n等分(偶数):'));
m = n/2;
% 真实定积分的结果:
R = int(f,x,low,up);
fmiddle1 = zeros(1,m); % 对大括号中第一项的记录
fmiddle2 = zeros(1,m-1); % 对大括号中第二项的记录
for num1 = 1:m
x = low + (2*num1-1)*(up-low)/(2*m);
fmiddle1(num1) = double(subs(f));
end
for num2 = 1:m-1
x = low + (2*num2)*(up-low)/(2*m);
fmiddle2(num2) = double(subs(f));
end
x = up;
fup = double(subs(f));
x = low;
flow = double(subs(f));
% 复化梯形公式近似结果:
FS = (up-low)/(6*m)*( flow + 4*sum(fmiddle1) + 2*sum(fmiddle2) + fup );
fprintf('复化梯形求积公式近似结果为: %.7f\n',FS);
fprintf('真实结果为: %f\n',R);
(3)复化梯形公式加密:名字看上去很复杂,其实就是一个快速计算复化梯形公式当前分区数加倍后的新复化梯形公式结果的公式。就是一个计算规律而已。具体含义如下:
当区间n等分时,复化梯形结果为:
现将每个小区间对分,即将原区间划分成为2n份,新分区下每个区间长度为:h = (b-a)/(2n);此时新的结果有一个快速计算的方法(注意:式中红色n是原始的等分数,常数不会变;h每次都变):
现将新的每个小区间再对分,即将原区间划分成为4n份,新分区下每个区间长度为:h = (b-a)/(4n);此时新的结果为:
通式为:
总结一句:复化梯形加密,其实就是基于现有分区情况下的复化梯形结果,快速推算每个小区间再对分的新结果;新结果相当于再原基础上又增加了2倍的插值节点。就是一个快推的规律。
% 复化梯形公式加密
% 作用: 快速计算"等分"后再在"每个小份不断对分"的加速算法! 就是为了加速计算的
% 方式: 知道1等分后, 用加密方法可以快速计算任意2^k等分的结果
% 以f(x) = 4/(1+x^2)函数为例
clear; clc
syms x;
f = 4/(1+x^2); % 每次这里修改不同函数即可
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
k = double(input('输入目标对分/加密次数:'));
% 真实定积分的结果:
R = int(f,x,low,up);
% 先计算1等分的结果, 后面加密高等分都是基于此推出的!
x = up;
fup = double(subs(f));
x = low;
flow = double(subs(f));
Torg = (up - low)/2*(fup+flow); % 0等分(梯形公式结果)
% 复化梯形加密:
T = zeros(1,k+1); % 记录每一次加密的结果
T(1) = Torg; % 把1等分的放到第一个, 方便后面的循环
h = up - low; % 区间原长
tmp = 1; % 记录T索引用的
for numk = 0:k
fmiddle = zeros(1,2^(numk));
% 小循环里所有和k有关都是变化的!都和当前numk有关:
for numi = 1:2^(numk)
x = low + (2*numi-1)*h/2^(numk+1);
fmiddle(numi) = double(subs(f));
end
T(tmp+1) = 0.5*T(tmp) + h/2^(numk+1)*sum(fmiddle);
tmp = tmp + 1;
if tmp == k+1 % 已经满了,可以结束了
break;
end
end
% 加密过程中的一系列结果都在T中, 打印只显示最后精度最高的那个T(k+1):
fprintf('复化梯形加密为%d等分后,当前近似结果为: %.7f\n',2^k,T(k+1));
fprintf('真实结果为: %.10f\n',R);
(4)龙贝格公式:就是在复化梯形加密之后,再做一个加速操作并进一步提高精度!加速的内涵其实是改变多项式相加时的各个系数。一般设表示k次加密后复化梯形公式的值(T的下标是0即表示没有加速);以表示在k加密的基础上又做了m次加速后的结果。加密与加速的递推公式为:
其中加密次数k和加速次数m的数值范围为:
过程举例:先依次做0,1,2,3,4次加密(都做!),然后在这4个加密结果基础上,依次做1,2,3,4次加速;最后得到的在4次加密基础上的4次加速,精度是最高的。先做完所有的加密操作,再做所有的加速操作,两个过程是先后独立进行的。
% 龙贝格公式——在加密复化梯形公式的基础上再循环加速(提高精度)!
% 作用: 在加密复化梯形公式基础上, 再提高精度
% 方式: 先全部加密完后, 再依次进行加速——两个过程是"独立"进行的!
% 以f(x) = 4/(1+x^2)函数为例
clear; clc
format long;
syms x;
f = 4/(1+x^2); % 每次这里修改不同函数即可
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
k = double(input('输入目标对分/加密次数:'));
% 真实定积分的结果:
R = int(f,x,low,up);
% 先计算1等分的结果, 后面加密高等分都是基于此推出的!
x = up;
fup = double(subs(f));
x = low;
flow = double(subs(f));
Torg = (up - low)/2*(fup+flow); % 0等分(梯形公式结果)
% 复化梯形加密:
T = zeros(1,k+1); % 记录每一次加密的结果
T(1) = Torg; % 把1等分的放到第一个, 方便后面的循环
h = up - low; % 区间原长
tmp = 1; % 记录T索引用的
for numk = 0:k
fmiddle = zeros(1,2^(numk));
% 小循环里所有和k有关都是变化的!都和当前numk有关:
for numi = 1:2^(numk)
x = low + (2*numi-1)*h/2^(numk+1);
fmiddle(numi) = double(subs(f));
end
T(tmp+1) = 0.5*T(tmp) + h/2^(numk+1)*sum(fmiddle);
tmp = tmp + 1;
if tmp == k+1 % 已经满了,可以结束了
break;
end
end
% 龙贝格/理查森加速: 用表格/矩阵表示更好编程
Rbg = zeros(k+1,k+1);
Rbg(:,1) = T; % 第一列初始化(为加速前,即所有的加密点结果)
m = 1;
for col = 2:k+1
for row = 1:k+1-m
Rbg(row,col) = ( 4^m * Rbg(row+1,col-1) - Rbg(row,col-1) )/( 4^m-1 );
end
m = m + 1;
end
% 加密过程中的一系列结果都在T中, 打印只显示最后精度最高的那个T(k+1):
fprintf('复化梯形加密为%d等分后,当前近似结果为: %.10f\n',2^k,T(k+1));
fprintf('基于当前加密后的龙贝格加速结果为:%.10f\n',Rbg(1,k+1));
fprintf('真实结果为: %.10f\n',R);
三、高斯型积分公式
前文我们提到:拉格朗日型求积公式要想提高精度,只能不断增加插值节点!它存在的一个问题是:选择的插值点"效率不高",即都是一些没有实际内涵和意义的点(比如均分点、等比分点等)。可以想象到:如果插值节点都是"精心挑选"的,很能代表原函数特征和变化趋势的有意义的点,那对这些点再做n阶拉格朗日插值精度肯定会再次提高!高斯型积分公式,就是为了精心挑选插值节点。
- 拉格朗日型:大量增加效率不高插值节点;然后非重叠2点间做线性拉格朗日插值;
- 高斯型:插值节点数目一定时,通过精心调整节点的位置,提高每个节点的效率;然后做n+1个节点的n阶拉格朗日插值。
高斯型缺点:用的还是高阶拉格朗日插值!!!本文还是采用这种方式。
高斯型改进:在选择了好的插值点后,再使用分段线性插值。
高斯型求积公式要用到正交多项式相关知识。简言之就是求n阶正交多项式的零点(插值节点)和系数(求积系数);节点和系数的计算公式如下:
节点求解:另n阶正交多项式为0,求这个方程的零点(这就是"精心挑选"的过程)。几阶正交多项式就会用几个零点。
系数求解:是选择的正交多项式序列所对应的权函数;是n阶拉格朗日插值多项式(插值节点就是上面求得的)。
有了插值节点和对应的系数后,原定积分式可以进行如下近似计算:
注意:左边待积函数中必须带有所选的正交多项式对应的权函数!!个人觉得这样虽然限制了高斯型积分的普遍性,但是特殊问题可以选用特殊的正交多项式序列和对应权函数来解决,其实影响不大。注意到:如果权函数的话,那么相当于任何原待积函数都有这个权函数,也就是说该权函数所对应的那种正交多项式序列最具有通用性!这就是我最推荐使用的:高斯-勒让德求积公式。高斯-勒让德多项式在这里查看。
高斯-勒让德节点、系数、求积一套流程的Matlab程序如下:
% 任意个数插值节点的高斯-勒让德求积公式:
% 思路: 其内涵还是n阶拉格朗日插值! 只不过原先的拉格朗日插值点都是等间距均匀分布的那种,
% 高斯-勒让德就是点数一定时, 挑选那些能让精度提高的点(选最好的点)!而不再使用无特殊意义的等间距分隔点。
% 补充: 高斯-勒让德"不能"与前面的方法混用(比如选最好点后再龙贝格)!
% 因为前面所有的方法要求"必须是等间距"的点才能写出"那样的公式"!
% 优点: 小区间/积分限上,限制了只能有几个控制点时, 用高斯积分效果不错!
% 缺点: 大区间上, 高斯-勒让德的求积节点必须得增多! 因为内涵还是n阶拉格朗日插值!——这个缺点非常大!
% 建议: 没有控制点限制时, 我还是推荐用龙贝格! 因为它是万能适用的高精度!
clear; clc;
format long;
syms x;
n = double(input('输入使用几点(n)的高斯-勒让德插值:'));
% n点插值的高斯-勒让德多项式P:
f = x^2 -1;
fprintf('%d点高斯-勒让德多项式为:\n',n)
P = vpa(1/(2^n*factorial(n)) * diff(f^n,x,n))
% n点高斯-勒让德插值节点Xi:
Xi = sort(double(solve(P)))';
% n点高斯-勒让德插值节点对应的插值系数Ai:
xnum = Xi;
% l用来记录拉格朗日多项式的: n点拉格朗日插值多项式有n个基函数!
% 高斯-勒让德求积系数就是对"每个多项式/基函数"求其[-1 1]定积分——
% 插值点数、多项式/基函数个数、求积系数个数一样,相互对应。
l = sym(zeros(1,n));
Ai = zeros(1,n);
for m = 1:n
l(m) = prod(x - xnum([1:m-1 m+1:n]))/prod(xnum(m) - xnum([1:m-1 m+1:n]));
Ai(m) = double(int(l(m),x,-1,1));
end
fprintf('%d点高斯-勒让德插值节点为:\n',n);
Xi
fprintf('%d点高斯-勒让德插值节点对应的系数为:\n',n);
Ai
% 对具体函数的积分近似:
syms t;
up = double(input('输入积分上限:'));
low = double(input('输入积分下限:'));
% 积分限x必须是[-1 1], 因此这里统一做一下转换, 即任何限都可以:
t = (up-low)/2*x + (up+low)/2;
y = 4/(1+t^2); % 每次修改这里的函数即可, 注意这里自变量是t (修改√√√)
Result = 0; % 记录最终近似结果
for num = 1:n
x = Xi(num);
y_tmp = (up-low)/2*double(subs(y));
Result = Result + Ai(num)*y_tmp;
end
syms w;
y_r = 4/(1+w^2); % 实际的结果, 注意这里自变量是w (修改√√√)
R = int(y_r,w,low,up);
fprintf('%d点高斯-勒让德积分近似结果为:%.10f\n', n, Result);
fprintf('真实结果为:%.10f\n', R);
需要注意一点的是:高斯-勒让德的积分限必须是!如果你给的积分限不是这个,程序中会自动帮你转化到这个范围当中。
3.1 其他高斯型求积公式
其他常用的还有:1. 高斯-拉盖尔求积;2. 高斯-埃尔米特求积。多项式参考这里。
(1)高斯-拉盖尔求积:积分区间为,权函数为:
使用时:原被函数函数中必须有一项相乘的才能使用这种。
% 任意个数插值节点的高斯-拉盖尔求积公式:
% 说明等信息同高斯-勒让德
% 用处: 原始待积函数形式中必须有一个exp(-x)项! 即f(x) = exp(-x)*g(x)
% 注意: 近似计算时用的是g(x)! 即Ai*g(xi)! 前面那个exp(-x)在近似计算中是不用的!
clear; clc;
format long;
syms x;
n = double(input('输入使用几点(n)的高斯-拉盖尔插值:'));
% n点插值的高斯-拉盖尔多项式P:
f = exp(-x)*x^n;
fprintf('%d点高斯-拉盖尔多项式为:\n',n)
L = vpa( exp(x) * diff(f,x,n) )
% n点高斯-拉盖尔插值节点Xi:
Xi = sort(double(solve(L)))';
% n点高斯-拉盖尔插值节点对应的插值系数Ai:
xnum = Xi;
% l用来记录拉格朗日多项式的: n点拉格朗日插值多项式有n个基函数!
% 高斯-拉盖尔求积系数就是对"每个多项式/基函数"求其[0 +inf]定积分——
% 插值点数、多项式/基函数个数、求积系数个数一样,相互对应。
l = sym(zeros(1,n));
Ai = zeros(1,n);
for m = 1:n
l(m) = prod(x - xnum([1:m-1 m+1:n]))/prod(xnum(m) - xnum([1:m-1 m+1:n]));
l_tmp = exp(-x)*l(m);
Ai(m) = double(int(l_tmp,x,0,+inf));
end
fprintf('%d点高斯-拉盖尔插值节点为:\n',n);
Xi
fprintf('%d点高斯-拉盖尔插值节点对应的系数为:\n',n);
Ai
% 对具体函数的积分近似: 高斯-拉盖尔积分限很简单,必须是[0,+inf]
syms t;
gt = sin(t); % 每次修改这里的函数即可, 注意exp(-t)这一项必须有
y = exp(-t)*gt;
up = +inf;
low = 0;
R = int(y,t,low,up); % 实际结果
Result = 0; % 记录最终近似结果
for num = 1:n
t = Xi(num);
y_tmp = double(subs(gt));
Result = Result + Ai(num)*y_tmp;
end
fprintf('%d点高斯-拉盖尔积分近似结果为:%.10f\n', n, Result);
fprintf('真实结果为:%.10f\n', R);
(2)高斯-埃尔米特求积:积分区间为,权函数为:
使用时:原被函数函数中必须有一项相乘的才能使用这种。
% 任意个数插值节点的高斯-埃尔米特求积公式:
% 说明等信息同高斯-勒让德
% 用处: 原始待积函数形式中必须有一个exp(-x^2)项! 即f(x) = exp(-x^2)*g(x)
% 注意1: 近似计算时用的是g(x)! 即Ai*g(xi)! 前面那个exp(-x^2)在近似计算中是不用的!
% 注意2: 埃尔米特特殊! 当插值节点大于4个后, 只能用"奇数个数"的插值点!!!!!!!!
% 因为"解插值节点的方程"的解此时个数 < n 也就不能再往下计算了。
clear; clc;
format long;
syms x;
n = double(input('输入使用几点(n)的高斯-埃尔米特插值:'));
% n点插值的高斯-埃尔米特多项式P:
f = exp(-x^2);
fprintf('%d点高斯-埃尔米特多项式为:\n',n)
H = vpa( (-1)^(n-1) * exp(x^2) * diff(f,x,n) )
% n点高斯-埃尔米特插值节点Xi:
Xi = sort(double(solve(H)))'
% n点高斯-埃尔米特插值节点对应的插值系数Ai:
xnum = Xi;
% l用来记录拉格朗日多项式的: n点拉格朗日插值多项式有n个基函数!
% 高斯-埃尔米特求积系数就是对"每个多项式/基函数"求其[-inf +inf]定积分——
% 插值点数、多项式/基函数个数、求积系数个数一样,相互对应。
l = sym(zeros(1,n));
Ai = zeros(1,n);
for m = 1:n
l(m) = prod(x - xnum([1:m-1 m+1:n]))/prod(xnum(m) - xnum([1:m-1 m+1:n]));
l_tmp = exp(-x^2)*l(m);
Ai(m) = double(int(l_tmp,x,-inf,+inf));
end
fprintf('%d点高斯-埃尔米特插值节点为:\n',n);
Xi
fprintf('%d点高斯-埃尔米特插值节点对应的系数为:\n',n);
Ai
% 对具体函数的积分近似: 高斯-拉盖尔积分限很简单,必须是[-inf,+inf]
syms t;
gt = sin(t)^2; % 每次修改这里的函数即可, 注意exp(-t)这一项必须有
y = exp(-t^2)*gt;
up = +inf;
low = -inf;
R = int(y,t,low,up); % 实际结果
Result = 0; % 记录最终近似结果
for num = 1:n
t = Xi(num);
y_tmp = double(subs(gt));
Result = Result + Ai(num)*y_tmp;
end
fprintf('%d点高斯-埃尔米特积分近似结果为:%.10f\n', n, Result);
fprintf('真实结果为:%.10f\n', R);
注意:高斯-埃尔米特的使用,当插值节点大于4个后,只能用奇数个插值节点。