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 */
017package org.apache.commons.math3.distribution;
018
019import org.apache.commons.math3.exception.NumberIsTooSmallException;
020import org.apache.commons.math3.exception.util.LocalizedFormats;
021import org.apache.commons.math3.random.RandomGenerator;
022import org.apache.commons.math3.random.Well19937c;
023import org.apache.commons.math3.special.Beta;
024import org.apache.commons.math3.special.Gamma;
025import org.apache.commons.math3.util.FastMath;
026import org.apache.commons.math3.util.Precision;
027
028/**
029 * Implements the Beta distribution.
030 *
031 * @see <a href="http://en.wikipedia.org/wiki/Beta_distribution">Beta distribution</a>
032 * @since 2.0 (changed to concrete class in 3.0)
033 */
034public class BetaDistribution extends AbstractRealDistribution {
035    /**
036     * Default inverse cumulative probability accuracy.
037     * @since 2.1
038     */
039    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
040    /** Serializable version identifier. */
041    private static final long serialVersionUID = -1221965979403477668L;
042    /** First shape parameter. */
043    private final double alpha;
044    /** Second shape parameter. */
045    private final double beta;
046    /** Normalizing factor used in density computations.
047     * updated whenever alpha or beta are changed.
048     */
049    private double z;
050    /** Inverse cumulative probability accuracy. */
051    private final double solverAbsoluteAccuracy;
052
053    /**
054     * Build a new instance.
055     * <p>
056     * <b>Note:</b> this constructor will implicitly create an instance of
057     * {@link Well19937c} as random generator to be used for sampling only (see
058     * {@link #sample()} and {@link #sample(int)}). In case no sampling is
059     * needed for the created distribution, it is advised to pass {@code null}
060     * as random generator via the appropriate constructors to avoid the
061     * additional initialisation overhead.
062     *
063     * @param alpha First shape parameter (must be positive).
064     * @param beta Second shape parameter (must be positive).
065     */
066    public BetaDistribution(double alpha, double beta) {
067        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
068    }
069
070    /**
071     * Build a new instance.
072     * <p>
073     * <b>Note:</b> this constructor will implicitly create an instance of
074     * {@link Well19937c} as random generator to be used for sampling only (see
075     * {@link #sample()} and {@link #sample(int)}). In case no sampling is
076     * needed for the created distribution, it is advised to pass {@code null}
077     * as random generator via the appropriate constructors to avoid the
078     * additional initialisation overhead.
079     *
080     * @param alpha First shape parameter (must be positive).
081     * @param beta Second shape parameter (must be positive).
082     * @param inverseCumAccuracy Maximum absolute error in inverse
083     * cumulative probability estimates (defaults to
084     * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
085     * @since 2.1
086     */
087    public BetaDistribution(double alpha, double beta, double inverseCumAccuracy) {
088        this(new Well19937c(), alpha, beta, inverseCumAccuracy);
089    }
090
091    /**
092     * Creates a &beta; distribution.
093     *
094     * @param rng Random number generator.
095     * @param alpha First shape parameter (must be positive).
096     * @param beta Second shape parameter (must be positive).
097     * @since 3.3
098     */
099    public BetaDistribution(RandomGenerator rng, double alpha, double beta) {
100        this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
101    }
102
103    /**
104     * Creates a &beta; distribution.
105     *
106     * @param rng Random number generator.
107     * @param alpha First shape parameter (must be positive).
108     * @param beta Second shape parameter (must be positive).
109     * @param inverseCumAccuracy Maximum absolute error in inverse
110     * cumulative probability estimates (defaults to
111     * {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
112     * @since 3.1
113     */
114    public BetaDistribution(RandomGenerator rng,
115                            double alpha,
116                            double beta,
117                            double inverseCumAccuracy) {
118        super(rng);
119
120        this.alpha = alpha;
121        this.beta = beta;
122        z = Double.NaN;
123        solverAbsoluteAccuracy = inverseCumAccuracy;
124    }
125
126    /**
127     * Access the first shape parameter, {@code alpha}.
128     *
129     * @return the first shape parameter.
130     */
131    public double getAlpha() {
132        return alpha;
133    }
134
135    /**
136     * Access the second shape parameter, {@code beta}.
137     *
138     * @return the second shape parameter.
139     */
140    public double getBeta() {
141        return beta;
142    }
143
144    /** Recompute the normalization factor. */
145    private void recomputeZ() {
146        if (Double.isNaN(z)) {
147            z = Gamma.logGamma(alpha) + Gamma.logGamma(beta) - Gamma.logGamma(alpha + beta);
148        }
149    }
150
151    /** {@inheritDoc} */
152    public double density(double x) {
153        final double logDensity = logDensity(x);
154        return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity);
155    }
156
157    /** {@inheritDoc} **/
158    @Override
159    public double logDensity(double x) {
160        recomputeZ();
161        if (x < 0 || x > 1) {
162            return Double.NEGATIVE_INFINITY;
163        } else if (x == 0) {
164            if (alpha < 1) {
165                throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA, alpha, 1, false);
166            }
167            return Double.NEGATIVE_INFINITY;
168        } else if (x == 1) {
169            if (beta < 1) {
170                throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA, beta, 1, false);
171            }
172            return Double.NEGATIVE_INFINITY;
173        } else {
174            double logX = FastMath.log(x);
175            double log1mX = FastMath.log1p(-x);
176            return (alpha - 1) * logX + (beta - 1) * log1mX - z;
177        }
178    }
179
180    /** {@inheritDoc} */
181    public double cumulativeProbability(double x)  {
182        if (x <= 0) {
183            return 0;
184        } else if (x >= 1) {
185            return 1;
186        } else {
187            return Beta.regularizedBeta(x, alpha, beta);
188        }
189    }
190
191    /**
192     * Return the absolute accuracy setting of the solver used to estimate
193     * inverse cumulative probabilities.
194     *
195     * @return the solver absolute accuracy.
196     * @since 2.1
197     */
198    @Override
199    protected double getSolverAbsoluteAccuracy() {
200        return solverAbsoluteAccuracy;
201    }
202
203    /**
204     * {@inheritDoc}
205     *
206     * For first shape parameter {@code alpha} and second shape parameter
207     * {@code beta}, the mean is {@code alpha / (alpha + beta)}.
208     */
209    public double getNumericalMean() {
210        final double a = getAlpha();
211        return a / (a + getBeta());
212    }
213
214    /**
215     * {@inheritDoc}
216     *
217     * For first shape parameter {@code alpha} and second shape parameter
218     * {@code beta}, the variance is
219     * {@code (alpha * beta) / [(alpha + beta)^2 * (alpha + beta + 1)]}.
220     */
221    public double getNumericalVariance() {
222        final double a = getAlpha();
223        final double b = getBeta();
224        final double alphabetasum = a + b;
225        return (a * b) / ((alphabetasum * alphabetasum) * (alphabetasum + 1));
226    }
227
228    /**
229     * {@inheritDoc}
230     *
231     * The lower bound of the support is always 0 no matter the parameters.
232     *
233     * @return lower bound of the support (always 0)
234     */
235    public double getSupportLowerBound() {
236        return 0;
237    }
238
239    /**
240     * {@inheritDoc}
241     *
242     * The upper bound of the support is always 1 no matter the parameters.
243     *
244     * @return upper bound of the support (always 1)
245     */
246    public double getSupportUpperBound() {
247        return 1;
248    }
249
250    /** {@inheritDoc} */
251    public boolean isSupportLowerBoundInclusive() {
252        return false;
253    }
254
255    /** {@inheritDoc} */
256    public boolean isSupportUpperBoundInclusive() {
257        return false;
258    }
259
260    /**
261     * {@inheritDoc}
262     *
263     * The support of this distribution is connected.
264     *
265     * @return {@code true}
266     */
267    public boolean isSupportConnected() {
268        return true;
269    }
270
271
272    /** {@inheritDoc}
273    * <p>
274    * Sampling is performed using Cheng algorithms:
275    * </p>
276    * <p>
277    * R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
278    *                 Communications of the ACM, 21, 317–322, 1978.
279    * </p>
280    */
281    @Override
282    public double sample() {
283        return ChengBetaSampler.sample(random, alpha, beta);
284    }
285
286    /** Utility class implementing Cheng's algorithms for beta distribution sampling.
287     * <p>
288     * R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
289     *                 Communications of the ACM, 21, 317–322, 1978.
290     * </p>
291     * @since 3.6
292     */
293    private static final class ChengBetaSampler {
294
295        /**
296         * Returns one sample using Cheng's sampling algorithm.
297         * @param random random generator to use
298         * @param alpha distribution first shape parameter
299         * @param beta distribution second shape parameter
300         * @return sampled value
301         */
302        static double sample(RandomGenerator random, final double alpha, final double beta) {
303            final double a = FastMath.min(alpha, beta);
304            final double b = FastMath.max(alpha, beta);
305
306            if (a > 1) {
307                return algorithmBB(random, alpha, a, b);
308            } else {
309                return algorithmBC(random, alpha, b, a);
310            }
311        }
312
313        /**
314         * Returns one sample using Cheng's BB algorithm, when both &alpha; and &beta; are greater than 1.
315         * @param random random generator to use
316         * @param a0 distribution first shape parameter (&alpha;)
317         * @param a min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
318         * @param b max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
319         * @return sampled value
320         */
321        private static double algorithmBB(RandomGenerator random,
322                                          final double a0,
323                                          final double a,
324                                          final double b) {
325            final double alpha = a + b;
326            final double beta = FastMath.sqrt((alpha - 2.) / (2. * a * b - alpha));
327            final double gamma = a + 1. / beta;
328
329            double r;
330            double w;
331            double t;
332            do {
333                final double u1 = random.nextDouble();
334                final double u2 = random.nextDouble();
335                final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
336                w = a * FastMath.exp(v);
337                final double z = u1 * u1 * u2;
338                r = gamma * v - 1.3862944;
339                final double s = a + r - w;
340                if (s + 2.609438 >= 5 * z) {
341                    break;
342                }
343
344                t = FastMath.log(z);
345                if (s >= t) {
346                    break;
347                }
348            } while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t);
349
350            w = FastMath.min(w, Double.MAX_VALUE);
351            return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
352        }
353
354        /**
355         * Returns one sample using Cheng's BC algorithm, when at least one of &alpha; and &beta; is smaller than 1.
356         * @param random random generator to use
357         * @param a0 distribution first shape parameter (&alpha;)
358         * @param a max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
359         * @param b min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
360         * @return sampled value
361         */
362        private static double algorithmBC(RandomGenerator random,
363                                          final double a0,
364                                          final double a,
365                                          final double b) {
366            final double alpha = a + b;
367            final double beta = 1. / b;
368            final double delta = 1. + a - b;
369            final double k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 0.777778);
370            final double k2 = 0.25 + (0.5 + 0.25 / delta) * b;
371
372            double w;
373            for (;;) {
374                final double u1 = random.nextDouble();
375                final double u2 = random.nextDouble();
376                final double y = u1 * u2;
377                final double z = u1 * y;
378                if (u1 < 0.5) {
379                    if (0.25 * u2 + z - y >= k1) {
380                        continue;
381                    }
382                } else {
383                    if (z <= 0.25) {
384                        final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
385                        w = a * FastMath.exp(v);
386                        break;
387                    }
388
389                    if (z >= k2) {
390                        continue;
391                    }
392                }
393
394                final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
395                w = a * FastMath.exp(v);
396                if (alpha * (FastMath.log(alpha) - FastMath.log(b + w) + v) - 1.3862944 >= FastMath.log(z)) {
397                    break;
398                }
399            }
400
401            w = FastMath.min(w, Double.MAX_VALUE);
402            return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
403        }
404
405    }
406}