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    *      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.statistics.distribution;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.EnumMap;
22  import java.util.EnumSet;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Properties;
26  import java.util.Set;
27  import java.util.regex.Pattern;
28  
29  /**
30   * Contains the data for the distribution parameters, the expected properties
31   * of the distribution (moments and support bounds) and test points to evaluate
32   * with expected values.
33   */
34  abstract class DistributionTestData {
35      // Keys for values that are set to test defaults for a distribution.
36      // These values are expected to be the same for all test cases
37      // and may be set in the properties before creating the test data instance.
38  
39      /** The key for the absolute tolerance value. */
40      static final String KEY_TOLERANCE_ABSOLUTE = "tolerance.absolute";
41      /** The key for the relative tolerance value. */
42      static final String KEY_TOLERANCE_RELATIVE = "tolerance.relative";
43  
44      /** The key suffix to disable a test. */
45      private static final String SUFFIX_DISABLE = ".disable";
46      /** The key suffix for the absolute tolerance value. */
47      private static final String SUFFIX_TOLERANCE_ABSOLUTE = ".absolute";
48      /** The key suffix for the relative tolerance value. */
49      private static final String SUFFIX_TOLERANCE_RELATIVE = ".relative";
50      /** The index for the absolute tolerance value in the array of tolerances. */
51      private static final int INDEX_ABSOLUTE = 0;
52      /** The index for the relative tolerance value in the array of tolerances. */
53      private static final int INDEX_RELATIVE = 1;
54      /** The unset (default) value for the tolerance. */
55      private static final double UNSET_TOLERANCE = -1;
56      /** The unset (default) values for the array of tolerances. */
57      private static final double[] UNSET_TOLERANCES = {UNSET_TOLERANCE, UNSET_TOLERANCE};
58  
59      /** Regex to split delimited text data (e.g. arrays of numbers). */
60      private static final Pattern PATTERN = Pattern.compile("[ ,]+");
61  
62      /** Expected probability function values. */
63      protected final double[] pfValues;
64      /** Expected log probability function values. */
65      protected final double[] logPfValues;
66  
67      /** Distribution parameters. */
68      private final Object[] parameters;
69      /** Mean. */
70      private final double mean;
71      /** Variance. */
72      private final double variance;
73      /** Test tolerances. */
74      private final Map<TestName, double[]> tolerance;
75      /** Disabled tests. */
76      private final Set<TestName> disabled;
77  
78      /** Test absolute tolerance for calculations. */
79      private final double absoluteTolerance;
80      /** Test relative tolerance for calculations. */
81      private final double relativeTolerance;
82      /** Expected CDF values. */
83      private final double[] cdfValues;
84      /** Expected SF values for the survival function test points. */
85      private final double[] sfValues;
86      /** Expected CDF values for the high-precision CDF test points. */
87      private final double[] cdfHpValues;
88      /** Expected CDF values for the high-precision survival function test points. */
89      private final double[] sfHpValues;
90  
91      /**
92       * Contains the data for the continuous distribution parameters, the expected properties
93       * of the distribution (moments and support bounds) and test points to evaluate
94       * with expected values.
95       */
96      static class ContinuousDistributionTestData extends DistributionTestData {
97          /** Support lower bound. */
98          private final double lower;
99          /** Support upper bound. */
100         private final double upper;
101         /** Test points to evaluate the CDF. */
102         private final double[] cdfPoints;
103         /** Test points to evaluate the PDF. */
104         private final double[] pdfPoints;
105         /** Test points to evaluate survival function computations. */
106         private final double[] sfPoints;
107         /** Test points to evaluate high-precision CDF computations. */
108         private final double[] cdfHpPoints;
109         /** Test points to evaluate high-precision survival function computations. */
110         private final double[] sfHpPoints;
111         /** Test points to evaluate the inverse CDF. */
112         private final double[] icdfPoints;
113         /** Expected inverse CDF values. */
114         private final double[] icdfValues;
115         /** Test points to evaluate the inverse SF. */
116         private final double[] isfPoints;
117         /** Expected inverse SF values. */
118         private final double[] isfValues;
119 
120         /**
121          * @param props Properties containing the test data
122          */
123         ContinuousDistributionTestData(Properties props) {
124             super(props);
125             // Load all the data
126             lower = getAsDouble(props, "lower", Double.NEGATIVE_INFINITY);
127             upper = getAsDouble(props, "upper", Double.POSITIVE_INFINITY);
128             // Required
129             cdfPoints = getAsDoubleArray(props, "cdf.points");
130             // Optional
131             pdfPoints = getAsDoubleArray(props, "pdf.points", cdfPoints);
132             sfPoints = getAsDoubleArray(props, "sf.points", cdfPoints);
133             cdfHpPoints = getAsDoubleArray(props, "cdf.hp.points", null);
134             sfHpPoints = getAsDoubleArray(props, "sf.hp.points", null);
135             // Do not default to an inverse mapping.
136             // A separate [cdf|sf].inverse property controls an inverse mapping test.
137             icdfPoints = getAsDoubleArray(props, "icdf.points", null);
138             icdfValues = getAsDoubleArray(props, "icdf.values", null);
139             isfPoints = getAsDoubleArray(props, "isf.points", null);
140             isfValues = getAsDoubleArray(props, "isf.values", null);
141             // Validation
142             validatePair(cdfPoints, getCdfValues(), "cdf");
143             validatePair(pdfPoints, getPdfValues(), "pdf");
144             validatePair(pdfPoints, getLogPdfValues(), "logpdf");
145             validatePair(sfPoints, getSfValues(), "sf");
146             validatePair(cdfHpPoints, getCdfHpValues(), "cdf.hp");
147             validatePair(sfHpPoints, getSfHpValues(), "sf.hp");
148             validatePair(icdfPoints, icdfValues, "icdf");
149             validatePair(isfPoints, isfValues, "isf");
150         }
151 
152         @Override
153         String getProbabilityFunctionName() {
154             return "pdf";
155         }
156 
157         /**
158          * Gets the support lower bound of the distribution.
159          *
160          * @return the lower bound
161          */
162         double getLower() {
163             return lower;
164         }
165 
166         /**
167          * Gets the support upper bound of the distribution.
168          *
169          * @return the upper bound
170          */
171         double getUpper() {
172             return upper;
173         }
174 
175         /**
176          * Gets the points to evaluate the CDF.
177          *
178          * @return the points
179          */
180         double[] getCdfPoints() {
181             return cdfPoints;
182         }
183 
184         /**
185          * Gets the points to evaluate the PDF.
186          *
187          * @return the points
188          */
189         double[] getPdfPoints() {
190             return pdfPoints;
191         }
192 
193         /**
194          * Gets the expected density values for the PDF test points.
195          *
196          * @return the PDF values
197          */
198         double[] getPdfValues() {
199             return pfValues;
200         }
201 
202         /**
203          * Gets the expected log density values for the PDF test points.
204          *
205          * @return the log PDF values
206          */
207         double[] getLogPdfValues() {
208             return logPfValues;
209         }
210 
211         /**
212          * Gets the points to evaluate for survival function.
213          *
214          * @return the SF points
215          */
216         double[] getSfPoints() {
217             return sfPoints;
218         }
219 
220         /**
221          * Gets the points to evaluate the cumulative probability where the result
222          * is expected to be approaching zero and requires a high-precision computation.
223          *
224          * @return the CDF high-precision points
225          */
226         double[] getCdfHpPoints() {
227             return cdfHpPoints;
228         }
229 
230         /**
231          * Gets the points to evaluate the survival probability where the result
232          * is expected to be approaching zero and requires a high-precision computation.
233          *
234          * @return the survival function high-precision points
235          */
236         double[] getSfHpPoints() {
237             return sfHpPoints;
238         }
239 
240         @Override
241         double[] getIcdfPoints() {
242             return icdfPoints;
243         }
244 
245         /**
246          * Gets the expected inverse cumulative probability values for the test inverse CDF points.
247          *
248          * @return the inverse CDF values
249          */
250         double[] getIcdfValues() {
251             return icdfValues;
252         }
253 
254         @Override
255         double[] getIsfPoints() {
256             return isfPoints;
257         }
258 
259         /**
260          * Gets the expected inverse survival probability values for the test inverse SF points.
261          *
262          * @return the inverse SF values
263          */
264         double[] getIsfValues() {
265             return isfValues;
266         }
267     }
268 
269     /**
270      * Contains the data for the continuous distribution parameters, the expected properties
271      * of the distribution (moments and support bounds) and test points to evaluate
272      * with expected values.
273      */
274     static class DiscreteDistributionTestData extends DistributionTestData {
275         /** Support lower bound. */
276         private final int lower;
277         /** Support upper bound. */
278         private final int upper;
279         /** Test points to evaluate the CDF. */
280         private final int[] cdfPoints;
281         /** Test points to evaluate the PDF. */
282         private final int[] pmfPoints;
283         /** Test points to evaluate survival function computations. */
284         private final int[] sfPoints;
285         /** Test points to evaluate high-precision CDF computations. */
286         private final int[] cdfHpPoints;
287         /** Test points to evaluate high-precision survival function computations. */
288         private final int[] sfHpPoints;
289         /** Test points to evaluate the inverse CDF. */
290         private final double[] icdfPoints;
291         /** Expected inverse CDF values. */
292         private final int[] icdfValues;
293         /** Test points to evaluate the inverse SF. */
294         private final double[] isfPoints;
295         /** Expected inverse SF values. */
296         private final int[] isfValues;
297 
298         /**
299          * @param props Properties containing the test data
300          */
301         DiscreteDistributionTestData(Properties props) {
302             super(props);
303             // Load all the data
304             lower = getAsInt(props, "lower", Integer.MIN_VALUE);
305             upper = getAsInt(props, "upper", Integer.MAX_VALUE);
306             // Required
307             cdfPoints = getAsIntArray(props, "cdf.points");
308             // Optional
309             pmfPoints = getAsIntArray(props, "pmf.points", cdfPoints);
310             sfPoints = getAsIntArray(props, "sf.points", cdfPoints);
311             cdfHpPoints = getAsIntArray(props, "cdf.hp.points", null);
312             sfHpPoints = getAsIntArray(props, "sf.hp.points", null);
313             // Do not default to an inverse mapping.
314             // A separate [cdf|sf].inverse property controls an inverse mapping test.
315             icdfPoints = getAsDoubleArray(props, "icdf.points", null);
316             icdfValues = getAsIntArray(props, "icdf.values", null);
317             isfPoints = getAsDoubleArray(props, "isf.points", null);
318             isfValues = getAsIntArray(props, "isf.values", null);
319             // Validation
320             validatePair(cdfPoints, getCdfValues(), "cdf");
321             validatePair(pmfPoints, getPmfValues(), "pmf");
322             validatePair(pmfPoints, getLogPmfValues(), "logpmf");
323             validatePair(sfPoints, getSfValues(), "sf");
324             validatePair(cdfHpPoints, getCdfHpValues(), "cdf.hp");
325             validatePair(sfHpPoints, getSfHpValues(), "sf.hp");
326             validatePair(icdfPoints, icdfValues, "icdf");
327             validatePair(isfPoints, isfValues, "isf");
328         }
329 
330         @Override
331         String getProbabilityFunctionName() {
332             return "pmf";
333         }
334 
335         /**
336          * Gets the support lower bound of the distribution.
337          *
338          * @return the lower bound
339          */
340         int getLower() {
341             return lower;
342         }
343 
344         /**
345          * Gets the support upper bound of the distribution.
346          *
347          * @return the upper bound
348          */
349         int getUpper() {
350             return upper;
351         }
352 
353         /**
354          * Gets the points to evaluate the CDF.
355          *
356          * @return the points
357          */
358         int[] getCdfPoints() {
359             return cdfPoints;
360         }
361 
362         /**
363          * Gets the points to evaluate the PMF.
364          *
365          * @return the points
366          */
367         int[] getPmfPoints() {
368             return pmfPoints;
369         }
370 
371         /**
372          * Gets the expected density values for the PMF test points.
373          *
374          * @return the PDF values
375          */
376         double[] getPmfValues() {
377             return pfValues;
378         }
379 
380         /**
381          * Gets the expected log density values for the PMF test points.
382          *
383          * @return the log PDF values
384          */
385         double[] getLogPmfValues() {
386             return logPfValues;
387         }
388 
389         /**
390          * Gets the points to evaluate for survival function.
391          *
392          * @return the SF points
393          */
394         int[] getSfPoints() {
395             return sfPoints;
396         }
397 
398         /**
399          * Gets the points to evaluate the cumulative probability where the result
400          * is expected to be approaching zero and requires a high-precision computation.
401          *
402          * @return the CDF high-precision points
403          */
404         int[] getCdfHpPoints() {
405             return cdfHpPoints;
406         }
407 
408         /**
409          * Gets the points to evaluate the survival probability where the result
410          * is expected to be approaching zero and requires a high-precision computation.
411          *
412          * @return the survival function high-precision points
413          */
414         int[] getSfHpPoints() {
415             return sfHpPoints;
416         }
417 
418         @Override
419         double[] getIcdfPoints() {
420             return icdfPoints;
421         }
422 
423         /**
424          * Gets the expected inverse cumulative probability values for the test inverse CDF points.
425          *
426          * @return the inverse CDF values
427          */
428         int[] getIcdfValues() {
429             return icdfValues;
430         }
431 
432         @Override
433         double[] getIsfPoints() {
434             return isfPoints;
435         }
436 
437         /**
438          * Gets the expected inverse survival probability values for the test inverse SF points.
439          *
440          * @return the inverse SF values
441          */
442         int[] getIsfValues() {
443             return isfValues;
444         }
445     }
446 
447     /**
448      * @param props Properties containing the test data
449      */
450     DistributionTestData(Properties props) {
451         // Load all the data
452         parameters = PATTERN.splitAsStream(get(props, "parameters"))
453                             .map(DistributionTestData::parseParameter).toArray();
454         mean = getAsDouble(props, "mean");
455         variance = getAsDouble(props, "variance");
456         absoluteTolerance = getAsDouble(props, KEY_TOLERANCE_ABSOLUTE);
457         relativeTolerance = getAsDouble(props, KEY_TOLERANCE_RELATIVE);
458         // Required
459         cdfValues = getAsDoubleArray(props, "cdf.values");
460         final String pf = getProbabilityFunctionName();
461         pfValues = getAsDoubleArray(props, pf + ".values");
462         // Optional
463         double[] tmp = getAsDoubleArray(props, "log" + pf + ".values", null);
464         if (tmp == null && pfValues != null) {
465             tmp = Arrays.stream(pfValues).map(Math::log).toArray();
466         }
467         logPfValues = tmp;
468         tmp = getAsDoubleArray(props, "sf.values", null);
469         if (tmp == null && cdfValues != null) {
470             tmp = Arrays.stream(cdfValues).map(d -> 1.0 - d).toArray();
471         }
472         sfValues = tmp;
473         cdfHpValues = getAsDoubleArray(props, "cdf.hp.values", null);
474         sfHpValues = getAsDoubleArray(props, "sf.hp.values", null);
475 
476         // Remove keys to prevent detection in when searching for test tolerances
477         props.remove(KEY_TOLERANCE_ABSOLUTE);
478         props.remove(KEY_TOLERANCE_RELATIVE);
479 
480         // Store custom tolerances and disabled tests
481         EnumMap<TestName, double[]> map = new EnumMap<>(TestName.class);
482         EnumSet<TestName> set = EnumSet.noneOf(TestName.class);
483         props.stringPropertyNames().forEach(key -> {
484             if (key.endsWith(SUFFIX_DISABLE) && getAsBoolean(props, key, false)) {
485                 final TestName name = TestName.fromString(key.substring(0, key.length() - SUFFIX_DISABLE.length()));
486                 if (name != null) {
487                     set.add(name);
488                 }
489             } else if (key.endsWith(SUFFIX_TOLERANCE_ABSOLUTE)) {
490                 final TestName name = TestName.fromString(key.substring(0, key.length() - SUFFIX_TOLERANCE_ABSOLUTE.length()));
491                 if (name != null) {
492                     final double[] tolerances = map.computeIfAbsent(name, k -> UNSET_TOLERANCES.clone());
493                     tolerances[INDEX_ABSOLUTE] = getAsDouble(props, key);
494                 }
495             } else if (key.endsWith(SUFFIX_TOLERANCE_RELATIVE)) {
496                 final TestName name = TestName.fromString(key.substring(0, key.length() - SUFFIX_TOLERANCE_RELATIVE.length()));
497                 if (name != null) {
498                     final double[] tolerances = map.computeIfAbsent(name, k -> UNSET_TOLERANCES.clone());
499                     tolerances[INDEX_RELATIVE] = getAsDouble(props, key);
500                 }
501             }
502         });
503 
504         this.tolerance = map.isEmpty() ? Collections.emptyMap() : map;
505         this.disabled = set.isEmpty() ? Collections.emptySet() : set;
506     }
507 
508     /**
509      * Gets the name of the probability density function.
510      * For continuous distributions this is PDF and discrete distributions is PMF.
511      *
512      * @return the PDF name
513      */
514     abstract String getProbabilityFunctionName();
515 
516     /**
517      * Parses the String parameter to an appropriate object. Supports Double and Integer.
518      *
519      * @param value Value
520      * @return the object
521      * @throws IllegalArgumentException if the parameter type is unknown
522      */
523     private static Object parseParameter(String value) {
524         // Only support int or double parameters.
525         // This uses inefficient parsing which will relies on catching parse exceptions.
526         try {
527             return parseInt(value);
528         } catch (NumberFormatException ex) { /* ignore */ }
529         try {
530             return parseDouble(value);
531         } catch (NumberFormatException ex) {
532             throw new IllegalArgumentException("Unknown parameter type: " + value, ex);
533         }
534     }
535 
536     /**
537      * Gets the property.
538      *
539      * @param props Properties
540      * @param key Key
541      * @return the value
542      * @throws NullPointerException if the parameter is missing
543      */
544     private static String get(Properties props, String key) {
545         return Objects.requireNonNull(props.getProperty(key), () -> "Missing test data: " + key);
546     }
547 
548     /**
549      * Returns a new {@code int} initialized to the value
550      * represented by the input String.
551      *
552      * <p>A special concession is made for 'max' and 'min'
553      * as a short representation of the maximum and minimum
554      * integer values.
555      *
556      * @param s Input String
557      * @return the int
558      * @see Integer#parseInt(String)
559      * @see Integer#MAX_VALUE
560      * @see Integer#MIN_VALUE
561      */
562     private static int parseInt(String s) {
563         if ("max".equals(s)) {
564             return Integer.MAX_VALUE;
565         } else if ("min".equals(s)) {
566             return Integer.MIN_VALUE;
567         }
568         return Integer.parseInt(s);
569     }
570 
571     /**
572      * Returns a new {@code double} initialized to the value
573      * represented by the input String.
574      *
575      * <p>A special concession is made for 'Inf' or 'inf' as a short
576      * representation of 'Infinity'. This format is used by
577      * matlab and R (Inf) and python (inf).
578      *
579      * @param s Input String
580      * @return the double
581      * @see Double#parseDouble(String)
582      */
583     private static double parseDouble(String s) {
584         // Detect other forms of infinity: -Inf, Inf or inf, -inf
585         final int len = s.length();
586         if ((len == 3 || len == 4) &&
587             s.charAt(len - 1) == 'f' &&
588             s.charAt(len - 2) == 'n') {
589             // Sign detection
590             final int start = s.charAt(0) == '-' ? 1 : 0;
591             // Remaining length must be 3.
592             // Final unchecked character is 'i'.
593             if (s.length() - start == 3 && (s.charAt(start) == 'I' || s.charAt(start) == 'i')) {
594                 return start == 0 ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
595             }
596         }
597         return Double.parseDouble(s);
598     }
599 
600     /**
601      * Gets the property as a double.
602      *
603      * @param props Properties
604      * @param key Key
605      * @return the value
606      * @throws NullPointerException if the parameter is missing.
607      * @throws IllegalArgumentException if the parameter is not a double.
608      */
609     private static double getAsDouble(Properties props, String key) {
610         try {
611             return parseDouble(get(props, key));
612         } catch (NumberFormatException ex) {
613             throw new IllegalArgumentException("Invalid double: " + key, ex);
614         }
615     }
616 
617     /**
618      * Gets the property as a double, or a default value if the property is missing.
619      *
620      * @param props Properties
621      * @param key Key
622      * @param defaultValue Default value
623      * @return the value
624      * @throws IllegalArgumentException if the parameter is not a double.
625      */
626     private static double getAsDouble(Properties props, String key, double defaultValue) {
627         try {
628             final String s = props.getProperty(key);
629             return s == null ? defaultValue : parseDouble(s);
630         } catch (NumberFormatException ex) {
631             throw new IllegalArgumentException("Invalid double: " + key, ex);
632         }
633     }
634 
635     /**
636      * Gets the property as a double, or a default value if the property is missing.
637      *
638      * @param props Properties
639      * @param key Key
640      * @param defaultValue Default value
641      * @return the value
642      * @throws IllegalArgumentException if the parameter is not a double.
643      */
644     private static int getAsInt(Properties props, String key, int defaultValue) {
645         try {
646             final String s = props.getProperty(key);
647             return s == null ? defaultValue : parseInt(s);
648         } catch (NumberFormatException ex) {
649             throw new IllegalArgumentException("Invalid double: " + key, ex);
650         }
651     }
652 
653     /**
654      * Gets the property as a boolean, or a default value if the property is missing.
655      *
656      * @param props Properties
657      * @param key Key
658      * @param defaultValue Default value
659      * @return the value
660      * @throws IllegalArgumentException if the parameter is not a boolean.
661      */
662     private static boolean getAsBoolean(Properties props, String key, boolean defaultValue) {
663         try {
664             final String s = props.getProperty(key);
665             return s == null ? defaultValue : Boolean.parseBoolean(s);
666         } catch (NumberFormatException ex) {
667             throw new IllegalArgumentException("Invalid boolean: " + key, ex);
668         }
669     }
670 
671     /**
672      * Gets the property as a double array.
673      *
674      * @param props Properties
675      * @param key Key
676      * @return the value
677      * @throws NullPointerException if the parameter is missing.
678      * @throws IllegalArgumentException if the parameter is not a double array.
679      */
680     private static double[] getAsDoubleArray(Properties props, String key) {
681         try {
682             return PATTERN.splitAsStream(get(props, key)).mapToDouble(DistributionTestData::parseDouble).toArray();
683         } catch (NumberFormatException ex) {
684             throw new IllegalArgumentException("Invalid double: " + key, ex);
685         }
686     }
687 
688     /**
689      * Gets the property as a double array, or a default value if the property is missing.
690      *
691      * @param props Properties
692      * @param key Key
693      * @param defaultValue Default value
694      * @return the value
695      * @throws IllegalArgumentException if the parameter is not a double array.
696      */
697     private static double[] getAsDoubleArray(Properties props, String key, double[] defaultValue) {
698         try {
699             final String s = props.getProperty(key);
700             return s == null ? defaultValue :
701                 PATTERN.splitAsStream(s).mapToDouble(DistributionTestData::parseDouble).toArray();
702         } catch (NumberFormatException ex) {
703             throw new IllegalArgumentException("Invalid double: " + key, ex);
704         }
705     }
706     /**
707      * Gets the property as a double array.
708      *
709      * @param props Properties
710      * @param key Key
711      * @return the value
712      * @throws NullPointerException if the parameter is missing.
713      * @throws IllegalArgumentException if the parameter is not a double array.
714      */
715     private static int[] getAsIntArray(Properties props, String key) {
716         try {
717             return PATTERN.splitAsStream(get(props, key)).mapToInt(DistributionTestData::parseInt).toArray();
718         } catch (NumberFormatException ex) {
719             throw new IllegalArgumentException("Invalid double: " + key, ex);
720         }
721     }
722 
723     /**
724      * Gets the property as a double array, or a default value if the property is missing.
725      *
726      * @param props Properties
727      * @param key Key
728      * @param defaultValue Default value
729      * @return the value
730      * @throws IllegalArgumentException if the parameter is not a double array.
731      */
732     private static int[] getAsIntArray(Properties props, String key, int[] defaultValue) {
733         try {
734             final String s = props.getProperty(key);
735             return s == null ? defaultValue :
736                 PATTERN.splitAsStream(s).mapToInt(DistributionTestData::parseInt).toArray();
737         } catch (NumberFormatException ex) {
738             throw new IllegalArgumentException("Invalid double: " + key, ex);
739         }
740     }
741 
742     /**
743      * Validate a pair of point-value arrays have the same length if they are both non-zero length.
744      *
745      * @param p Array 1
746      * @param v Array 2
747      * @param name Name of the pair
748      */
749     private static void validatePair(double[] p, double[] v, String name) {
750         validatePair(TestUtils.getLength(p), TestUtils.getLength(v), name);
751     }
752 
753     /**
754      * Validate a pair of point-value arrays have the same length if they are both non-zero length.
755      *
756      * @param p Array 1
757      * @param v Array 2
758      * @param name Name of the pair
759      */
760     private static void validatePair(int[] p, double[] v, String name) {
761         validatePair(TestUtils.getLength(p), TestUtils.getLength(v), name);
762     }
763 
764     /**
765      * Validate a pair of point-value arrays have the same length if they are both non-zero length.
766      *
767      * @param p Array 1
768      * @param v Array 2
769      * @param name Name of the pair
770      */
771     private static void validatePair(double[] p, int[] v, String name) {
772         validatePair(TestUtils.getLength(p), TestUtils.getLength(v), name);
773     }
774 
775     /**
776      * Validate a pair of point-value arrays have the same length if they are both non-zero length.
777      *
778      * @param l1 Length 1
779      * @param l2 Length 2
780      * @param name Name of the pair
781      */
782     private static void validatePair(int l1, int l2, String name) {
783         // Arrays are used when non-zero in length. The lengths must match.
784         if (l1 != 0 && l2 != 0 && l1 != l2) {
785             throw new IllegalArgumentException(
786                 String.format("Points-Values length mismatch for %s: %d != %d", name, l1, l2));
787         }
788     }
789 
790     /**
791      * Gets the parameters used to create the distribution.
792      *
793      * @return the parameters
794      */
795     Object[] getParameters() {
796         return parameters;
797     }
798 
799     /**
800      * Gets the mean of the distribution.
801      *
802      * @return the mean
803      */
804     double getMean() {
805         return mean;
806     }
807 
808     /**
809      * Gets the variance of the distribution.
810      *
811      * @return the variance
812      */
813     double getVariance() {
814         return variance;
815     }
816 
817     /**
818      * Gets the absolute tolerance used when comparing expected and actual results.
819      * If no tolerance exists for the named test then the default is returned.
820      *
821      * @param name Name of the test.
822      * @return the absolute tolerance
823      */
824     double getAbsoluteTolerance(TestName name) {
825         return getTolerance(name, INDEX_ABSOLUTE, absoluteTolerance);
826     }
827 
828     /**
829      * Gets the relative tolerance used when comparing expected and actual results.
830      * If no tolerance exists for the named test then the default is returned.
831      *
832      * @param name Name of the test.
833      * @return the relative tolerance
834      */
835     double getRelativeTolerance(TestName name) {
836         return getTolerance(name, INDEX_RELATIVE, relativeTolerance);
837     }
838 
839     /**
840      * Gets the specified tolerance for the named test.
841      * If no tolerance exists for the named test then the default is returned.
842      *
843      * @param name Name of the test.
844      * @param index Index of the tolerance.
845      * @param defaultValue Default value is the tolerance is unset.
846      * @return the relative tolerance
847      */
848     private double getTolerance(TestName name, int index, double defaultValue) {
849         final double[] tol = tolerance.get(name);
850         if (tol != null && tol[index] != UNSET_TOLERANCE) {
851             return tol[index];
852         }
853         return defaultValue;
854     }
855 
856     /**
857      * Gets the default absolute tolerance used when comparing expected and actual results.
858      *
859      * @return the absolute tolerance
860      */
861     double getAbsoluteTolerance() {
862         return absoluteTolerance;
863     }
864 
865     /**
866      * Gets the default relative tolerance used when comparing expected and actual results.
867      *
868      * @return the relative tolerance
869      */
870     double getRelativeTolerance() {
871         return relativeTolerance;
872     }
873 
874     /**
875      * Checks if the named test is disabled.
876      *
877      * @param name Name of the test.
878      * @return true if test is disabled.
879      */
880     boolean isDisabled(TestName name) {
881         return disabled.contains(name);
882     }
883 
884     /**
885      * Checks if the named test is enabled.
886      *
887      * @param name Name of the test.
888      * @return true if test is enabled.
889      */
890     boolean isEnabled(TestName name) {
891         return !isDisabled(name);
892     }
893 
894     /**
895      * Gets the expected cumulative probability values for the CDF test points.
896      *
897      * @return the CDF values
898      */
899     double[] getCdfValues() {
900         return cdfValues;
901     }
902 
903     /**
904      * Gets the expected survival function values for the survival function test points.
905      *
906      * @return the SF values
907      */
908     double[] getSfValues() {
909         return sfValues;
910     }
911 
912     /**
913      * Gets the expected cumulative probability values for the CDF high-precision test points.
914      *
915      * @return the CDF high-precision values
916      */
917     double[] getCdfHpValues() {
918         return cdfHpValues;
919     }
920 
921     /**
922      * Gets the expected survival probability values for the survival function high-precision test points.
923      *
924      * @return the survival function high-precision values
925      */
926     double[] getSfHpValues() {
927         return sfHpValues;
928     }
929 
930     /**
931      * Gets the points to evaluate the inverse CDF.
932      *
933      * @return the inverse CDF points
934      */
935     abstract double[] getIcdfPoints();
936 
937     /**
938      * Gets the points to evaluate the inverse SF.
939      *
940      * @return the inverse SF points
941      */
942     abstract double[] getIsfPoints();
943 }
944