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