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 /** 0.5 * ln(2 * pi). Computed to 25-digits precision. */ 45 private static final double HALF_LOG_TWO_PI = 0.9189385332046727417803297; 46 /** √(2 π). */ 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 > 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 > 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 }