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 org.junit.Assert;
20  import org.junit.Test;
21  
22  import java.util.function.DoubleUnaryOperator;
23  import org.apache.commons.rng.UniformRandomProvider;
24  import org.apache.commons.rng.simple.RandomSource;
25  
26  /**
27   * Abstract test for classes implementing the {@link RealTransform} interface.
28   * This abstract test handles the automatic generation of random data of various
29   * sizes. For each generated data array, actual values (returned by the
30   * transformer to be tested) are compared to expected values, returned by the
31   * {@link #transform(double[], boolean)} (to be implemented by the user: a naive method may
32   * be used). Methods are also provided to test that invalid parameters throw the
33   * expected exceptions.
34   *
35   * @since 3.0
36   */
37  public abstract class RealTransformerAbstractTest {
38      /** RNG. */
39      private static final UniformRandomProvider RNG = RandomSource.MWC_256.create();
40  
41      /**
42       * Returns a new instance of the {@link RealTransform} to be tested.
43       *
44       * @param inverse Whether to apply the inverse transform.
45       * @return a the transformer to be tested
46       */
47      abstract RealTransform createRealTransformer(boolean inverse);
48  
49      /**
50       * Returns an invalid data size. Transforms with this data size should
51       * trigger a {@link IllegalArgumentException}.
52       *
53       * @param i the index of the invalid data size ({@code 0 <= i <}
54       * {@link #getNumberOfInvalidDataSizes()}
55       * @return an invalid data size
56       */
57      abstract int getInvalidDataSize(int i);
58  
59      /**
60       * Returns the total number of invalid data sizes to be tested. If data
61       * array of any
62       * size can be handled by the {@link RealTransform} to be tested, this
63       * method should return {@code 0}.
64       *
65       * @return the total number of invalid data sizes
66       */
67      abstract int getNumberOfInvalidDataSizes();
68  
69      /**
70       * Returns the total number of valid data sizes to be tested.
71       *
72       * @return the total number of valid data sizes
73       */
74      abstract int getNumberOfValidDataSizes();
75  
76      /**
77       * Returns the expected relative accuracy for data arrays of size
78       * {@code getValidDataSize(i)}.
79       *
80       * @param i the index of the valid data size
81       * @return the expected relative accuracy
82       */
83      abstract double getRelativeTolerance(int i);
84  
85      /**
86       * Returns a valid data size. This method allows for data arrays of various
87       * sizes to be automatically tested (by allowing multiple values of the
88       * specified index).
89       *
90       * @param i the index of the valid data size ({@code 0 <= i <}
91       * {@link #getNumberOfValidDataSizes()}
92       * @return a valid data size
93       */
94      abstract int getValidDataSize(int i);
95  
96      /**
97       * Returns a function for the accuracy check of
98       * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}.
99       * This function should be valid. In other words, none of the above methods
100      * should throw an exception when passed this function.
101      *
102      * @return a valid function
103      */
104     abstract DoubleUnaryOperator getValidFunction();
105 
106     /**
107      * Returns a sampling lower bound for the accuracy check of
108      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}.
109      * This lower bound should be valid. In other words, none of the above
110      * methods should throw an exception when passed this bound.
111      *
112      * @return a valid lower bound
113      */
114     abstract double getValidLowerBound();
115 
116     /**
117      * Returns a sampling upper bound for the accuracy check of
118      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}.
119      * This upper bound should be valid. In other words, none of the above
120      * methods should throw an exception when passed this bound.
121      *
122      * @return a valid bound
123      */
124     abstract double getValidUpperBound();
125 
126     /**
127      * Returns the expected transform of the specified real data array.
128      *
129      * @param x Data to be transformed.
130      * @param type Whether to perform the inverse) transform.
131      * @return the expected transform.
132      */
133     abstract double[] transform(double[] x, boolean type);
134 
135     // Check of preconditions.
136 
137     /**
138      * {@link RealTransform#apply(double[])} should throw a
139      * {@link IllegalArgumentException} if data size is invalid.
140      */
141     @Test
142     public void testTransformRealInvalidDataSize() {
143         for (int i = 0; i < getNumberOfInvalidDataSizes(); i++) {
144             final int n = getInvalidDataSize(i);
145             for (boolean type : new boolean[] {true, false}) {
146                 try {
147                     final RealTransform transformer = createRealTransformer(type);
148                     transformer.apply(createRealData(n));
149                     Assert.fail(type + ", " + n);
150                 } catch (IllegalArgumentException e) {
151                     // Expected: do nothing
152                 }
153             }
154         }
155     }
156 
157     /**
158      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}
159      * should throw {@link IllegalArgumentException} if number of samples is
160      * invalid.
161      */
162     @Test
163     public void testTransformFunctionInvalidDataSize() {
164         final DoubleUnaryOperator f = getValidFunction();
165         final double a = getValidLowerBound();
166         final double b = getValidUpperBound();
167         for (int i = 0; i < getNumberOfInvalidDataSizes(); i++) {
168             final int n = getInvalidDataSize(i);
169             for (boolean type : new boolean[] {true, false}) {
170                 try {
171                     final RealTransform transformer = createRealTransformer(type);
172                     transformer.apply(f, a, b, n);
173                     Assert.fail(type + ", " + n);
174                 } catch (IllegalArgumentException e) {
175                     // Expected: do nothing
176                 }
177             }
178         }
179     }
180 
181     /**
182      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}
183      * should throw {@link IllegalArgumentException} if number of samples
184      * is not strictly positive.
185      */
186     @Test
187     public void testTransformFunctionNotStrictlyPositiveNumberOfSamples() {
188         final DoubleUnaryOperator f = getValidFunction();
189         final double a = getValidLowerBound();
190         final double b = getValidUpperBound();
191         for (int i = 0; i < getNumberOfValidDataSizes(); i++) {
192             final int n = getValidDataSize(i);
193             for (boolean type : new boolean[] {true, false}) {
194                 try {
195                     final RealTransform transformer = createRealTransformer(type);
196                     transformer.apply(f, a, b, -n);
197                     Assert.fail(type + ", " + (-n));
198                 } catch (IllegalArgumentException e) {
199                     // Expected: do nothing
200                 }
201             }
202         }
203     }
204 
205     /**
206      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}
207      * should throw {@link IllegalArgumentException} if sampling bounds are
208      * not correctly ordered.
209      */
210     @Test
211     public void testTransformFunctionInvalidBounds() {
212         final DoubleUnaryOperator f = getValidFunction();
213         final double a = getValidLowerBound();
214         final double b = getValidUpperBound();
215         for (int i = 0; i < getNumberOfValidDataSizes(); i++) {
216             final int n = getValidDataSize(i);
217             for (boolean type : new boolean[] {true, false}) {
218                 try {
219                     final RealTransform transformer = createRealTransformer(type);
220                     transformer.apply(f, b, a, n);
221                     Assert.fail(type + ", " + b + ", " + a);
222                 } catch (IllegalArgumentException e) {
223                     // Expected: do nothing
224                 }
225             }
226         }
227     }
228 
229     // Accuracy tests of transform of valid data.
230 
231     /**
232      * Accuracy check of {@link RealTransform#apply(double[])}.
233      * For each valid data size returned by
234      * {@link #getValidDataSize(int) getValidDataSize(i)},
235      * a random data array is generated with
236      * {@link #createRealData(int) createRealData(i)}. The actual
237      * transform is computed and compared to the expected transform, return by
238      * {@link #transform(double[], boolean)}. Actual and expected values
239      * should be equal to within the relative error returned by
240      * {@link #getRelativeTolerance(int) getRelativeTolerance(i)}.
241      */
242     @Test
243     public void testTransformReal() {
244         for (int i = 0; i < getNumberOfValidDataSizes(); i++) {
245             final int n = getValidDataSize(i);
246             final double tol = getRelativeTolerance(i);
247             for (boolean type : new boolean[] {true, false}) {
248                 doTestTransformReal(n, tol, type);
249             }
250         }
251     }
252 
253     /**
254      * Accuracy check of
255      * {@link RealTransform#apply(DoubleUnaryOperator, double, double, int)}.
256      * For each valid data size returned by
257      * {@link #getValidDataSize(int) getValidDataSize(i)},
258      * the {@link org.apache.commons.math3.analysis.UnivariateFunction UnivariateFunction}
259      * returned by {@link #getValidFunction()} is
260      * sampled. The actual transform is computed and compared to the expected
261      * transform, return by {@link #transform(double[], boolean)}. Actual
262      * and expected values should be equal to within the relative error returned
263      * by {@link #getRelativeTolerance(int) getRelativeTolerance(i)}.
264      */
265     @Test
266     public void testTransformFunction() {
267         for (int i = 0; i < getNumberOfValidDataSizes(); i++) {
268             final int n = getValidDataSize(i);
269             final double tol = getRelativeTolerance(i);
270             for (boolean type : new boolean[] {true, false}) {
271                 doTestTransformFunction(n, tol, type);
272             }
273         }
274     }
275 
276     // Utility methods.
277 
278     /**
279      * Returns a random array of doubles.
280      *
281      * @param n the size of the array to be returned
282      * @return a random array of specified size
283      */
284     double[] createRealData(final int n) {
285         final double[] data = new double[n];
286         for (int i = 0; i < n; i++) {
287             data[i] = 2 * RNG.nextDouble() - 1;
288         }
289         return data;
290     }
291 
292     // Actual tests.
293 
294     private void doTestTransformReal(final int n,
295                                      final double tol,
296                                      final boolean type) {
297         final RealTransform transformer = createRealTransformer(type);
298         final double[] x = createRealData(n);
299         final double[] expected = transform(x, type);
300         final double[] actual = transformer.apply(x);
301         for (int i = 0; i < n; i++) {
302             final String msg = String.format("%d, %d", n, i);
303             final double delta = tol * Math.abs(expected[i]);
304             Assert.assertEquals(msg, expected[i], actual[i], delta);
305         }
306     }
307 
308     private void doTestTransformFunction(final int n,
309                                          final double tol,
310                                          final boolean type) {
311         final RealTransform transformer = createRealTransformer(type);
312         final DoubleUnaryOperator f = getValidFunction();
313         final double a = getValidLowerBound();
314         final double b = getValidUpperBound();
315         final double[] x = createRealData(n);
316         for (int i = 0; i < n; i++) {
317             final double t = a + i * (b - a) / n;
318             x[i] = f.applyAsDouble(t);
319         }
320         final double[] expected = transform(x, type);
321         final double[] actual = transformer.apply(f, a, b, n);
322         for (int i = 0; i < n; i++) {
323             final String msg = String.format("%d, %d", n, i);
324             final double delta = tol * Math.abs(expected[i]);
325             Assert.assertEquals(msg, expected[i], actual[i], delta);
326         }
327     }
328 }