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  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 }