WeibullDistribution.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.LogGamma;
  19. import org.apache.commons.rng.UniformRandomProvider;
  20. import org.apache.commons.rng.sampling.distribution.ZigguratSampler;

  21. /**
  22.  * Implementation of the Weibull distribution.
  23.  *
  24.  * <p>The probability density function of \( X \) is:
  25.  *
  26.  * <p>\[ f(x;k,\lambda) = \frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{k-1}e^{-(x/\lambda)^{k}} \]
  27.  *
  28.  * <p>for \( k &gt; 0 \) the shape,
  29.  * \( \lambda &gt; 0 \) the scale, and
  30.  * \( x \in (0, \infty) \).
  31.  *
  32.  * <p>Note the special cases:
  33.  * <ul>
  34.  * <li>\( k = 1 \) is the exponential distribution
  35.  * <li>\( k = 2 \) is the Rayleigh distribution with scale \( \sigma = \frac {\lambda}{\sqrt{2}} \)
  36.  * </ul>
  37.  *
  38.  * @see <a href="https://en.wikipedia.org/wiki/Weibull_distribution">Weibull distribution (Wikipedia)</a>
  39.  * @see <a href="https://mathworld.wolfram.com/WeibullDistribution.html">Weibull distribution (MathWorld)</a>
  40.  */
  41. public final class WeibullDistribution extends AbstractContinuousDistribution {
  42.     /** Support lower bound. */
  43.     private static final double SUPPORT_LO = 0;
  44.     /** Support upper bound. */
  45.     private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
  46.     /** The shape parameter. */
  47.     private final double shape;
  48.     /** The scale parameter. */
  49.     private final double scale;
  50.     /** shape / scale. */
  51.     private final double shapeOverScale;
  52.     /** log(shape / scale). */
  53.     private final double logShapeOverScale;

  54.     /**
  55.      * @param shape Shape parameter.
  56.      * @param scale Scale parameter.
  57.      */
  58.     private WeibullDistribution(double shape,
  59.                                 double scale) {
  60.         this.scale = scale;
  61.         this.shape = shape;
  62.         shapeOverScale = shape / scale;
  63.         logShapeOverScale = Math.log(shapeOverScale);
  64.     }

  65.     /**
  66.      * Creates a Weibull distribution.
  67.      *
  68.      * @param shape Shape parameter.
  69.      * @param scale Scale parameter.
  70.      * @return the distribution
  71.      * @throws IllegalArgumentException if {@code shape <= 0} or {@code scale <= 0}.
  72.      */
  73.     public static WeibullDistribution of(double shape,
  74.                                          double scale) {
  75.         if (shape <= 0) {
  76.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
  77.                                             shape);
  78.         }
  79.         if (scale <= 0) {
  80.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
  81.                                             scale);
  82.         }
  83.         return new WeibullDistribution(shape, scale);
  84.     }

  85.     /**
  86.      * Gets the shape parameter of this distribution.
  87.      *
  88.      * @return the shape parameter.
  89.      */
  90.     public double getShape() {
  91.         return shape;
  92.     }

  93.     /**
  94.      * Gets the scale parameter of this distribution.
  95.      *
  96.      * @return the scale parameter.
  97.      */
  98.     public double getScale() {
  99.         return scale;
  100.     }

  101.     /** {@inheritDoc}
  102.      *
  103.      * <p>Returns the limit when {@code x = 0}:
  104.      * <ul>
  105.      * <li>{@code shape < 1}: Infinity
  106.      * <li>{@code shape == 1}: 1 / scale
  107.      * <li>{@code shape > 1}: 0
  108.      * </ul>
  109.      */
  110.     @Override
  111.     public double density(double x) {
  112.         if (x <= SUPPORT_LO || x >= SUPPORT_HI) {
  113.             // Special case x=0
  114.             if (x == SUPPORT_LO && shape <= 1) {
  115.                 return shape == 1 ?
  116.                     // Exponential distribution
  117.                     shapeOverScale :
  118.                     Double.POSITIVE_INFINITY;
  119.             }
  120.             return 0;
  121.         }

  122.         final double xscale = x / scale;
  123.         final double xscalepow = Math.pow(xscale, shape - 1);

  124.         /*
  125.          * Math.pow(x / scale, shape) =
  126.          * Math.pow(xscale, shape) =
  127.          * Math.pow(xscale, shape - 1) * xscale
  128.          */
  129.         final double xscalepowshape = xscalepow * xscale;

  130.         return shapeOverScale * xscalepow * Math.exp(-xscalepowshape);
  131.     }

  132.     /** {@inheritDoc}
  133.      *
  134.      * <p>Returns the limit when {@code x = 0}:
  135.      * <ul>
  136.      * <li>{@code shape < 1}: Infinity
  137.      * <li>{@code shape == 1}: log(1 / scale)
  138.      * <li>{@code shape > 1}: -Infinity
  139.      * </ul>
  140.      */
  141.     @Override
  142.     public double logDensity(double x) {
  143.         if (x <= SUPPORT_LO || x >= SUPPORT_HI) {
  144.             // Special case x=0
  145.             if (x == SUPPORT_LO && shape <= 1) {
  146.                 return shape == 1 ?
  147.                     // Exponential distribution
  148.                     logShapeOverScale :
  149.                     Double.POSITIVE_INFINITY;
  150.             }
  151.             return Double.NEGATIVE_INFINITY;
  152.         }

  153.         final double xscale = x / scale;
  154.         final double logxscalepow = Math.log(xscale) * (shape - 1);

  155.         /*
  156.          * Math.pow(x / scale, shape) =
  157.          * Math.pow(xscale, shape) =
  158.          * Math.pow(xscale, shape - 1) * xscale
  159.          * Math.exp(log(xscale) * (shape - 1)) * xscale
  160.          */
  161.         final double xscalepowshape = Math.exp(logxscalepow) * xscale;

  162.         return logShapeOverScale + logxscalepow - xscalepowshape;
  163.     }

  164.     /** {@inheritDoc} */
  165.     @Override
  166.     public double cumulativeProbability(double x) {
  167.         if (x <= SUPPORT_LO) {
  168.             return 0;
  169.         }

  170.         return -Math.expm1(-Math.pow(x / scale, shape));
  171.     }

  172.     /** {@inheritDoc} */
  173.     @Override
  174.     public double survivalProbability(double x) {
  175.         if (x <= SUPPORT_LO) {
  176.             return 1;
  177.         }

  178.         return Math.exp(-Math.pow(x / scale, shape));
  179.     }

  180.     /**
  181.      * {@inheritDoc}
  182.      *
  183.      * <p>Returns {@code 0} when {@code p == 0} and
  184.      * {@link Double#POSITIVE_INFINITY} when {@code p == 1}.
  185.      */
  186.     @Override
  187.     public double inverseCumulativeProbability(double p) {
  188.         ArgumentUtils.checkProbability(p);
  189.         if (p == 0) {
  190.             return 0.0;
  191.         } else  if (p == 1) {
  192.             return Double.POSITIVE_INFINITY;
  193.         }
  194.         return scale * Math.pow(-Math.log1p(-p), 1.0 / shape);
  195.     }

  196.     /**
  197.      * {@inheritDoc}
  198.      *
  199.      * <p>Returns {@code 0} when {@code p == 1} and
  200.      * {@link Double#POSITIVE_INFINITY} when {@code p == 0}.
  201.      */
  202.     @Override
  203.     public double inverseSurvivalProbability(double p) {
  204.         ArgumentUtils.checkProbability(p);
  205.         if (p == 1) {
  206.             return 0.0;
  207.         } else  if (p == 0) {
  208.             return Double.POSITIVE_INFINITY;
  209.         }
  210.         return scale * Math.pow(-Math.log(p), 1.0 / shape);
  211.     }

  212.     /**
  213.      * {@inheritDoc}
  214.      *
  215.      * <p>For shape parameter \( k \) and scale parameter \( \lambda \), the mean is:
  216.      *
  217.      * <p>\[ \lambda \, \Gamma(1+\frac{1}{k}) \]
  218.      *
  219.      * <p>where \( \Gamma \) is the Gamma-function.
  220.      */
  221.     @Override
  222.     public double getMean() {
  223.         final double sh = getShape();
  224.         final double sc = getScale();

  225.         // Special case of exponential when shape is 1
  226.         return sh == 1 ? sc : sc * Math.exp(LogGamma.value(1 + (1 / sh)));
  227.     }

  228.     /**
  229.      * {@inheritDoc}
  230.      *
  231.      * <p>For shape parameter \( k \) and scale parameter \( \lambda \), the variance is:
  232.      *
  233.      * <p>\[ \lambda^2 \left[ \Gamma\left(1+\frac{2}{k}\right) -
  234.      *                        \left(\Gamma\left(1+\frac{1}{k}\right)\right)^2 \right] \]
  235.      *
  236.      * <p>where \( \Gamma \) is the Gamma-function.
  237.      */
  238.     @Override
  239.     public double getVariance() {
  240.         final double sh = getShape();
  241.         final double sc = getScale();
  242.         final double mn = getMean();

  243.         // Special case of exponential when shape is 1
  244.         return sh == 1 ?
  245.                sc * sc :
  246.                (sc * sc) * Math.exp(LogGamma.value(1 + (2 / sh))) -
  247.                (mn * mn);
  248.     }

  249.     /**
  250.      * {@inheritDoc}
  251.      *
  252.      * <p>The lower bound of the support is always 0.
  253.      *
  254.      * @return 0.
  255.      */
  256.     @Override
  257.     public double getSupportLowerBound() {
  258.         return SUPPORT_LO;
  259.     }

  260.     /**
  261.      * {@inheritDoc}
  262.      *
  263.      * <p>The upper bound of the support is always positive infinity.
  264.      *
  265.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  266.      */
  267.     @Override
  268.     public double getSupportUpperBound() {
  269.         return SUPPORT_HI;
  270.     }

  271.     /** {@inheritDoc} */
  272.     @Override
  273.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  274.         // Special case: shape=1 is the exponential distribution
  275.         if (shape == 1) {
  276.             // Exponential distribution sampler.
  277.             return ZigguratSampler.Exponential.of(rng, scale)::sample;
  278.         }
  279.         return super.createSampler(rng);
  280.     }
  281. }