LevyDistribution.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.Erf;
  19. import org.apache.commons.numbers.gamma.Erfc;
  20. import org.apache.commons.numbers.gamma.InverseErf;
  21. import org.apache.commons.numbers.gamma.InverseErfc;
  22. import org.apache.commons.rng.UniformRandomProvider;
  23. import org.apache.commons.rng.sampling.distribution.LevySampler;

  24. /**
  25.  * Implementation of the Lévy distribution.
  26.  *
  27.  * <p>The probability density function of \( X \) is:
  28.  *
  29.  * <p>\[ f(x; \mu, c) = \sqrt{\frac{c}{2\pi}}~~\frac{e^{ -\frac{c}{2(x-\mu)}}} {(x-\mu)^{3/2}} \]
  30.  *
  31.  * <p>for \( \mu \) the location,
  32.  * \( c &gt; 0 \) the scale, and
  33.  * \( x \in [\mu, \infty) \).
  34.  *
  35.  * @see <a href="https://en.wikipedia.org/wiki/L%C3%A9vy_distribution">L&eacute;vy distribution (Wikipedia)</a>
  36.  * @see <a href="https://mathworld.wolfram.com/LevyDistribution.html">L&eacute;vy distribution (MathWorld)</a>
  37.  */
  38. public final class LevyDistribution extends AbstractContinuousDistribution {
  39.     /** 1 / 2(erfc^-1 (0.5))^2. Computed using Matlab's VPA to 30 digits. */
  40.     private static final double HALF_OVER_ERFCINV_HALF_SQUARED = 2.1981093383177324039996779530797;
  41.     /** Location parameter. */
  42.     private final double mu;
  43.     /** Scale parameter. */
  44.     private final double c;
  45.     /** Half of c (for calculations). */
  46.     private final double halfC;

  47.     /**
  48.      * @param mu Location parameter.
  49.      * @param c Scale parameter.
  50.      */
  51.     private LevyDistribution(double mu,
  52.                              double c) {
  53.         this.mu = mu;
  54.         this.c = c;
  55.         this.halfC = 0.5 * c;
  56.     }

  57.     /**
  58.      * Creates a Levy distribution.
  59.      *
  60.      * @param mu Location parameter.
  61.      * @param c Scale parameter.
  62.      * @return the distribution
  63.      * @throws IllegalArgumentException if {@code c <= 0}.
  64.      */
  65.     public static LevyDistribution of(double mu,
  66.                                       double c) {
  67.         if (c <= 0) {
  68.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
  69.                                             c);
  70.         }
  71.         return new LevyDistribution(mu, c);
  72.     }

  73.     /**
  74.      * Gets the location parameter of this distribution.
  75.      *
  76.      * @return the location parameter.
  77.      */
  78.     public double getLocation() {
  79.         return mu;
  80.     }

  81.     /**
  82.      * Gets the scale parameter of this distribution.
  83.      *
  84.      * @return the scale parameter.
  85.      */
  86.     public double getScale() {
  87.         return c;
  88.     }

  89.     /**
  90.      * {@inheritDoc}
  91.      *
  92.      * <p>If {@code x} is less than the location parameter then {@code 0} is
  93.      * returned, as in these cases the distribution is not defined.
  94.      */
  95.     @Override
  96.     public double density(final double x) {
  97.         if (x <= mu) {
  98.             // x=mu creates NaN:
  99.             // sqrt(c / 2pi) * exp(-c / 2(x-mu)) / (x-mu)^1.5
  100.             // = F * exp(-inf) * (x-mu)^-1.5 = F * 0 * inf
  101.             // Return 0 for this case.
  102.             return 0;
  103.         }

  104.         final double delta = x - mu;
  105.         final double f = halfC / delta;
  106.         return Math.sqrt(f / Math.PI) * Math.exp(-f) / delta;
  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     public double logDensity(double x) {
  111.         if (x <= mu) {
  112.             return Double.NEGATIVE_INFINITY;
  113.         }

  114.         final double delta = x - mu;
  115.         final double f     = halfC / delta;
  116.         return 0.5 * Math.log(f / Math.PI) - f - Math.log(delta);
  117.     }

  118.     /** {@inheritDoc} */
  119.     @Override
  120.     public double cumulativeProbability(final double x) {
  121.         if (x <= mu) {
  122.             return 0;
  123.         }
  124.         return Erfc.value(Math.sqrt(halfC / (x - mu)));
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public double survivalProbability(final double x) {
  129.         if (x <= mu) {
  130.             return 1;
  131.         }
  132.         return Erf.value(Math.sqrt(halfC / (x - mu)));
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public double inverseCumulativeProbability(double p) {
  137.         ArgumentUtils.checkProbability(p);
  138.         final double t = InverseErfc.value(p);
  139.         return mu + halfC / (t * t);
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public double inverseSurvivalProbability(double p) {
  144.         ArgumentUtils.checkProbability(p);
  145.         final double t = InverseErf.value(p);
  146.         return mu + halfC / (t * t);
  147.     }

  148.     /**
  149.      * {@inheritDoc}
  150.      *
  151.      * <p>The mean is equal to positive infinity.
  152.      *
  153.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  154.      */
  155.     @Override
  156.     public double getMean() {
  157.         return Double.POSITIVE_INFINITY;
  158.     }

  159.     /**
  160.      * {@inheritDoc}
  161.      *
  162.      * <p>The variance is equal to positive infinity.
  163.      *
  164.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  165.      */
  166.     @Override
  167.     public double getVariance() {
  168.         return Double.POSITIVE_INFINITY;
  169.     }

  170.     /**
  171.      * {@inheritDoc}
  172.      *
  173.      * <p>The lower bound of the support is the {@linkplain #getLocation() location}.
  174.      *
  175.      * @return location.
  176.      */
  177.     @Override
  178.     public double getSupportLowerBound() {
  179.         return getLocation();
  180.     }

  181.     /**
  182.      * {@inheritDoc}
  183.      *
  184.      * <p>The upper bound of the support is always positive infinity.
  185.      *
  186.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  187.      */
  188.     @Override
  189.     public double getSupportUpperBound() {
  190.         return Double.POSITIVE_INFINITY;
  191.     }

  192.     /** {@inheritDoc} */
  193.     @Override
  194.     double getMedian() {
  195.         // Overridden for the probability(double, double) method.
  196.         // This is intentionally not a public method.
  197.         // u + c / 2(erfc^-1 (0.5))^2
  198.         return mu + c * HALF_OVER_ERFCINV_HALF_SQUARED;
  199.     }

  200.     /** {@inheritDoc} */
  201.     @Override
  202.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  203.         // Levy distribution sampler.
  204.         return LevySampler.of(rng, getLocation(), getScale())::sample;
  205.     }
  206. }