View Javadoc
1   package nl.tudelft.simulation.jstats.distributions;
2   
3   import org.djutils.exceptions.Throw;
4   import org.djutils.logger.CategoryLogger;
5   
6   import nl.tudelft.simulation.jstats.math.ProbMath;
7   import nl.tudelft.simulation.jstats.streams.StreamInterface;
8   
9   /**
10   * The Gamma distribution. For more information on this distribution see
11   * <a href="https://mathworld.wolfram.com/GammaDistribution.html"> https://mathworld.wolfram.com/GammaDistribution.html </a><br>
12   * The parameters are not rate-related, but average-related, so the mean is shape*scale (or &alpha;&theta;), and the variance is
13   * &alpha;&theta;<sup>2</sup>.
14   * <p>
15   * Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
16   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
17   * project is distributed under a three-clause BSD-style license, which can be found at
18   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
19   * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
20   * </p>
21   * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
22   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   */
24  public class DistGamma extends DistContinuous
25  {
26      /** */
27      private static final long serialVersionUID = 1L;
28  
29      /** the shape parameter of the distribution, also often called &alpha; or k. */
30      private final double shape;
31  
32      /** the scale parameter of the distribution, also often called &theta;. */
33      private final double scale;
34  
35      /**
36       * constructs a new gamma distribution. The gamma distribution represents the time to complete some task, e.g. customer
37       * service or machine repair. The parameters are not rate-related, but average-related, so the mean is shape*scale (or
38       * &alpha;&theta; or k&theta;), and the variance is &alpha;&theta;<sup>2</sup>.
39       * @param stream StreamInterface; the random number stream
40       * @param shape double; is the shape parameter &gt; 0, also known as &alpha; or k
41       * @param scale double; is the scale parameter&gt; 0, also known as &theta;
42       * @throws IllegalArgumentException when shape &lt;= 0.0 or scale &lt;= 0.0
43       */
44      public DistGamma(final StreamInterface stream, final double shape, final double scale)
45      {
46          super(stream);
47          Throw.when(shape <= 0.0 || scale <= 0.0, IllegalArgumentException.class, "Error Gamma - shape <= 0.0 or scale <= 0.0");
48          this.shape = shape;
49          this.scale = scale;
50      }
51  
52      /** {@inheritDoc} */
53      @Override
54      public double draw()
55      {
56          // according to Law and Kelton, Simulation Modeling and Analysis, 1991
57          // pages 488-489
58          if (this.shape < 1.0)
59          {
60              double b = (Math.E + this.shape) / Math.E;
61              int counter = 0;
62              while (counter < 1000)
63              {
64                  // step 1.
65                  double p = b * this.stream.nextDouble();
66                  if (p <= 1.0d)
67                  {
68                      // step 2.
69                      double y = Math.pow(p, 1.0d / this.shape);
70                      double u2 = this.stream.nextDouble();
71                      if (u2 <= Math.exp(-y))
72                      {
73                          return this.scale * y;
74                      }
75                  }
76                  else
77                  {
78                      // step 3.
79                      double y = -Math.log((b - p) / this.shape);
80                      double u2 = this.stream.nextDouble();
81                      if (u2 <= Math.pow(y, this.shape - 1.0d))
82                      {
83                          return this.scale * y;
84                      }
85                  }
86                  counter++;
87              }
88              CategoryLogger.always().info("Gamma distribution -- 1000 tries for alpha<1.0");
89              return 1.0d;
90          }
91          else if (this.shape > 1.0)
92          {
93              // according to Law and Kelton, Simulation Modeling and
94              // Analysis, 1991, pages 488-489
95              double a = 1.0d / Math.sqrt(2.0d * this.shape - 1.0d);
96              double b = this.shape - Math.log(4.0d);
97              double q = this.shape + (1.0d / a);
98              double theta = 4.5d;
99              double d = 1.0d + Math.log(theta);
100             int counter = 0;
101             while (counter < 1000)
102             {
103                 // step 1.
104                 double u1 = this.stream.nextDouble();
105                 double u2 = this.stream.nextDouble();
106                 // step 2.
107                 double v = a * Math.log(u1 / (1.0d - u1));
108                 double y = this.shape * Math.exp(v);
109                 double z = u1 * u1 * u2;
110                 double w = b + q * v - y;
111                 // step 3.
112                 if ((w + d - theta * z) >= 0.0d)
113                 {
114                     return this.scale * y;
115                 }
116                 // step 4.
117                 if (w > Math.log(z))
118                 {
119                     return this.scale * y;
120                 }
121                 counter++;
122             }
123             CategoryLogger.always().info("Gamma distribution -- 1000 tries for alpha>1.0");
124             return 1.0d;
125         }
126         else
127         // shape == 1.0
128         {
129             // Gamma(1.0, scale) ~ exponential with mean = scale
130             return -this.scale * Math.log(this.stream.nextDouble());
131         }
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public double getProbabilityDensity(final double x)
137     {
138         if (x <= 0)
139         {
140             return 0.0;
141         }
142         return (Math.pow(this.scale, -this.shape) * Math.pow(x, this.shape - 1) * Math.exp(-1 * x / this.scale))
143                 / ProbMath.gamma(this.shape);
144     }
145 
146     /**
147      * @return alpha
148      */
149     public double getShape()
150     {
151         return this.shape;
152     }
153 
154     /**
155      * @return beta
156      */
157     public double getScale()
158     {
159         return this.scale;
160     }
161 
162     /** {@inheritDoc} */
163     @Override
164     public String toString()
165     {
166         return "Gamma(" + this.shape + "," + this.scale + ")";
167     }
168 }