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