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 }