1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.math4.transform;
18
19 import java.util.function.DoubleUnaryOperator;
20 import java.util.function.Supplier;
21 import org.junit.Test;
22 import org.junit.jupiter.api.Assertions;
23 import org.apache.commons.rng.UniformRandomProvider;
24 import org.apache.commons.rng.simple.RandomSource;
25 import org.apache.commons.numbers.complex.Complex;
26 import org.apache.commons.numbers.core.Precision;
27 import org.apache.commons.math3.analysis.UnivariateFunction;
28 import org.apache.commons.math3.analysis.function.Sin;
29 import org.apache.commons.math3.analysis.function.Sinc;
30
31
32
33
34
35
36
37 public final class FastFourierTransformerTest {
38 private static final Sin SIN_FUNCTION = new Sin();
39 private static final DoubleUnaryOperator SIN = x -> SIN_FUNCTION.value(x);
40
41
42 private static final UniformRandomProvider RNG = RandomSource.MWC_256.create();
43
44
45 private static final double EPSILON = Math.ulp(1.0);
46
47
48
49 @Test
50 public void testTransformComplexSizeNotAPowerOfTwo() {
51 final int n = 127;
52 final Complex[] x = createComplexData(n);
53 for (FastFourierTransform.Norm norm : FastFourierTransform.Norm.values()) {
54 for (boolean type : new boolean[] {true, false}) {
55 final FastFourierTransform fft = new FastFourierTransform(norm, type);
56 Assertions.assertThrows(IllegalArgumentException.class, () -> fft.apply(x),
57 () -> norm + ", " + type);
58 }
59 }
60 }
61
62 @Test
63 public void testTransformRealSizeNotAPowerOfTwo() {
64 final int n = 127;
65 final double[] x = createRealData(n);
66 for (FastFourierTransform.Norm norm : FastFourierTransform.Norm.values()) {
67 for (boolean type : new boolean[] {true, false}) {
68 final FastFourierTransform fft = new FastFourierTransform(norm, type);
69 Assertions.assertThrows(IllegalArgumentException.class, () -> fft.apply(x),
70 () -> norm + ", " + type);
71 }
72 }
73 }
74
75 @Test
76 public void testTransformFunctionSizeNotAPowerOfTwo() {
77 final int n = 127;
78 for (FastFourierTransform.Norm norm : FastFourierTransform.Norm.values()) {
79 for (boolean type : new boolean[] {true, false}) {
80 final FastFourierTransform fft = new FastFourierTransform(norm, type);
81 Assertions.assertThrows(IllegalArgumentException.class, () -> fft.apply(SIN, 0.0, Math.PI, n),
82 () -> norm + ", " + type);
83 }
84 }
85 }
86
87 @Test
88 public void testTransformFunctionNotStrictlyPositiveNumberOfSamples() {
89 final int n = -128;
90 for (FastFourierTransform.Norm norm : FastFourierTransform.Norm.values()) {
91 for (boolean type : new boolean[] {true, false}) {
92 final FastFourierTransform fft = new FastFourierTransform(norm, type);
93 Assertions.assertThrows(IllegalArgumentException.class, () -> fft.apply(SIN, 0.0, Math.PI, n),
94 () -> norm + ", " + type);
95 }
96 }
97 }
98
99 @Test
100 public void testTransformFunctionInvalidBounds() {
101 final int n = 128;
102 for (FastFourierTransform.Norm norm : FastFourierTransform.Norm.values()) {
103 for (boolean type : new boolean[] {true, false}) {
104 final FastFourierTransform fft = new FastFourierTransform(norm, type);
105 Assertions.assertThrows(IllegalArgumentException.class, () -> fft.apply(SIN, Math.PI, 0.0, n),
106 () -> norm + ", " + type);
107 }
108 }
109 }
110
111
112
113 private static Complex[] createComplexData(final int n) {
114 final Complex[] data = new Complex[n];
115 for (int i = 0; i < n; i++) {
116 final double re = 2 * RNG.nextDouble() - 1;
117 final double im = 2 * RNG.nextDouble() - 1;
118 data[i] = Complex.ofCartesian(re, im);
119 }
120 return data;
121 }
122
123 private static double[] createRealData(final int n) {
124 final double[] data = new double[n];
125 for (int i = 0; i < n; i++) {
126 data[i] = 2 * RNG.nextDouble() - 1;
127 }
128 return data;
129 }
130
131
132 private static Complex[] dft(final Complex[] x, final int sgn) {
133 final int n = x.length;
134 final double[] cos = new double[n];
135 final double[] sin = new double[n];
136 final Complex[] y = new Complex[n];
137 for (int i = 0; i < n; i++) {
138 final double arg = 2.0 * Math.PI * i / n;
139 cos[i] = Math.cos(arg);
140 sin[i] = Math.sin(arg);
141 }
142 for (int i = 0; i < n; i++) {
143 double yr = 0.0;
144 double yi = 0.0;
145 for (int j = 0; j < n; j++) {
146 final int index = (i * j) % n;
147 final double c = cos[index];
148 final double s = sin[index];
149 final double xr = x[j].getReal();
150 final double xi = x[j].getImaginary();
151 yr += c * xr - sgn * s * xi;
152 yi += sgn * s * xr + c * xi;
153 }
154 y[i] = Complex.ofCartesian(yr, yi);
155 }
156 return y;
157 }
158
159 private static void doTestTransformComplex(final int n,
160 final double tol,
161 final double absTol,
162 final FastFourierTransform.Norm normalization,
163 boolean inverse) {
164 final FastFourierTransform fft = new FastFourierTransform(normalization, inverse);
165 final Complex[] x = createComplexData(n);
166 final Complex[] expected;
167 final double s;
168 if (!inverse) {
169 expected = dft(x, -1);
170 if (normalization == FastFourierTransform.Norm.STD) {
171 s = 1.0;
172 } else {
173 s = 1.0 / Math.sqrt(n);
174 }
175 } else {
176 expected = dft(x, 1);
177 if (normalization == FastFourierTransform.Norm.STD) {
178 s = 1.0 / n;
179 } else {
180 s = 1.0 / Math.sqrt(n);
181 }
182 }
183 final Complex[] actual = fft.apply(x);
184 for (int i = 0; i < n; i++) {
185 final int index = i;
186 final double re = s * expected[i].getReal();
187 assertEqualsRelativeOrAbsolute(re, actual[i].getReal(), tol, absTol,
188 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
189 final double im = s * expected[i].getImaginary();
190 assertEqualsRelativeOrAbsolute(im, actual[i].getImaginary(), tol, absTol,
191 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
192 }
193 }
194
195 private static void doTestTransformReal(final int n,
196 final double tol,
197 final double absTol,
198 final FastFourierTransform.Norm normalization,
199 final boolean inverse) {
200 final FastFourierTransform fft = new FastFourierTransform(normalization, inverse);
201 final double[] x = createRealData(n);
202 final Complex[] xc = new Complex[n];
203 for (int i = 0; i < n; i++) {
204 xc[i] = Complex.ofCartesian(x[i], 0.0);
205 }
206 final Complex[] expected;
207 final double s;
208 if (!inverse) {
209 expected = dft(xc, -1);
210 if (normalization == FastFourierTransform.Norm.STD) {
211 s = 1.0;
212 } else {
213 s = 1.0 / Math.sqrt(n);
214 }
215 } else {
216 expected = dft(xc, 1);
217 if (normalization == FastFourierTransform.Norm.STD) {
218 s = 1.0 / n;
219 } else {
220 s = 1.0 / Math.sqrt(n);
221 }
222 }
223 final Complex[] actual = fft.apply(x);
224 for (int i = 0; i < n; i++) {
225 final int index = i;
226 final double re = s * expected[i].getReal();
227 assertEqualsRelativeOrAbsolute(re, actual[i].getReal(), tol, absTol,
228 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
229 final double im = s * expected[i].getImaginary();
230 assertEqualsRelativeOrAbsolute(im, actual[i].getImaginary(), tol, absTol,
231 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
232 }
233 }
234
235 private static void doTestTransformFunction(final DoubleUnaryOperator f,
236 final double min,
237 final double max,
238 int n,
239 final double tol,
240 final double absTol,
241 final FastFourierTransform.Norm normalization,
242 final boolean inverse) {
243 final FastFourierTransform fft = new FastFourierTransform(normalization, inverse);
244 final Complex[] x = new Complex[n];
245 for (int i = 0; i < n; i++) {
246 final double t = min + i * (max - min) / n;
247 x[i] = Complex.ofCartesian(f.applyAsDouble(t), 0);
248 }
249 final Complex[] expected;
250 final double s;
251 if (!inverse) {
252 expected = dft(x, -1);
253 if (normalization == FastFourierTransform.Norm.STD) {
254 s = 1.0;
255 } else {
256 s = 1.0 / Math.sqrt(n);
257 }
258 } else {
259 expected = dft(x, 1);
260 if (normalization == FastFourierTransform.Norm.STD) {
261 s = 1.0 / n;
262 } else {
263 s = 1.0 / Math.sqrt(n);
264 }
265 }
266 final Complex[] actual = fft.apply(f, min, max, n);
267 for (int i = 0; i < n; i++) {
268 final int index = i;
269 final double re = s * expected[i].getReal();
270 assertEqualsRelativeOrAbsolute(re, actual[i].getReal(), tol, absTol,
271 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
272 final double im = s * expected[i].getImaginary();
273 assertEqualsRelativeOrAbsolute(im, actual[i].getImaginary(), tol, absTol,
274 () -> String.format("%s, %s, %d, %d", normalization, inverse, n, index));
275 }
276 }
277
278 private static void assertEqualsRelativeOrAbsolute(double expected, double actual,
279 double relativeTolerance, double absoluteTolerance, Supplier<String> msg) {
280 if (!(Precision.equals(expected, actual, absoluteTolerance) ||
281 Precision.equalsWithRelativeTolerance(expected, actual, relativeTolerance))) {
282
283 final double absoluteMax = Math.max(Math.abs(expected), Math.abs(actual));
284 final double abs = Math.abs(expected - actual);
285 final double rel = abs / absoluteMax;
286 final Supplier<String> message = () -> String.format("%s: rel=%s, abs=%s", msg.get(), rel, abs);
287
288 Assertions.assertEquals(expected, actual, message);
289 }
290 }
291
292
293
294 @Test
295 public void testTransformComplex() {
296 final FastFourierTransform.Norm[] norm = FastFourierTransform.Norm.values();
297 for (int i = 0; i < norm.length; i++) {
298 for (boolean type : new boolean[] {true, false}) {
299 doTestTransformComplex(2, 1e-15, EPSILON, norm[i], type);
300 doTestTransformComplex(4, 1e-14, EPSILON, norm[i], type);
301 doTestTransformComplex(8, 1e-13, EPSILON, norm[i], type);
302 doTestTransformComplex(16, 1e-13, EPSILON, norm[i], type);
303 doTestTransformComplex(32, 1e-12, EPSILON, norm[i], type);
304 doTestTransformComplex(64, 1e-11, EPSILON, norm[i], type);
305 doTestTransformComplex(128, 1e-11, EPSILON, norm[i], type);
306 }
307 }
308 }
309
310 @Test
311 public void testStandardTransformReal() {
312 final FastFourierTransform.Norm[] norm = FastFourierTransform.Norm.values();
313 for (int i = 0; i < norm.length; i++) {
314 for (boolean type : new boolean[] {true, false}) {
315 doTestTransformReal(2, 1e-15, EPSILON, norm[i], type);
316 doTestTransformReal(4, 1e-14, EPSILON, norm[i], type);
317 doTestTransformReal(8, 1e-13, 2 * EPSILON, norm[i], type);
318 doTestTransformReal(16, 1e-13, 2 * EPSILON, norm[i], type);
319 doTestTransformReal(32, 1e-12, 4 * EPSILON, norm[i], type);
320 doTestTransformReal(64, 1e-12, 4 * EPSILON, norm[i], type);
321 doTestTransformReal(128, 1e-11, 8 * EPSILON, norm[i], type);
322 }
323 }
324 }
325
326 @Test
327 public void testStandardTransformFunction() {
328 final UnivariateFunction sinc = new Sinc();
329 final DoubleUnaryOperator f = x -> sinc.value(x);
330
331 final double min = -Math.PI;
332 final double max = Math.PI;
333 final FastFourierTransform.Norm[] norm = FastFourierTransform.Norm.values();
334
335 for (int i = 0; i < norm.length; i++) {
336 for (boolean type : new boolean[] {true, false}) {
337 doTestTransformFunction(f, min, max, 2, 1e-15, 4 * EPSILON, norm[i], type);
338 doTestTransformFunction(f, min, max, 4, 1e-14, 4 * EPSILON, norm[i], type);
339 doTestTransformFunction(f, min, max, 8, 1e-14, 4 * EPSILON, norm[i], type);
340 doTestTransformFunction(f, min, max, 16, 1e-13, 4 * EPSILON, norm[i], type);
341 doTestTransformFunction(f, min, max, 32, 1e-13, 8 * EPSILON, norm[i], type);
342 doTestTransformFunction(f, min, max, 64, 1e-12, 16 * EPSILON, norm[i], type);
343 doTestTransformFunction(f, min, max, 128, 1e-11, 64 * EPSILON, norm[i], type);
344 }
345 }
346 }
347
348
349
350
351
352
353 @Test
354 public void testAdHocData() {
355 FastFourierTransform transformer;
356 Complex[] result;
357 double tolerance = 1e-12;
358
359 final double[] x = {1.3, 2.4, 1.7, 4.1, 2.9, 1.7, 5.1, 2.7};
360 final Complex[] y = {
361 Complex.ofCartesian(21.9, 0.0),
362 Complex.ofCartesian(-2.09497474683058, 1.91507575950825),
363 Complex.ofCartesian(-2.6, 2.7),
364 Complex.ofCartesian(-1.10502525316942, -4.88492424049175),
365 Complex.ofCartesian(0.1, 0.0),
366 Complex.ofCartesian(-1.10502525316942, 4.88492424049175),
367 Complex.ofCartesian(-2.6, -2.7),
368 Complex.ofCartesian(-2.09497474683058, -1.91507575950825)};
369
370 transformer = new FastFourierTransform(FastFourierTransform.Norm.STD);
371 result = transformer.apply(x);
372 for (int i = 0; i < result.length; i++) {
373 Assertions.assertEquals(y[i].getReal(), result[i].getReal(), tolerance);
374 Assertions.assertEquals(y[i].getImaginary(), result[i].getImaginary(), tolerance);
375 }
376
377 transformer = new FastFourierTransform(FastFourierTransform.Norm.STD, true);
378 result = transformer.apply(y);
379 for (int i = 0; i < result.length; i++) {
380 Assertions.assertEquals(x[i], result[i].getReal(), tolerance);
381 Assertions.assertEquals(0.0, result[i].getImaginary(), tolerance);
382 }
383
384 double[] x2 = {10.4, 21.6, 40.8, 13.6, 23.2, 32.8, 13.6, 19.2};
385 TransformUtils.scaleInPlace(x2, 1.0 / Math.sqrt(x2.length));
386 Complex[] y2 = y;
387
388 transformer = new FastFourierTransform(FastFourierTransform.Norm.UNIT);
389 result = transformer.apply(y2);
390 for (int i = 0; i < result.length; i++) {
391 Assertions.assertEquals(x2[i], result[i].getReal(), tolerance);
392 Assertions.assertEquals(0.0, result[i].getImaginary(), tolerance);
393 }
394
395 transformer = new FastFourierTransform(FastFourierTransform.Norm.UNIT, true);
396 result = transformer.apply(x2);
397 for (int i = 0; i < result.length; i++) {
398 Assertions.assertEquals(y2[i].getReal(), result[i].getReal(), tolerance);
399 Assertions.assertEquals(y2[i].getImaginary(), result[i].getImaginary(), tolerance);
400 }
401 }
402
403
404
405
406 @Test
407 public void testSinFunction() {
408 FastFourierTransform transformer;
409 Complex[] result;
410 int size = 1 << 8;
411 double tolerance = 1e-12;
412
413 double min = 0.0;
414 double max = 2 * Math.PI;
415 transformer = new FastFourierTransform(FastFourierTransform.Norm.STD);
416 result = transformer.apply(SIN, min, max, size);
417 Assertions.assertEquals(0.0, result[1].getReal(), tolerance);
418 Assertions.assertEquals(-(size >> 1), result[1].getImaginary(), tolerance);
419 Assertions.assertEquals(0.0, result[size - 1].getReal(), tolerance);
420 Assertions.assertEquals(size >> 1, result[size - 1].getImaginary(), tolerance);
421 for (int i = 0; i < size - 1; i += i == 0 ? 2 : 1) {
422 Assertions.assertEquals(0.0, result[i].getReal(), tolerance);
423 Assertions.assertEquals(0.0, result[i].getImaginary(), tolerance);
424 }
425
426 min = -Math.PI;
427 max = Math.PI;
428 transformer = new FastFourierTransform(FastFourierTransform.Norm.STD, true);
429 result = transformer.apply(SIN, min, max, size);
430 Assertions.assertEquals(0.0, result[1].getReal(), tolerance);
431 Assertions.assertEquals(-0.5, result[1].getImaginary(), tolerance);
432 Assertions.assertEquals(0.0, result[size - 1].getReal(), tolerance);
433 Assertions.assertEquals(0.5, result[size - 1].getImaginary(), tolerance);
434 for (int i = 0; i < size - 1; i += i == 0 ? 2 : 1) {
435 Assertions.assertEquals(0.0, result[i].getReal(), tolerance);
436 Assertions.assertEquals(0.0, result[i].getImaginary(), tolerance);
437 }
438 }
439 }