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 β 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 β 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 α and β are greater than 1. 315 * @param random random generator to use 316 * @param a0 distribution first shape parameter (α) 317 * @param a min(α, β) where α, β are the two distribution shape parameters 318 * @param b max(α, β) where α, β 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 α and β is smaller than 1. 356 * @param random random generator to use 357 * @param a0 distribution first shape parameter (α) 358 * @param a max(α, β) where α, β are the two distribution shape parameters 359 * @param b min(α, β) where α, β 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}