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