/************************************************************************* 1-dimensional Fast Hartley Transform. Algorithm has O(N*logN) complexity for any N (composite or prime). INPUT PARAMETERS A - array[0..N-1] - real function to be transformed N - problem size OUTPUT PARAMETERS A - FHT of a input array, array[0..N-1], A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1) -- ALGLIB -- Copyright 04.06.2009 by Bochkanov Sergey *************************************************************************/ public static void fhtr1d(ref double[] a, int n) { ftbase.ftplan plan = new ftbase.ftplan(); int i = 0; AP.Complex[] fa = new AP.Complex[0]; System.Diagnostics.Debug.Assert(n>0, "FHTR1D: incorrect N!"); // // Special case: N=1, FHT is just identity transform. // After this block we assume that N is strictly greater than 1. // if( n==1 ) { return; } // // Reduce FHt to real FFT // fft.fftr1d(ref a, n, ref fa); for(i=0; i<=n-1; i++) { a[i] = fa[i].x-fa[i].y; } }
/************************************************************************* 1-dimensional complex FFT. Array size N may be arbitrary number (composite or prime). Composite N's are handled with cache-oblivious variation of a Cooley-Tukey algorithm. Small prime-factors are transformed using hard coded codelets (similar to FFTW codelets, but without low-level optimization), large prime-factors are handled with Bluestein's algorithm. Fastests transforms are for smooth N's (prime factors are 2, 3, 5 only), most fast for powers of 2. When N have prime factors larger than these, but orders of magnitude smaller than N, computations will be about 4 times slower than for nearby highly composite N's. When N itself is prime, speed will be 6 times lower. Algorithm has O(N*logN) complexity for any N (composite or prime). INPUT PARAMETERS A - array[0..N-1] - complex function to be transformed N - problem size OUTPUT PARAMETERS A - DFT of a input array, array[0..N-1] A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) -- ALGLIB -- Copyright 29.05.2009 by Bochkanov Sergey *************************************************************************/ public static void fftc1d(ref AP.Complex[] a, int n) { ftbase.ftplan plan = new ftbase.ftplan(); int i = 0; double[] buf = new double[0]; System.Diagnostics.Debug.Assert(n>0, "FFTC1D: incorrect N!"); // // Special case: N=1, FFT is just identity transform. // After this block we assume that N is strictly greater than 1. // if( n==1 ) { return; } // // convert input array to the more convinient format // buf = new double[2*n]; for(i=0; i<=n-1; i++) { buf[2*i+0] = a[i].x; buf[2*i+1] = a[i].y; } // // Generate plan and execute it. // // Plan is a combination of a successive factorizations of N and // precomputed data. It is much like a FFTW plan, but is not stored // between subroutine calls and is much simpler. // ftbase.ftbasegeneratecomplexfftplan(n, ref plan); ftbase.ftbaseexecuteplan(ref buf, 0, n, ref plan); // // result // for(i=0; i<=n-1; i++) { a[i].x = buf[2*i+0]; a[i].y = buf[2*i+1]; } }
/************************************************************************* Test *************************************************************************/ public static bool testfft(bool silent) { bool result = new bool(); int n = 0; int i = 0; int k = 0; AP.Complex[] a1 = new AP.Complex[0]; AP.Complex[] a2 = new AP.Complex[0]; AP.Complex[] a3 = new AP.Complex[0]; double[] r1 = new double[0]; double[] r2 = new double[0]; double[] buf = new double[0]; ftbase.ftplan plan = new ftbase.ftplan(); int maxn = 0; double bidierr = 0; double bidirerr = 0; double referr = 0; double refrerr = 0; double reinterr = 0; double errtol = 0; bool referrors = new bool(); bool bidierrors = new bool(); bool refrerrors = new bool(); bool bidirerrors = new bool(); bool reinterrors = new bool(); bool waserrors = new bool(); int i_ = 0; maxn = 128; errtol = 100000*Math.Pow(maxn, (double)(3)/(double)(2))*AP.Math.MachineEpsilon; bidierrors = false; referrors = false; bidirerrors = false; refrerrors = false; reinterrors = false; waserrors = false; // // Test bi-directional error: norm(x-invFFT(FFT(x))) // bidierr = 0; bidirerr = 0; for(n=1; n<=maxn; n++) { // // Complex FFT/invFFT // a1 = new AP.Complex[n]; a2 = new AP.Complex[n]; a3 = new AP.Complex[n]; for(i=0; i<=n-1; i++) { a1[i].x = 2*AP.Math.RandomReal()-1; a1[i].y = 2*AP.Math.RandomReal()-1; a2[i] = a1[i]; a3[i] = a1[i]; } fft.fftc1d(ref a2, n); fft.fftc1dinv(ref a2, n); fft.fftc1dinv(ref a3, n); fft.fftc1d(ref a3, n); for(i=0; i<=n-1; i++) { bidierr = Math.Max(bidierr, AP.Math.AbsComplex(a1[i]-a2[i])); bidierr = Math.Max(bidierr, AP.Math.AbsComplex(a1[i]-a3[i])); } // // Real // r1 = new double[n]; r2 = new double[n]; for(i=0; i<=n-1; i++) { r1[i] = 2*AP.Math.RandomReal()-1; r2[i] = r1[i]; } fft.fftr1d(ref r2, n, ref a1); for(i_=0; i_<=n-1;i_++) { r2[i_] = 0*r2[i_]; } fft.fftr1dinv(ref a1, n, ref r2); for(i=0; i<=n-1; i++) { bidirerr = Math.Max(bidirerr, AP.Math.AbsComplex(r1[i]-r2[i])); } } bidierrors = bidierrors | (double)(bidierr)>(double)(errtol); bidirerrors = bidirerrors | (double)(bidirerr)>(double)(errtol); // // Test against reference O(N^2) implementation // referr = 0; refrerr = 0; for(n=1; n<=maxn; n++) { // // Complex FFT // a1 = new AP.Complex[n]; a2 = new AP.Complex[n]; for(i=0; i<=n-1; i++) { a1[i].x = 2*AP.Math.RandomReal()-1; a1[i].y = 2*AP.Math.RandomReal()-1; a2[i] = a1[i]; } fft.fftc1d(ref a1, n); reffftc1d(ref a2, n); for(i=0; i<=n-1; i++) { referr = Math.Max(referr, AP.Math.AbsComplex(a1[i]-a2[i])); } // // Complex inverse FFT // a1 = new AP.Complex[n]; a2 = new AP.Complex[n]; for(i=0; i<=n-1; i++) { a1[i].x = 2*AP.Math.RandomReal()-1; a1[i].y = 2*AP.Math.RandomReal()-1; a2[i] = a1[i]; } fft.fftc1dinv(ref a1, n); reffftc1dinv(ref a2, n); for(i=0; i<=n-1; i++) { referr = Math.Max(referr, AP.Math.AbsComplex(a1[i]-a2[i])); } // // Real forward/inverse FFT: // * calculate and check forward FFT // * use precalculated FFT to check backward FFT // fill unused parts of frequencies array with random numbers // to ensure that they are not really used // r1 = new double[n]; r2 = new double[n]; for(i=0; i<=n-1; i++) { r1[i] = 2*AP.Math.RandomReal()-1; r2[i] = r1[i]; } fft.fftr1d(ref r1, n, ref a1); refinternalrfft(ref r2, n, ref a2); for(i=0; i<=n-1; i++) { refrerr = Math.Max(refrerr, AP.Math.AbsComplex(a1[i]-a2[i])); } a3 = new AP.Complex[(int)Math.Floor((double)(n)/(double)(2))+1]; for(i=0; i<=(int)Math.Floor((double)(n)/(double)(2)); i++) { a3[i] = a2[i]; } a3[0].y = 2*AP.Math.RandomReal()-1; if( n%2==0 ) { a3[(int)Math.Floor((double)(n)/(double)(2))].y = 2*AP.Math.RandomReal()-1; } for(i=0; i<=n-1; i++) { r1[i] = 0; } fft.fftr1dinv(ref a3, n, ref r1); for(i=0; i<=n-1; i++) { refrerr = Math.Max(refrerr, Math.Abs(r2[i]-r1[i])); } } referrors = referrors | (double)(referr)>(double)(errtol); refrerrors = refrerrors | (double)(refrerr)>(double)(errtol); // // test internal real even FFT // reinterr = 0; for(k=1; k<=maxn/2; k++) { n = 2*k; // // Real forward FFT // r1 = new double[n]; r2 = new double[n]; for(i=0; i<=n-1; i++) { r1[i] = 2*AP.Math.RandomReal()-1; r2[i] = r1[i]; } ftbase.ftbasegeneratecomplexfftplan(n/2, ref plan); buf = new double[n]; fft.fftr1dinternaleven(ref r1, n, ref buf, ref plan); refinternalrfft(ref r2, n, ref a2); reinterr = Math.Max(reinterr, Math.Abs(r1[0]-a2[0].x)); reinterr = Math.Max(reinterr, Math.Abs(r1[1]-a2[n/2].x)); for(i=1; i<=n/2-1; i++) { reinterr = Math.Max(reinterr, Math.Abs(r1[2*i+0]-a2[i].x)); reinterr = Math.Max(reinterr, Math.Abs(r1[2*i+1]-a2[i].y)); } // // Real backward FFT // r1 = new double[n]; for(i=0; i<=n-1; i++) { r1[i] = 2*AP.Math.RandomReal()-1; } a2 = new AP.Complex[(int)Math.Floor((double)(n)/(double)(2))+1]; a2[0] = r1[0]; for(i=1; i<=(int)Math.Floor((double)(n)/(double)(2))-1; i++) { a2[i].x = r1[2*i+0]; a2[i].y = r1[2*i+1]; } a2[(int)Math.Floor((double)(n)/(double)(2))] = r1[1]; ftbase.ftbasegeneratecomplexfftplan(n/2, ref plan); buf = new double[n]; fft.fftr1dinvinternaleven(ref r1, n, ref buf, ref plan); fft.fftr1dinv(ref a2, n, ref r2); for(i=0; i<=n-1; i++) { reinterr = Math.Max(reinterr, Math.Abs(r1[i]-r2[i])); } } reinterrors = reinterrors | (double)(reinterr)>(double)(errtol); // // end // waserrors = bidierrors | bidirerrors | referrors | refrerrors | reinterrors; if( !silent ) { System.Console.Write("TESTING FFT"); System.Console.WriteLine(); System.Console.Write("FINAL RESULT: "); if( waserrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } System.Console.Write("* BI-DIRECTIONAL COMPLEX TEST: "); if( bidierrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } System.Console.Write("* AGAINST REFERENCE COMPLEX FFT: "); if( referrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } System.Console.Write("* BI-DIRECTIONAL REAL TEST: "); if( bidirerrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } System.Console.Write("* AGAINST REFERENCE REAL FFT: "); if( refrerrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } System.Console.Write("* INTERNAL EVEN FFT: "); if( reinterrors ) { System.Console.Write("FAILED"); System.Console.WriteLine(); } else { System.Console.Write("OK"); System.Console.WriteLine(); } if( waserrors ) { System.Console.Write("TEST FAILED"); System.Console.WriteLine(); } else { System.Console.Write("TEST PASSED"); System.Console.WriteLine(); } } result = !waserrors; return result; }
/************************************************************************* 1-dimensional real FFT. Algorithm has O(N*logN) complexity for any N (composite or prime). INPUT PARAMETERS A - array[0..N-1] - real function to be transformed N - problem size OUTPUT PARAMETERS F - DFT of a input array, array[0..N-1] F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) NOTE: F[] satisfies symmetry property F[k] = conj(F[N-k]), so just one half of array is usually needed. But for convinience subroutine returns full complex array (with frequencies above N/2), so its result may be used by other FFT-related subroutines. -- ALGLIB -- Copyright 01.06.2009 by Bochkanov Sergey *************************************************************************/ public static void fftr1d(ref double[] a, int n, ref AP.Complex[] f) { int i = 0; int n2 = 0; int idx = 0; AP.Complex hn = 0; AP.Complex hmnc = 0; AP.Complex v = 0; double[] buf = new double[0]; ftbase.ftplan plan = new ftbase.ftplan(); int i_ = 0; System.Diagnostics.Debug.Assert(n>0, "FFTR1D: incorrect N!"); // // Special cases: // * N=1, FFT is just identity transform. // * N=2, FFT is simple too // // After this block we assume that N is strictly greater than 2 // if( n==1 ) { f = new AP.Complex[1]; f[0] = a[0]; return; } if( n==2 ) { f = new AP.Complex[2]; f[0].x = a[0]+a[1]; f[0].y = 0; f[1].x = a[0]-a[1]; f[1].y = 0; return; } // // Choose between odd-size and even-size FFTs // if( n%2==0 ) { // // even-size real FFT, use reduction to the complex task // n2 = n/2; buf = new double[n]; for(i_=0; i_<=n-1;i_++) { buf[i_] = a[i_]; } ftbase.ftbasegeneratecomplexfftplan(n2, ref plan); ftbase.ftbaseexecuteplan(ref buf, 0, n2, ref plan); f = new AP.Complex[n]; for(i=0; i<=n2; i++) { idx = 2*(i%n2); hn.x = buf[idx+0]; hn.y = buf[idx+1]; idx = 2*((n2-i)%n2); hmnc.x = buf[idx+0]; hmnc.y = -buf[idx+1]; v.x = -Math.Sin(-(2*Math.PI*i/n)); v.y = Math.Cos(-(2*Math.PI*i/n)); f[i] = hn+hmnc-v*(hn-hmnc); f[i].x = 0.5*f[i].x; f[i].y = 0.5*f[i].y; } for(i=n2+1; i<=n-1; i++) { f[i] = AP.Math.Conj(f[n-i]); } return; } else { // // use complex FFT // f = new AP.Complex[n]; for(i=0; i<=n-1; i++) { f[i] = a[i]; } fftc1d(ref f, n); return; } }
/************************************************************************* 1-dimensional complex convolution. Extended subroutine which allows to choose convolution algorithm. Intended for internal use, ALGLIB users should call ConvC1D()/ConvC1DCircular(). INPUT PARAMETERS A - array[0..M-1] - complex function to be transformed M - problem size B - array[0..N-1] - complex function to be transformed N - problem size, N<=M Alg - algorithm type: *-2 auto-select Q for overlap-add *-1 auto-select algorithm and parameters * 0 straightforward formula for small N's * 1 general FFT-based code * 2 overlap-add with length Q Q - length for overlap-add OUTPUT PARAMETERS R - convolution: A*B. array[0..N+M-1]. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convc1dx(ref AP.Complex[] a, int m, ref AP.Complex[] b, int n, bool circular, int alg, int q, ref AP.Complex[] r) { int i = 0; int j = 0; int p = 0; int ptotal = 0; int i1 = 0; int i2 = 0; int j1 = 0; int j2 = 0; AP.Complex[] bbuf = new AP.Complex[0]; AP.Complex v = 0; double ax = 0; double ay = 0; double bx = 0; double by = 0; double t = 0; double tx = 0; double ty = 0; double flopcand = 0; double flopbest = 0; int algbest = 0; ftbase.ftplan plan = new ftbase.ftplan(); double[] buf = new double[0]; double[] buf2 = new double[0]; int i_ = 0; int i1_ = 0; System.Diagnostics.Debug.Assert(n>0 & m>0, "ConvC1DX: incorrect N or M!"); System.Diagnostics.Debug.Assert(n<=m, "ConvC1DX: N<M assumption is false!"); // // Auto-select // if( alg==-1 | alg==-2 ) { // // Initial candidate: straightforward implementation. // // If we want to use auto-fitted overlap-add, // flop count is initialized by large real number - to force // another algorithm selection // algbest = 0; if( alg==-1 ) { flopbest = 2*m*n; } else { flopbest = AP.Math.MaxRealNumber; } // // Another candidate - generic FFT code // if( alg==-1 ) { if( circular & ftbase.ftbaseissmooth(m) ) { // // special code for circular convolution of a sequence with a smooth length // flopcand = 3*ftbase.ftbasegetflopestimate(m)+6*m; if( (double)(flopcand)<(double)(flopbest) ) { algbest = 1; flopbest = flopcand; } } else { // // general cyclic/non-cyclic convolution // p = ftbase.ftbasefindsmooth(m+n-1); flopcand = 3*ftbase.ftbasegetflopestimate(p)+6*p; if( (double)(flopcand)<(double)(flopbest) ) { algbest = 1; flopbest = flopcand; } } } // // Another candidate - overlap-add // q = 1; ptotal = 1; while( ptotal<n ) { ptotal = ptotal*2; } while( ptotal<=m+n-1 ) { p = ptotal-n+1; flopcand = (int)Math.Ceiling((double)(m)/(double)(p))*(2*ftbase.ftbasegetflopestimate(ptotal)+8*ptotal); if( (double)(flopcand)<(double)(flopbest) ) { flopbest = flopcand; algbest = 2; q = p; } ptotal = ptotal*2; } alg = algbest; convc1dx(ref a, m, ref b, n, circular, alg, q, ref r); return; } // // straightforward formula for // circular and non-circular convolutions. // // Very simple code, no further comments needed. // if( alg==0 ) { // // Special case: N=1 // if( n==1 ) { r = new AP.Complex[m]; v = b[0]; for(i_=0; i_<=m-1;i_++) { r[i_] = v*a[i_]; } return; } // // use straightforward formula // if( circular ) { // // circular convolution // r = new AP.Complex[m]; v = b[0]; for(i_=0; i_<=m-1;i_++) { r[i_] = v*a[i_]; } for(i=1; i<=n-1; i++) { v = b[i]; i1 = 0; i2 = i-1; j1 = m-i; j2 = m-1; i1_ = (j1) - (i1); for(i_=i1; i_<=i2;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } i1 = i; i2 = m-1; j1 = 0; j2 = m-i-1; i1_ = (j1) - (i1); for(i_=i1; i_<=i2;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } } } else { // // non-circular convolution // r = new AP.Complex[m+n-1]; for(i=0; i<=m+n-2; i++) { r[i] = 0; } for(i=0; i<=n-1; i++) { v = b[i]; i1_ = (0) - (i); for(i_=i; i_<=i+m-1;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } } } return; } // // general FFT-based code for // circular and non-circular convolutions. // // First, if convolution is circular, we test whether M is smooth or not. // If it is smooth, we just use M-length FFT to calculate convolution. // If it is not, we calculate non-circular convolution and wrap it arount. // // IF convolution is non-circular, we use zero-padding + FFT. // if( alg==1 ) { if( circular & ftbase.ftbaseissmooth(m) ) { // // special code for circular convolution with smooth M // ftbase.ftbasegeneratecomplexfftplan(m, ref plan); buf = new double[2*m]; for(i=0; i<=m-1; i++) { buf[2*i+0] = a[i].x; buf[2*i+1] = a[i].y; } buf2 = new double[2*m]; for(i=0; i<=n-1; i++) { buf2[2*i+0] = b[i].x; buf2[2*i+1] = b[i].y; } for(i=n; i<=m-1; i++) { buf2[2*i+0] = 0; buf2[2*i+1] = 0; } ftbase.ftbaseexecuteplan(ref buf, 0, m, ref plan); ftbase.ftbaseexecuteplan(ref buf2, 0, m, ref plan); for(i=0; i<=m-1; i++) { ax = buf[2*i+0]; ay = buf[2*i+1]; bx = buf2[2*i+0]; by = buf2[2*i+1]; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*i+0] = tx; buf[2*i+1] = -ty; } ftbase.ftbaseexecuteplan(ref buf, 0, m, ref plan); t = (double)(1)/(double)(m); r = new AP.Complex[m]; for(i=0; i<=m-1; i++) { r[i].x = +(t*buf[2*i+0]); r[i].y = -(t*buf[2*i+1]); } } else { // // M is non-smooth, general code (circular/non-circular): // * first part is the same for circular and non-circular // convolutions. zero padding, FFTs, inverse FFTs // * second part differs: // * for non-circular convolution we just copy array // * for circular convolution we add array tail to its head // p = ftbase.ftbasefindsmooth(m+n-1); ftbase.ftbasegeneratecomplexfftplan(p, ref plan); buf = new double[2*p]; for(i=0; i<=m-1; i++) { buf[2*i+0] = a[i].x; buf[2*i+1] = a[i].y; } for(i=m; i<=p-1; i++) { buf[2*i+0] = 0; buf[2*i+1] = 0; } buf2 = new double[2*p]; for(i=0; i<=n-1; i++) { buf2[2*i+0] = b[i].x; buf2[2*i+1] = b[i].y; } for(i=n; i<=p-1; i++) { buf2[2*i+0] = 0; buf2[2*i+1] = 0; } ftbase.ftbaseexecuteplan(ref buf, 0, p, ref plan); ftbase.ftbaseexecuteplan(ref buf2, 0, p, ref plan); for(i=0; i<=p-1; i++) { ax = buf[2*i+0]; ay = buf[2*i+1]; bx = buf2[2*i+0]; by = buf2[2*i+1]; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*i+0] = tx; buf[2*i+1] = -ty; } ftbase.ftbaseexecuteplan(ref buf, 0, p, ref plan); t = (double)(1)/(double)(p); if( circular ) { // // circular, add tail to head // r = new AP.Complex[m]; for(i=0; i<=m-1; i++) { r[i].x = +(t*buf[2*i+0]); r[i].y = -(t*buf[2*i+1]); } for(i=m; i<=m+n-2; i++) { r[i-m].x = r[i-m].x+t*buf[2*i+0]; r[i-m].y = r[i-m].y-t*buf[2*i+1]; } } else { // // non-circular, just copy // r = new AP.Complex[m+n-1]; for(i=0; i<=m+n-2; i++) { r[i].x = +(t*buf[2*i+0]); r[i].y = -(t*buf[2*i+1]); } } } return; } // // overlap-add method for // circular and non-circular convolutions. // // First part of code (separate FFTs of input blocks) is the same // for all types of convolution. Second part (overlapping outputs) // differs for different types of convolution. We just copy output // when convolution is non-circular. We wrap it around, if it is // circular. // if( alg==2 ) { buf = new double[2*(q+n-1)]; // // prepare R // if( circular ) { r = new AP.Complex[m]; for(i=0; i<=m-1; i++) { r[i] = 0; } } else { r = new AP.Complex[m+n-1]; for(i=0; i<=m+n-2; i++) { r[i] = 0; } } // // pre-calculated FFT(B) // bbuf = new AP.Complex[q+n-1]; for(i_=0; i_<=n-1;i_++) { bbuf[i_] = b[i_]; } for(j=n; j<=q+n-2; j++) { bbuf[j] = 0; } fft.fftc1d(ref bbuf, q+n-1); // // prepare FFT plan for chunks of A // ftbase.ftbasegeneratecomplexfftplan(q+n-1, ref plan); // // main overlap-add cycle // i = 0; while( i<=m-1 ) { p = Math.Min(q, m-i); for(j=0; j<=p-1; j++) { buf[2*j+0] = a[i+j].x; buf[2*j+1] = a[i+j].y; } for(j=p; j<=q+n-2; j++) { buf[2*j+0] = 0; buf[2*j+1] = 0; } ftbase.ftbaseexecuteplan(ref buf, 0, q+n-1, ref plan); for(j=0; j<=q+n-2; j++) { ax = buf[2*j+0]; ay = buf[2*j+1]; bx = bbuf[j].x; by = bbuf[j].y; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*j+0] = tx; buf[2*j+1] = -ty; } ftbase.ftbaseexecuteplan(ref buf, 0, q+n-1, ref plan); t = (double)(1)/((double)(q+n-1)); if( circular ) { j1 = Math.Min(i+p+n-2, m-1)-i; j2 = j1+1; } else { j1 = p+n-2; j2 = j1+1; } for(j=0; j<=j1; j++) { r[i+j].x = r[i+j].x+buf[2*j+0]*t; r[i+j].y = r[i+j].y-buf[2*j+1]*t; } for(j=j2; j<=p+n-2; j++) { r[j-j2].x = r[j-j2].x+buf[2*j+0]*t; r[j-j2].y = r[j-j2].y-buf[2*j+1]*t; } i = i+p; } return; } }
/************************************************************************* 1-dimensional complex deconvolution (inverse of ConvC1D()). Algorithm has M*log(M)) complexity for any M (composite or prime). INPUT PARAMETERS A - array[0..M-1] - convolved signal, A = conv(R, B) M - convolved signal length B - array[0..N-1] - response N - response length OUTPUT PARAMETERS R - deconvolved signal. array[0..M-N]. NOTE: deconvolution is unstable process and may result in division by zero (if your response function is degenerate, i.e. has zero Fourier coefficient). NOTE: It is assumed that B is zero at T<0. If it has non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convr1dcircularinv(ref double[] a, int m, ref double[] b, int n, ref double[] r) { int i = 0; int i1 = 0; int i2 = 0; int j2 = 0; double[] buf = new double[0]; double[] buf2 = new double[0]; double[] buf3 = new double[0]; AP.Complex[] cbuf = new AP.Complex[0]; AP.Complex[] cbuf2 = new AP.Complex[0]; ftbase.ftplan plan = new ftbase.ftplan(); AP.Complex c1 = 0; AP.Complex c2 = 0; AP.Complex c3 = 0; int i_ = 0; int i1_ = 0; System.Diagnostics.Debug.Assert(n>0 & m>0, "ConvR1DCircularInv: incorrect N or M!"); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if( m<n ) { buf = new double[m]; for(i=0; i<=m-1; i++) { buf[i] = 0; } i1 = 0; while( i1<n ) { i2 = Math.Min(i1+m-1, n-1); j2 = i2-i1; i1_ = (i1) - (0); for(i_=0; i_<=j2;i_++) { buf[i_] = buf[i_] + b[i_+i1_]; } i1 = i1+m; } convr1dcircularinv(ref a, m, ref buf, m, ref r); return; } // // Task is normalized // if( m%2==0 ) { // // size is even, use fast even-size FFT // buf = new double[m]; for(i_=0; i_<=m-1;i_++) { buf[i_] = a[i_]; } buf2 = new double[m]; for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(i=n; i<=m-1; i++) { buf2[i] = 0; } buf3 = new double[m]; ftbase.ftbasegeneratecomplexfftplan(m/2, ref plan); fft.fftr1dinternaleven(ref buf, m, ref buf3, ref plan); fft.fftr1dinternaleven(ref buf2, m, ref buf3, ref plan); buf[0] = buf[0]/buf2[0]; buf[1] = buf[1]/buf2[1]; for(i=1; i<=m/2-1; i++) { c1.x = buf[2*i+0]; c1.y = buf[2*i+1]; c2.x = buf2[2*i+0]; c2.y = buf2[2*i+1]; c3 = c1/c2; buf[2*i+0] = c3.x; buf[2*i+1] = c3.y; } fft.fftr1dinvinternaleven(ref buf, m, ref buf3, ref plan); r = new double[m]; for(i_=0; i_<=m-1;i_++) { r[i_] = buf[i_]; } } else { // // odd-size, use general real FFT // fft.fftr1d(ref a, m, ref cbuf); buf2 = new double[m]; for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(i=n; i<=m-1; i++) { buf2[i] = 0; } fft.fftr1d(ref buf2, m, ref cbuf2); for(i=0; i<=(int)Math.Floor((double)(m)/(double)(2)); i++) { cbuf[i] = cbuf[i]/cbuf2[i]; } fft.fftr1dinv(ref cbuf, m, ref r); } }
/************************************************************************* 1-dimensional real deconvolution (inverse of ConvC1D()). Algorithm has M*log(M)) complexity for any M (composite or prime). INPUT PARAMETERS A - array[0..M-1] - convolved signal, A = conv(R, B) M - convolved signal length B - array[0..N-1] - response N - response length, N<=M OUTPUT PARAMETERS R - deconvolved signal. array[0..M-N]. NOTE: deconvolution is unstable process and may result in division by zero (if your response function is degenerate, i.e. has zero Fourier coefficient). NOTE: It is assumed that A is zero at T<0, B is zero too. If one or both functions have non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convr1dinv(ref double[] a, int m, ref double[] b, int n, ref double[] r) { int i = 0; int p = 0; double[] buf = new double[0]; double[] buf2 = new double[0]; double[] buf3 = new double[0]; ftbase.ftplan plan = new ftbase.ftplan(); AP.Complex c1 = 0; AP.Complex c2 = 0; AP.Complex c3 = 0; int i_ = 0; System.Diagnostics.Debug.Assert(n>0 & m>0 & n<=m, "ConvR1DInv: incorrect N or M!"); p = ftbase.ftbasefindsmootheven(m); buf = new double[p]; for(i_=0; i_<=m-1;i_++) { buf[i_] = a[i_]; } for(i=m; i<=p-1; i++) { buf[i] = 0; } buf2 = new double[p]; for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(i=n; i<=p-1; i++) { buf2[i] = 0; } buf3 = new double[p]; ftbase.ftbasegeneratecomplexfftplan(p/2, ref plan); fft.fftr1dinternaleven(ref buf, p, ref buf3, ref plan); fft.fftr1dinternaleven(ref buf2, p, ref buf3, ref plan); buf[0] = buf[0]/buf2[0]; buf[1] = buf[1]/buf2[1]; for(i=1; i<=p/2-1; i++) { c1.x = buf[2*i+0]; c1.y = buf[2*i+1]; c2.x = buf2[2*i+0]; c2.y = buf2[2*i+1]; c3 = c1/c2; buf[2*i+0] = c3.x; buf[2*i+1] = c3.y; } fft.fftr1dinvinternaleven(ref buf, p, ref buf3, ref plan); r = new double[m-n+1]; for(i_=0; i_<=m-n;i_++) { r[i_] = buf[i_]; } }
/************************************************************************* 1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()). Algorithm has M*log(M)) complexity for any M (composite or prime). INPUT PARAMETERS A - array[0..M-1] - convolved periodic signal, A = conv(R, B) M - convolved signal length B - array[0..N-1] - non-periodic response N - response length OUTPUT PARAMETERS R - deconvolved signal. array[0..M-1]. NOTE: deconvolution is unstable process and may result in division by zero (if your response function is degenerate, i.e. has zero Fourier coefficient). NOTE: It is assumed that B is zero at T<0. If it has non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convc1dcircularinv(ref AP.Complex[] a, int m, ref AP.Complex[] b, int n, ref AP.Complex[] r) { int i = 0; int i1 = 0; int i2 = 0; int j2 = 0; double[] buf = new double[0]; double[] buf2 = new double[0]; AP.Complex[] cbuf = new AP.Complex[0]; ftbase.ftplan plan = new ftbase.ftplan(); AP.Complex c1 = 0; AP.Complex c2 = 0; AP.Complex c3 = 0; double t = 0; int i_ = 0; int i1_ = 0; System.Diagnostics.Debug.Assert(n>0 & m>0, "ConvC1DCircularInv: incorrect N or M!"); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if( m<n ) { cbuf = new AP.Complex[m]; for(i=0; i<=m-1; i++) { cbuf[i] = 0; } i1 = 0; while( i1<n ) { i2 = Math.Min(i1+m-1, n-1); j2 = i2-i1; i1_ = (i1) - (0); for(i_=0; i_<=j2;i_++) { cbuf[i_] = cbuf[i_] + b[i_+i1_]; } i1 = i1+m; } convc1dcircularinv(ref a, m, ref cbuf, m, ref r); return; } // // Task is normalized // ftbase.ftbasegeneratecomplexfftplan(m, ref plan); buf = new double[2*m]; for(i=0; i<=m-1; i++) { buf[2*i+0] = a[i].x; buf[2*i+1] = a[i].y; } buf2 = new double[2*m]; for(i=0; i<=n-1; i++) { buf2[2*i+0] = b[i].x; buf2[2*i+1] = b[i].y; } for(i=n; i<=m-1; i++) { buf2[2*i+0] = 0; buf2[2*i+1] = 0; } ftbase.ftbaseexecuteplan(ref buf, 0, m, ref plan); ftbase.ftbaseexecuteplan(ref buf2, 0, m, ref plan); for(i=0; i<=m-1; i++) { c1.x = buf[2*i+0]; c1.y = buf[2*i+1]; c2.x = buf2[2*i+0]; c2.y = buf2[2*i+1]; c3 = c1/c2; buf[2*i+0] = c3.x; buf[2*i+1] = -c3.y; } ftbase.ftbaseexecuteplan(ref buf, 0, m, ref plan); t = (double)(1)/(double)(m); r = new AP.Complex[m]; for(i=0; i<=m-1; i++) { r[i].x = +(t*buf[2*i+0]); r[i].y = -(t*buf[2*i+1]); } }
/************************************************************************* 1-dimensional real convolution. Extended subroutine which allows to choose convolution algorithm. Intended for internal use, ALGLIB users should call ConvR1D(). INPUT PARAMETERS A - array[0..M-1] - complex function to be transformed M - problem size B - array[0..N-1] - complex function to be transformed N - problem size, N<=M Alg - algorithm type: *-2 auto-select Q for overlap-add *-1 auto-select algorithm and parameters * 0 straightforward formula for small N's * 1 general FFT-based code * 2 overlap-add with length Q Q - length for overlap-add OUTPUT PARAMETERS R - convolution: A*B. array[0..N+M-1]. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convr1dx(ref double[] a, int m, ref double[] b, int n, bool circular, int alg, int q, ref double[] r) { double v = 0; int i = 0; int j = 0; int p = 0; int ptotal = 0; int i1 = 0; int i2 = 0; int j1 = 0; int j2 = 0; double ax = 0; double ay = 0; double bx = 0; double by = 0; double tx = 0; double ty = 0; double flopcand = 0; double flopbest = 0; int algbest = 0; ftbase.ftplan plan = new ftbase.ftplan(); double[] buf = new double[0]; double[] buf2 = new double[0]; double[] buf3 = new double[0]; int i_ = 0; int i1_ = 0; System.Diagnostics.Debug.Assert(n>0 & m>0, "ConvC1DX: incorrect N or M!"); System.Diagnostics.Debug.Assert(n<=m, "ConvC1DX: N<M assumption is false!"); // // handle special cases // if( Math.Min(m, n)<=2 ) { alg = 0; } // // Auto-select // if( alg<0 ) { // // Initial candidate: straightforward implementation. // // If we want to use auto-fitted overlap-add, // flop count is initialized by large real number - to force // another algorithm selection // algbest = 0; if( alg==-1 ) { flopbest = 0.15*m*n; } else { flopbest = AP.Math.MaxRealNumber; } // // Another candidate - generic FFT code // if( alg==-1 ) { if( circular & ftbase.ftbaseissmooth(m) & m%2==0 ) { // // special code for circular convolution of a sequence with a smooth length // flopcand = 3*ftbase.ftbasegetflopestimate(m/2)+(double)(6*m)/(double)(2); if( (double)(flopcand)<(double)(flopbest) ) { algbest = 1; flopbest = flopcand; } } else { // // general cyclic/non-cyclic convolution // p = ftbase.ftbasefindsmootheven(m+n-1); flopcand = 3*ftbase.ftbasegetflopestimate(p/2)+(double)(6*p)/(double)(2); if( (double)(flopcand)<(double)(flopbest) ) { algbest = 1; flopbest = flopcand; } } } // // Another candidate - overlap-add // q = 1; ptotal = 1; while( ptotal<n ) { ptotal = ptotal*2; } while( ptotal<=m+n-1 ) { p = ptotal-n+1; flopcand = (int)Math.Ceiling((double)(m)/(double)(p))*(2*ftbase.ftbasegetflopestimate(ptotal/2)+1*(ptotal/2)); if( (double)(flopcand)<(double)(flopbest) ) { flopbest = flopcand; algbest = 2; q = p; } ptotal = ptotal*2; } alg = algbest; convr1dx(ref a, m, ref b, n, circular, alg, q, ref r); return; } // // straightforward formula for // circular and non-circular convolutions. // // Very simple code, no further comments needed. // if( alg==0 ) { // // Special case: N=1 // if( n==1 ) { r = new double[m]; v = b[0]; for(i_=0; i_<=m-1;i_++) { r[i_] = v*a[i_]; } return; } // // use straightforward formula // if( circular ) { // // circular convolution // r = new double[m]; v = b[0]; for(i_=0; i_<=m-1;i_++) { r[i_] = v*a[i_]; } for(i=1; i<=n-1; i++) { v = b[i]; i1 = 0; i2 = i-1; j1 = m-i; j2 = m-1; i1_ = (j1) - (i1); for(i_=i1; i_<=i2;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } i1 = i; i2 = m-1; j1 = 0; j2 = m-i-1; i1_ = (j1) - (i1); for(i_=i1; i_<=i2;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } } } else { // // non-circular convolution // r = new double[m+n-1]; for(i=0; i<=m+n-2; i++) { r[i] = 0; } for(i=0; i<=n-1; i++) { v = b[i]; i1_ = (0) - (i); for(i_=i; i_<=i+m-1;i_++) { r[i_] = r[i_] + v*a[i_+i1_]; } } } return; } // // general FFT-based code for // circular and non-circular convolutions. // // First, if convolution is circular, we test whether M is smooth or not. // If it is smooth, we just use M-length FFT to calculate convolution. // If it is not, we calculate non-circular convolution and wrap it arount. // // If convolution is non-circular, we use zero-padding + FFT. // // We assume that M+N-1>2 - we should call small case code otherwise // if( alg==1 ) { System.Diagnostics.Debug.Assert(m+n-1>2, "ConvR1DX: internal error!"); if( circular & ftbase.ftbaseissmooth(m) & m%2==0 ) { // // special code for circular convolution with smooth even M // buf = new double[m]; for(i_=0; i_<=m-1;i_++) { buf[i_] = a[i_]; } buf2 = new double[m]; for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(i=n; i<=m-1; i++) { buf2[i] = 0; } buf3 = new double[m]; ftbase.ftbasegeneratecomplexfftplan(m/2, ref plan); fft.fftr1dinternaleven(ref buf, m, ref buf3, ref plan); fft.fftr1dinternaleven(ref buf2, m, ref buf3, ref plan); buf[0] = buf[0]*buf2[0]; buf[1] = buf[1]*buf2[1]; for(i=1; i<=m/2-1; i++) { ax = buf[2*i+0]; ay = buf[2*i+1]; bx = buf2[2*i+0]; by = buf2[2*i+1]; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*i+0] = tx; buf[2*i+1] = ty; } fft.fftr1dinvinternaleven(ref buf, m, ref buf3, ref plan); r = new double[m]; for(i_=0; i_<=m-1;i_++) { r[i_] = buf[i_]; } } else { // // M is non-smooth or non-even, general code (circular/non-circular): // * first part is the same for circular and non-circular // convolutions. zero padding, FFTs, inverse FFTs // * second part differs: // * for non-circular convolution we just copy array // * for circular convolution we add array tail to its head // p = ftbase.ftbasefindsmootheven(m+n-1); buf = new double[p]; for(i_=0; i_<=m-1;i_++) { buf[i_] = a[i_]; } for(i=m; i<=p-1; i++) { buf[i] = 0; } buf2 = new double[p]; for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(i=n; i<=p-1; i++) { buf2[i] = 0; } buf3 = new double[p]; ftbase.ftbasegeneratecomplexfftplan(p/2, ref plan); fft.fftr1dinternaleven(ref buf, p, ref buf3, ref plan); fft.fftr1dinternaleven(ref buf2, p, ref buf3, ref plan); buf[0] = buf[0]*buf2[0]; buf[1] = buf[1]*buf2[1]; for(i=1; i<=p/2-1; i++) { ax = buf[2*i+0]; ay = buf[2*i+1]; bx = buf2[2*i+0]; by = buf2[2*i+1]; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*i+0] = tx; buf[2*i+1] = ty; } fft.fftr1dinvinternaleven(ref buf, p, ref buf3, ref plan); if( circular ) { // // circular, add tail to head // r = new double[m]; for(i_=0; i_<=m-1;i_++) { r[i_] = buf[i_]; } if( n>=2 ) { i1_ = (m) - (0); for(i_=0; i_<=n-2;i_++) { r[i_] = r[i_] + buf[i_+i1_]; } } } else { // // non-circular, just copy // r = new double[m+n-1]; for(i_=0; i_<=m+n-2;i_++) { r[i_] = buf[i_]; } } } return; } // // overlap-add method // if( alg==2 ) { System.Diagnostics.Debug.Assert((q+n-1)%2==0, "ConvR1DX: internal error!"); buf = new double[q+n-1]; buf2 = new double[q+n-1]; buf3 = new double[q+n-1]; ftbase.ftbasegeneratecomplexfftplan((q+n-1)/2, ref plan); // // prepare R // if( circular ) { r = new double[m]; for(i=0; i<=m-1; i++) { r[i] = 0; } } else { r = new double[m+n-1]; for(i=0; i<=m+n-2; i++) { r[i] = 0; } } // // pre-calculated FFT(B) // for(i_=0; i_<=n-1;i_++) { buf2[i_] = b[i_]; } for(j=n; j<=q+n-2; j++) { buf2[j] = 0; } fft.fftr1dinternaleven(ref buf2, q+n-1, ref buf3, ref plan); // // main overlap-add cycle // i = 0; while( i<=m-1 ) { p = Math.Min(q, m-i); i1_ = (i) - (0); for(i_=0; i_<=p-1;i_++) { buf[i_] = a[i_+i1_]; } for(j=p; j<=q+n-2; j++) { buf[j] = 0; } fft.fftr1dinternaleven(ref buf, q+n-1, ref buf3, ref plan); buf[0] = buf[0]*buf2[0]; buf[1] = buf[1]*buf2[1]; for(j=1; j<=(q+n-1)/2-1; j++) { ax = buf[2*j+0]; ay = buf[2*j+1]; bx = buf2[2*j+0]; by = buf2[2*j+1]; tx = ax*bx-ay*by; ty = ax*by+ay*bx; buf[2*j+0] = tx; buf[2*j+1] = ty; } fft.fftr1dinvinternaleven(ref buf, q+n-1, ref buf3, ref plan); if( circular ) { j1 = Math.Min(i+p+n-2, m-1)-i; j2 = j1+1; } else { j1 = p+n-2; j2 = j1+1; } i1_ = (0) - (i); for(i_=i; i_<=i+j1;i_++) { r[i_] = r[i_] + buf[i_+i1_]; } if( p+n-2>=j2 ) { i1_ = (j2) - (0); for(i_=0; i_<=p+n-2-j2;i_++) { r[i_] = r[i_] + buf[i_+i1_]; } } i = i+p; } return; } }
/************************************************************************* 1-dimensional complex non-circular deconvolution (inverse of ConvC1D()). Algorithm has M*log(M)) complexity for any M (composite or prime). INPUT PARAMETERS A - array[0..M-1] - convolved signal, A = conv(R, B) M - convolved signal length B - array[0..N-1] - response N - response length, N<=M OUTPUT PARAMETERS R - deconvolved signal. array[0..M-N]. NOTE: deconvolution is unstable process and may result in division by zero (if your response function is degenerate, i.e. has zero Fourier coefficient). NOTE: It is assumed that A is zero at T<0, B is zero too. If one or both functions have non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************/ public static void convc1dinv(ref AP.Complex[] a, int m, ref AP.Complex[] b, int n, ref AP.Complex[] r) { int i = 0; int p = 0; double[] buf = new double[0]; double[] buf2 = new double[0]; ftbase.ftplan plan = new ftbase.ftplan(); AP.Complex c1 = 0; AP.Complex c2 = 0; AP.Complex c3 = 0; double t = 0; System.Diagnostics.Debug.Assert(n>0 & m>0 & n<=m, "ConvC1DInv: incorrect N or M!"); p = ftbase.ftbasefindsmooth(m); ftbase.ftbasegeneratecomplexfftplan(p, ref plan); buf = new double[2*p]; for(i=0; i<=m-1; i++) { buf[2*i+0] = a[i].x; buf[2*i+1] = a[i].y; } for(i=m; i<=p-1; i++) { buf[2*i+0] = 0; buf[2*i+1] = 0; } buf2 = new double[2*p]; for(i=0; i<=n-1; i++) { buf2[2*i+0] = b[i].x; buf2[2*i+1] = b[i].y; } for(i=n; i<=p-1; i++) { buf2[2*i+0] = 0; buf2[2*i+1] = 0; } ftbase.ftbaseexecuteplan(ref buf, 0, p, ref plan); ftbase.ftbaseexecuteplan(ref buf2, 0, p, ref plan); for(i=0; i<=p-1; i++) { c1.x = buf[2*i+0]; c1.y = buf[2*i+1]; c2.x = buf2[2*i+0]; c2.y = buf2[2*i+1]; c3 = c1/c2; buf[2*i+0] = c3.x; buf[2*i+1] = -c3.y; } ftbase.ftbaseexecuteplan(ref buf, 0, p, ref plan); t = (double)(1)/(double)(p); r = new AP.Complex[m-n+1]; for(i=0; i<=m-n; i++) { r[i].x = +(t*buf[2*i+0]); r[i].y = -(t*buf[2*i+1]); } }