Julia实现数值微分、牛顿cotes公式、复化求积、Romberg算法

可能用到的知识
Julia数值代数
Julia插值算法

数值微积分

数值微分

导数的定义为

f ′ ( a ) = lim ⁡ h → 0 f ( a + h ) − f ( a ) h f'(a)=\lim\limits_{h\to0}\frac{f(a+h)-f(a)}{h} f(a)=h0limhf(a+h)f(a)

则对于某精度条件下,选择适当的 h h h值,即可将导数表示为

f ′ ( a ) ≈ f ( a + h ) − f ( a ) h ≈ f ( a ) − f ( a − h ) h ≈ f ( a + h ) − f ( a − h ) 2 h \begin{aligned} f'(a)\approx&\frac{f(a+h)-f(a)}{h}\\ \approx&\frac{f(a)-f(a-h)}{h}\\ \approx&\frac{f(a+h)-f(a-h)}{2h}\\ \end{aligned} f(a)hf(a+h)f(a)hf(a)f(ah)2hf(a+h)f(ah)

如果我们得到的是一组一一对应的 x , y x,y x,y值,那么可以对这组数据进行多项式插值,然后再求解多项式的导数。Lagrange插值函数的表达式为

L n ( x ) = ∑ k = 0 n y k ω n + 1 ( x ) ( x − x k ) ω n + 1 ′ ( x k ) L_n(x)=\displaystyle\sum_{k=0}^ny_k\frac{\omega_{n+1}(x)}{(x-x_k)\omega'_{n+1}(x_k)} Ln(x)=k=0nyk(xxk)ωn+1(xk)ωn+1(x)

其中,

ω n + 1 ( x ) = ( x − x 0 ) ( x − x 1 ) … ( x − x n ) \omega_{n+1}(x)=(x-x_0)(x-x_1)\ldots(x-x_n) ωn+1(x)=(xx0)(xx1)(xxn)

其两点形式为

L 2 ( x ) = y 1 x − x 2 x 1 − x 2 + y 2 x − x 1 x 2 − x 1 L_2(x)=y_1\frac{x-x_2}{x_1-x_2}+y_2\frac{x-x_1}{x_2-x_1} L2(x)=y1x1x2xx2+y2x2x1xx1

由此可得两点插值的导数为

L 2 ′ ( x ) = y 1 x 1 − x 2 + y 2 x 2 − x 1 = y 2 − y 1 h L_2'(x)=\frac{y_1}{x_1-x_2}+\frac{y_2}{x_2-x_1}=\frac{y_2-y_1}{h} L2(x)=x1x2y1+x2x1y2=hy2y1

显然这是个常量,相当于根本没插值,直接用的前向或者后向差分。

Lagrange插值的三点表达式为

L 3 ( x ) = ( x − x 1 ) ( x − x 2 ) ( x 0 − x 1 ) ( x 0 − x 2 ) f ( x 0 ) + ( x − x 0 ) ( x − x 2 ) ( x 1 − x 0 ) ( x 1 − x 2 ) f ( x 1 ) + ( x − x 0 ) ( x − x 1 ) ( x 2 − x 1 ) ( x 2 − x 0 ) f ( x 2 ) L_3(x)=\frac{(x-x_1)(x-x_2)}{(x_0-x_1)(x_0-x_2)}f(x_0)+\frac{(x-x_0)(x-x_2)}{(x_1-x_0)(x_1-x_2)}f(x_1)+\frac{(x-x_0)(x-x_1)}{(x_2-x_1)(x_2-x_0)}f(x_2) L3(x)=(x0x1)(x0x2)(xx1)(xx2)f(x0)+(x1x0)(x1x2)(xx0)(xx2)f(x1)+(x2x1)(x2x0)(xx0)(xx1)f(x2)

其导数为

L 3 ′ ( x ) = 2 x − x 1 − x 2 ( x 0 − x 1 ) ( x 0 − x 2 ) f ( x 0 ) + 2 x − x 0 − x 2 ( x 1 − x 0 ) ( x 1 − x 2 ) f ( x 1 ) + 2 x − x 0 − x 1 ( x 2 − x 1 ) ( x 2 − x 0 ) f ( x 2 ) L_3'(x)=\frac{2x-x_1-x_2}{(x_0-x_1)(x_0-x_2)}f(x_0)+\frac{2x-x_0-x_2}{(x_1-x_0)(x_1-x_2)}f(x_1)+\frac{2x-x_0-x_1}{(x_2-x_1)(x_2-x_0)}f(x_2) L3(x)=(x0x1)(x0x2)2xx1x2f(x0)+(x1x0)(x1x2)2xx0x2f(x1)+(x2x1)(x2x0)2xx0x1f(x2)

由此可得 x 0 , x 1 , x 2 x_0,x_1,x_2 x0,x1,x2点的导数分别为

{ L 3 ′ ( x 0 ) = 1 2 h [ − 3 f ( x 0 ) + 4 f ( x 1 ) − f ( x 2 ) ] L 3 ′ ( x 0 ) = 1 2 h [ − f ( x 0 ) + f ( x 2 ) ] L 3 ′ ( x 0 ) = 1 2 h [ − f ( x 0 ) + 4 f ( x 1 ) + 3 f ( x 2 ) ] \left\{\begin{aligned} L_3'(x_0)=&\frac{1}{2h}[-3f(x_0)+4f(x_1)-f(x_2)]\\ L_3'(x_0)=&\frac{1}{2h}[-f(x_0)+f(x_2)]\\ L_3'(x_0)=&\frac{1}{2h}[-f(x_0)+4f(x_1)+3f(x_2)]\\ \end{aligned}\right. L3(x0)=L3(x0)=L3(x0)=2h1[3f(x0)+4f(x1)f(x2)]2h1[f(x0)+f(x2)]2h1[f(x0)+4f(x1)+3f(x2)]

此即三点插值下的数值求导公式,通过同样的构造方式可以获取更多点数插值下的求导公式,在此便不再详述了。

Newton-Cotes公式

定积分的定义式具有十分鲜明的数值特征

∫ a b f ( x ) d x = lim ⁡ n → ∞ ∑ k = 1 n f ( a + k n ( b − a ) ) b − a n \int_{a}^{b}f(x)\text{d}x=\lim_{n\to\infty}\displaystyle\sum_{k=1}^{n}f(a+\frac{k}{n}(b-a))\frac{b-a}{n} abf(x)dx=nlimk=1nf(a+nk(ba))nba

n ↛ ∞ n\not\to\infty n时,令 h = b − a n h=\frac{b-a}{n} h=nba,则上式变为

∫ a b f ( x ) d x ≈ ∑ k = 1 n f ( a + k h ) h \int_{a}^{b}f(x)\text{d}x\approx\displaystyle\sum_{k=1}^{n}f(a+kh)h abf(x)dxk=1nf(a+kh)h

可以十分方便地写成

# 无脑的数值积分算法
function nativeInt(f,a,b,n)
    h = (b-a)/n
    integ = 0
    for k = 1:n
        integ += f(a+k*h)*h
    end
    return integ
end

验证

julia> include("integration.jl");
julia> f(x) = 2*x^2 + 1     #定义被积函数
julia> nativeInt(f,0,1,20)  #积分区间为[0,1],积分值为5/3
1.7175000000000002          #当步长为20的时候,误差很大

julia> nativeInt(f,0,1,100) #步长为100时,误差降到了1%
1.6766999999999999

可见其精度不怎么样,让牛顿那些人手算100次也的确有些强人所难了,所以牛顿他们就想办法在提高精度的前提下降低运算量。

考虑到定积分定义式的特点,令 x k = a + k ( b − a ) n x_k=a+k\frac{(b-a)}{n} xk=a+kn(ba),为函数上的某些节点,则积分值可以表示为积分节点函数值 f ( x k ) f(x_k) f(xk)的加权和,其权系数取决于当前点的斜率,定义为求积系数 A k A_k Ak

∫ a b f ( x ) d x ≈ ∑ k = 1 n A k f ( x k ) \int_{a}^{b}f(x)\text{d}x\approx\displaystyle\sum_{k=1}^{n}A_kf(x_k) abf(x)dxk=1nAkf(xk)

如果 f ( x k ) f(x_k) f(xk)为已经确定形式的多项式,由于多项式积分具有精确的表达式,所以可以确定最佳的 A k A_k Ak使得计算过程简化至最小计算量并且有最佳精度。于是我们马上想到了插值算法,如果令 L n ( x ) L_n(x) Ln(x)为插值函数,则可构造原积分的近似积分

I n = ∫ a b L n ( x ) d x I_n=\int_a^bL_n(x)\text{d}x In=abLn(x)dx

其中,

L n ( x ) = ∑ k = 0 n f ( x k ) l k ( x ) L_n(x)=\displaystyle\sum_{k=0}^nf(x_k)l_k(x) Ln(x)=k=0nf(xk)lk(x)

通过插值积分来构造的插值型积分公式

I n = ( b − a ) ∑ k = 0 n C k ( n ) f ( x k ) I_n=(b-a)\displaystyle\sum_{k=0}^{n}C^{(n)}_kf(x_k) In=(ba)k=0nCk(n)f(xk)

被称为 Newton-Cotes公式 \textbf{Newton-Cotes公式} Newton-Cotes公式,其中 C k ( n ) C^{(n)}_k Ck(n) Cotes系数 \textbf{Cotes系数} Cotes系数

将lagrange插值函数代入可得

( b − a ) ∑ k = 0 n C k ( n ) f ( x k ) = ∑ k = 0 n f ( x k ) ∫ a b l k ( x ) d x (b-a)\displaystyle\sum_{k=0}^{n}C^{(n)}_kf(x_k)=\displaystyle\sum_{k=0}^nf(x_k)\int_a^bl_k(x)\text{d}x (ba)k=0nCk(n)f(xk)=k=0nf(xk)ablk(x)dx

( b − a ) C k ( n ) = ∫ a b l k ( x ) d x → C k ( n ) = 1 b − a ∫ a b ω n + 1 ( x ) ( x − x k ) ω n + 1 ′ ( x k ) d x → x = a + h z = ∫ 0 n ω n + 1 ( a + h z ) ( a + h z − x k ) ω n + 1 ′ ( x k ) d x \begin{aligned} &&(b-a)C^{(n)}_k&=\int_a^bl_k(x)\text{d}x\\ \to&&C^{(n)}_k&=\frac{1}{b-a}\int_a^b\frac{\omega_{n+1}(x)}{(x-x_k)\omega'_{n+1}(x_k)}\text{d}x\\ &&\xrightarrow{x=a+hz}&=\int_0^n\frac{\omega_{n+1}(a+hz)}{(a+hz-x_k)\omega'_{n+1}(x_k)}\text{d}x\\ \end{aligned} (ba)Ck(n)Ck(n)x=a+hz =ablk(x)dx=ba1ab(xxk)ωn+1(xk)ωn+1(x)dx=0n(a+hzxk)ωn+1(xk)ωn+1(a+hz)dx

其中 x k x_k xk如前文所定义 x k = a + k ( b − a ) n = a + h k x_k=a+k\frac{(b-a)}{n}=a+hk xk=a+kn(ba)=a+hk

{ ω n + 1 ( x ) = ( x − x 0 ) ( x − x 1 ) … ( x − x n ) ω n + 1 ′ ( x k ) = ( x k − x 0 ) … ( x k − x k − 1 ) ( x k − x k + 1 ) … ( x k − x n ) \left\{\begin{aligned} \omega_{n+1}(x)&=(x-x_0)(x-x_1)\ldots(x-x_n)\\ \omega'_{n+1}(x_k)&=(x_k-x_0)\ldots(x_k-x_{k-1})(x_k-x_{k+1})\ldots(x_k-x_n) \end{aligned}\right. {ωn+1(x)ωn+1(xk)=(xx0)(xx1)(xxn)=(xkx0)(xkxk1)(xkxk+1)(xkxn)

化简可得

C k ( n ) = ( − 1 ) n − k n k ! ( n − k ) ! ∫ 0 n Ω n + 1 ( z ) z − k d z C^{(n)}_k=\frac{(-1)^{n-k}}{nk!(n-k)!}\int_0^n\frac{\Omega_{n+1}(z)}{z-k}\text{d}z Ck(n)=nk!(nk)!(1)nk0nzkΩn+1(z)dz

其中

Ω n + 1 ( z ) = ( z − 0 ) ( z − 1 ) … ( z − n ) \Omega_{n+1}(z)=(z-0)(z-1)\ldots(z-n) Ωn+1(z)=(z0)(z1)(zn)

所以,数值积分被转化成求Cotes系数的问题,若将Cotes系数进行数值求解,那么有三个问题需要解决,一是阶乘,二是 Ω \Omega Ω函数,三是多项式积分公式。

其中,阶乘几乎是我们在学习编程语言过程中,学习递归的第一个案例,几乎没有人没写过,没有什么太大的花样。

# 阶乘
function factorial(n)
    if n <= 1
        return  1
    else
        return n*factorial(n-1)
    end
end

Ω \Omega Ω函数在此前的插值算法中曾经写过,不过在Newton-Cotes公式中,其输入为一个自然数而非一组系数,所以需要做一些改进。对于多项式的表示则无需改变,通过一个数组来表示多项式即可。注意多项式除法的输出为两项,分别是值和余数。

# 多项式乘法,形式为an*x^n,大端小端自定义,输出和输入的格式相同
# 建议a[1]为最低位常数项
function polyMulti(a,b)
    na = length(a); nb = length(b)
    c = zeros(na+nb-1)
    for i = 1:na
        c[i:i+nb-1] += a[i]*b
    end
    return c
end
#将(x-x1)(x-x2)...(x-xn)化成an*x^n形式,A[1]为最低位(常数项)
#输入为[x1,x2,...,xn]输出为[a1,a2,...,an]
function polySimplify(x)
    return length(x)==1 ? [-x[1],1] : polyMulti(
        [-x[1],1],polySimplify(x[2:end]))
end

多项式积分亦只需变动一位,即

∫ a k x k d x = a k k + 1 x k + 1 \int a_kx^k\text{d}x=\frac{a_k}{k+1}x^{k+1} akxkdx=k+1akxk+1

易写为

#多项式积分,输入为([a0,a1,...,an],aStart,aEnd)
#输出为[b0,b1,...,bn+1]
function polyInf(a,as,ae)
    #因为积分后位数升1,所以幂数从1开始
    return sum([(ae^i-as^i)*a[i]/i for i in 1:length(a)])    
end

至此,我们就能够得到Cotes系数

# 获取Cotes系数
function getCotes(n,k=[])
    if k == []
        return [getCotes(n,i) for i in 0:n]
    end
    kerr = polySimplify([i for i in 0:n if i != k])
    cotes = (-1)^(n-k)/n/factorial(k)/factorial(n-k)
    return cotes*polyInf(kerr,0,n)
end

验证

julia> for n = 1:5
       print(getCotes(n))
       print('\n')
       end
[0.5, 0.5]
[0.16666666666666663, 0.6666666666666667, 0.16666666666666663]
[0.125, 0.375, 0.375, 0.125]
[0.0777777777777775, 0.3555555555555566, 0.13333333333333286, 0.3555555555555543, 0.07777777777777779]
[0.06597222222222247, 0.26041666666666285, 0.17361111111110858, 0.17361111111110858, 0.26041666666666285, 0.06597222222222172]

与Cotes系数表比较,可见结果是对的。

|n||
|-|-|-|-
|1| 1 2 1 2 \frac{1}{2}\quad\frac{1}{2} 2121
|2| 1 6 4 6 1 6 \frac{1}{6}\quad\frac{4}{6}\quad\frac{1}{6} 616461
|3| 1 8 3 8 3 8 1 8 \frac{1}{8}\quad\frac{3}{8}\quad\frac{3}{8}\quad\frac{1}{8} 81838381
|4| 7 90 16 45 2 15 16 45 7 90 \frac{7}{90}\quad\frac{16}{45}\quad\frac{2}{15}\quad\frac{16}{45}\quad\frac{7}{90} 90745161524516907
|5| 19 288 25 96 25 144 25 144 25 96 19 288 \frac{19}{288}\quad\frac{25}{96}\quad\frac{25}{144}\quad\frac{25}{144}\quad\frac{25}{96}\quad\frac{19}{288} 2881996251442514425962528819

将Cotes系数代入公式

I n = ( b − a ) ∑ k = 0 n C k ( n ) f ( x k ) I_n=(b-a)\displaystyle\sum_{k=0}^{n}C^{(n)}_kf(x_k) In=(ba)k=0nCk(n)f(xk)

则当 n = 1 n=1 n=1时,可得到梯形公式

I 1 = ( b − a ) b − a 2 [ f ( a ) + f ( b ) ] I_1=(b-a)\frac{b-a}{2}[f(a)+f(b)] I1=(ba)2ba[f(a)+f(b)]

n = 2 n=2 n=2时,得到 Simpson公式 \textbf{Simpson公式} Simpson公式

I 2 = ( b − a ) b − a 6 [ f ( a ) + 4 f ( a + b 2 ) + f ( b ) ] I_2=(b-a)\frac{b-a}{6}[f(a)+4f(\frac{a+b}{2})+f(b)] I2=(ba)6ba[f(a)+4f(2a+b)+f(b)]

n = 4 n=4 n=4时,被特称为 Cotes公式 \textbf{Cotes公式} Cotes公式

I 4 = ( b − a ) b − a 90 [ 7 f ( x 0 ) + 32 f ( x 1 ) + 12 f ( x 2 ) + 32 f ( x 3 ) + 7 f ( x 4 ) ] I_4=(b-a)\frac{b-a}{90} [7f(x_0)+32f(x_1)+12f(x_2)+32f(x_3)+7f(x_4)] I4=(ba)90ba[7f(x0)+32f(x1)+12f(x2)+32f(x3)+7f(x4)]

则对于任意阶的Newton-Cotes公式,可以写为

# Newton-Cotes公式
function NewtonCotes(f,a,b,n)
    cotes = getCotes(n)
    return sum([cotes[i]*f(a+(b-a)/n*(i-1)) for i in 1:n+1])
end

验证

julia> include("integration.jl");
julia> f(x) = x^2+2*x+3;
julia> NewtonCotes(f,0,1,3)
4.333333333333333

复化求积法

Lagrange插值法所存在的问题在于,高阶插值可能会导致局部最优,但其他区域误差很大,基于Lagrange插值的Newton-Cotes公式也有类似的特征,当阶数较大时会导致求解结果不稳定。此外,不同阶数的Newton-Cotes公式针对于不同阶数的多项式拟合精度不同,对于指数函数的无穷阶数,当拟合区间较大时,必然存在精度上的灾难。

julia> f(x) = x^2+2*x+3;
julia> NewtonCotes(f,0,1,30)
9.752372644513475e25
julia> NewtonCotes(exp,0,10,5)-(exp(10)-1) 
-19714.53098399712

解决Lagrange插值中Runge现象的方法是样条插值,其基本思想是对定义域进行分段,在每个小段进行低阶拟合,其总拟合效果要优于对所有区域的高阶拟合。同理,对于Newton-Cotes公式也可以对积分区域进行细分,从而用更低阶的Cotes系数在更狭小的区域中进行求积,这种方法叫做复化求积法。

二阶复化求积法,即复化梯形公式为

T n = ∑ k = 0 n − 1 h 2 [ f ( x k ) + f ( x k + 1 ) ] = h 2 [ f ( a ) + 2 ∑ k = 1 n − 1 f ( x k ) + f ( b ) ] T_n=\displaystyle\sum^{n-1}_{k=0}\frac{h}{2}[f(x_k)+f(x_{k+1})]=\frac{h}{2}[f(a)+2\displaystyle\sum^{n-1}_{k=1}f(x_k)+f(b)] Tn=k=0n12h[f(xk)+f(xk+1)]=2h[f(a)+2k=1n1f(xk)+f(b)]

记子区间 [ x k , x k + 1 ] [x_k,x_{k+1}] [xk,xk+1]的中点为 x k + 1 2 x_{k+\frac{1}{2}} xk+21,则复化Simpson公式为

S n = ∑ k = 0 n − 1 h 6 [ f ( x k ) + 4 f ( x k + 1 2 ) + f ( x k + 1 ) ] = h 6 [ f ( a ) + 4 ∑ k = 0 n − 1 f ( x k + 1 2 ) + 2 ∑ k = 0 n − 1 f ( x k ) + f ( b ) ] \begin{aligned} S_n&=\displaystyle\sum^{n-1}_{k=0}\frac{h}{6}[f(x_k)+4f(x_{k+\frac{1}{2}})+f(x_{k+1})]\\ &=\frac{h}{6}[f(a)+4\displaystyle\sum^{n-1}_{k=0}f(x_{k+\frac{1}{2}})+2\displaystyle\sum^{n-1}_{k=0}f(x_k)+f(b)] \end{aligned} Sn=k=0n16h[f(xk)+4f(xk+21)+f(xk+1)]=6h[f(a)+4k=0n1f(xk+21)+2k=0n1f(xk)+f(b)]

更高阶的Newton-Cotes公式亦然。

复化公式的好处是通过缩减区间长度,可以提高数据精度,因为对于连续函数来说,越短的区间意味着这个区间内的映射关系更趋近于线性。问题在于更细的区段划分意味着更大的运算量,所以实际应用的过程中往往求一步看一步,当不满足精度要求的时候尽量缩短步长,在满足精度之后则进行输出。

对于复化梯形公式来说,对于任意一段积分区间 [ x k , x k + 1 ] [x_k,x_{k+1}] [xk,xk+1],当我们对其进行二分时,其实只是增加了一个中点,则这个子区间上的积分值为

h 4 [ f ( x k ) + 2 f ( x k + 1 2 ) + f ( x k + 1 ) ] \frac{h}{4}[f(x_k)+2f(x_{k+\frac{1}{2}})+f(x_{k+1})] 4h[f(xk)+2f(xk+21)+f(xk+1)]

则整个区间的求和公式变为

T 2 n = h 4 ∑ k = 0 n − 1 [ f ( x k ) + f ( x k + 1 ) ] + h 2 ∑ k = 0 n − 1 f ( x k + 1 2 ) = 1 2 T n + h 2 ∑ k = 0 n − 1 f ( x k + 1 2 ) \begin{aligned} T_{2n}&=\frac{h}{4}\displaystyle\sum^{n-1}_{k=0}[f(x_k)+f(x_{k+1})]+\frac{h}{2}\displaystyle\sum^{n-1}_{k=0}f(x_{k+\frac{1}{2}})\\ &=\frac{1}{2}Tn+\frac{h}{2}\displaystyle\sum^{n-1}_{k=0}f(x_{k+\frac{1}{2}}) \end{aligned} T2n=4hk=0n1[f(xk)+f(xk+1)]+2hk=0n1f(xk+21)=21Tn+2hk=0n1f(xk+21)

其Julia实现为

# 复化梯形公式,f为函数,a,b为积分区间,err为要求精度
function Trapz(f,a,b,err=0.01,nMax=10)
    n = 2
    nMax = 2^nMax
    h = (b-a)/2
    T = h/2*(f(a)+f(b)+2*f(a+h))
    myErr = err + 1                 #初始误差大于目标误差

    while myErr > err && n < nMax
        newX = [2*i-1 for i in 1:n]         #新的x序列
        n = n*2; h = h/2                    #更新n,h值
        new  = sum(f.(a.+h*newX))*h
        myErr = abs(T/2-new)
        T = T/2 + new           #新的梯形值
    end
    return T,myErr
end

测试一下指数函数,结果表明当划分区间为 2 1 0 2^10 210时,其拟合精度小于1,优于Newton-Cotes公式。

julia> include("integration.jl");
julia> Trapz(exp,0,10)
(22025.640837203784, 0.5251238525834196)
julia> exp(10)-1
22025.465794806718

Romberg算法

梯形复化求积虽然缩减了求积区间,从而极大提高了求积精度,但同时牺牲Newton-Cotes公式中的高阶项。最直观的方法当然是对Simpson公式或者Cotes公式进行复化求积,但从算法复用的角度考虑,如果能够基于梯形复化求积法构造出高阶Newton-Cotes公式,那将是即为便利的。

对比Simpson公式和梯形求积公式

T = ( b − a ) b − a 2 [ f ( a ) + f ( b ) ] → T 2 = ( b − a ) b − a 4 [ f ( a ) + f ( b ) + 2 f ( a + b 2 ) ] S = ( b − a ) b − a 6 [ f ( a ) + 4 f ( a + b 2 ) + f ( b ) ] \begin{aligned} T=&(b-a)\frac{b-a}{2}[f(a)+f(b)]\\ \to T_2=&(b-a)\frac{b-a}{4}[f(a)+f(b)+2f(\frac{a+b}{2})]\\ S=&(b-a)\frac{b-a}{6}[f(a)+4f(\frac{a+b}{2})+f(b)]\\ \end{aligned} T=T2=S=(ba)2ba[f(a)+f(b)](ba)4ba[f(a)+f(b)+2f(2a+b)](ba)6ba[f(a)+4f(2a+b)+f(b)]

解得

S n = 4 T 2 − T 3 → S n = 4 T 2 n − T n 3 S_n = \frac{4T_2-T}{3}\to S_n=\frac{4T_{2n}-T_n}{3} Sn=34T2TSn=34T2nTn

同理可得复化Cotes公式和 Romberg公式 \textbf{Romberg公式} Romberg公式

C n = 16 T 2 n − T n 15 R n = 64 C 2 n − 1 n 63 \begin{aligned} C_n =&\frac{16T_{2n}-T_n}{15}\\ R_n =&\frac{64C_{2n}-1_n}{63}\\ \end{aligned} Cn=Rn=1516T2nTn6364C2n1n

所以,我们可以直接对梯形复化公式进行修改

# 复化梯形公式,f为函数,a,b为积分区间,err为要求精度
# flag为1234分别表示梯形、Simpson、Cotes、Romberg算法
function Trapz(f,a,b,flag=1,err=0.01,nMax=10)
    flag = min(flag,nMax)       #
    nMax = 2^nMax

    n = 1;    h = b-a                       #初始化n,h
    T = h/2*(f(a)+f(b))
    Tn = zeros(flag)                        #存储梯形公式的值
    Tn[end] = T
    myErr = err + 1                         #初始误差大于目标误差

    while myErr > err && n < nMax
        newX = [2*i-1 for i in 1:n]         #新的x序列
        n = n*2; h = h/2                    #更新n,h值
        new  = sum(f.(a.+h*newX))*h
        myErr = abs(T/2-new)
        T = T/2 + new                       #新的梯形值
        if flag > 1
            Tn[1:end-1] = Tn[2:end]
        end
        Tn[end] = T                         #更新梯形公式
    end

    if flag > 1
        Sn = (4*Tn[2:end]-Tn[1:end-1])/3
        Tn = [Tn[end],Sn[end]]
    end

    if flag > 2
        Cn = (16*Sn[2:end]-Sn[1:end-1])/15  #Cotes公式
        Tn = vcat(Tn,Cn[end])
    end

    if flag > 3
        Rn = (64*Cn[2:end]-Cn[1:end-1])/63  #Romberg公式
        Tn = vcat(Tn,Rn[end])
    end

    return Tn,myErr
end

验证

julia> include("integration.jl");
julia> Trapz(exp,0,10,4)[1]
4-element Array{Float64,1}:
 22025.640837203784
 22025.46579591959
 22025.465794806754
 22025.46579480671
julia> exp(10)-1
22025.465794806718

可见在相同积分步长的前提下,Romberg公式的精度优于Cotes优于Simpson优于梯形公式。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
本书由美国洛斯阿拉莫斯国家实验室主WilliamH.Press和其他三位从事科学计算的学者合著。本书及其姊妹篇已被美国哈佛大学、英国剑桥大学等国际著名大学选为本科生和研究生数值计算课程的教材。选材内容丰富,包含了当代科学计算过程中涉及的大量内定,如线性方程组求解,特殊函数值数值计算,多项式和有理函数的内插,随机娄的产生。傅里叶变换和快速傅里叶变换。等 ,科学性和实用性统一。不仅对每种算法进行了数学分析和比较,而且根据作者经验对算法给出了评论和建议,并在此基础上提供了300多个用C语言编写的实用程序。其内容包括:线性方程组的求解,逆矩阵和行列式计算,多项式和有理函数的内插与外推,函数的积分和估值,特殊函数的数值计算,随机数的产生,非线性方程求解,傅里叶变换和FFT,谱分析和小波变换,统计描述和数据建模,常微分方程和偏微分方程求解,线性预测和线性预测编码,数字滤波,格雷码和算术码等。全书内容丰富,层次分明,是一本不可多得的有关数值计算的C语言程序大全。本书每章中都论述了有关专题的数学分析、算法的讨论与比较,以及算法实施的技巧,并给出了标准C语言实用程序。这些程序可在不同计算机的C语言编程环境下运行。 本书可作为从事科学计算的科技工作者的工具书,计算机软件开发者的参考书,也可以作为大学本科生和研究生的参考书或教材。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微小冷

请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值