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 */ 017package org.apache.commons.statistics.distribution; 018 019import org.apache.commons.numbers.gamma.Erf; 020import org.apache.commons.numbers.gamma.Erfc; 021import org.apache.commons.numbers.gamma.InverseErf; 022import org.apache.commons.numbers.gamma.InverseErfc; 023import org.apache.commons.rng.UniformRandomProvider; 024import org.apache.commons.rng.sampling.distribution.LevySampler; 025 026/** 027 * Implementation of the Lévy distribution. 028 * 029 * <p>The probability density function of \( X \) is: 030 * 031 * <p>\[ f(x; \mu, c) = \sqrt{\frac{c}{2\pi}}~~\frac{e^{ -\frac{c}{2(x-\mu)}}} {(x-\mu)^{3/2}} \] 032 * 033 * <p>for \( \mu \) the location, 034 * \( c > 0 \) the scale, and 035 * \( x \in [\mu, \infty) \). 036 * 037 * @see <a href="https://en.wikipedia.org/wiki/L%C3%A9vy_distribution">Lévy distribution (Wikipedia)</a> 038 * @see <a href="https://mathworld.wolfram.com/LevyDistribution.html">Lévy distribution (MathWorld)</a> 039 */ 040public final class LevyDistribution extends AbstractContinuousDistribution { 041 /** 1 / 2(erfc^-1 (0.5))^2. Computed using Matlab's VPA to 30 digits. */ 042 private static final double HALF_OVER_ERFCINV_HALF_SQUARED = 2.1981093383177324039996779530797; 043 /** Location parameter. */ 044 private final double mu; 045 /** Scale parameter. */ 046 private final double c; 047 /** Half of c (for calculations). */ 048 private final double halfC; 049 050 /** 051 * @param mu Location parameter. 052 * @param c Scale parameter. 053 */ 054 private LevyDistribution(double mu, 055 double c) { 056 this.mu = mu; 057 this.c = c; 058 this.halfC = 0.5 * c; 059 } 060 061 /** 062 * Creates a Levy distribution. 063 * 064 * @param mu Location parameter. 065 * @param c Scale parameter. 066 * @return the distribution 067 * @throws IllegalArgumentException if {@code c <= 0}. 068 */ 069 public static LevyDistribution of(double mu, 070 double c) { 071 if (c <= 0) { 072 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, 073 c); 074 } 075 return new LevyDistribution(mu, c); 076 } 077 078 /** 079 * Gets the location parameter of this distribution. 080 * 081 * @return the location parameter. 082 */ 083 public double getLocation() { 084 return mu; 085 } 086 087 /** 088 * Gets the scale parameter of this distribution. 089 * 090 * @return the scale parameter. 091 */ 092 public double getScale() { 093 return c; 094 } 095 096 /** 097 * {@inheritDoc} 098 * 099 * <p>If {@code x} is less than the location parameter then {@code 0} is 100 * returned, as in these cases the distribution is not defined. 101 */ 102 @Override 103 public double density(final double x) { 104 if (x <= mu) { 105 // x=mu creates NaN: 106 // sqrt(c / 2pi) * exp(-c / 2(x-mu)) / (x-mu)^1.5 107 // = F * exp(-inf) * (x-mu)^-1.5 = F * 0 * inf 108 // Return 0 for this case. 109 return 0; 110 } 111 112 final double delta = x - mu; 113 final double f = halfC / delta; 114 return Math.sqrt(f / Math.PI) * Math.exp(-f) / delta; 115 } 116 117 /** {@inheritDoc} */ 118 @Override 119 public double logDensity(double x) { 120 if (x <= mu) { 121 return Double.NEGATIVE_INFINITY; 122 } 123 124 final double delta = x - mu; 125 final double f = halfC / delta; 126 return 0.5 * Math.log(f / Math.PI) - f - Math.log(delta); 127 } 128 129 /** {@inheritDoc} */ 130 @Override 131 public double cumulativeProbability(final double x) { 132 if (x <= mu) { 133 return 0; 134 } 135 return Erfc.value(Math.sqrt(halfC / (x - mu))); 136 } 137 138 /** {@inheritDoc} */ 139 @Override 140 public double survivalProbability(final double x) { 141 if (x <= mu) { 142 return 1; 143 } 144 return Erf.value(Math.sqrt(halfC / (x - mu))); 145 } 146 147 /** {@inheritDoc} */ 148 @Override 149 public double inverseCumulativeProbability(double p) { 150 ArgumentUtils.checkProbability(p); 151 final double t = InverseErfc.value(p); 152 return mu + halfC / (t * t); 153 } 154 155 /** {@inheritDoc} */ 156 @Override 157 public double inverseSurvivalProbability(double p) { 158 ArgumentUtils.checkProbability(p); 159 final double t = InverseErf.value(p); 160 return mu + halfC / (t * t); 161 } 162 163 /** 164 * {@inheritDoc} 165 * 166 * <p>The mean is equal to positive infinity. 167 * 168 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}. 169 */ 170 @Override 171 public double getMean() { 172 return Double.POSITIVE_INFINITY; 173 } 174 175 /** 176 * {@inheritDoc} 177 * 178 * <p>The variance is equal to positive infinity. 179 * 180 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}. 181 */ 182 @Override 183 public double getVariance() { 184 return Double.POSITIVE_INFINITY; 185 } 186 187 /** 188 * {@inheritDoc} 189 * 190 * <p>The lower bound of the support is the {@linkplain #getLocation() location}. 191 * 192 * @return location. 193 */ 194 @Override 195 public double getSupportLowerBound() { 196 return getLocation(); 197 } 198 199 /** 200 * {@inheritDoc} 201 * 202 * <p>The upper bound of the support is always positive infinity. 203 * 204 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}. 205 */ 206 @Override 207 public double getSupportUpperBound() { 208 return Double.POSITIVE_INFINITY; 209 } 210 211 /** {@inheritDoc} */ 212 @Override 213 double getMedian() { 214 // Overridden for the probability(double, double) method. 215 // This is intentionally not a public method. 216 // u + c / 2(erfc^-1 (0.5))^2 217 return mu + c * HALF_OVER_ERFCINV_HALF_SQUARED; 218 } 219 220 /** {@inheritDoc} */ 221 @Override 222 public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) { 223 // Levy distribution sampler. 224 return LevySampler.of(rng, getLocation(), getScale())::sample; 225 } 226}