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 package org.apache.commons.statistics.distribution;
19
20 import java.lang.reflect.Array;
21 import java.text.DecimalFormat;
22 import java.util.function.Supplier;
23 import org.apache.commons.math3.stat.inference.ChiSquareTest;
24 import org.junit.jupiter.api.Assertions;
25
26 /**
27 * Test utilities.
28 *
29 * <p>This class is public and has public methods to allow testing within the other modules.
30 */
31 public final class TestUtils {
32 /**
33 * The relative error threshold below which absolute error is reported in ULP.
34 */
35 private static final double ULP_THRESHOLD = 100 * Math.ulp(1.0);
36 /**
37 * The prefix for the formatted expected value.
38 *
39 * <p>This should be followed by the expected value then '>'.
40 */
41 private static final String EXPECTED_FORMAT = "expected: <";
42 /**
43 * The prefix for the formatted actual value.
44 *
45 * <p>It is assumed this will be following the expected value.
46 *
47 * <p>This should be followed by the actual value then '>'.
48 */
49 private static final String ACTUAL_FORMAT = ">, actual: <";
50 /**
51 * The prefix for the formatted relative error value.
52 *
53 * <p>It is assumed this will be following the actual value.
54 *
55 * <p>This should be followed by the relative error value then '>'.
56 */
57 private static final String RELATIVE_ERROR_FORMAT = ">, rel.error: <";
58 /**
59 * The prefix for the formatted absolute error value.
60 *
61 * <p>It is assumed this will be following the relative value.
62 *
63 * <p>This should be followed by the absolute error value then '>'.
64 */
65 private static final String ABSOLUTE_ERROR_FORMAT = ">, abs.error: <";
66 /**
67 * The prefix for the formatted ULP error value.
68 *
69 * <p>It is assumed this will be following the relative value.
70 *
71 * <p>This should be followed by the ULP error value then '>'.
72 */
73 private static final String ULP_ERROR_FORMAT = ">, ulp error: <";
74 /** Positive zero bits. */
75 private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0);
76 /** Negative zero bits. */
77 private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0);
78
79 /**
80 * Collection of static methods used in math unit tests.
81 */
82 private TestUtils() {}
83
84 ////////////////////////////////////////////////////////////////////////////////////////////
85 // Custom assertions using a DoubleTolerance
86 ////////////////////////////////////////////////////////////////////////////////////////////
87
88 /**
89 * <em>Asserts</em> {@code expected} and {@code actual} are considered equal with the
90 * provided tolerance.
91 *
92 * @param expected The expected value.
93 * @param actual The value to tolerance against {@code expected}.
94 * @param tolerance The tolerance.
95 * @throws AssertionError If the values are not considered equal
96 */
97 public static void assertEquals(double expected, double actual, DoubleTolerance tolerance) {
98 assertEquals(expected, actual, tolerance, (String) null);
99 }
100
101 /**
102 * <em>Asserts</em> {@code expected} and {@code actual} are considered equal with the
103 * provided tolerance.
104 *
105 * <p>Fails with the supplied failure {@code message}.
106 *
107 * @param expected The expected value.
108 * @param actual The value to tolerance against {@code expected}.
109 * @param tolerance The tolerance.
110 * @param message The message.
111 * @throws AssertionError If the values are not considered equal
112 */
113 public static void assertEquals(double expected, double actual, DoubleTolerance tolerance, String message) {
114 if (!tolerance.test(expected, actual)) {
115 throw new AssertionError(format(expected, actual, tolerance, message));
116 }
117 }
118
119 /**
120 * <em>Asserts</em> {@code expected} and {@code actual} are considered equal with the
121 * provided tolerance.
122 *
123 * <p>If necessary, the failure message will be retrieved lazily from the supplied
124 * {@code messageSupplier}.
125 *
126 * @param expected The expected value.
127 * @param actual The value to tolerance against {@code expected}.
128 * @param tolerance The tolerance.
129 * @param messageSupplier The message supplier.
130 * @throws AssertionError If the values are not considered equal
131 */
132 public static void assertEquals(double expected, double actual, DoubleTolerance tolerance,
133 Supplier<String> messageSupplier) {
134 if (!tolerance.test(expected, actual)) {
135 throw new AssertionError(
136 format(expected, actual, tolerance, messageSupplier == null ? null : messageSupplier.get()));
137 }
138 }
139
140 /**
141 * Format the message.
142 *
143 * @param expected The expected value.
144 * @param actual The value to check against <code>expected</code>.
145 * @param tolerance The tolerance.
146 * @param message The message.
147 * @return the formatted message
148 */
149 private static String format(double expected, double actual, DoubleTolerance tolerance, String message) {
150 return buildPrefix(message) + formatValues(expected, actual, tolerance);
151 }
152
153 /**
154 * Builds the fail message prefix.
155 *
156 * @param message the message
157 * @return the prefix
158 */
159 private static String buildPrefix(String message) {
160 return StringUtils.isNotEmpty(message) ? message + " ==> " : "";
161 }
162
163 /**
164 * Format the values.
165 *
166 * @param expected The expected value.
167 * @param actual The value to check against <code>expected</code>.
168 * @param tolerance The tolerance.
169 * @return the formatted values
170 */
171 private static String formatValues(double expected, double actual, DoubleTolerance tolerance) {
172 // Add error
173 final double diff = Math.abs(expected - actual);
174 final double rel = diff / Math.max(Math.abs(expected), Math.abs(actual));
175 final StringBuilder msg = new StringBuilder(EXPECTED_FORMAT).append(expected).append(ACTUAL_FORMAT)
176 .append(actual).append(RELATIVE_ERROR_FORMAT).append(rel);
177 if (rel < ULP_THRESHOLD) {
178 msg.append(ULP_ERROR_FORMAT).append(formatUlpDifference(expected, actual));
179 } else {
180 msg.append(ABSOLUTE_ERROR_FORMAT).append(diff);
181 }
182 msg.append('>');
183 appendTolerance(msg, tolerance);
184 return msg.toString();
185 }
186
187 /**
188 * Format the absolute difference in ULP between two arguments. This will return "0" for values
189 * that are binary equal, or for the difference between zeros of opposite signs.
190 *
191 * @param expected first argument
192 * @param actual second argument
193 * @return Absolute ULP difference between the arguments as a string
194 */
195 private static String formatUlpDifference(double expected, double actual) {
196 final long e = Double.doubleToLongBits(expected);
197 final long a = Double.doubleToLongBits(actual);
198
199 // Code adapted from Precision#equals(double, double, int).
200 // Compute the absolute delta; this is done carefully if there is a sign difference
201 // to allow reporting errors above Long.MAX_VALUE.
202
203 if (e == a) {
204 // Binary equal
205 return "0";
206 }
207 if ((a ^ e) < 0L) {
208 // The difference is the count of numbers between each and zero.
209 // This makes -0.0 and 0.0 equal.
210 long d1;
211 long d2;
212 if (a < e) {
213 d1 = e - POSITIVE_ZERO_DOUBLE_BITS;
214 d2 = a - NEGATIVE_ZERO_DOUBLE_BITS;
215 } else {
216 d1 = a - POSITIVE_ZERO_DOUBLE_BITS;
217 d2 = e - NEGATIVE_ZERO_DOUBLE_BITS;
218 }
219 // This may overflow so we report it using an unsigned formatter
220 return Long.toUnsignedString(d1 + d2);
221 }
222 // Same sign, no overflow of the difference
223 return Long.toString(Math.abs(e - a));
224 }
225
226 /**
227 * Append the tolerance to the message.
228 *
229 * @param msg The message
230 * @param tolerance the tolerance
231 */
232 private static void appendTolerance(final StringBuilder msg, final Object tolerance) {
233 final String description = StringUtils.toString(tolerance);
234 if (StringUtils.isNotEmpty(description)) {
235 msg.append(", tolerance: ").append(description);
236 }
237 }
238
239 ////////////////////////////////////////////////////////////////////////////////////////////
240
241 /**
242 * Verifies that the relative error in actual vs. expected is less than or
243 * equal to relativeError. If expected is infinite or NaN, actual must be
244 * the same (NaN or infinity of the same sign).
245 *
246 * @param msg message to return with failure
247 * @param expected expected value
248 * @param actual observed value
249 * @param relativeError maximum allowable relative error
250 */
251 static void assertRelativelyEquals(Supplier<String> msg,
252 double expected,
253 double actual,
254 double relativeError) {
255 if (Double.isNaN(expected)) {
256 Assertions.assertTrue(Double.isNaN(actual), msg);
257 } else if (Double.isNaN(actual)) {
258 Assertions.assertTrue(Double.isNaN(expected), msg);
259 } else if (Double.isInfinite(actual) || Double.isInfinite(expected)) {
260 Assertions.assertEquals(expected, actual, relativeError);
261 } else if (expected == 0.0) {
262 Assertions.assertEquals(actual, expected, relativeError, msg);
263 } else {
264 final double absError = Math.abs(expected) * relativeError;
265 Assertions.assertEquals(expected, actual, absError, msg);
266 }
267 }
268
269 /**
270 * Asserts the null hypothesis for a ChiSquare test. Fails and dumps arguments and test
271 * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
272 *
273 * @param valueLabels labels for the values of the discrete distribution under test
274 * @param expected expected counts
275 * @param observed observed counts
276 * @param alpha significance level of the test
277 */
278 private static void assertChiSquare(int[] valueLabels,
279 double[] expected,
280 long[] observed,
281 double alpha) {
282 final ChiSquareTest chiSquareTest = new ChiSquareTest();
283
284 // Fail if we can reject null hypothesis that distributions are the same
285 if (chiSquareTest.chiSquareTest(expected, observed, alpha)) {
286 final StringBuilder msgBuffer = new StringBuilder();
287 final DecimalFormat df = new DecimalFormat("#.##");
288 msgBuffer.append("Chisquare test failed");
289 msgBuffer.append(" p-value = ");
290 msgBuffer.append(chiSquareTest.chiSquareTest(expected, observed));
291 msgBuffer.append(" chisquare statistic = ");
292 msgBuffer.append(chiSquareTest.chiSquare(expected, observed));
293 msgBuffer.append(". \n");
294 msgBuffer.append("value\texpected\tobserved\n");
295 for (int i = 0; i < expected.length; i++) {
296 msgBuffer.append(valueLabels[i]);
297 msgBuffer.append('\t');
298 msgBuffer.append(df.format(expected[i]));
299 msgBuffer.append("\t\t");
300 msgBuffer.append(observed[i]);
301 msgBuffer.append('\n');
302 }
303 msgBuffer.append("This test can fail randomly due to sampling error with probability ");
304 msgBuffer.append(alpha);
305 msgBuffer.append('.');
306 Assertions.fail(msgBuffer.toString());
307 }
308 }
309
310 /**
311 * Asserts the null hypothesis for a ChiSquare test. Fails and dumps arguments and test
312 * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
313 *
314 * @param values integer values whose observed and expected counts are being compared
315 * @param expected expected counts
316 * @param observed observed counts
317 * @param alpha significance level of the test
318 */
319 static void assertChiSquareAccept(int[] values,
320 double[] expected,
321 long[] observed,
322 double alpha) {
323 assertChiSquare(values, expected, observed, alpha);
324 }
325
326 /**
327 * Asserts the null hypothesis for a ChiSquare test. Fails and dumps arguments and test
328 * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
329 *
330 * @param expected expected counts
331 * @param observed observed counts
332 * @param alpha significance level of the test
333 */
334 static void assertChiSquareAccept(double[] expected,
335 long[] observed,
336 double alpha) {
337 final int[] values = new int[expected.length];
338 for (int i = 0; i < values.length; i++) {
339 values[i] = i + 1;
340 }
341 assertChiSquare(values, expected, observed, alpha);
342 }
343
344 /**
345 * Computes the 25th, 50th and 75th percentiles of the given distribution and returns
346 * these values in an array.
347 *
348 * @param distribution Distribution.
349 * @return the quartiles
350 */
351 static double[] getDistributionQuartiles(ContinuousDistribution distribution) {
352 final double[] quantiles = new double[3];
353 quantiles[0] = distribution.inverseCumulativeProbability(0.25d);
354 quantiles[1] = distribution.inverseCumulativeProbability(0.5d);
355 quantiles[2] = distribution.inverseCumulativeProbability(0.75d);
356 return quantiles;
357 }
358
359 /**
360 * Computes the 25th, 50th and 75th percentiles of the given distribution and returns
361 * these values in an array.
362 *
363 * @param distribution Distribution.
364 * @return the quartiles
365 */
366 static int[] getDistributionQuartiles(DiscreteDistribution distribution) {
367 final int[] quantiles = new int[3];
368 quantiles[0] = distribution.inverseCumulativeProbability(0.25d);
369 quantiles[1] = distribution.inverseCumulativeProbability(0.5d);
370 quantiles[2] = distribution.inverseCumulativeProbability(0.75d);
371 return quantiles;
372 }
373
374 /**
375 * Updates observed counts of values in quartiles.
376 * counts[0] <-> 1st quartile ... counts[3] <-> top quartile
377 *
378 * @param value Observed value.
379 * @param counts Counts for each quartile.
380 * @param quartiles Quartiles.
381 */
382 static void updateCounts(double value, long[] counts, double[] quartiles) {
383 if (value > quartiles[1]) {
384 counts[value <= quartiles[2] ? 2 : 3]++;
385 } else {
386 counts[value <= quartiles[0] ? 0 : 1]++;
387 }
388 }
389
390 /**
391 * Updates observed counts of values in quartiles.
392 * counts[0] <-> 1st quartile ... counts[3] <-> top quartile
393 *
394 * @param value Observed value.
395 * @param counts Counts for each quartile.
396 * @param quartiles Quartiles.
397 */
398 static void updateCounts(double value, long[] counts, int[] quartiles) {
399 if (value > quartiles[1]) {
400 counts[value <= quartiles[2] ? 2 : 3]++;
401 } else {
402 counts[value <= quartiles[0] ? 0 : 1]++;
403 }
404 }
405
406 /**
407 * Eliminates points with zero mass from densityPoints and densityValues parallel
408 * arrays. Returns the number of positive mass points and collapses the arrays so that
409 * the first <returned value> elements of the input arrays represent the positive mass
410 * points.
411 *
412 * @param densityPoints Density points.
413 * @param densityValues Density values.
414 * @return number of positive mass points
415 */
416 static int eliminateZeroMassPoints(int[] densityPoints, double[] densityValues) {
417 int positiveMassCount = 0;
418 for (int i = 0; i < densityValues.length; i++) {
419 if (densityValues[i] > 0) {
420 positiveMassCount++;
421 }
422 }
423 if (positiveMassCount < densityValues.length) {
424 final int[] newPoints = new int[positiveMassCount];
425 final double[] newValues = new double[positiveMassCount];
426 int j = 0;
427 for (int i = 0; i < densityValues.length; i++) {
428 if (densityValues[i] > 0) {
429 newPoints[j] = densityPoints[i];
430 newValues[j] = densityValues[i];
431 j++;
432 }
433 }
434 System.arraycopy(newPoints, 0, densityPoints, 0, positiveMassCount);
435 System.arraycopy(newValues, 0, densityValues, 0, positiveMassCount);
436 }
437 return positiveMassCount;
438 }
439
440 /**
441 * Utility function for allocating an array and filling it with {@code n}
442 * samples generated by the given {@code sampler}.
443 *
444 * @param n Number of samples.b
445 * @param sampler Sampler.
446 * @return an array of size {@code n}.
447 */
448 static double[] sample(int n,
449 ContinuousDistribution.Sampler sampler) {
450 final double[] samples = new double[n];
451 for (int i = 0; i < n; i++) {
452 samples[i] = sampler.sample();
453 }
454 return samples;
455 }
456
457 /**
458 * Utility function for allocating an array and filling it with {@code n}
459 * samples generated by the given {@code sampler}.
460 *
461 * @param n Number of samples.
462 * @param sampler Sampler.
463 * @return an array of size {@code n}.
464 */
465 static int[] sample(int n,
466 DiscreteDistribution.Sampler sampler) {
467 final int[] samples = new int[n];
468 for (int i = 0; i < n; i++) {
469 samples[i] = sampler.sample();
470 }
471 return samples;
472 }
473
474 /**
475 * Gets the length of the array.
476 *
477 * @param array Array
478 * @return the length (or 0 for null array)
479 */
480 static int getLength(double[] array) {
481 return array == null ? 0 : array.length;
482 }
483
484 /**
485 * Gets the length of the array.
486 *
487 * @param array Array
488 * @return the length (or 0 for null array)
489 */
490 static int getLength(int[] array) {
491 return array == null ? 0 : array.length;
492 }
493
494 /**
495 * Gets the length of the array.
496 *
497 * @param array Array
498 * @return the length (or 0 for null array)
499 * @throws IllegalArgumentException if the object is not an array
500 */
501 static int getLength(Object array) {
502 return array == null ? 0 : Array.getLength(array);
503 }
504 }