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.exception.MathInternalError;
022    import org.apache.commons.math.exception.NotStrictlyPositiveException;
023    import org.apache.commons.math.exception.NumberIsTooSmallException;
024    import org.apache.commons.math.exception.OutOfRangeException;
025    import org.apache.commons.math.exception.util.LocalizedFormats;
026    import org.apache.commons.math.random.RandomDataImpl;
027    import org.apache.commons.math.util.FastMath;
028    
029    
030    /**
031     * Base class for integer-valued discrete distributions.  Default
032     * implementations are provided for some of the methods that do not vary
033     * from distribution to distribution.
034     *
035     * @version $Id: AbstractIntegerDistribution.java 1178295 2011-10-03 04:36:27Z psteitz $
036     */
037    public abstract class AbstractIntegerDistribution extends AbstractDistribution
038        implements IntegerDistribution, Serializable {
039       /** Serializable version identifier */
040        private static final long serialVersionUID = -1146319659338487221L;
041        /**
042         * RandomData instance used to generate samples from the distribution.
043         * @since 2.2
044         */
045        protected final RandomDataImpl randomData = new RandomDataImpl();
046    
047        /**
048         * Default constructor.
049         */
050        protected AbstractIntegerDistribution() {}
051    
052        /**
053         * For a random variable {@code X} whose values are distributed according
054         * to this distribution, this method returns {@code P(X <= x)}.  In other
055         * words, this method represents the (cumulative) distribution function,
056         * or CDF, for this distribution.
057         * If {@code x} does not represent an integer value, the CDF is
058         * evaluated at the greatest integer less than {@code x}.
059         *
060         * @param x Value at which the distribution function is evaluated.
061         * @return the cumulative probability that a random variable with this
062         * distribution takes a value less than or equal to {@code x}.
063         */
064        public double cumulativeProbability(double x) {
065            return cumulativeProbability((int) FastMath.floor(x));
066        }
067    
068        /**
069         * For a random variable {@code X} whose values are distributed
070         * according to this distribution, this method returns
071         * {@code P(x0 <= X <= x1)}.
072         *
073         * @param x0 Inclusive lower bound.
074         * @param x1 Inclusive upper bound.
075         * @return the probability that a random variable with this distribution
076         * will take a value between {@code x0} and {@code x1},
077         * including the endpoints.
078         * @throws NumberIsTooSmallException if {@code x1 > x0}.
079         */
080        @Override
081        public double cumulativeProbability(double x0, double x1) {
082            if (x1 < x0) {
083                throw new NumberIsTooSmallException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
084                                                    x1, x0, true);
085            }
086            if (FastMath.floor(x0) < x0) {
087                return cumulativeProbability(((int) FastMath.floor(x0)) + 1,
088                   (int) FastMath.floor(x1)); // don't want to count mass below x0
089            } else { // x0 is mathematical integer, so use as is
090                return cumulativeProbability((int) FastMath.floor(x0),
091                    (int) FastMath.floor(x1));
092            }
093        }
094    
095        /**
096         * For a random variable {@code X} whose values are distributed according
097         * to this distribution, this method returns {@code P(X <= x)}. In other
098         * words, this method represents the probability distribution function,
099         * or PDF, for this distribution.
100         *
101         * @param x Value at which the PDF is evaluated.
102         * @return PDF for this distribution.
103         */
104        public abstract double cumulativeProbability(int x);
105    
106        /**
107         * For a random variable {@code X} whose values are distributed according
108         * to this distribution, this method returns {@code P(X = x)}. In other
109         * words, this method represents the probability mass function, or PMF,
110         * for the distribution.
111         * If {@code x} does not represent an integer value, 0 is returned.
112         *
113         * @param x Value at which the probability density function is evaluated.
114         * @return the value of the probability density function at {@code x}.
115         */
116        public double probability(double x) {
117            double fl = FastMath.floor(x);
118            if (fl == x) {
119                return this.probability((int) x);
120            } else {
121                return 0;
122            }
123        }
124    
125        /**
126         * For a random variable {@code X} whose values are distributed according
127         * to this distribution, this method returns {@code P(x0 < X < x1)}.
128         *
129         * @param x0 Inclusive lower bound.
130         * @param x1 Inclusive upper bound.
131         * @return the cumulative probability.
132         * @throws NumberIsTooSmallException {@code if x0 > x1}.
133         */
134        public double cumulativeProbability(int x0, int x1) {
135            if (x1 < x0) {
136                throw new NumberIsTooSmallException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
137                                                    x1, x0, true);
138            }
139            return cumulativeProbability(x1) - cumulativeProbability(x0 - 1);
140        }
141    
142        /**
143         * For a random variable {@code X} whose values are distributed according
144         * to this distribution, this method returns the largest {@code x}, such
145         * that {@code P(X <= x) <= p}.
146         *
147         * @param p Desired probability.
148         * @return the largest {@code x} such that {@code P(X < x) <= p}.
149         * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}.
150         */
151        public int inverseCumulativeProbability(final double p) {
152            if (p < 0 || p > 1) {
153                throw new OutOfRangeException(p, 0, 1);
154            }
155    
156            // by default, do simple bisection.
157            // subclasses can override if there is a better method.
158            int x0 = getDomainLowerBound(p);
159            int x1 = getDomainUpperBound(p);
160            double pm;
161            while (x0 < x1) {
162                int xm = x0 + (x1 - x0) / 2;
163                pm = checkedCumulativeProbability(xm);
164                if (pm > p) {
165                    // update x1
166                    if (xm == x1) {
167                        // this can happen with integer division
168                        // simply decrement x1
169                        --x1;
170                    } else {
171                        // update x1 normally
172                        x1 = xm;
173                    }
174                } else {
175                    // update x0
176                    if (xm == x0) {
177                        // this can happen with integer division
178                        // simply increment x0
179                        ++x0;
180                    } else {
181                        // update x0 normally
182                        x0 = xm;
183                    }
184                }
185            }
186    
187            // insure x0 is the correct critical point
188            pm = checkedCumulativeProbability(x0);
189            while (pm > p) {
190                --x0;
191                pm = checkedCumulativeProbability(x0);
192            }
193    
194            return x0;
195        }
196    
197        /**
198         * {@inheritDoc}
199         */
200        public void reseedRandomGenerator(long seed) {
201            randomData.reSeed(seed);
202        }
203    
204        /**
205         * Generates a random value sampled from this distribution. The default
206         * implementation uses the
207         * <a href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">
208         *  inversion method.
209         * </a>
210         *
211         * @return a random value.
212         * @since 2.2
213         */
214        public int sample() {
215            return randomData.nextInversionDeviate(this);
216        }
217    
218        /**
219         * Generates a random sample from the distribution.  The default
220         * implementation generates the sample by calling {@link #sample()}
221         * in a loop.
222         *
223         * @param sampleSize number of random values to generate.
224         * @since 2.2
225         * @return an array representing the random sample.
226         * @throws NotStrictlyPositiveException if {@code sampleSize <= 0}.
227         */
228        public int[] sample(int sampleSize) {
229            if (sampleSize <= 0) {
230                throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES,
231                                                       sampleSize);
232            }
233            int[] out = new int[sampleSize];
234            for (int i = 0; i < sampleSize; i++) {
235                out[i] = sample();
236            }
237            return out;
238        }
239    
240        /**
241         * Computes the cumulative probability function and checks for NaN
242         * values returned.
243         * Throws MathInternalError if the value is NaN. Rethrows any Exception encountered
244         * evaluating the cumulative probability function. Throws
245         * MathInternalError if the cumulative probability function returns NaN.
246         *
247         * @param argument Input value.
248         * @return the cumulative probability.
249         * @throws MathInternalError if the cumulative probability is NaN
250         */
251        private double checkedCumulativeProbability(int argument)
252            throws MathInternalError {
253            double result = Double.NaN;
254                result = cumulativeProbability(argument);
255            if (Double.isNaN(result)) {
256                throw new MathInternalError(
257                        LocalizedFormats.DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN, argument);
258            }
259            return result;
260        }
261    
262        /**
263         * Access the domain value lower bound, based on {@code p}, used to
264         * bracket a PDF root.  This method is used by
265         * {@link #inverseCumulativeProbability(double)} to find critical values.
266         *
267         * @param p Desired probability for the critical value
268         * @return the domain value lower bound, i.e. {@code P(X < 'lower bound') < p}.
269         */
270        protected abstract int getDomainLowerBound(double p);
271    
272        /**
273         * Access the domain value upper bound, based on {@code p}, used to
274         * bracket a PDF root.  This method is used by
275         * {@link #inverseCumulativeProbability(double)} to find critical values.
276         *
277         * @param p Desired probability for the critical value.
278         * @return the domain value upper bound, i.e. {@code P(X < 'upper bound') > p}.
279         */
280        protected abstract int getDomainUpperBound(double p);
281    
282        /**
283         * Access the lower bound of the support.
284         *
285         * @return lower bound of the support (Integer.MIN_VALUE for negative infinity)
286         */
287        public abstract int getSupportLowerBound();
288    
289        /**
290         * Access the upper bound of the support.
291         *
292         * @return upper bound of the support (Integer.MAX_VALUE for positive infinity)
293         */
294        public abstract int getSupportUpperBound();
295    
296        /**
297         * Use this method to get information about whether the lower bound
298         * of the support is inclusive or not. For discrete support,
299         * only true here is meaningful.
300         *
301         * @return true (always but at Integer.MIN_VALUE because of the nature of discrete support)
302         */
303        @Override
304        public boolean isSupportLowerBoundInclusive() {
305            return true;
306        }
307    
308        /**
309         * Use this method to get information about whether the upper bound
310         * of the support is inclusive or not. For discrete support,
311         * only true here is meaningful.
312         *
313         * @return true (always but at Integer.MAX_VALUE because of the nature of discrete support)
314         */
315        @Override
316        public boolean isSupportUpperBoundInclusive() {
317            return true;
318        }
319    }