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 java.io.Serializable;
020    
021    import org.apache.commons.math.MathException;
022    
023    /**
024     * The default implementation of {@link ChiSquaredDistribution}
025     *
026     * @version $Revision: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $
027     */
028    public class ChiSquaredDistributionImpl
029        extends AbstractContinuousDistribution
030        implements ChiSquaredDistribution, Serializable  {
031        
032        /** Serializable version identifier */
033        private static final long serialVersionUID = -8352658048349159782L;
034    
035        /** Internal Gamma distribution. */    
036        private GammaDistribution gamma;
037        
038        /**
039         * Create a Chi-Squared distribution with the given degrees of freedom.
040         * @param df degrees of freedom.
041         */
042        public ChiSquaredDistributionImpl(double df) {
043            this(df, new GammaDistributionImpl(df / 2.0, 2.0));
044        }
045        
046        /**
047         * Create a Chi-Squared distribution with the given degrees of freedom.
048         * @param df degrees of freedom.
049         * @param g the underlying gamma distribution used to compute probabilities.
050         * @since 1.2
051         */
052        public ChiSquaredDistributionImpl(double df, GammaDistribution g) {
053            super();
054            setGamma(g);
055            setDegreesOfFreedom(df);
056        }
057        
058        /**
059         * Modify the degrees of freedom.
060         * @param degreesOfFreedom the new degrees of freedom.
061         */
062        public void setDegreesOfFreedom(double degreesOfFreedom) {
063            getGamma().setAlpha(degreesOfFreedom / 2.0);
064        }
065            
066        /**
067         * Access the degrees of freedom.
068         * @return the degrees of freedom.
069         */
070        public double getDegreesOfFreedom() {
071            return getGamma().getAlpha() * 2.0;
072        }
073    
074        /**
075         * Return the probability density for a particular point.
076         *
077         * @param x The point at which the density should be computed.
078         * @return The pdf at point x.
079         */
080        public double density(Double x) {
081            return gamma.density(x);
082        }
083    
084        /**
085         * For this distribution, X, this method returns P(X < x).
086         * @param x the value at which the CDF is evaluated.
087         * @return CDF for this distribution. 
088         * @throws MathException if the cumulative probability can not be
089         *            computed due to convergence or other numerical errors.
090         */
091        public double cumulativeProbability(double x) throws MathException {
092            return getGamma().cumulativeProbability(x);
093        }
094        
095        /**
096         * For this distribution, X, this method returns the critical point x, such
097         * that P(X &lt; x) = <code>p</code>.
098         * <p>
099         * Returns 0 for p=0 and <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
100         *
101         * @param p the desired probability
102         * @return x, such that P(X &lt; x) = <code>p</code>
103         * @throws MathException if the inverse cumulative probability can not be
104         *         computed due to convergence or other numerical errors.
105         * @throws IllegalArgumentException if <code>p</code> is not a valid
106         *         probability.
107         */
108        @Override
109        public double inverseCumulativeProbability(final double p)
110            throws MathException {
111            if (p == 0) {
112                return 0d;
113            }
114            if (p == 1) {
115                return Double.POSITIVE_INFINITY;
116            }
117            return super.inverseCumulativeProbability(p);
118        }
119            
120        /**
121         * Access the domain value lower bound, based on <code>p</code>, used to
122         * bracket a CDF root.  This method is used by
123         * {@link #inverseCumulativeProbability(double)} to find critical values.
124         * 
125         * @param p the desired probability for the critical value
126         * @return domain value lower bound, i.e.
127         *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code> 
128         */
129        @Override
130        protected double getDomainLowerBound(double p) {
131            return Double.MIN_VALUE * getGamma().getBeta();
132        }
133    
134        /**
135         * Access the domain value upper bound, based on <code>p</code>, used to
136         * bracket a CDF root.  This method is used by
137         * {@link #inverseCumulativeProbability(double)} to find critical values.
138         * 
139         * @param p the desired probability for the critical value
140         * @return domain value upper bound, i.e.
141         *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code> 
142         */
143        @Override
144        protected double getDomainUpperBound(double p) {
145            // NOTE: chi squared is skewed to the left
146            // NOTE: therefore, P(X < &mu;) > .5
147    
148            double ret;
149    
150            if (p < .5) {
151                // use mean
152                ret = getDegreesOfFreedom();
153            } else {
154                // use max
155                ret = Double.MAX_VALUE;
156            }
157            
158            return ret;
159        }
160    
161        /**
162         * Access the initial domain value, based on <code>p</code>, used to
163         * bracket a CDF root.  This method is used by
164         * {@link #inverseCumulativeProbability(double)} to find critical values.
165         * 
166         * @param p the desired probability for the critical value
167         * @return initial domain value
168         */
169        @Override
170        protected double getInitialDomain(double p) {
171            // NOTE: chi squared is skewed to the left
172            // NOTE: therefore, P(X < &mu;) > .5
173            
174            double ret;
175    
176            if (p < .5) {
177                // use 1/2 mean
178                ret = getDegreesOfFreedom() * .5;
179            } else {
180                // use mean
181                ret = getDegreesOfFreedom();
182            }
183            
184            return ret;
185        }
186        
187        /**
188         * Modify the underlying gamma distribution.  The caller is responsible for
189         * insuring the gamma distribution has the proper parameter settings.
190         * @param g the new distribution.
191         * @since 1.2 made public
192         */
193        public void setGamma(GammaDistribution g) {
194            this.gamma = g;
195            
196        }
197    
198        /**
199         * Access the Gamma distribution.
200         * @return the internal Gamma distribution.
201         */
202        private GammaDistribution getGamma() {
203            return gamma;
204        }
205    }