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 FastCosineTransform}.
35   * <p>
36   * FCT algorithm is exact, the small tolerance number is used only to account
37   * for round-off errors.
38   */
39  @RunWith(value = Parameterized.class)
40  public final class FastCosineTransformerTest
41      extends RealTransformerAbstractTest {
42  
43      private final FastCosineTransform.Norm normalization;
44  
45      private final int[] invalidDataSize;
46  
47      private final double[] relativeTolerance;
48  
49      private final int[] validDataSize;
50  
51      public FastCosineTransformerTest(final FastCosineTransform.Norm normalization) {
52          this.normalization = normalization;
53          this.validDataSize = new int[] {
54              2, 3, 5, 9, 17, 33, 65, 129
55          };
56          this.invalidDataSize = new int[] {
57              128
58          };
59          this.relativeTolerance = new double[] {
60              1e-15, 1e-15, 1e-14, 1e-13, 1e-13, 1e-12, 1e-11, 1e-10
61          };
62      }
63  
64      /**
65       * Returns an array containing {@code true, false} in order to
66       * check both standard and orthogonal DCTs.
67       *
68       * @return an array of parameters for this parameterized test
69       */
70      @Parameters
71      public static Collection<Object[]> data() {
72          final FastCosineTransform.Norm[] normalization = FastCosineTransform.Norm.values();
73          final Object[][] data = new FastCosineTransform.Norm[normalization.length][1];
74          for (int i = 0; i < normalization.length; i++) {
75              data[i][0] = normalization[i];
76          }
77          return Arrays.asList(data);
78      }
79  
80      @Override
81      RealTransform createRealTransformer(boolean inverse) {
82          return new FastCosineTransform(normalization, inverse);
83      }
84  
85      @Override
86      int getInvalidDataSize(final int i) {
87          return invalidDataSize[i];
88      }
89  
90      @Override
91      int getNumberOfInvalidDataSizes() {
92          return invalidDataSize.length;
93      }
94  
95      @Override
96      int getNumberOfValidDataSizes() {
97          return validDataSize.length;
98      }
99  
100     @Override
101     double getRelativeTolerance(final int i) {
102         return relativeTolerance[i];
103     }
104 
105     @Override
106     int getValidDataSize(final int i) {
107         return validDataSize[i];
108     }
109 
110     @Override
111     DoubleUnaryOperator getValidFunction() {
112         final UnivariateFunction sinc = new Sinc();
113         return x -> sinc.value(x);
114     }
115 
116     @Override
117     double getValidLowerBound() {
118         return 0.0;
119     }
120 
121     @Override
122     double getValidUpperBound() {
123         return Math.PI;
124     }
125 
126     @Override
127     double[] transform(final double[] x,
128                        final boolean inverse) {
129         final int n = x.length;
130         final double[] y = new double[n];
131         final double[] cos = new double[2 * (n - 1)];
132         for (int i = 0; i < cos.length; i++) {
133             cos[i] = Math.cos(Math.PI * i / (n - 1.0));
134         }
135         int sgn = 1;
136         for (int j = 0; j < n; j++) {
137             double yj = 0.5 * (x[0] + sgn * x[n - 1]);
138             for (int i = 1; i < n - 1; i++) {
139                 yj += x[i] * cos[(i * j) % cos.length];
140             }
141             y[j] = yj;
142             sgn *= -1;
143         }
144         final double s;
145         if (!inverse) {
146             if (normalization == FastCosineTransform.Norm.STD) {
147                 s = 1.0;
148             } else if (normalization == FastCosineTransform.Norm.ORTHO) {
149                 s = Math.sqrt(2.0 / (n - 1.0));
150             } else {
151                 throw new IllegalStateException();
152             }
153         } else {
154             if (normalization == FastCosineTransform.Norm.STD) {
155                 s = 2.0 / (n - 1.0);
156             } else if (normalization == FastCosineTransform.Norm.ORTHO) {
157                 s = Math.sqrt(2.0 / (n - 1.0));
158             } else {
159                 throw new IllegalStateException();
160             }
161         }
162         TransformUtils.scaleInPlace(y, s);
163         return y;
164     }
165 
166     // Additional tests.
167 
168     /** Test of transformer for the ad hoc data. */
169     @Test
170     public void testAdHocData() {
171         FastCosineTransform transformer;
172         double[] result;
173         double tolerance = 1e-12;
174 
175         final double[] x = {
176             0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0
177         };
178         final double[] y = {
179             172.0, -105.096569476353, 27.3137084989848, -12.9593152353742,
180             8.0, -5.78585076868676, 4.68629150101524, -4.15826451958632,
181             4.0
182         };
183 
184         transformer = new FastCosineTransform(FastCosineTransform.Norm.STD);
185         result = transformer.apply(x);
186         for (int i = 0; i < result.length; i++) {
187             Assert.assertEquals(y[i], result[i], tolerance);
188         }
189 
190         transformer = new FastCosineTransform(FastCosineTransform.Norm.STD, true);
191         result = transformer.apply(y);
192         for (int i = 0; i < result.length; i++) {
193             Assert.assertEquals(x[i], result[i], tolerance);
194         }
195 
196         TransformUtils.scaleInPlace(x, Math.sqrt(0.5 * (x.length - 1)));
197 
198         transformer = new FastCosineTransform(FastCosineTransform.Norm.ORTHO);
199         result = transformer.apply(y);
200         for (int i = 0; i < result.length; i++) {
201             Assert.assertEquals(x[i], result[i], tolerance);
202         }
203 
204         transformer = new FastCosineTransform(FastCosineTransform.Norm.ORTHO, true);
205         result = transformer.apply(x);
206         for (int i = 0; i < result.length; i++) {
207             Assert.assertEquals(y[i], result[i], tolerance);
208         }
209     }
210 
211     /** Test of parameters for the transformer. */
212     @Test
213     public void testParameters() throws Exception {
214         final UnivariateFunction sinFunction = new Sin();
215         final DoubleUnaryOperator f = x -> sinFunction.value(x);
216         final FastCosineTransform transformer = new FastCosineTransform(FastCosineTransform.Norm.STD);
217 
218         try {
219             // bad interval
220             transformer.apply(f, 1, -1, 65);
221             Assert.fail("Expecting IllegalArgumentException - bad interval");
222         } catch (IllegalArgumentException ex) {
223             // expected
224         }
225         try {
226             // bad samples number
227             transformer.apply(f, -1, 1, 1);
228             Assert.fail("Expecting IllegalArgumentException - bad samples number");
229         } catch (IllegalArgumentException ex) {
230             // expected
231         }
232         try {
233             // bad samples number
234             transformer.apply(f, -1, 1, 64);
235             Assert.fail("Expecting IllegalArgumentException - bad samples number");
236         } catch (IllegalArgumentException ex) {
237             // expected
238         }
239     }
240 
241     /** Test of transformer for the sine function. */
242     @Test
243     public void testSinFunction() {
244         final UnivariateFunction sinFunction = new Sin();
245         final DoubleUnaryOperator f = x -> sinFunction.value(x);
246         final FastCosineTransform transformer = new FastCosineTransform(FastCosineTransform.Norm.STD);
247         double tolerance = 1e-12;
248         int size = 9;
249 
250         final double[] expected = {
251             0.0, 3.26197262739567, 0.0, -2.17958042710327, 0.0,
252             -0.648846697642915, 0.0, -0.433545502649478, 0.0
253         };
254         double min = 0.0;
255         double max = 2.0 * Math.PI * size / (size - 1);
256         double[] result = transformer.apply(f, min, max, size);
257         for (int i = 0; i < size; i++) {
258             Assert.assertEquals(expected[i], result[i], tolerance);
259         }
260 
261         min = -Math.PI;
262         max = Math.PI * (size + 1) / (size - 1);
263         result = transformer.apply(f, min, max, size);
264         for (int i = 0; i < size; i++) {
265             Assert.assertEquals(-expected[i], result[i], tolerance);
266         }
267     }
268 }