我编写了一个C程序,它使用FFTW来计算函数的导数(重复)。我正在测试简单的sin(x)函数。每个步骤计算前一步骤的答案的导数。我观察到错误构建,并且在20步之后,数字是纯垃圾。附件是样本输出。答案(在特定点)应该是0,+ 1或-1,但它不是。
---- out ---- data '(0) = 1.000000 -0.000000
---- out ---- data '(1) = 0.000000 -0.000000
---- out ---- data '(2) = -1.000000 0.000000
---- out ---- data '(3) = -0.000000 0.000000
---- out ---- data '(4) = 1.000000 -0.000000
---- out ---- data '(5) = 0.000000 -0.000000
---- out ---- data '(6) = -1.000000 0.000000
---- out ---- data '(7) = -0.000000 0.000000
---- out ---- data '(8) = 1.000000 -0.000000
---- out ---- data '(9) = 0.000000 -0.000000
---- out ---- data '(10) = -1.000000 0.000000
---- out ---- data '(11) = -0.000000 0.000000
---- out ---- data '(12) = 1.000000 -0.000002
---- out ---- data '(13) = 0.000007 -0.000000
---- out ---- data '(14) = -1.000000 0.000028
---- out ---- data '(15) = -0.000113 0.000000
---- out ---- data '(16) = 0.999997 -0.000444
---- out ---- data '(17) = 0.001798 -0.000000
---- out ---- data '(18) = -0.999969 0.007110
---- out ---- data '(19) = -0.028621 0.000004
我无法弄清楚为什么错误会不断增长。任何建议都深表感谢。我将实函数包装成复杂的数据类型,并将虚部设置为零。 这是代码:
# include <stdlib.h>
# include <stdio.h>
# include <time.h>
# include <math.h>
# include <complex.h>
# include <fftw3.h>
int main ( int argc, char *argv[] ){
int i;
fftw_complex *in;
fftw_complex *in2;
fftw_complex *out;
double pi = 3.14159265359;
int nx = 8, k, t, tf = 20;
double xi = 0, xf = 2*pi;
double dx = (xf - xi)/((double)nx); // Step size
complex double *kx;
fftw_plan plan_backward;
fftw_plan plan_forward;
in = fftw_malloc ( sizeof ( fftw_complex ) * nx );
out = fftw_malloc ( sizeof ( fftw_complex ) * nx );
in2 = fftw_malloc ( sizeof ( fftw_complex ) * nx );
kx = malloc ( sizeof ( complex ) * nx );
// only need it once, hence outside the loop
for (k = 0; k < nx; k++){
if (k < nx/2){
kx[k] = I*2*pi*k/xf;
} else if (k > nx/2){
kx[k] = I*2*pi*(k-nx)/xf;
} else if (k == nx/2){
kx[k] = 0.0;
}
}
// create plan outside the loop
plan_forward = fftw_plan_dft_1d ( nx, in, out, FFTW_FORWARD, FFTW_ESTIMATE );
plan_backward = fftw_plan_dft_1d ( nx, out, in2, FFTW_BACKWARD, FFTW_ESTIMATE );
// initialize data
for ( i = 0; i < nx; i++ )
{
in[i] = sin(i*dx) + I*0.0; // note the complex notation.
}
//-------------------- start time loop ---------------------------------------//
for (t = 0; t < tf; t++){
// print input data
//for ( i = 0; i < nx; i++ ) { printf("initial data '(%f) = %f %f \n", i*dx, creal(in[i]), cimag(in[i]) ); }
fftw_execute ( plan_forward );
for ( i = 0; i < nx; i++ )
{
out[i] = out[i]*kx[i]; // for first order derivative
}
fftw_execute ( plan_backward );
// normalize
for ( i = 0; i < nx; i++ )
{
in2[i] = in2[i]/nx;
}
printf("---- out ---- data '(%d) = %f %f \n", t, creal(in2[0]), cimag(in2[0]) );
// overwrite input array with this new output and loop over
for ( i = 0; i < nx; i++ )
{
in[i] = in2[i];
}
// done with the curent loop.
}
//--------------------- end of loop ----------------------------------------//
fftw_destroy_plan ( plan_forward );
fftw_destroy_plan ( plan_backward );
fftw_free ( in );
fftw_free ( in2 );
fftw_free ( out );
return 0;
}
使用gcc source.c -lfftw3 -lm
编译更新:这是M_PI循环25次的输出。相同的错误累积。
---- out ---- data '(0) = 1.000000 0.000000
---- out ---- data '(1) = -0.000000 -0.000000
---- out ---- data '(2) = -1.000000 -0.000000
---- out ---- data '(3) = 0.000000 0.000000
---- out ---- data '(4) = 1.000000 0.000000
---- out ---- data '(5) = -0.000000 -0.000000
---- out ---- data '(6) = -1.000000 -0.000000
---- out ---- data '(7) = 0.000000 0.000000
---- out ---- data '(8) = 1.000000 0.000000
---- out ---- data '(9) = -0.000000 -0.000000
---- out ---- data '(10) = -1.000000 -0.000000
---- out ---- data '(11) = 0.000000 0.000000
---- out ---- data '(12) = 1.000000 0.000000
---- out ---- data '(13) = -0.000000 -0.000000
---- out ---- data '(14) = -1.000000 -0.000000
---- out ---- data '(15) = 0.000000 0.000000
---- out ---- data '(16) = 1.000000 0.000001
---- out ---- data '(17) = -0.000002 -0.000000
---- out ---- data '(18) = -0.999999 -0.000008
---- out ---- data '(19) = 0.000033 0.000004
---- out ---- data '(20) = 0.999984 0.000132
---- out ---- data '(21) = -0.000527 -0.000069
---- out ---- data '(22) = -0.999735 -0.002104
---- out ---- data '(23) = 0.008427 0.001099
---- out ---- data '(24) = 0.995697 0.033667
答案 0 :(得分:4)
实际上,提高pi的价值并不能解决你的问题,即使它提高了20阶导数的准确性。 问题是通过重复导数滤波器会导致小错误。要限制此问题,可以建议引入低通滤波器以及使用四倍精度。引入条件数的概念有助于理解错误的膨胀方式并相应地设置过滤器。 尽管如此,计算20阶导数仍然是一场噩梦,因为computing the 20th derivative is ill-conditionned:即使对于最干净的实验输入,这根本不可能......
<强> 1。总是存在小错误。
导数滤波器,也称为斜坡滤波器,比高频滤波器更高的频率。通过重复使用斜坡滤波器,高频上的最轻微错误就会大大增加。
让我们通过
打印频率来查看小的初始错误printf("---- out ---- data '(%d) %d = %20g %20g \n", t,i, creal(out[i]), cimag(out[i]) );
使用pi=3.14159265359
时,您会得到:
---- out ---- data '(0) 0 = -2.06712e-13 0
---- out ---- data '(0) 1 = 6.20699e-13 -4
---- out ---- data '(0) 2 = -2.06823e-13 2.92322e-13
---- out ---- data '(0) 3 = -2.07053e-13 1.03695e-13
---- out ---- data '(0) 4 = -2.06934e-13 0
---- out ---- data '(0) 5 = -2.07053e-13 -1.03695e-13
---- out ---- data '(0) 6 = -2.06823e-13 -2.92322e-13
---- out ---- data '(0) 7 = 6.20699e-13 4
由于pi的缺失数字引起的不连续性,所有频率都存在小的非空值,并且这些值通过取导数而膨胀。
使用pi=M_PI
时,这些初始错误较小,但仍为非空:
---- out ---- data '(0) 0 = 1.14424e-17 0
---- out ---- data '(0) 1 = -4.36483e-16 -4
---- out ---- data '(0) 2 = 1.22465e-16 -1.11022e-16
---- out ---- data '(0) 3 = 1.91554e-16 -4.44089e-16
---- out ---- data '(0) 4 = 2.33487e-16 0
---- out ---- data '(0) 5 = 1.91554e-16 4.44089e-16
---- out ---- data '(0) 6 = 1.22465e-16 1.11022e-16
---- out ---- data '(0) 7 = -4.36483e-16 4
这些小错误就像以前的错误一样膨胀,问题并没有完全解决。让我们尝试在第一个循环期间将这些频率归零:
if(t==0){
for (k = 0; k < nx; k++){
if (k==1 || nx-k==1){
out[k] = I*4.0;
}else{
out[k] =0.0;
}
}
}
这次在第一个循环t=0
期间唯一的非零频率是正确的。让我们看看第二个循环:
---- out ---- data '(1) 0 = 0 0
---- out ---- data '(1) 1 = -4 0
---- out ---- data '(1) 2 = 0 0
---- out ---- data '(1) 3 = -4.44089e-16 0
---- out ---- data '(1) 4 = 0 0
---- out ---- data '(1) 5 = 4.44089e-16 0
---- out ---- data '(1) 6 = 0 0
---- out ---- data '(1) 7 = 4 0
由于在DFT后向/前向变换和缩放期间进行有限精度计算,因此会出现小错误并导致膨胀。试。
<强> 2。为了限制错误的增长,可以引入过滤。
大多数实验输入都是由大的高频噪声引起的,可以通过应用低通滤波器(如Butterworth滤波器)来降低这些噪声。有关详细信息和替代方案,请参阅https://www.hindawi.com/journals/ijbi/2011/693795/。该滤波器的特点是切割频率kc和指数,斜坡滤波器的频率响应修改如下:
//parameters of Butterworth Filter:
double kc=3;
double n=16;
// only need it once, hence outside the loop
for (k = 0; k < nx; k++){
if (k < nx/2){
// add low pass filter:
kx[k] = I*2*pi*k/xf;
kx[k]*=1./(1.+pow(k/kc,n));
} else if (k > nx/2){
kx[k] = I*2*pi*(k-nx)/xf;
kx[k]*=1./(1.+pow((nx-k)/kc,n));
} else if (k == nx/2){
kx[k] = 0.0;
}
}
使用这些参数,可以减少20次导数的误差 5.27e-7至1.22e-12。
通过不回到衍生品之间的真实空间,可以实现另一种改进。这样,避免了浮点计算期间的许多舍入误差。在这种特殊情况下,将输入频率归零可确保误差保持为零,但这有点人为......从实际的角度来看,如果在真实空间中提供输入信号,则几乎需要使用滤波器来计算导数。
第3。由于导数滤波器的条件数
,误差正在增加导数是线性应用程序,其特征为condition number。让我们说输入受到所有频率上的错误eps
的困扰。如果第一个频率被因子alpha
放大,则频率k
被因子k*alpha
放大。因此,每次应用导数时,信噪比除以比率kc(最大频率),称为条件数。如果滤波器重复20次,则信噪比除以kc ^ 20。
双精度数约为eps=10e-14
精确:这是您可以获得的最佳信噪比!大多数实验投入都会比这更糟糕。例如,通常使用16位= 65536灰度级对灰度图像进行采样。因此,灰度图像最多精确eps=1/65536
。类似地,典型的音频比特深度是24,对应于eps = 6e-8。对于接近分析的输入,可以建议四倍精度,精度约为esp = 1e-34 ...让我们找到频率kc ^ 20 * eps <1:
eps=10e-14 kc=5
eps=1/65536 kc=1
eps=1/2^24 kc=2
esp=1e-34 kc=44
因此,如果输入是双精度,最多只有20个导数的4个频率将是重要的...... 所有高于4的频率必须通过强低 - 通过过滤器。因此,可以建议使用四元精度:请参阅fftw's documentation以编译fft的gft&#39}连接quadmath的四元精度类型__float128
。 如果输入是图像,那么计算20阶导数就超出了范围:没有一个频率会变得很重要!