001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.statistics.distribution;
019
020import org.apache.commons.numbers.gamma.ErfDifference;
021import org.apache.commons.numbers.gamma.Erfc;
022import org.apache.commons.numbers.gamma.InverseErfc;
023import org.apache.commons.rng.UniformRandomProvider;
024import org.apache.commons.rng.sampling.distribution.LogNormalSampler;
025import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
026
027/**
028 * Implementation of the log-normal distribution.
029 *
030 * <p>\( X \) is log-normally distributed if its natural logarithm \( \ln(x) \)
031 * is normally distributed. The probability density function of \( X \) is:
032 *
033 * <p>\[ f(x; \mu, \sigma) = \frac 1 {x\sigma\sqrt{2\pi\,}} e^{-{\frac 1 2}\left( \frac{\ln x-\mu}{\sigma} \right)^2 } \]
034 *
035 * <p>for \( \mu \) the mean of the normally distributed natural logarithm of this distribution,
036 * \( \sigma &gt; 0 \) the standard deviation of the normally distributed natural logarithm of this
037 * distribution, and
038 * \( x \in (0, \infty) \).
039 *
040 * @see <a href="https://en.wikipedia.org/wiki/Log-normal_distribution">Log-normal distribution (Wikipedia)</a>
041 * @see <a href="https://mathworld.wolfram.com/LogNormalDistribution.html">Log-normal distribution (MathWorld)</a>
042 */
043public final class LogNormalDistribution extends AbstractContinuousDistribution {
044    /** 0.5 * ln(2 * pi). Computed to 25-digits precision. */
045    private static final double HALF_LOG_TWO_PI = 0.9189385332046727417803297;
046    /** &radic;(2 &pi;). */
047    private static final double SQRT2PI = Math.sqrt(2 * Math.PI);
048    /** The mu parameter of this distribution. */
049    private final double mu;
050    /** The sigma parameter of this distribution. */
051    private final double sigma;
052    /** The value of {@code log(sigma) + 0.5 * log(2*PI)} stored for faster computation. */
053    private final double logSigmaPlusHalfLog2Pi;
054    /** Sigma multiplied by sqrt(2). */
055    private final double sigmaSqrt2;
056    /** Sigma multiplied by sqrt(2 * pi). */
057    private final double sigmaSqrt2Pi;
058
059    /**
060     * @param mu Mean of the natural logarithm of the distribution values.
061     * @param sigma Standard deviation of the natural logarithm of the distribution values.
062     */
063    private LogNormalDistribution(double mu,
064                                  double sigma) {
065        this.mu = mu;
066        this.sigma = sigma;
067        logSigmaPlusHalfLog2Pi = Math.log(sigma) + HALF_LOG_TWO_PI;
068        sigmaSqrt2 = ExtendedPrecision.sqrt2xx(sigma);
069        sigmaSqrt2Pi = sigma * SQRT2PI;
070    }
071
072    /**
073     * Creates a log-normal distribution.
074     *
075     * @param mu Mean of the natural logarithm of the distribution values.
076     * @param sigma Standard deviation of the natural logarithm of the distribution values.
077     * @return the distribution
078     * @throws IllegalArgumentException if {@code sigma <= 0}.
079     */
080    public static LogNormalDistribution of(double mu,
081                                           double sigma) {
082        if (sigma <= 0) {
083            throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, sigma);
084        }
085        return new LogNormalDistribution(mu, sigma);
086    }
087
088    /**
089     * Gets the {@code mu} parameter of this distribution.
090     * This is the mean of the natural logarithm of the distribution values,
091     * not the mean of distribution.
092     *
093     * @return the mu parameter.
094     */
095    public double getMu() {
096        return mu;
097    }
098
099    /**
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}