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
19 import org.apache.commons.numbers.gamma.Erf;
20 import org.apache.commons.numbers.gamma.Erfc;
21 import org.apache.commons.numbers.gamma.InverseErf;
22 import org.apache.commons.numbers.gamma.InverseErfc;
23 import org.apache.commons.rng.UniformRandomProvider;
24 import org.apache.commons.rng.sampling.distribution.LevySampler;
25
26 /**
27 * Implementation of the Lévy distribution.
28 *
29 * <p>The probability density function of \( X \) is:
30 *
31 * <p>\[ f(x; \mu, c) = \sqrt{\frac{c}{2\pi}}~~\frac{e^{ -\frac{c}{2(x-\mu)}}} {(x-\mu)^{3/2}} \]
32 *
33 * <p>for \( \mu \) the location,
34 * \( c > 0 \) the scale, and
35 * \( x \in [\mu, \infty) \).
36 *
37 * @see <a href="https://en.wikipedia.org/wiki/L%C3%A9vy_distribution">Lévy distribution (Wikipedia)</a>
38 * @see <a href="https://mathworld.wolfram.com/LevyDistribution.html">Lévy distribution (MathWorld)</a>
39 */
40 public final class LevyDistribution extends AbstractContinuousDistribution {
41 /** 1 / 2(erfc^-1 (0.5))^2. Computed using Matlab's VPA to 30 digits. */
42 private static final double HALF_OVER_ERFCINV_HALF_SQUARED = 2.1981093383177324039996779530797;
43 /** Location parameter. */
44 private final double mu;
45 /** Scale parameter. */
46 private final double c;
47 /** Half of c (for calculations). */
48 private final double halfC;
49
50 /**
51 * @param mu Location parameter.
52 * @param c Scale parameter.
53 */
54 private LevyDistribution(double mu,
55 double c) {
56 this.mu = mu;
57 this.c = c;
58 this.halfC = 0.5 * c;
59 }
60
61 /**
62 * Creates a Levy distribution.
63 *
64 * @param mu Location parameter.
65 * @param c Scale parameter.
66 * @return the distribution
67 * @throws IllegalArgumentException if {@code c <= 0}.
68 */
69 public static LevyDistribution of(double mu,
70 double c) {
71 if (c <= 0) {
72 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
73 c);
74 }
75 return new LevyDistribution(mu, c);
76 }
77
78 /**
79 * Gets the location parameter of this distribution.
80 *
81 * @return the location parameter.
82 */
83 public double getLocation() {
84 return mu;
85 }
86
87 /**
88 * Gets the scale parameter of this distribution.
89 *
90 * @return the scale parameter.
91 */
92 public double getScale() {
93 return c;
94 }
95
96 /**
97 * {@inheritDoc}
98 *
99 * <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 }