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