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 }