Romberg求积法是一种数值分析中的求积算法, 它利用了外推的思想方法, 以提高代数精度. 这种方法通过不断缩小积分区间, 将区间划分为更小的子区间, 并计算每个子区间的积分值, 从而逼近更为准确的求积值. 这个过程是通过计算一系列的近似值, 并逐步改进这些近似值, 以得到更为精确的结果. 在实现过程中, 通常采用逐次分半加速法, 将积分区间逐次分割为更小的子区间. 前一次分割得到的函数值在分半之后仍可被利用, 且易于编程实现.
算法说明
首先我们有复化梯形公式的余项:
I
−
T
(
h
)
=
∑
k
=
1
m
B
2
k
(
2
k
)
!
[
f
(
2
k
−
1
)
(
a
)
−
f
(
2
k
−
1
)
(
b
)
]
h
2
k
+
r
m
+
1
I-T(h)=\sum_{k=1}^m\frac{B_{2k}}{(2k)!}[f^{(2k-1)}(a)-f^{(2k-1)}(b)]h^{2k}+r_{m+1}
I−T(h)=k=1∑m(2k)!B2k[f(2k−1)(a)−f(2k−1)(b)]h2k+rm+1
其中,
B
2
k
B_{2k}
B2k为Bernoulli常数,
r
m
+
1
:
=
−
B
2
m
+
2
(
2
m
+
2
)
!
(
b
−
a
)
f
(
2
m
+
2
)
(
η
)
h
2
m
+
2
,
η
∈
(
a
,
b
)
r_{m+1}:=-\frac{B_{2m+2}}{\left(2m+2\right)!}(b-a)f^{\left(2m+2\right)}\left(\eta\right)h^{2m+2},\quad\eta\in(a,b)
rm+1:=−(2m+2)!B2m+2(b−a)f(2m+2)(η)h2m+2,η∈(a,b)
此外, 我们还有Richardson外推法如下:
假设
F
(
h
)
F(h)
F(h)逼近
F
(
0
)
F(0)
F(0)的余项为
F
(
h
)
−
F
(
0
)
=
a
1
h
p
1
+
a
2
h
p
2
+
⋯
F(h)-F(0)=a_1h^{p_1}+a_2h^{p_2}+\cdots
F(h)−F(0)=a1hp1+a2hp2+⋯
其中
p
1
<
p
2
<
⋯
p_1<p_2<\cdots
p1<p2<⋯,
a
k
a_k
ak是与
h
h
h无关的非零常数, 则由
{
F
0
(
h
)
=
F
(
h
)
F
k
+
1
(
h
)
=
q
p
k
F
k
(
h
/
q
)
−
F
k
(
h
)
q
p
k
−
1
,
k
=
0
,
1
,
2
,
⋯
\begin{cases} F_0(h)=F(h)\\ F_{k+1}(h)=\dfrac{q^{p_k}F_k(h/q)-F_k(h)}{q^{p_k}-1},k=0,1,2,\cdots \end{cases}
⎩⎨⎧F0(h)=F(h)Fk+1(h)=qpk−1qpkFk(h/q)−Fk(h),k=0,1,2,⋯
定义的序列
{
F
k
(
h
)
}
\{F_k(h)\}
{Fk(h)}有
F
n
(
h
)
−
F
(
0
)
=
a
n
+
1
(
n
)
h
p
n
+
1
+
a
n
+
2
(
n
)
h
p
n
+
2
+
a
n
+
3
(
n
)
h
p
n
+
3
+
⋯
F_n(h)-F(0)=a_{n+1}^{(n)}h^{p_{n+1}}+a_{n+2}^{(n)}h^{p_{n+2}}+a_{n+3}^{(n)}h^{p_{n+3}}+\cdots
Fn(h)−F(0)=an+1(n)hpn+1+an+2(n)hpn+2+an+3(n)hpn+3+⋯
其中,
a
n
+
k
(
n
)
(
k
=
1
,
2
,
⋯
)
a_{n+k}^{(n)}(k=1,2,\cdots)
an+k(n)(k=1,2,⋯)与
h
h
h无关,
q
>
1
q>1
q>1.
利用复化梯形余项公式和Richardson外推法可得Romberg求积法:
{
T
1
(
0
)
=
b
−
a
2
[
f
(
a
)
+
f
(
b
)
]
T
1
(
i
)
=
1
2
T
1
(
i
−
1
)
+
b
−
a
2
i
∑
j
=
1
2
i
−
1
f
[
a
+
2
j
−
1
2
i
(
b
−
a
)
]
,
i
=
1
,
2
,
⋯
T
m
+
1
(
k
−
1
)
=
4
m
T
m
(
k
)
−
T
m
(
k
−
1
)
4
m
−
1
,
k
=
1
,
2
,
⋯
,
i
,
m
=
1
,
2
,
⋯
\begin{cases} T_{1}^{(0)}=\dfrac{b-a}{2}\big[f(a)+f(b)\big]\\ \displaystyle T_{1}^{(i)}=\frac{1}{2}T_{1}^{(i-1)}+\frac{b-a}{2^{i}}\sum_{j=1}^{2^{i-1}}f\left[a+\frac{2j-1}{2^{i}}(b-a)\right],i=1,2,\cdots\\ \displaystyle T_{m+1}^{(k-1)}=\frac{4^{m}T_{m}^{(k)}-T_{m}^{(k-1)}}{4^{m}-1},k=1,2,\cdots,i,m=1,2,\cdots \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧T1(0)=2b−a[f(a)+f(b)]T1(i)=21T1(i−1)+2ib−aj=1∑2i−1f[a+2i2j−1(b−a)],i=1,2,⋯Tm+1(k−1)=4m−14mTm(k)−Tm(k−1),k=1,2,⋯,i,m=1,2,⋯
对给定的精度标准
ε
\varepsilon
ε, 我们可由
∣
T
m
(
0
)
−
T
m
−
1
(
0
)
∣
<
ε
|T^{(0)}_m-T^{(0)}_{m-1}|<\varepsilon
∣Tm(0)−Tm−1(0)∣<ε
作为迭代终止的标准.
算法实现
首先包含头文件如下:
#include <functional>
#include <math.h>
using namespace std;
typedef function<double(double)> func;
接着定义一个“T”表类:
class Romberg_Table
{
double *arr;
unsigned n, k;
public:
Romberg_Table(const func &f, const double &a, const double &b, const double &fa, const double &fb, unsigned n0) : n(n0), k(0)
{
if (!n0)
throw "n不能为零!";
double *p(arr = new double[n0]), h(b - a);
*p = h * (fa + fb) / 2;
unsigned i(n0);
while (--i)
{
double pre_h(h), x(a + (h /= 2)), fx(f(x)), &I(*p);
if (isnan(fx))
{
delete[] arr;
throw "区间内存在奇点!";
}
while ((x += pre_h) < b)
if (isnan(fx += f(x)))
{
delete[] arr;
throw "区间内存在奇点!";
}
*++p = I / 2 + fx * h;
}
}
~Romberg_Table()
{
delete[] arr;
}
// 外推, 注意n不能等于1
void extrapolation() noexcept
{
unsigned k4(1 << (++k << 1)), k41(k4 - 1), m(--n); // k4表示4^k
double *p(arr);
*p = (k4 * *(p + 1) - *p) / k41;
while (--m)
{
++p;
*p = (k4 * *(p + 1) - *p) / k41;
}
}
// 输出当前值
inline double &get_value() noexcept
{
return arr[n - 1];
}
// 判断是否达到精度要求
inline bool judge_accuracy(const double &e) noexcept
{
if (n == 1)
return false;
return abs(arr[n - 2] - arr[n - 1]) >= e;
}
};
接着实现Romberg求积法:
/*
* Romberg求积法(端点可能为奇点)
* f :被积函数
* a :积分下限
* b :积分上限
* fa:积分下限处的函数值
* fb:积分上限处的函数值
* e :精度
* n :复化次数
*/
double Romberg(func f, const double &a, const double &b, const double &fa, const double &fb, const double &e = 1e-8, const unsigned &n = 16)
{
if (a >= b)
throw "区间上限小于区间下限!";
Romberg_Table table(f, a, b, fa, fb, n);
while (table.judge_accuracy(e))
table.extrapolation();
return table.get_value();
}
/*
* Romberg求积法
* f :被积函数
* a :积分下限
* b :积分上限
* e :精度
* n :复化次数
*/
double Romberg(func f, const double &a, const double &b, const double &e = 1e-8, const unsigned &n = 16)
{
double fa(f(a));
if (isnan(fa))
throw "积分下限为奇点!";
double fb(f(b));
if (isnan(fb))
throw "积分上限为奇点!";
return Romberg(f, a, b, fa, fb, e, n);
}
其中, 对于积分上下限为奇点的情形提供了手动输入积分上下限函数值的接口.
测试案例
计算积分 I = ∫ 0 1 x ln x d x \displaystyle I=\int_0^1\sqrt x\ln x{\rm d}x I=∫01xlnxdx.
#include <stdio.h>
// Romberg实现代码略
int main()
{
auto f = [](double x) -> double
{
return sqrt(x) * log(x);
};
try
{
printf("%f", Romberg(f, 0., 1., 0., 0., 1e-6, 15));
}
catch (const char *s)
{
printf(s);
}
return 0;
}
最终得到结果为-0.444444, 计算过程如下:
k k k | T 1 k T_1^k T1k | T 2 k T_2^k T2k |
---|---|---|
0 | 0.000000 | -0.326753 |
1 | -0.245065 | -0.395784 |
2 | -0.358104 | -0.424752 |
3 | -0.408090 | -0.436603 |
4 | -0.429475 | -0.441361 |
5 | -0.438389 | -0.443244 |
6 | -0.442031 | -0.443981 |
7 | -0.443494 | -0.444267 |
8 | -0.444074 | -0.444377 |
9 | -0.444301 | -0.444419 |
10 | -0.444389 | -0.444435 |
11 | -0.444423 | -0.444441 |
12 | -0.444436 | -0.444443 |
13 | -0.444441 | -0.444444 |
14 | -0.444443 | - |