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 αθ), and the variance is
13 * αθ<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 α or k. */
30 private final double shape;
31
32 /** the scale parameter of the distribution, also often called θ. */
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 * αθ or kθ), and the variance is αθ<sup>2</sup>.
39 * @param stream StreamInterface; the random number stream
40 * @param shape double; is the shape parameter > 0, also known as α or k
41 * @param scale double; is the scale parameter> 0, also known as θ
42 * @throws IllegalArgumentException when shape <= 0.0 or scale <= 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 }