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 /*
19 * This is not the original file distributed by the Apache Software Foundation
20 * It has been modified by the Hipparchus project
21 */
22 package org.hipparchus.distribution.continuous;
23
24 import org.hipparchus.UnitTestUtils;
25 import org.hipparchus.analysis.UnivariateFunction;
26 import org.hipparchus.analysis.integration.BaseAbstractUnivariateIntegrator;
27 import org.hipparchus.analysis.integration.IterativeLegendreGaussIntegrator;
28 import org.hipparchus.distribution.RealDistribution;
29 import org.hipparchus.exception.MathIllegalArgumentException;
30 import org.hipparchus.util.FastMath;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34
35 import java.util.ArrayList;
36 import java.util.Collections;
37
38 import static org.junit.jupiter.api.Assertions.assertEquals;
39 import static org.junit.jupiter.api.Assertions.fail;
40
41 /**
42 * Abstract base class for {@link RealDistribution} tests.
43 * <p>
44 * To create a concrete test class for a continuous distribution
45 * implementation, first implement makeDistribution() to return a distribution
46 * instance to use in tests. Then implement each of the test data generation
47 * methods below. In each case, the test points and test values arrays
48 * returned represent parallel arrays of inputs and expected values for the
49 * distribution returned by makeDistribution(). Default implementations
50 * are provided for the makeInverseXxx methods that just invert the mapping
51 * defined by the arrays returned by the makeCumulativeXxx methods.
52 * <p>
53 * makeCumulativeTestPoints() -- arguments used to test cumulative probabilities
54 * makeCumulativeTestValues() -- expected cumulative probabilities
55 * makeDensityTestValues() -- expected density values at cumulativeTestPoints
56 * makeInverseCumulativeTestPoints() -- arguments used to test inverse cdf
57 * makeInverseCumulativeTestValues() -- expected inverse cdf values
58 * <p>
59 * To implement additional test cases with different distribution instances and
60 * test data, use the setXxx methods for the instance data in test cases and
61 * call the verifyXxx methods to verify results.
62 * <p>
63 * Error tolerance can be overridden by implementing getTolerance().
64 * <p>
65 * Test data should be validated against reference tables or other packages
66 * where possible, and the source of the reference data and/or validation
67 * should be documented in the test cases. A framework for validating
68 * distribution data against R is included in the /src/test/R source tree.
69 * <p>
70 * See {@link NormalDistributionTest} and {@link ChiSquaredDistributionTest}
71 * for examples.
72 */
73 public abstract class RealDistributionAbstractTest {
74
75 //-------------------- Private test instance data -------------------------
76 /** Distribution instance used to perform tests */
77 private RealDistribution distribution;
78
79 /** Tolerance used in comparing expected and returned values */
80 private double tolerance = 1E-4;
81
82 /** Arguments used to test cumulative probability density calculations */
83 private double[] cumulativeTestPoints;
84
85 /** Values used to test cumulative probability density calculations */
86 private double[] cumulativeTestValues;
87
88 /** Arguments used to test inverse cumulative probability density calculations */
89 private double[] inverseCumulativeTestPoints;
90
91 /** Values used to test inverse cumulative probability density calculations */
92 private double[] inverseCumulativeTestValues;
93
94 /** Values used to test density calculations */
95 private double[] densityTestValues;
96
97 /** Values used to test logarithmic density calculations */
98 private double[] logDensityTestValues;
99
100 //-------------------- Abstract methods -----------------------------------
101
102 /** Creates the default continuous distribution instance to use in tests. */
103 public abstract RealDistribution makeDistribution();
104
105 /** Creates the default cumulative probability test input values */
106 public abstract double[] makeCumulativeTestPoints();
107
108 /** Creates the default cumulative probability test expected values */
109 public abstract double[] makeCumulativeTestValues();
110
111 /** Creates the default density test expected values */
112 public abstract double[] makeDensityTestValues();
113
114 /** Creates the default logarithmic density test expected values.
115 * The default implementation simply computes the logarithm
116 * of each value returned by {@link #makeDensityTestValues()}.*/
117 public double[] makeLogDensityTestValues() {
118 final double[] densityTestValues = makeDensityTestValues();
119 final double[] logDensityTestValues = new double[densityTestValues.length];
120 for (int i = 0; i < densityTestValues.length; i++) {
121 logDensityTestValues[i] = FastMath.log(densityTestValues[i]);
122 }
123 return logDensityTestValues;
124 }
125
126 //---- Default implementations of inverse test data generation methods ----
127
128 /** Creates the default inverse cumulative probability test input values */
129 public double[] makeInverseCumulativeTestPoints() {
130 return makeCumulativeTestValues();
131 }
132
133 /** Creates the default inverse cumulative probability density test expected values */
134 public double[] makeInverseCumulativeTestValues() {
135 return makeCumulativeTestPoints();
136 }
137
138 //-------------------- Setup / tear down ----------------------------------
139
140 /**
141 * Setup sets all test instance data to default values
142 */
143 @BeforeEach
144 public void setUp() {
145 distribution = makeDistribution();
146 cumulativeTestPoints = makeCumulativeTestPoints();
147 cumulativeTestValues = makeCumulativeTestValues();
148 inverseCumulativeTestPoints = makeInverseCumulativeTestPoints();
149 inverseCumulativeTestValues = makeInverseCumulativeTestValues();
150 densityTestValues = makeDensityTestValues();
151 logDensityTestValues = makeLogDensityTestValues();
152 }
153
154 /**
155 * Cleans up test instance data
156 */
157 @AfterEach
158 public void tearDown() {
159 distribution = null;
160 cumulativeTestPoints = null;
161 cumulativeTestValues = null;
162 inverseCumulativeTestPoints = null;
163 inverseCumulativeTestValues = null;
164 densityTestValues = null;
165 logDensityTestValues = null;
166 }
167
168 //-------------------- Verification methods -------------------------------
169
170 /**
171 * Verifies that cumulative probability density calculations match expected values
172 * using current test instance data
173 */
174 protected void verifyCumulativeProbabilities() {
175 // verify cumulativeProbability(double)
176 for (int i = 0; i < cumulativeTestPoints.length; i++) {
177 UnitTestUtils.customAssertEquals("Incorrect cumulative probability value returned for "
178 + cumulativeTestPoints[i], cumulativeTestValues[i],
179 distribution.cumulativeProbability(cumulativeTestPoints[i]),
180 getTolerance());
181 }
182 // verify probability(double, double)
183 for (int i = 0; i < cumulativeTestPoints.length; i++) {
184 for (int j = 0; j < cumulativeTestPoints.length; j++) {
185 if (cumulativeTestPoints[i] <= cumulativeTestPoints[j]) {
186 UnitTestUtils.customAssertEquals(cumulativeTestValues[j] - cumulativeTestValues[i],
187 distribution.probability(cumulativeTestPoints[i], cumulativeTestPoints[j]),
188 getTolerance());
189 } else {
190 try {
191 distribution.probability(cumulativeTestPoints[i], cumulativeTestPoints[j]);
192 } catch (MathIllegalArgumentException e) {
193 continue;
194 }
195 fail("distribution.probability(double, double) should have thrown an exception that second argument is too large");
196 }
197 }
198 }
199 }
200
201 /**
202 * Verifies that inverse cumulative probability density calculations match expected values
203 * using current test instance data
204 */
205 protected void verifyInverseCumulativeProbabilities() {
206 for (int i = 0; i < inverseCumulativeTestPoints.length; i++) {
207 UnitTestUtils.customAssertEquals("Incorrect inverse cumulative probability value returned for "
208 + inverseCumulativeTestPoints[i], inverseCumulativeTestValues[i],
209 distribution.inverseCumulativeProbability(inverseCumulativeTestPoints[i]),
210 getTolerance());
211 }
212 }
213
214 /**
215 * Verifies that density calculations match expected values
216 */
217 protected void verifyDensities() {
218 for (int i = 0; i < cumulativeTestPoints.length; i++) {
219 UnitTestUtils.customAssertEquals("Incorrect probability density value returned for "
220 + cumulativeTestPoints[i], densityTestValues[i],
221 distribution.density(cumulativeTestPoints[i]),
222 getTolerance());
223 }
224 }
225
226 /**
227 * Verifies that logarithmic density calculations match expected values
228 */
229 protected void verifyLogDensities() {
230 for (int i = 0; i < cumulativeTestPoints.length; i++) {
231 UnitTestUtils.customAssertEquals("Incorrect probability density value returned for "
232 + cumulativeTestPoints[i], logDensityTestValues[i],
233 distribution.logDensity(cumulativeTestPoints[i]),
234 getTolerance());
235 }
236 }
237
238 //------------------------ Default test cases -----------------------------
239
240 /**
241 * Verifies that cumulative probability density calculations match expected values
242 * using default test instance data
243 */
244 @Test
245 public void testCumulativeProbabilities() {
246 verifyCumulativeProbabilities();
247 }
248
249 /**
250 * Verifies that inverse cumulative probability density calculations match expected values
251 * using default test instance data
252 */
253 @Test
254 public void testInverseCumulativeProbabilities() {
255 verifyInverseCumulativeProbabilities();
256 }
257
258 /**
259 * Verifies that density calculations return expected values
260 * for default test instance data
261 */
262 @Test
263 public void testDensities() {
264 verifyDensities();
265 }
266
267 /**
268 * Verifies that logarithmic density calculations return expected values
269 * for default test instance data
270 */
271 @Test
272 public void testLogDensities() {
273 verifyLogDensities();
274 }
275
276 /**
277 * Verifies that probability computations are consistent
278 */
279 @Test
280 public void testConsistency() {
281 for (int i=1; i < cumulativeTestPoints.length; i++) {
282
283 // check that cdf(x, x) = 0
284 UnitTestUtils.customAssertEquals(0d,
285 distribution.probability
286 (cumulativeTestPoints[i], cumulativeTestPoints[i]), tolerance);
287
288 // check that P(a < X <= b) = P(X <= b) - P(X <= a)
289 double upper = FastMath.max(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
290 double lower = FastMath.min(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
291 double diff = distribution.cumulativeProbability(upper) -
292 distribution.cumulativeProbability(lower);
293 double direct = distribution.probability(lower, upper);
294 UnitTestUtils.customAssertEquals("Inconsistent probability for ("
295 + lower + "," + upper + ")", diff, direct, tolerance);
296 }
297 }
298
299 /**
300 * Verifies that illegal arguments are correctly handled
301 */
302 @Test
303 public void testIllegalArguments() {
304 try {
305 distribution.probability(1, 0);
306 fail("Expecting MathIllegalArgumentException for bad cumulativeProbability interval");
307 } catch (MathIllegalArgumentException ex) {
308 // expected
309 }
310 try {
311 distribution.inverseCumulativeProbability(-1);
312 fail("Expecting MathIllegalArgumentException for p = -1");
313 } catch (MathIllegalArgumentException ex) {
314 // expected
315 }
316 try {
317 distribution.inverseCumulativeProbability(2);
318 fail("Expecting MathIllegalArgumentException for p = 2");
319 } catch (MathIllegalArgumentException ex) {
320 // expected
321 }
322 }
323
324 /**
325 * Verify that density integrals match the distribution.
326 * The (filtered, sorted) cumulativeTestPoints array is used to source
327 * integration limits. The integral of the density (estimated using a
328 * Legendre-Gauss integrator) is compared with the cdf over the same
329 * interval. Test points outside of the domain of the density function
330 * are discarded.
331 */
332 @Test
333 public void testDensityIntegrals() {
334 final double tol = 1.0e-9;
335 final BaseAbstractUnivariateIntegrator integrator =
336 new IterativeLegendreGaussIntegrator(5, 1.0e-12, 1.0e-10);
337 final UnivariateFunction d = new UnivariateFunction() {
338 public double value(double x) {
339 return distribution.density(x);
340 }
341 };
342 final ArrayList<Double> integrationTestPoints = new ArrayList<Double>();
343 for (int i = 0; i < cumulativeTestPoints.length; i++) {
344 if (Double.isNaN(cumulativeTestValues[i]) ||
345 cumulativeTestValues[i] < 1.0e-5 ||
346 cumulativeTestValues[i] > 1 - 1.0e-5) {
347 continue; // exclude integrals outside domain.
348 }
349 integrationTestPoints.add(cumulativeTestPoints[i]);
350 }
351 Collections.sort(integrationTestPoints);
352 for (int i = 1; i < integrationTestPoints.size(); i++) {
353 assertEquals(
354 distribution.probability(
355 integrationTestPoints.get(0), integrationTestPoints.get(i)),
356 integrator.integrate(
357 1000000, // Triangle integrals are very slow to converge
358 d, integrationTestPoints.get(0),
359 integrationTestPoints.get(i)), tol);
360 }
361 }
362
363 //------------------ Getters / Setters for test instance data -----------
364 /**
365 * @return Returns the cumulativeTestPoints.
366 */
367 protected double[] getCumulativeTestPoints() {
368 return cumulativeTestPoints;
369 }
370
371 /**
372 * @param cumulativeTestPoints The cumulativeTestPoints to set.
373 */
374 protected void setCumulativeTestPoints(double[] cumulativeTestPoints) {
375 this.cumulativeTestPoints = cumulativeTestPoints;
376 }
377
378 /**
379 * @return Returns the cumulativeTestValues.
380 */
381 protected double[] getCumulativeTestValues() {
382 return cumulativeTestValues;
383 }
384
385 /**
386 * @param cumulativeTestValues The cumulativeTestValues to set.
387 */
388 protected void setCumulativeTestValues(double[] cumulativeTestValues) {
389 this.cumulativeTestValues = cumulativeTestValues;
390 }
391
392 protected double[] getDensityTestValues() {
393 return densityTestValues;
394 }
395
396 protected void setDensityTestValues(double[] densityTestValues) {
397 this.densityTestValues = densityTestValues;
398 }
399
400 /**
401 * @return Returns the distribution.
402 */
403 protected RealDistribution getDistribution() {
404 return distribution;
405 }
406
407 /**
408 * @param distribution The distribution to set.
409 */
410 protected void setDistribution(RealDistribution distribution) {
411 this.distribution = distribution;
412 }
413
414 /**
415 * @return Returns the inverseCumulativeTestPoints.
416 */
417 protected double[] getInverseCumulativeTestPoints() {
418 return inverseCumulativeTestPoints;
419 }
420
421 /**
422 * @param inverseCumulativeTestPoints The inverseCumulativeTestPoints to set.
423 */
424 protected void setInverseCumulativeTestPoints(double[] inverseCumulativeTestPoints) {
425 this.inverseCumulativeTestPoints = inverseCumulativeTestPoints;
426 }
427
428 /**
429 * @return Returns the inverseCumulativeTestValues.
430 */
431 protected double[] getInverseCumulativeTestValues() {
432 return inverseCumulativeTestValues;
433 }
434
435 /**
436 * @param inverseCumulativeTestValues The inverseCumulativeTestValues to set.
437 */
438 protected void setInverseCumulativeTestValues(double[] inverseCumulativeTestValues) {
439 this.inverseCumulativeTestValues = inverseCumulativeTestValues;
440 }
441
442 /**
443 * @return Returns the tolerance.
444 */
445 protected double getTolerance() {
446 return tolerance;
447 }
448
449 /**
450 * @param tolerance The tolerance to set.
451 */
452 protected void setTolerance(double tolerance) {
453 this.tolerance = tolerance;
454 }
455
456 }