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.Arrays;
20  import java.util.Collection;
21  import java.util.function.DoubleUnaryOperator;
22  
23  import org.junit.Assert;
24  import org.junit.Test;
25  import org.junit.runner.RunWith;
26  import org.junit.runners.Parameterized;
27  import org.junit.runners.Parameterized.Parameters;
28  
29  import org.apache.commons.math3.analysis.UnivariateFunction;
30  import org.apache.commons.math3.analysis.function.Sin;
31  import org.apache.commons.math3.analysis.function.Sinc;
32  
33  /**
34   * Test case for {@link FastSineTransform}.
35   * <p>
36   * FST algorithm is exact, the small tolerance number is used only
37   * to account for round-off errors.
38   */
39  @RunWith(value = Parameterized.class)
40  public final class FastSineTransformerTest extends RealTransformerAbstractTest {
41  
42      private final FastSineTransform.Norm normalization;
43  
44      private final int[] invalidDataSize;
45  
46      private final double[] relativeTolerance;
47  
48      private final int[] validDataSize;
49  
50      public FastSineTransformerTest(final FastSineTransform.Norm normalization) {
51          this.normalization = normalization;
52          this.validDataSize = new int[] {
53              1, 2, 4, 8, 16, 32, 64, 128
54          };
55          this.invalidDataSize = new int[] {
56              129
57          };
58          this.relativeTolerance = new double[] {
59              1e-15, 1e-15, 1e-14, 1e-14, 1e-13, 1e-12, 1e-11, 1e-11
60          };
61      }
62  
63      /**
64       * Returns an array containing {@code true, false} in order to check both
65       * standard and orthogonal DSTs.
66       *
67       * @return an array of parameters for this parameterized test.
68       */
69      @Parameters
70      public static Collection<Object[]> data() {
71          final FastSineTransform.Norm[] normalization = FastSineTransform.Norm.values();
72          final Object[][] data = new FastSineTransform.Norm[normalization.length][1];
73          for (int i = 0; i < normalization.length; i++) {
74              data[i][0] = normalization[i];
75          }
76          return Arrays.asList(data);
77      }
78  
79      /**
80       * {@inheritDoc}
81       *
82       * Overriding the default implementation allows to ensure that the first
83       * element of the data set is zero.
84       */
85      @Override
86      double[] createRealData(final int n) {
87          final double[] data = super.createRealData(n);
88          data[0] = 0;
89          return data;
90      }
91  
92      @Override
93      RealTransform createRealTransformer(boolean inverse) {
94          return new FastSineTransform(normalization, inverse);
95      }
96  
97      @Override
98      int getInvalidDataSize(final int i) {
99          return invalidDataSize[i];
100     }
101 
102     @Override
103     int getNumberOfInvalidDataSizes() {
104         return invalidDataSize.length;
105     }
106 
107     @Override
108     int getNumberOfValidDataSizes() {
109         return validDataSize.length;
110     }
111 
112     @Override
113     double getRelativeTolerance(final int i) {
114         return relativeTolerance[i];
115     }
116 
117     @Override
118     int getValidDataSize(final int i) {
119         return validDataSize[i];
120     }
121 
122     @Override
123     DoubleUnaryOperator getValidFunction() {
124         final UnivariateFunction sinc = new Sinc();
125         return x -> sinc.value(x);
126     }
127 
128     @Override
129     double getValidLowerBound() {
130         return 0.0;
131     }
132 
133     @Override
134     double getValidUpperBound() {
135         return Math.PI;
136     }
137 
138     @Override
139     double[] transform(final double[] x, boolean inverse) {
140         final int n = x.length;
141         final double[] y = new double[n];
142         final double[] sin = new double[2 * n];
143         for (int i = 0; i < sin.length; i++) {
144             sin[i] = Math.sin(Math.PI * i / n);
145         }
146         for (int j = 0; j < n; j++) {
147             double yj = 0.0;
148             for (int i = 0; i < n; i++) {
149                 yj += x[i] * sin[(i * j) % sin.length];
150             }
151             y[j] = yj;
152         }
153         final double s;
154         if (!inverse) {
155             if (normalization == FastSineTransform.Norm.STD) {
156                 s = 1;
157             } else if (normalization == FastSineTransform.Norm.ORTHO) {
158                 s = Math.sqrt(2d / n);
159             } else {
160                 throw new IllegalStateException();
161             }
162         } else {
163             if (normalization == FastSineTransform.Norm.STD) {
164                 s = 2d / n;
165             } else if (normalization == FastSineTransform.Norm.ORTHO) {
166                 s = Math.sqrt(2d / n);
167             } else {
168                 throw new IllegalStateException();
169             }
170         }
171 
172         TransformUtils.scaleInPlace(y, s);
173         return y;
174     }
175 
176     // Additional tests.
177 
178     @Test
179     public void testTransformRealFirstElementNotZero() {
180         final double[] data = new double[] {
181             1, 1, 1, 1
182         };
183         for (boolean type : new boolean[] {true, false}) {
184             try {
185                 final RealTransform transformer = createRealTransformer(type);
186                 transformer.apply(data);
187                 Assert.fail("type=" + type);
188             } catch (IllegalArgumentException e) {
189                 // Expected: do nothing
190             }
191         }
192     }
193 
194     // Additional (legacy) tests.
195 
196     /**
197      * Test of transformer for the ad hoc data.
198      */
199     @Test
200     public void testAdHocData() {
201         FastSineTransform transformer;
202         double tolerance = 1e-12;
203 
204         final double[] x = {
205             0, 1, 2, 3, 4, 5, 6, 7
206         };
207         final double[] y = {
208             0.0, 20.1093579685034, -9.65685424949238,
209             5.98642305066196, -4.0, 2.67271455167720,
210             -1.65685424949238, 0.795649469518633
211         };
212 
213         transformer = new FastSineTransform(FastSineTransform.Norm.STD);
214         double[] result = transformer.apply(x);
215         for (int i = 0; i < result.length; i++) {
216             Assert.assertEquals(y[i], result[i], tolerance);
217         }
218 
219         transformer = new FastSineTransform(FastSineTransform.Norm.STD, true);
220         result = transformer.apply(y);
221         for (int i = 0; i < result.length; i++) {
222             Assert.assertEquals(x[i], result[i], tolerance);
223         }
224 
225         TransformUtils.scaleInPlace(x, Math.sqrt(x.length / 2d));
226         transformer = new FastSineTransform(FastSineTransform.Norm.ORTHO);
227 
228         result = transformer.apply(y);
229         for (int i = 0; i < result.length; i++) {
230             Assert.assertEquals(x[i], result[i], tolerance);
231         }
232 
233         transformer = new FastSineTransform(FastSineTransform.Norm.ORTHO, true);
234         result = transformer.apply(x);
235         for (int i = 0; i < result.length; i++) {
236             Assert.assertEquals(y[i], result[i], tolerance);
237         }
238     }
239 
240     /**
241      * Test of transformer for the sine function.
242      */
243     @Test
244     public void testSinFunction() {
245         final UnivariateFunction sinFunction = new Sin();
246         final DoubleUnaryOperator f = x -> sinFunction.value(x);
247         final FastSineTransform transformer = new FastSineTransform(FastSineTransform.Norm.STD);
248         double tolerance = 1e-12;
249         int size = 1 << 8;
250 
251         double min = 0.0;
252         double max = 2 * Math.PI;
253         double[] result = transformer.apply(f, min, max, size);
254         Assert.assertEquals(size >> 1, result[2], tolerance);
255         for (int i = 0; i < size; i += i == 1 ? 2 : 1) {
256             Assert.assertEquals(0.0, result[i], tolerance);
257         }
258 
259         min = -Math.PI;
260         max = Math.PI;
261         result = transformer.apply(f, min, max, size);
262         Assert.assertEquals(-(size >> 1), result[2], tolerance);
263         for (int i = 0; i < size; i += i == 1 ? 2 : 1) {
264             Assert.assertEquals(0.0, result[i], tolerance);
265         }
266     }
267 
268     /**
269      * Test of parameters for the transformer.
270      */
271     @Test
272     public void testParameters() throws Exception {
273         final UnivariateFunction sinFunction = new Sin();
274         final DoubleUnaryOperator f = x -> sinFunction.value(x);
275         final FastSineTransform transformer = new FastSineTransform(FastSineTransform.Norm.STD);
276 
277         try {
278             // bad interval
279             transformer.apply(f, 1, -1, 64);
280             Assert.fail("Expecting IllegalArgumentException - bad interval");
281         } catch (IllegalArgumentException ex) {
282             // expected
283         }
284         try {
285             // bad samples number
286             transformer.apply(f, -1, 1, 0);
287             Assert.fail("Expecting IllegalArgumentException - bad samples number");
288         } catch (IllegalArgumentException ex) {
289             // expected
290         }
291         try {
292             // bad samples number
293             transformer.apply(f, -1, 1, 100);
294             Assert.fail("Expecting IllegalArgumentException - bad samples number");
295         } catch (IllegalArgumentException ex) {
296             // expected
297         }
298     }
299 }