GammaDistribution.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.statistics.distribution;

  18. import org.apache.commons.numbers.gamma.LogGamma;
  19. import org.apache.commons.numbers.gamma.RegularizedGamma;
  20. import org.apache.commons.rng.UniformRandomProvider;
  21. import org.apache.commons.rng.sampling.distribution.AhrensDieterMarsagliaTsangGammaSampler;

  22. /**
  23.  * Implementation of the gamma distribution.
  24.  *
  25.  * <p>The probability density function of \( X \) is:
  26.  *
  27.  * <p>\[ f(x;k,\theta) = \frac{x^{k-1}e^{-x/\theta}}{\theta^k\Gamma(k)} \]
  28.  *
  29.  * <p>for \( k &gt; 0 \) the shape, \( \theta &gt; 0 \) the scale, \( \Gamma(k) \) is the gamma function
  30.  * and \( x \in (0, \infty) \).
  31.  *
  32.  * @see <a href="https://en.wikipedia.org/wiki/Gamma_distribution">Gamma distribution (Wikipedia)</a>
  33.  * @see <a href="https://mathworld.wolfram.com/GammaDistribution.html">Gamma distribution (MathWorld)</a>
  34.  */
  35. public final class GammaDistribution extends AbstractContinuousDistribution {
  36.     /** Support lower bound. */
  37.     private static final double SUPPORT_LO = 0;
  38.     /** Support upper bound. */
  39.     private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;

  40.     /** The shape parameter. */
  41.     private final double shape;
  42.     /** The scale parameter. */
  43.     private final double scale;
  44.     /** Precomputed term for the log density: {@code -log(gamma(shape)) - log(scale)}. */
  45.     private final double minusLogGammaShapeMinusLogScale;
  46.     /** Cached value for inverse probability function. */
  47.     private final double mean;
  48.     /** Cached value for inverse probability function. */
  49.     private final double variance;

  50.     /**
  51.      * @param shape Shape parameter.
  52.      * @param scale Scale parameter.
  53.      */
  54.     private GammaDistribution(double shape,
  55.                               double scale) {
  56.         this.shape = shape;
  57.         this.scale = scale;
  58.         this.minusLogGammaShapeMinusLogScale = -LogGamma.value(shape) - Math.log(scale);
  59.         mean = shape * scale;
  60.         variance = shape * scale * scale;
  61.     }

  62.     /**
  63.      * Creates a gamma distribution.
  64.      *
  65.      * @param shape Shape parameter.
  66.      * @param scale Scale parameter.
  67.      * @return the distribution
  68.      * @throws IllegalArgumentException if {@code shape <= 0} or {@code scale <= 0}.
  69.      */
  70.     public static GammaDistribution of(double shape,
  71.                                        double scale) {
  72.         if (shape <= 0) {
  73.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, shape);
  74.         }
  75.         if (scale <= 0) {
  76.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, scale);
  77.         }
  78.         return new GammaDistribution(shape, scale);
  79.     }

  80.     /**
  81.      * Gets the shape parameter of this distribution.
  82.      *
  83.      * @return the shape parameter.
  84.      */
  85.     public double getShape() {
  86.         return shape;
  87.     }

  88.     /**
  89.      * Gets the scale parameter of this distribution.
  90.      *
  91.      * @return the scale parameter.
  92.      */
  93.     public double getScale() {
  94.         return scale;
  95.     }

  96.     /** {@inheritDoc}
  97.      *
  98.      * <p>Returns the limit when {@code x = 0}:
  99.      * <ul>
  100.      * <li>{@code shape < 1}: Infinity
  101.      * <li>{@code shape == 1}: 1 / scale
  102.      * <li>{@code shape > 1}: 0
  103.      * </ul>
  104.      */
  105.     @Override
  106.     public double density(double x) {
  107.         if (x <= SUPPORT_LO ||
  108.             x >= SUPPORT_HI) {
  109.             // Special case x=0
  110.             if (x == SUPPORT_LO && shape <= 1) {
  111.                 return shape == 1 ?
  112.                     1 / scale :
  113.                     Double.POSITIVE_INFINITY;
  114.             }
  115.             return 0;
  116.         }

  117.         return RegularizedGamma.P.derivative(shape, x / scale) / scale;
  118.     }

  119.     /** {@inheritDoc}
  120.      *
  121.      * <p>Returns the limit when {@code x = 0}:
  122.      * <ul>
  123.      * <li>{@code shape < 1}: Infinity
  124.      * <li>{@code shape == 1}: -log(scale)
  125.      * <li>{@code shape > 1}: -Infinity
  126.      * </ul>
  127.      */
  128.     @Override
  129.     public double logDensity(double x) {
  130.         if (x <= SUPPORT_LO ||
  131.             x >= SUPPORT_HI) {
  132.             // Special case x=0
  133.             if (x == SUPPORT_LO && shape <= 1) {
  134.                 return shape == 1 ?
  135.                     -Math.log(scale) :
  136.                     Double.POSITIVE_INFINITY;
  137.             }
  138.             return Double.NEGATIVE_INFINITY;
  139.         }

  140.         final double y = x / scale;

  141.         // More accurate to log the density when it is finite.
  142.         // See NUMBERS-174: 'Log of the Gamma P Derivative'
  143.         final double p = RegularizedGamma.P.derivative(shape, y) / scale;
  144.         if (p <= Double.MAX_VALUE && p >= Double.MIN_NORMAL) {
  145.             return Math.log(p);
  146.         }
  147.         // Use the log computation
  148.         return minusLogGammaShapeMinusLogScale - y + Math.log(y) * (shape - 1);
  149.     }

  150.     /** {@inheritDoc} */
  151.     @Override
  152.     public double cumulativeProbability(double x) {
  153.         if (x <= SUPPORT_LO) {
  154.             return 0;
  155.         } else if (x >= SUPPORT_HI) {
  156.             return 1;
  157.         }
  158.         return RegularizedGamma.P.value(shape, x / scale);
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     public double survivalProbability(double x) {
  163.         if (x <= SUPPORT_LO) {
  164.             return 1;
  165.         } else if (x >= SUPPORT_HI) {
  166.             return 0;
  167.         }
  168.         return RegularizedGamma.Q.value(shape, x / scale);
  169.     }

  170.     /**
  171.      * {@inheritDoc}
  172.      *
  173.      * <p>For shape parameter \( k \) and scale parameter \( \theta \), the
  174.      * mean is \( k \theta \).
  175.      */
  176.     @Override
  177.     public double getMean() {
  178.         return mean;
  179.     }

  180.     /**
  181.      * {@inheritDoc}
  182.      *
  183.      * <p>For shape parameter \( k \) and scale parameter \( \theta \), the
  184.      * variance is \( k \theta^2 \).
  185.      */
  186.     @Override
  187.     public double getVariance() {
  188.         return variance;
  189.     }

  190.     /**
  191.      * {@inheritDoc}
  192.      *
  193.      * <p>The lower bound of the support is always 0.
  194.      *
  195.      * @return 0.
  196.      */
  197.     @Override
  198.     public double getSupportLowerBound() {
  199.         return SUPPORT_LO;
  200.     }

  201.     /**
  202.      * {@inheritDoc}
  203.      *
  204.      * <p>The upper bound of the support is always positive infinity.
  205.      *
  206.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  207.      */
  208.     @Override
  209.     public double getSupportUpperBound() {
  210.         return SUPPORT_HI;
  211.     }

  212.     /** {@inheritDoc} */
  213.     @Override
  214.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  215.         // Gamma distribution sampler.
  216.         return AhrensDieterMarsagliaTsangGammaSampler.of(rng, shape, scale)::sample;
  217.     }
  218. }