View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Test case for {@link FastFourierTransform}.
33   * <p>
34   * FFT algorithm is exact, the small tolerance number is used only
35   * to account for round-off errors.
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      /** RNG. */
42      private static final UniformRandomProvider RNG = RandomSource.MWC_256.create();
43  
44      /** Minimum relative error epsilon. Can be used a small absolute delta epsilon. */
45      private static final double EPSILON = Math.ulp(1.0);
46  
47      // Precondition checks.
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     // Utility methods for checking (successful) transforms.
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     /** Naive implementation of DFT, for reference. */
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             // Custom supplier to provide relative and absolute error
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             // Re-use assertEquals to obtain the formatting
288             Assertions.assertEquals(expected, actual, message);
289         }
290     }
291 
292     // Tests of standard transform (when data is valid).
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     // Additional tests for 1D data.
349 
350     /**
351      * Test of transformer for the ad hoc data taken from Mathematica.
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      * Test of transformer for the sine function.
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 }