001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.math.distribution;
018    
019    import junit.framework.TestCase;
020    
021    import org.apache.commons.math.TestUtils;
022    
023    /**
024     * Abstract base class for {@link ContinuousDistribution} tests.
025     * <p>
026     * To create a concrete test class for a continuous distribution
027     * implementation, first implement makeDistribution() to return a distribution
028     * instance to use in tests. Then implement each of the test data generation
029     * methods below.  In each case, the test points and test values arrays
030     * returned represent parallel arrays of inputs and expected values for the
031     * distribution returned by makeDistribution().  Default implementations
032     * are provided for the makeInverseXxx methods that just invert the mapping
033     * defined by the arrays returned by the makeCumulativeXxx methods.
034     * <p>
035     * makeCumulativeTestPoints() -- arguments used to test cumulative probabilities
036     * makeCumulativeTestValues() -- expected cumulative probabilites
037     * makeInverseCumulativeTestPoints() -- arguments used to test inverse cdf
038     * makeInverseCumulativeTestValues() -- expected inverse cdf values
039     * <p>
040     * To implement additional test cases with different distribution instances and
041     * test data, use the setXxx methods for the instance data in test cases and
042     * call the verifyXxx methods to verify results. 
043     * <p>
044     * Error tolerance can be overriden by implementing getTolerance().
045     * <p>
046     * Test data should be validated against reference tables or other packages
047     * where possible, and the source of the reference data and/or validation
048     * should be documented in the test cases.  A framework for validating
049     * distribution data against R is included in the /src/test/R source tree.
050     * <p>
051     * See {@link NormalDistributionTest} and {@link ChiSquareDistributionTest}
052     * for examples.
053     * 
054     * @version $Revision: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $
055     */
056    public abstract class ContinuousDistributionAbstractTest extends TestCase {
057        
058    //-------------------- Private test instance data -------------------------
059        /**  Distribution instance used to perform tests */
060        private ContinuousDistribution distribution;
061        
062        /** Tolerance used in comparing expected and returned values */
063        private double tolerance = 1E-4;
064        
065        /** Arguments used to test cumulative probability density calculations */
066        private double[] cumulativeTestPoints;
067        
068        /** Values used to test cumulative probability density calculations */
069        private double[] cumulativeTestValues;
070        
071        /** Arguments used to test inverse cumulative probability density calculations */
072        private double[] inverseCumulativeTestPoints;
073        
074        /** Values used to test inverse cumulative probability density calculations */
075        private double[] inverseCumulativeTestValues;
076        
077        //-------------------------------------------------------------------------
078        
079        /**
080         * Constructor for ContinuousDistributionAbstractTest.
081         * @param name
082         */
083        public ContinuousDistributionAbstractTest(String name) {
084            super(name);
085        }
086        
087        //-------------------- Abstract methods -----------------------------------
088        
089        /** Creates the default continuous distribution instance to use in tests. */
090        public abstract ContinuousDistribution makeDistribution();
091        
092        /** Creates the default cumulative probability density test input values */
093        public abstract double[] makeCumulativeTestPoints();
094        
095        /** Creates the default cumulative probability density test expected values */
096        public abstract double[] makeCumulativeTestValues();
097        
098        //---- Default implementations of inverse test data generation methods ----
099        
100        /** Creates the default inverse cumulative probability test input values */
101        public double[] makeInverseCumulativeTestPoints() {
102            return makeCumulativeTestValues();
103        }
104        
105        /** Creates the default inverse cumulative probability density test expected values */
106        public double[] makeInverseCumulativeTestValues() {
107            return makeCumulativeTestPoints();
108        }
109        
110        //-------------------- Setup / tear down ----------------------------------
111         
112        /**
113         * Setup sets all test instance data to default values 
114         */
115        @Override
116        protected void setUp() throws Exception {
117            super.setUp();
118            distribution = makeDistribution();
119            cumulativeTestPoints = makeCumulativeTestPoints();
120            cumulativeTestValues = makeCumulativeTestValues();
121            inverseCumulativeTestPoints = makeInverseCumulativeTestPoints();
122            inverseCumulativeTestValues = makeInverseCumulativeTestValues();   
123        }
124        
125        /**
126         * Cleans up test instance data
127         */
128        @Override
129        protected void tearDown() throws Exception {      
130            super.tearDown();
131            distribution = null;
132            cumulativeTestPoints = null;
133            cumulativeTestValues = null;
134            inverseCumulativeTestPoints = null;
135            inverseCumulativeTestValues = null;   
136        }
137        
138        //-------------------- Verification methods -------------------------------
139        
140        /**
141         * Verifies that cumulative probability density calculations match expected values
142         * using current test instance data
143         */   
144        protected void verifyCumulativeProbabilities() throws Exception {
145            for (int i = 0; i < cumulativeTestPoints.length; i++) {
146                TestUtils.assertEquals("Incorrect cumulative probability value returned for " 
147                    + cumulativeTestPoints[i], cumulativeTestValues[i], 
148                    distribution.cumulativeProbability(cumulativeTestPoints[i]), 
149                    getTolerance());
150            }           
151        }
152        
153        /**
154         * Verifies that inverse cumulative probability density calculations match expected values
155         * using current test instance data
156         */
157        protected void verifyInverseCumulativeProbabilities() throws Exception {
158            for (int i = 0; i < inverseCumulativeTestPoints.length; i++) {
159                TestUtils.assertEquals("Incorrect inverse cumulative probability value returned for " 
160                    + inverseCumulativeTestPoints[i], inverseCumulativeTestValues[i], 
161                     distribution.inverseCumulativeProbability(inverseCumulativeTestPoints[i]), 
162                     getTolerance());
163            }           
164        }
165        
166        //------------------------ Default test cases -----------------------------
167        
168        /**
169         * Verifies that cumulative probability density calculations match expected values
170         * using default test instance data
171         */
172        public void testCumulativeProbabilities() throws Exception {
173            verifyCumulativeProbabilities();      
174        }
175        
176        /**
177         * Verifies that inverse cumulative probability density calculations match expected values
178         * using default test instance data
179         */
180        public void testInverseCumulativeProbabilities() throws Exception {
181            verifyInverseCumulativeProbabilities();       
182        }
183        
184        /**
185         * Verifies that probability computations are consistent
186         */
187        public void testConsistency() throws Exception {
188            for (int i=1; i < cumulativeTestPoints.length; i++) {
189                
190                // check that cdf(x, x) = 0
191                TestUtils.assertEquals(0d, 
192                   distribution.cumulativeProbability
193                     (cumulativeTestPoints[i], cumulativeTestPoints[i]), tolerance);
194                
195                // check that P(a < X < b) = P(X < b) - P(X < a)
196                double upper = Math.max(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
197                double lower = Math.min(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
198                double diff = distribution.cumulativeProbability(upper) - 
199                    distribution.cumulativeProbability(lower);
200                double direct = distribution.cumulativeProbability(lower, upper);
201                TestUtils.assertEquals("Inconsistent cumulative probabilities for (" 
202                        + lower + "," + upper + ")", diff, direct, tolerance);
203            }
204        }
205        
206        /**
207         * Verifies that illegal arguments are correctly handled
208         */
209        public void testIllegalArguments() throws Exception {
210            try {
211                distribution.cumulativeProbability(1, 0);
212                fail("Expecting IllegalArgumentException for bad cumulativeProbability interval");
213            } catch (IllegalArgumentException ex) {
214                // expected
215            }
216            try {
217                distribution.inverseCumulativeProbability(-1);
218                fail("Expecting IllegalArgumentException for p = -1");
219            } catch (IllegalArgumentException ex) {
220                // expected
221            }
222            try {
223                distribution.inverseCumulativeProbability(2);
224                fail("Expecting IllegalArgumentException for p = 2");
225            } catch (IllegalArgumentException ex) {
226                // expected
227            }       
228        }
229        
230        //------------------ Getters / Setters for test instance data -----------
231        /**
232         * @return Returns the cumulativeTestPoints.
233         */
234        protected double[] getCumulativeTestPoints() {
235            return cumulativeTestPoints;
236        }
237    
238        /**
239         * @param cumulativeTestPoints The cumulativeTestPoints to set.
240         */
241        protected void setCumulativeTestPoints(double[] cumulativeTestPoints) {
242            this.cumulativeTestPoints = cumulativeTestPoints;
243        }
244    
245        /**
246         * @return Returns the cumulativeTestValues.
247         */
248        protected double[] getCumulativeTestValues() {
249            return cumulativeTestValues;
250        }
251    
252        /**
253         * @param cumulativeTestValues The cumulativeTestValues to set.
254         */
255        protected void setCumulativeTestValues(double[] cumulativeTestValues) {
256            this.cumulativeTestValues = cumulativeTestValues;
257        }
258    
259        /**
260         * @return Returns the distribution.
261         */
262        protected ContinuousDistribution getDistribution() {
263            return distribution;
264        }
265    
266        /**
267         * @param distribution The distribution to set.
268         */
269        protected void setDistribution(ContinuousDistribution distribution) {
270            this.distribution = distribution;
271        }
272    
273        /**
274         * @return Returns the inverseCumulativeTestPoints.
275         */
276        protected double[] getInverseCumulativeTestPoints() {
277            return inverseCumulativeTestPoints;
278        }
279    
280        /**
281         * @param inverseCumulativeTestPoints The inverseCumulativeTestPoints to set.
282         */
283        protected void setInverseCumulativeTestPoints(double[] inverseCumulativeTestPoints) {
284            this.inverseCumulativeTestPoints = inverseCumulativeTestPoints;
285        }
286    
287        /**
288         * @return Returns the inverseCumulativeTestValues.
289         */
290        protected double[] getInverseCumulativeTestValues() {
291            return inverseCumulativeTestValues;
292        }
293    
294        /**
295         * @param inverseCumulativeTestValues The inverseCumulativeTestValues to set.
296         */
297        protected void setInverseCumulativeTestValues(double[] inverseCumulativeTestValues) {
298            this.inverseCumulativeTestValues = inverseCumulativeTestValues;
299        }
300    
301        /**
302         * @return Returns the tolerance.
303         */
304        protected double getTolerance() {
305            return tolerance;
306        }
307    
308        /**
309         * @param tolerance The tolerance to set.
310         */
311        protected void setTolerance(double tolerance) {
312            this.tolerance = tolerance;
313        }
314    
315    }