雷米兹介绍
切比雪夫只是提出理论,但是没给出计算方法。乌克兰数学家雷米兹Evgeny Yakovlevich Remez提出了计算切比雪夫近似值的算法。
雷米兹对切比雪夫近似值提供一个操作简单的算法,这是个迭代过程。
N次迭代,每次两步。对于这种迭代过程,是没法预测迭代多少步,类似的迭代过程还有线性代数中的Gram-Schmidt分解。但是他们有个共同的特点,就是数学家们证明了迭代过程的收敛性,假如不收敛,迭代再多次也是无益的,就比如Hessenberg矩阵的QR分解,迭代无数次还是Hessenberg矩阵,因为不收敛。雷米兹算法的收敛性,我这里不提供证明,因为太难了,哈哈,我也看不懂。
算法第一步——解方程
假设是求sin(x)在(-π/16, π/16)的九阶切比雪夫近似值。这个手算是不太可能的,会比较累。九阶多项式,有10个我们要求的未知数,其形式如下:
a
x
9
+
b
x
8
+
c
x
7
+
d
x
6
+
e
x
5
+
f
x
4
+
g
x
3
+
h
x
2
+
i
x
+
j
ax^9+bx^8+ cx^7+dx^6+ex^5+fx^4+gx^3+hx^2+ix+j
ax9+bx8+cx7+dx6+ex5+fx4+gx3+hx2+ix+j
我们把未知数有a到j十个字母来表示。N次迭代,按下面步骤来:
在区间内,找任意11(根据切比雪夫的N+2定义,
N
=
9
,
9
+
2
=
11
N=9,9+2=11
N=9,9+2=11)个点。
第一步 求出系数,这个系数要求在11个点(N+2)上,切换正负号之后,错误幅值相等。
将这11个点的值代入公式:
a
x
0
9
+
b
x
0
8
+
c
x
0
7
+
d
x
0
6
+
e
x
0
5
+
f
x
0
4
+
g
x
0
3
+
h
x
0
2
+
i
x
0
+
j
=
s
i
n
(
x
0
)
−
e
a
x
1
9
+
b
x
1
8
+
c
x
1
7
+
d
x
1
6
+
e
x
1
5
+
f
x
1
4
+
g
x
1
3
+
h
x
1
2
+
i
x
1
+
j
=
s
i
n
(
x
1
)
+
e
⋮
a
x
10
9
+
b
x
10
8
+
c
x
10
7
+
d
x
10
6
+
e
x
10
5
+
f
x
10
4
+
g
x
10
3
+
h
x
10
2
+
i
x
10
+
j
=
s
i
n
(
x
1
0
)
−
e
ax_0^9+bx_0^8+ cx_0^7+dx_0^6+ex_0^5+fx_0^4+gx_0^3+hx_0^2+ix_0+j = sin(x_0) - e\\ ax_1^9+bx_1^8+ cx_1^7+dx_1^6+ex_1^5+fx_1^4+gx_1^3+hx_1^2+ix_1+j = sin(x_1) + e\\ \vdots\\ ax_{10}^9+bx_{10}^8+ cx_{10}^7+dx_{10}^6+ex_{10}^5+fx_{10}^4+gx_{10}^3+hx_{10}^2+ix_{10}+j = sin(x_10) - e\\
ax09+bx08+cx07+dx06+ex05+fx04+gx03+hx02+ix0+j=sin(x0)−eax19+bx18+cx17+dx16+ex15+fx14+gx13+hx12+ix1+j=sin(x1)+e⋮ax109+bx108+cx107+dx106+ex105+fx104+gx103+hx102+ix10+j=sin(x10)−e
e是误差。所以误差e也是个未知数。这11个等式,计算出来的a~j和误差e肯定不是最终结果。这个计算过程,扩展到任意阶是这样的公式:
c
0
+
c
1
h
i
+
⋅
⋅
⋅
+
c
n
h
i
n
+
(
−
1
)
i
e
=
f
(
x
i
)
i
=
0
,
1
,
2
,
.
.
.
,
n
+
1
c_0 + c_1h_i + · · · + c_nh^n_i + (-1)^ie = f(x_i)\\ i = 0, 1, 2, . . . , n + 1
c0+c1hi+⋅⋅⋅+cnhin+(−1)ie=f(xi)i=0,1,2,...,n+1
公式中
h
i
n
h_i^n
hin是点的n次方,c是要求的系数。上面的例子,这一步我们得到了一个11元一次方程组,这个时候用高斯消元、LU分解或LUP分解可以解出10个系数和一个误差e。
但是第一步的11个点,并不恰好是最大误差的11个点。他们的误差值e,也不是最小误差值。
算法第二步——重新选点
把第一步得到的方程。利用二分法,把第一步的11个点,然后加上左边界和右边界重新划分为12个区域。
在这12个区域,找最大误差或最小误差,就是说找误差绝对值最大的点。误差值按以下公式计算:
e
(
x
)
=
s
i
n
(
x
)
−
p
(
x
)
e(x) = sin(x)-p(x)
e(x)=sin(x)−p(x)
在误差绝对值最大的点中去掉一个最小值,产生一个新的集合。
一直循环迭代,直到第一步计算出的误差值e与第二步的误差最大值相等。
比如三阶切比雪夫多项式的第二步就是下图这个样子:
图中的函数是误差函数,图上*号是第一步的五个点,可以看出一正一负的,绝对值相等。这些区间里找到了最大值的点
x
0
∗
,
x
1
∗
,
⋯
,
x
n
+
1
∗
x^∗ _0, x^∗_1, \cdots , x^∗_{n+1}
x0∗,x1∗,⋯,xn+1∗,也就是图上x坐标的圆圈。但是这样产生的点要比需要的点多一个的,所以必须去掉一个最大误差点。图中有一个区域,是误差绝对值最小的区域,我勾出来了,是所有区域里最大误差绝对值最小的区域,它的最大误差点也就是就是被剔除的点:
Java实现
雷米兹的核心代码如下:
/**
* @param n 几次多项式
*/
public static BigDecimal[] remez(final int n,
final BigDecimal min,
final BigDecimal max,
final Function<BigDecimal, BigDecimal> target) {
Random random = new Random();
BigDecimal step1Error = null;
BigDecimal step2Error = null;
final BigDecimal[] horners = new BigDecimal[n + 1];
while ((step1Error == null && step2Error == null) || step1Error.subtract(step2Error).abs().compareTo(MAX_ERROR) > 0) {
// step1
// 区间内区N个任意值
// 0 和 1 之间 产生 3和6之间 6-3 =3.0 min + 3.0 * r
BigDecimal[] randomValues = initRandomValue(n, min, max, random);
// 产生一个n+2阶一次方程组
final EquationGroup equationGroup = getEquationGroup(n, target, randomValues);
final BigDecimal[] resolve = equationGroup.resolve();
// 第一步最大误差
step1Error = resolve[resolve.length - 1];
// 将第一步得到11个值,利用二分法,形成10个分界点,这10个分界点与左右边界重新形成11个区域
Region[] regions = getRegion(randomValues, min, max);
// 在每个区域里找最大误差值或最小误差值
// 第一个区间是 sin(x) - p(x) 正数,也就是最大值,第二个区间是最小值
System.arraycopy(resolve, 0, horners,0, n + 1);
final BigDecimal[] errors = new BigDecimal[n + 2];
for (int i = 0; i < regions.length; i++) {
Region region = regions[i];
// 找出这些点
final Error maxErrorPoint = getMaxErrorPoint(region, target, horners);
randomValues[i] = maxErrorPoint.getPoint();
// 然后求出最大误差
errors[i] = maxErrorPoint.getError();
}
step2Error = Arrays.stream(errors).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
}
return horners;
}
Git地址
https://e.coding.net/buildt/learn/remez.git