LogNormalDistribution.java

  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. package org.apache.commons.statistics.distribution;

  18. import org.apache.commons.numbers.gamma.ErfDifference;
  19. import org.apache.commons.numbers.gamma.Erfc;
  20. import org.apache.commons.numbers.gamma.InverseErfc;
  21. import org.apache.commons.rng.UniformRandomProvider;
  22. import org.apache.commons.rng.sampling.distribution.LogNormalSampler;
  23. import org.apache.commons.rng.sampling.distribution.ZigguratSampler;

  24. /**
  25.  * Implementation of the log-normal distribution.
  26.  *
  27.  * <p>\( X \) is log-normally distributed if its natural logarithm \( \ln(x) \)
  28.  * is normally distributed. The probability density function of \( X \) is:
  29.  *
  30.  * <p>\[ f(x; \mu, \sigma) = \frac 1 {x\sigma\sqrt{2\pi\,}} e^{-{\frac 1 2}\left( \frac{\ln x-\mu}{\sigma} \right)^2 } \]
  31.  *
  32.  * <p>for \( \mu \) the mean of the normally distributed natural logarithm of this distribution,
  33.  * \( \sigma &gt; 0 \) the standard deviation of the normally distributed natural logarithm of this
  34.  * distribution, and
  35.  * \( x \in (0, \infty) \).
  36.  *
  37.  * @see <a href="https://en.wikipedia.org/wiki/Log-normal_distribution">Log-normal distribution (Wikipedia)</a>
  38.  * @see <a href="https://mathworld.wolfram.com/LogNormalDistribution.html">Log-normal distribution (MathWorld)</a>
  39.  */
  40. public final class LogNormalDistribution extends AbstractContinuousDistribution {
  41.     /** &radic;(2 &pi;). */
  42.     private static final double SQRT2PI = Math.sqrt(2 * Math.PI);
  43.     /** The mu parameter of this distribution. */
  44.     private final double mu;
  45.     /** The sigma parameter of this distribution. */
  46.     private final double sigma;
  47.     /** The value of {@code log(sigma) + 0.5 * log(2*PI)} stored for faster computation. */
  48.     private final double logSigmaPlusHalfLog2Pi;
  49.     /** Sigma multiplied by sqrt(2). */
  50.     private final double sigmaSqrt2;
  51.     /** Sigma multiplied by sqrt(2 * pi). */
  52.     private final double sigmaSqrt2Pi;

  53.     /**
  54.      * @param mu Mean of the natural logarithm of the distribution values.
  55.      * @param sigma Standard deviation of the natural logarithm of the distribution values.
  56.      */
  57.     private LogNormalDistribution(double mu,
  58.                                   double sigma) {
  59.         this.mu = mu;
  60.         this.sigma = sigma;
  61.         logSigmaPlusHalfLog2Pi = Math.log(sigma) + Constants.HALF_LOG_TWO_PI;
  62.         sigmaSqrt2 = ExtendedPrecision.sqrt2xx(sigma);
  63.         sigmaSqrt2Pi = sigma * SQRT2PI;
  64.     }

  65.     /**
  66.      * Creates a log-normal distribution.
  67.      *
  68.      * @param mu Mean of the natural logarithm of the distribution values.
  69.      * @param sigma Standard deviation of the natural logarithm of the distribution values.
  70.      * @return the distribution
  71.      * @throws IllegalArgumentException if {@code sigma <= 0}.
  72.      */
  73.     public static LogNormalDistribution of(double mu,
  74.                                            double sigma) {
  75.         if (sigma <= 0) {
  76.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, sigma);
  77.         }
  78.         return new LogNormalDistribution(mu, sigma);
  79.     }

  80.     /**
  81.      * Gets the {@code mu} parameter of this distribution.
  82.      * This is the mean of the natural logarithm of the distribution values,
  83.      * not the mean of distribution.
  84.      *
  85.      * @return the mu parameter.
  86.      */
  87.     public double getMu() {
  88.         return mu;
  89.     }

  90.     /**
  91.      * Gets the {@code sigma} parameter of this distribution.
  92.      * This is the standard deviation of the natural logarithm of the distribution values,
  93.      * not the standard deviation of distribution.
  94.      *
  95.      * @return the sigma parameter.
  96.      */
  97.     public double getSigma() {
  98.         return sigma;
  99.     }

  100.     /**
  101.      * {@inheritDoc}
  102.      *
  103.      * <p>For {@code mu}, and sigma {@code s} of this distribution, the PDF
  104.      * is given by
  105.      * <ul>
  106.      * <li>{@code 0} if {@code x <= 0},</li>
  107.      * <li>{@code exp(-0.5 * ((ln(x) - mu) / s)^2) / (s * sqrt(2 * pi) * x)}
  108.      * otherwise.</li>
  109.      * </ul>
  110.      */
  111.     @Override
  112.     public double density(double x) {
  113.         if (x <= 0) {
  114.             return 0;
  115.         }
  116.         final double x0 = Math.log(x) - mu;
  117.         final double x1 = x0 / sigma;
  118.         return Math.exp(-0.5 * x1 * x1) / (sigmaSqrt2Pi * x);
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     public double probability(double x0,
  123.                               double x1) {
  124.         if (x0 > x1) {
  125.             throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GT_HIGH,
  126.                                             x0, x1);
  127.         }
  128.         if (x0 <= 0) {
  129.             return cumulativeProbability(x1);
  130.         }
  131.         // Assumes x1 >= x0 && x0 > 0
  132.         final double v0 = (Math.log(x0) - mu) / sigmaSqrt2;
  133.         final double v1 = (Math.log(x1) - mu) / sigmaSqrt2;
  134.         return 0.5 * ErfDifference.value(v0, v1);
  135.     }

  136.     /** {@inheritDoc}
  137.      *
  138.      * <p>See documentation of {@link #density(double)} for computation details.
  139.      */
  140.     @Override
  141.     public double logDensity(double x) {
  142.         if (x <= 0) {
  143.             return Double.NEGATIVE_INFINITY;
  144.         }
  145.         final double logX = Math.log(x);
  146.         final double x0 = logX - mu;
  147.         final double x1 = x0 / sigma;
  148.         return -0.5 * x1 * x1 - (logSigmaPlusHalfLog2Pi + logX);
  149.     }

  150.     /**
  151.      * {@inheritDoc}
  152.      *
  153.      * <p>For {@code mu}, and sigma {@code s} of this distribution, the CDF
  154.      * is given by
  155.      * <ul>
  156.      * <li>{@code 0} if {@code x <= 0},</li>
  157.      * <li>{@code 0} if {@code ln(x) - mu < 0} and {@code mu - ln(x) > 40 * s}, as
  158.      * in these cases the actual value is within {@link Double#MIN_VALUE} of 0,
  159.      * <li>{@code 1} if {@code ln(x) - mu >= 0} and {@code ln(x) - mu > 40 * s},
  160.      * as in these cases the actual value is within {@link Double#MIN_VALUE} of
  161.      * 1,</li>
  162.      * <li>{@code 0.5 + 0.5 * erf((ln(x) - mu) / (s * sqrt(2))} otherwise.</li>
  163.      * </ul>
  164.      */
  165.     @Override
  166.     public double cumulativeProbability(double x)  {
  167.         if (x <= 0) {
  168.             return 0;
  169.         }
  170.         final double dev = Math.log(x) - mu;
  171.         return 0.5 * Erfc.value(-dev / sigmaSqrt2);
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public double survivalProbability(double x)  {
  176.         if (x <= 0) {
  177.             return 1;
  178.         }
  179.         final double dev = Math.log(x) - mu;
  180.         return 0.5 * Erfc.value(dev / sigmaSqrt2);
  181.     }

  182.     /** {@inheritDoc} */
  183.     @Override
  184.     public double inverseCumulativeProbability(double p) {
  185.         ArgumentUtils.checkProbability(p);
  186.         return Math.exp(mu - sigmaSqrt2 * InverseErfc.value(2 * p));
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public double inverseSurvivalProbability(double p) {
  191.         ArgumentUtils.checkProbability(p);
  192.         return Math.exp(mu + sigmaSqrt2 * InverseErfc.value(2 * p));
  193.     }

  194.     /**
  195.      * {@inheritDoc}
  196.      *
  197.      * <p>For \( \mu \) the mean of the normally distributed natural logarithm of
  198.      * this distribution, \( \sigma &gt; 0 \) the standard deviation of the normally
  199.      * distributed natural logarithm of this distribution, the mean is:
  200.      *
  201.      * <p>\[ \exp(\mu + \frac{\sigma^2}{2}) \]
  202.      */
  203.     @Override
  204.     public double getMean() {
  205.         final double s = sigma;
  206.         return Math.exp(mu + (s * s / 2));
  207.     }

  208.     /**
  209.      * {@inheritDoc}
  210.      *
  211.      * <p>For \( \mu \) the mean of the normally distributed natural logarithm of
  212.      * this distribution, \( \sigma &gt; 0 \) the standard deviation of the normally
  213.      * distributed natural logarithm of this distribution, the variance is:
  214.      *
  215.      * <p>\[ [\exp(\sigma^2) - 1)] \exp(2 \mu + \sigma^2) \]
  216.      */
  217.     @Override
  218.     public double getVariance() {
  219.         final double s = sigma;
  220.         final double ss = s * s;
  221.         return Math.expm1(ss) * Math.exp(2 * mu + ss);
  222.     }

  223.     /**
  224.      * {@inheritDoc}
  225.      *
  226.      * <p>The lower bound of the support is always 0.
  227.      *
  228.      * @return 0.
  229.      */
  230.     @Override
  231.     public double getSupportLowerBound() {
  232.         return 0;
  233.     }

  234.     /**
  235.      * {@inheritDoc}
  236.      *
  237.      * <p>The upper bound of the support is always positive infinity.
  238.      *
  239.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  240.      */
  241.     @Override
  242.     public double getSupportUpperBound() {
  243.         return Double.POSITIVE_INFINITY;
  244.     }

  245.     /** {@inheritDoc} */
  246.     @Override
  247.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  248.         // Log normal distribution sampler.
  249.         final ZigguratSampler.NormalizedGaussian gaussian = ZigguratSampler.NormalizedGaussian.of(rng);
  250.         return LogNormalSampler.of(gaussian, mu, sigma)::sample;
  251.     }
  252. }