blob: 060165410cd2dc1f4e08285afd9eb5e64a418e25 [file] [log] [blame]
dario mambrocb971842020-03-28 00:22:33 +01001/*
hayati ayguen63794b22020-03-29 03:14:43 +02002 Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
3 Copyright (c) 2020 Dario Mambro ( dario.mambro@gmail.com )
4 Copyright (c) 2020 Hayati Ayguen ( h_ayguen@web.de )
dario mambrocb971842020-03-28 00:22:33 +01005
6 Small test & bench for PFFFT, comparing its performance with the scalar
7 FFTPACK, FFTW, and Apple vDSP
8
9 How to build:
10
11 on linux, with fftw3:
12 gcc -o test_pffft -DHAVE_FFTW -msse -mfpmath=sse -O3 -Wall -W pffft.c
13 test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f -lm
14
15 on macos, without fftw3:
16 clang -o test_pffft -DHAVE_VECLIB -O3 -Wall -W pffft.c test_pffft.c fftpack.c
17 -L/usr/local/lib -I/usr/local/include/ -framework Accelerate
18
19 on macos, with fftw3:
20 clang -o test_pffft -DHAVE_FFTW -DHAVE_VECLIB -O3 -Wall -W pffft.c
21 test_pffft.c fftpack.c -L/usr/local/lib -I/usr/local/include/ -lfftw3f
22 -framework Accelerate
23
24 as alternative: replace clang by gcc.
25
26 on windows, with visual c++:
27 cl /Ox -D_USE_MATH_DEFINES /arch:SSE test_pffft.c pffft.c fftpack.c
28
29 build without SIMD instructions:
30 gcc -o test_pffft -DPFFFT_SIMD_DISABLE -O3 -Wall -W pffft.c test_pffft.c
31 fftpack.c -lm
32
33 */
34
35#include "pffft.hpp"
36
37#include <assert.h>
38#include <math.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <time.h>
43
hayati ayguenc974c1d2020-03-29 03:39:30 +020044/* define own constants required to turn off g++ extensions .. */
45#ifndef M_PI
46 #define M_PI 3.14159265358979323846 /* pi */
47#endif
48
dario mambrocb971842020-03-28 00:22:33 +010049/* maximum allowed phase error in degree */
50#define DEG_ERR_LIMIT 1E-4
51
52/* maximum allowed magnitude error in amplitude (of 1.0 or 1.1) */
53#define MAG_ERR_LIMIT 1E-6
54
55#define PRINT_SPEC 0
56
57#define PWR2LOG(PWR) ((PWR) < 1E-30 ? 10.0 * log10(1E-30) : 10.0 * log10(PWR))
58
59template<typename T>
60bool
61Ttest(int N, bool useOrdered)
62{
hayati ayguen63794b22020-03-29 03:14:43 +020063 typedef typename pffft::Fft<T> Fft;
64 typedef typename Fft::Scalar Scalar;
dario mambrocb971842020-03-28 00:22:33 +010065
hayati ayguen61ec6da2020-03-29 08:48:01 +020066 const bool cplx = Fft::isComplexTransform();
dario mambrocb971842020-03-28 00:22:33 +010067
hayati ayguen61ec6da2020-03-29 08:48:01 +020068 const double EXPECTED_DYN_RANGE = Fft::isDoubleScalar() ? 215.0 : 140.0;
dario mambrocb971842020-03-28 00:22:33 +010069
dario mambrocb971842020-03-28 00:22:33 +010070 int k, j, m, iter, kmaxOther;
71 bool retError = false;
72 double freq, dPhi, phi, phi0;
73 double pwr, pwrCar, pwrOther, err, errSum, mag, expextedMag;
74 double amp = 1.0;
75
76 assert(pffft::isPowerOfTwo(N));
77
hayati ayguen61ec6da2020-03-29 08:48:01 +020078 Fft fft = Fft(N); // instantiate and prepareLength() for length N
dario mambrocb971842020-03-28 00:22:33 +010079
hayati ayguen61ec6da2020-03-29 08:48:01 +020080 T* X = fft.allocateOrigin(); // for X = input vector
81 std::complex<Scalar>* Y = fft.allocateSpectrum(); // for Y = forward(X)
82 Scalar* R = fft.allocateInternalLayout(); // for R = forwardInternalLayout(X)
83 T* Z = fft.allocateOrigin(); // for Z = inverse(Y) = inverse( forward(X) )
84 // or Z = inverseInternalLayout(R)
85
86 // work with complex - without the capabilities of a higher c++ standard
87 Scalar* Xs = reinterpret_cast<Scalar*>(X); // for X = input vector
88 Scalar* Ys = reinterpret_cast<Scalar*>(Y); // for Y = forward(X)
89 Scalar* Zs = reinterpret_cast<Scalar*>(Z); // for Z = inverse(Y) = inverse( forward(X) )
dario mambrocb971842020-03-28 00:22:33 +010090
91 for (k = m = 0; k < (cplx ? N : (1 + N / 2)); k += N / 16, ++m) {
92 amp = ((m % 3) == 0) ? 1.0F : 1.1F;
93 freq = (k < N / 2) ? ((double)k / N) : ((double)(k - N) / N);
94 dPhi = 2.0 * M_PI * freq;
95 if (dPhi < 0.0)
96 dPhi += 2.0 * M_PI;
97
98 iter = -1;
99 while (1) {
100 ++iter;
101
102 if (iter)
103 printf("bin %d: dphi = %f for freq %f\n", k, dPhi, freq);
104
105 /* generate cosine carrier as time signal - start at defined phase phi0 */
106 phi = phi0 =
107 (m % 4) * 0.125 * M_PI; /* have phi0 < 90 deg to be normalized */
108 for (j = 0; j < N; ++j) {
109 if (cplx) {
110 Xs[2 * j] = amp * cos(phi); /* real part */
111 Xs[2 * j + 1] = amp * sin(phi); /* imag part */
112 } else
113 Xs[j] = amp * cos(phi); /* only real part */
114
115 /* phase increment .. stay normalized - cos()/sin() might degrade! */
116 phi += dPhi;
117 if (phi >= M_PI)
118 phi -= 2.0 * M_PI;
119 }
120
121 /* forward transform from X --> Y .. using work buffer W */
122 if (useOrdered)
123 fft.forward(X, Y);
124 else {
125 fft.forwardInternalLayout(X, R); /* temporarily use R for reordering */
hayati ayguen61ec6da2020-03-29 08:48:01 +0200126 fft.reorderSpectrum(R, Y, PFFFT_FORWARD); /* reorder into Y[] for power calculations */
dario mambrocb971842020-03-28 00:22:33 +0100127 }
128
129 pwrOther = -1.0;
130 pwrCar = 0;
131
132 /* for positive frequencies: 0 to 0.5 * samplerate */
133 /* and also for negative frequencies: -0.5 * samplerate to 0 */
134 for (j = 0; j < (cplx ? N : (1 + N / 2)); ++j) {
135 if (!cplx && !j) /* special treatment for DC for real input */
136 pwr = Ys[j] * Ys[j];
137 else if (!cplx && j == N / 2) /* treat 0.5 * samplerate */
138 pwr = Ys[1] *
139 Ys[1]; /* despite j (for freq calculation) we have index 1 */
140 else
141 pwr = Ys[2 * j] * Ys[2 * j] + Ys[2 * j + 1] * Ys[2 * j + 1];
142 if (iter || PRINT_SPEC)
143 printf("%s fft %d: pwr[j = %d] = %g == %f dB\n",
144 (cplx ? "cplx" : "real"),
145 N,
146 j,
147 pwr,
148 PWR2LOG(pwr));
149 if (k == j)
150 pwrCar = pwr;
151 else if (pwr > pwrOther) {
152 pwrOther = pwr;
153 kmaxOther = j;
154 }
155 }
156
157 if (PWR2LOG(pwrCar) - PWR2LOG(pwrOther) < EXPECTED_DYN_RANGE) {
158 printf("%s fft %d amp %f iter %d:\n",
159 (cplx ? "cplx" : "real"),
160 N,
161 amp,
162 iter);
163 printf(" carrier power at bin %d: %g == %f dB\n",
164 k,
165 pwrCar,
166 PWR2LOG(pwrCar));
167 printf(" carrier mag || at bin %d: %g\n", k, sqrt(pwrCar));
168 printf(" max other pwr at bin %d: %g == %f dB\n",
169 kmaxOther,
170 pwrOther,
171 PWR2LOG(pwrOther));
172 printf(" dynamic range: %f dB\n\n",
173 PWR2LOG(pwrCar) - PWR2LOG(pwrOther));
174 retError = true;
175 if (iter == 0)
176 continue;
177 }
178
179 if (k > 0 && k != N / 2) {
180 phi = atan2(Ys[2 * k + 1], Ys[2 * k]);
181 if (fabs(phi - phi0) > DEG_ERR_LIMIT * M_PI / 180.0) {
182 retError = true;
183 printf("%s fft %d bin %d amp %f : phase mismatch! phase = %f deg "
184 "expected = %f deg\n",
185 (cplx ? "cplx" : "real"),
186 N,
187 k,
188 amp,
189 phi * 180.0 / M_PI,
190 phi0 * 180.0 / M_PI);
191 }
192 }
193
194 expextedMag = cplx ? amp : ((k == 0 || k == N / 2) ? amp : (amp / 2));
195 mag = sqrt(pwrCar) / N;
196 if (fabs(mag - expextedMag) > MAG_ERR_LIMIT) {
197 retError = true;
198 printf("%s fft %d bin %d amp %f : mag = %g expected = %g\n",
199 (cplx ? "cplx" : "real"),
200 N,
201 k,
202 amp,
203 mag,
204 expextedMag);
205 }
206
207 /* now convert spectrum back */
hayati ayguen61ec6da2020-03-29 08:48:01 +0200208 if (useOrdered)
209 fft.inverse(Y, Z);
210 else
211 fft.inverseInternalLayout(R, Z); /* inverse() from internal Layout */
dario mambrocb971842020-03-28 00:22:33 +0100212
213 errSum = 0.0;
214 for (j = 0; j < (cplx ? (2 * N) : N); ++j) {
215 /* scale back */
hayati ayguen61ec6da2020-03-29 08:48:01 +0200216 Zs[j] /= N;
dario mambrocb971842020-03-28 00:22:33 +0100217 /* square sum errors over real (and imag parts) */
218 err = (Xs[j] - Zs[j]) * (Xs[j] - Zs[j]);
219 errSum += err;
220 }
221
222 if (errSum > N * 1E-7) {
223 retError = true;
224 printf("%s fft %d bin %d : inverse FFT doesn't match original signal! "
225 "errSum = %g ; mean err = %g\n",
226 (cplx ? "cplx" : "real"),
227 N,
228 k,
229 errSum,
230 errSum / N);
231 }
232
233 break;
234 }
235 }
236 pffft::alignedFree(X);
237 pffft::alignedFree(Y);
238 pffft::alignedFree(Z);
hayati ayguen61ec6da2020-03-29 08:48:01 +0200239 pffft::alignedFree(R);
dario mambrocb971842020-03-28 00:22:33 +0100240
241 return retError;
242}
243
244bool
245test(int N, bool useComplex, bool useOrdered)
246{
247 if (useComplex) {
hayati ayguen7b3ca7d2020-03-29 03:18:35 +0200248 return Ttest< std::complex<float> >(N, useOrdered) &&
249 Ttest< std::complex<double> >(N, useOrdered);
dario mambrocb971842020-03-28 00:22:33 +0100250 } else {
hayati ayguen7b3ca7d2020-03-29 03:18:35 +0200251 return Ttest<float>(N, useOrdered) &&
252 Ttest<double>(N, useOrdered);
dario mambrocb971842020-03-28 00:22:33 +0100253 }
254}
255
256int
257main(int argc, char** argv)
258{
259 int N, result, resN, resAll, k, resNextPw2, resIsPw2, resFFT;
260
261 int inp_power_of_two[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 511, 512, 513 };
262 int ref_power_of_two[] = { 1, 2, 4, 4, 8, 8, 8, 8, 16, 512, 512, 1024 };
263
264 resNextPw2 = 0;
265 resIsPw2 = 0;
266 for (k = 0; k < (sizeof(inp_power_of_two) / sizeof(inp_power_of_two[0]));
267 ++k) {
268 N = pffft::nextPowerOfTwo(inp_power_of_two[k]);
269 if (N != ref_power_of_two[k]) {
270 resNextPw2 = 1;
271 printf("pffft_next_power_of_two(%d) does deliver %d, which is not "
272 "reference result %d!\n",
273 inp_power_of_two[k],
274 N,
275 ref_power_of_two[k]);
276 }
277
278 result = pffft::isPowerOfTwo(inp_power_of_two[k]);
279 if (inp_power_of_two[k] == ref_power_of_two[k]) {
280 if (!result) {
281 resIsPw2 = 1;
282 printf("pffft_is_power_of_two(%d) delivers false; expected true!\n",
283 inp_power_of_two[k]);
284 }
285 } else {
286 if (result) {
287 resIsPw2 = 1;
288 printf("pffft_is_power_of_two(%d) delivers true; expected false!\n",
289 inp_power_of_two[k]);
290 }
291 }
292 }
293 if (!resNextPw2)
294 printf("tests for pffft_next_power_of_two() succeeded successfully.\n");
295 if (!resIsPw2)
296 printf("tests for pffft_is_power_of_two() succeeded successfully.\n");
297
298 resFFT = 0;
299 for (N = 32; N <= 65536; N *= 2) {
300 result = test(N, 1 /* cplx fft */, 1 /* useOrdered */);
301 resN = result;
302 resFFT |= result;
303
304 result = test(N, 0 /* cplx fft */, 1 /* useOrdered */);
305 resN |= result;
306 resFFT |= result;
307
308 result = test(N, 1 /* cplx fft */, 0 /* useOrdered */);
309 resN |= result;
310 resFFT |= result;
311
312 result = test(N, 0 /* cplx fft */, 0 /* useOrdered */);
313 resN |= result;
314 resFFT |= result;
315
316 if (!resN)
317 printf("tests for size %d succeeded successfully.\n", N);
318 }
319
320 if (!resFFT)
hayati ayguen63794b22020-03-29 03:14:43 +0200321 printf("all pffft transform tests (FORWARD/BACKWARD, REAL/COMPLEX, float/double) "
dario mambrocb971842020-03-28 00:22:33 +0100322 "succeeded successfully.\n");
323
324 resAll = resNextPw2 | resIsPw2 | resFFT;
325 if (!resAll)
326 printf("all tests succeeded successfully.\n");
327 else
328 printf("there are failed tests!\n");
329
330 return resAll;
331}