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
18 package org.apache.commons.statistics.distribution;
19
20 import org.apache.commons.numbers.gamma.ErfDifference;
21 import org.apache.commons.numbers.gamma.Erfc;
22 import org.apache.commons.numbers.gamma.InverseErfc;
23 import org.apache.commons.rng.UniformRandomProvider;
24 import org.apache.commons.rng.sampling.distribution.LogNormalSampler;
25 import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
26
27 /**
28 * Implementation of the log-normal distribution.
29 *
30 * <p>\( X \) is log-normally distributed if its natural logarithm \( \ln(x) \)
31 * is normally distributed. The probability density function of \( X \) is:
32 *
33 * <p>\[ f(x; \mu, \sigma) = \frac 1 {x\sigma\sqrt{2\pi\,}} e^{-{\frac 1 2}\left( \frac{\ln x-\mu}{\sigma} \right)^2 } \]
34 *
35 * <p>for \( \mu \) the mean of the normally distributed natural logarithm of this distribution,
36 * \( \sigma > 0 \) the standard deviation of the normally distributed natural logarithm of this
37 * distribution, and
38 * \( x \in (0, \infty) \).
39 *
40 * @see <a href="https://en.wikipedia.org/wiki/Log-normal_distribution">Log-normal distribution (Wikipedia)</a>
41 * @see <a href="https://mathworld.wolfram.com/LogNormalDistribution.html">Log-normal distribution (MathWorld)</a>
42 */
43 public final class LogNormalDistribution extends AbstractContinuousDistribution {
44 /** √(2 π). */
45 private static final double SQRT2PI = Math.sqrt(2 * Math.PI);
46 /** The mu parameter of this distribution. */
47 private final double mu;
48 /** The sigma parameter of this distribution. */
49 private final double sigma;
50 /** The value of {@code log(sigma) + 0.5 * log(2*PI)} stored for faster computation. */
51 private final double logSigmaPlusHalfLog2Pi;
52 /** Sigma multiplied by sqrt(2). */
53 private final double sigmaSqrt2;
54 /** Sigma multiplied by sqrt(2 * pi). */
55 private final double sigmaSqrt2Pi;
56
57 /**
58 * @param mu Mean of the natural logarithm of the distribution values.
59 * @param sigma Standard deviation of the natural logarithm of the distribution values.
60 */
61 private LogNormalDistribution(double mu,
62 double sigma) {
63 this.mu = mu;
64 this.sigma = sigma;
65 logSigmaPlusHalfLog2Pi = Math.log(sigma) + Constants.HALF_LOG_TWO_PI;
66 sigmaSqrt2 = ExtendedPrecision.sqrt2xx(sigma);
67 sigmaSqrt2Pi = sigma * SQRT2PI;
68 }
69
70 /**
71 * Creates a log-normal distribution.
72 *
73 * @param mu Mean of the natural logarithm of the distribution values.
74 * @param sigma Standard deviation of the natural logarithm of the distribution values.
75 * @return the distribution
76 * @throws IllegalArgumentException if {@code sigma <= 0}.
77 */
78 public static LogNormalDistribution of(double mu,
79 double sigma) {
80 if (sigma <= 0) {
81 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, sigma);
82 }
83 return new LogNormalDistribution(mu, sigma);
84 }
85
86 /**
87 * Gets the {@code mu} parameter of this distribution.
88 * This is the mean of the natural logarithm of the distribution values,
89 * not the mean of distribution.
90 *
91 * @return the mu parameter.
92 */
93 public double getMu() {
94 return mu;
95 }
96
97 /**
98 * Gets the {@code sigma} parameter of this distribution.
99 * This is the standard deviation of the natural logarithm of the distribution values,
100 * not the standard deviation of distribution.
101 *
102 * @return the sigma parameter.
103 */
104 public double getSigma() {
105 return sigma;
106 }
107
108 /**
109 * {@inheritDoc}
110 *
111 * <p>For {@code mu}, and sigma {@code s} of this distribution, the PDF
112 * is given by
113 * <ul>
114 * <li>{@code 0} if {@code x <= 0},</li>
115 * <li>{@code exp(-0.5 * ((ln(x) - mu) / s)^2) / (s * sqrt(2 * pi) * x)}
116 * otherwise.</li>
117 * </ul>
118 */
119 @Override
120 public double density(double x) {
121 if (x <= 0) {
122 return 0;
123 }
124 final double x0 = Math.log(x) - mu;
125 final double x1 = x0 / sigma;
126 return Math.exp(-0.5 * x1 * x1) / (sigmaSqrt2Pi * x);
127 }
128
129 /** {@inheritDoc} */
130 @Override
131 public double probability(double x0,
132 double x1) {
133 if (x0 > x1) {
134 throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GT_HIGH,
135 x0, x1);
136 }
137 if (x0 <= 0) {
138 return cumulativeProbability(x1);
139 }
140 // Assumes x1 >= x0 && x0 > 0
141 final double v0 = (Math.log(x0) - mu) / sigmaSqrt2;
142 final double v1 = (Math.log(x1) - mu) / sigmaSqrt2;
143 return 0.5 * ErfDifference.value(v0, v1);
144 }
145
146 /** {@inheritDoc}
147 *
148 * <p>See documentation of {@link #density(double)} for computation details.
149 */
150 @Override
151 public double logDensity(double x) {
152 if (x <= 0) {
153 return Double.NEGATIVE_INFINITY;
154 }
155 final double logX = Math.log(x);
156 final double x0 = logX - mu;
157 final double x1 = x0 / sigma;
158 return -0.5 * x1 * x1 - (logSigmaPlusHalfLog2Pi + logX);
159 }
160
161 /**
162 * {@inheritDoc}
163 *
164 * <p>For {@code mu}, and sigma {@code s} of this distribution, the CDF
165 * is given by
166 * <ul>
167 * <li>{@code 0} if {@code x <= 0},</li>
168 * <li>{@code 0} if {@code ln(x) - mu < 0} and {@code mu - ln(x) > 40 * s}, as
169 * in these cases the actual value is within {@link Double#MIN_VALUE} of 0,
170 * <li>{@code 1} if {@code ln(x) - mu >= 0} and {@code ln(x) - mu > 40 * s},
171 * as in these cases the actual value is within {@link Double#MIN_VALUE} of
172 * 1,</li>
173 * <li>{@code 0.5 + 0.5 * erf((ln(x) - mu) / (s * sqrt(2))} otherwise.</li>
174 * </ul>
175 */
176 @Override
177 public double cumulativeProbability(double x) {
178 if (x <= 0) {
179 return 0;
180 }
181 final double dev = Math.log(x) - mu;
182 return 0.5 * Erfc.value(-dev / sigmaSqrt2);
183 }
184
185 /** {@inheritDoc} */
186 @Override
187 public double survivalProbability(double x) {
188 if (x <= 0) {
189 return 1;
190 }
191 final double dev = Math.log(x) - mu;
192 return 0.5 * Erfc.value(dev / sigmaSqrt2);
193 }
194
195 /** {@inheritDoc} */
196 @Override
197 public double inverseCumulativeProbability(double p) {
198 ArgumentUtils.checkProbability(p);
199 return Math.exp(mu - sigmaSqrt2 * InverseErfc.value(2 * p));
200 }
201
202 /** {@inheritDoc} */
203 @Override
204 public double inverseSurvivalProbability(double p) {
205 ArgumentUtils.checkProbability(p);
206 return Math.exp(mu + sigmaSqrt2 * InverseErfc.value(2 * p));
207 }
208
209 /**
210 * {@inheritDoc}
211 *
212 * <p>For \( \mu \) the mean of the normally distributed natural logarithm of
213 * this distribution, \( \sigma > 0 \) the standard deviation of the normally
214 * distributed natural logarithm of this distribution, the mean is:
215 *
216 * <p>\[ \exp(\mu + \frac{\sigma^2}{2}) \]
217 */
218 @Override
219 public double getMean() {
220 final double s = sigma;
221 return Math.exp(mu + (s * s / 2));
222 }
223
224 /**
225 * {@inheritDoc}
226 *
227 * <p>For \( \mu \) the mean of the normally distributed natural logarithm of
228 * this distribution, \( \sigma > 0 \) the standard deviation of the normally
229 * distributed natural logarithm of this distribution, the variance is:
230 *
231 * <p>\[ [\exp(\sigma^2) - 1)] \exp(2 \mu + \sigma^2) \]
232 */
233 @Override
234 public double getVariance() {
235 final double s = sigma;
236 final double ss = s * s;
237 return Math.expm1(ss) * Math.exp(2 * mu + ss);
238 }
239
240 /**
241 * {@inheritDoc}
242 *
243 * <p>The lower bound of the support is always 0.
244 *
245 * @return 0.
246 */
247 @Override
248 public double getSupportLowerBound() {
249 return 0;
250 }
251
252 /**
253 * {@inheritDoc}
254 *
255 * <p>The upper bound of the support is always positive infinity.
256 *
257 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
258 */
259 @Override
260 public double getSupportUpperBound() {
261 return Double.POSITIVE_INFINITY;
262 }
263
264 /** {@inheritDoc} */
265 @Override
266 public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
267 // Log normal distribution sampler.
268 final ZigguratSampler.NormalizedGaussian gaussian = ZigguratSampler.NormalizedGaussian.of(rng);
269 return LogNormalSampler.of(gaussian, mu, sigma)::sample;
270 }
271 }