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