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