TriangularDistribution.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. /**
  19.  * Implementation of the triangular distribution.
  20.  *
  21.  * <p>The probability density function of \( X \) is:
  22.  *
  23.  * <p>\[ f(x; a, b, c) = \begin{cases}
  24.  *       \frac{2(x-a)}{(b-a)(c-a)} &amp; \text{for } a \le x \lt c \\
  25.  *       \frac{2}{b-a}             &amp; \text{for } x = c \\
  26.  *       \frac{2(b-x)}{(b-a)(b-c)} &amp; \text{for } c \lt x \le b \\
  27.  *       \end{cases} \]
  28.  *
  29.  * <p>for \( -\infty \lt a \le c \le b \lt \infty \) and
  30.  * \( x \in [a, b] \).
  31.  *
  32.  * @see <a href="https://en.wikipedia.org/wiki/Triangular_distribution">Triangular distribution (Wikipedia)</a>
  33.  * @see <a href="https://mathworld.wolfram.com/TriangularDistribution.html">Triangular distribution (MathWorld)</a>
  34.  */
  35. public final class TriangularDistribution extends AbstractContinuousDistribution {
  36.     /** Lower limit of this distribution (inclusive). */
  37.     private final double a;
  38.     /** Upper limit of this distribution (inclusive). */
  39.     private final double b;
  40.     /** Mode of this distribution. */
  41.     private final double c;
  42.     /** Cached value ((b - a) * (c - a). */
  43.     private final double divisor1;
  44.     /** Cached value ((b - a) * (b - c)). */
  45.     private final double divisor2;
  46.     /** Cumulative probability at the mode. */
  47.     private final double cdfMode;
  48.     /** Survival probability at the mode. */
  49.     private final double sfMode;

  50.     /**
  51.      * @param a Lower limit of this distribution (inclusive).
  52.      * @param c Mode of this distribution.
  53.      * @param b Upper limit of this distribution (inclusive).
  54.      */
  55.     private TriangularDistribution(double a,
  56.                                    double c,
  57.                                    double b) {
  58.         this.a = a;
  59.         this.c = c;
  60.         this.b = b;
  61.         divisor1 = (b - a) * (c - a);
  62.         divisor2 = (b - a) * (b - c);
  63.         cdfMode = (c - a) / (b - a);
  64.         sfMode = (b - c) / (b - a);
  65.     }

  66.     /**
  67.      * Creates a triangular distribution.
  68.      *
  69.      * @param a Lower limit of this distribution (inclusive).
  70.      * @param c Mode of this distribution.
  71.      * @param b Upper limit of this distribution (inclusive).
  72.      * @return the distribution
  73.      * @throws IllegalArgumentException if {@code a >= b}, if {@code c > b} or if
  74.      * {@code c < a}.
  75.      */
  76.     public static TriangularDistribution of(double a,
  77.                                             double c,
  78.                                             double b) {
  79.         if (a >= b) {
  80.             throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GTE_HIGH,
  81.                                             a, b);
  82.         }
  83.         if (c < a) {
  84.             throw new DistributionException(DistributionException.TOO_SMALL,
  85.                                             c, a);
  86.         }
  87.         if (c > b) {
  88.             throw new DistributionException(DistributionException.TOO_LARGE,
  89.                                             c, b);
  90.         }
  91.         return new TriangularDistribution(a, c, b);
  92.     }

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

  101.     /** {@inheritDoc} */
  102.     @Override
  103.     public double density(double x) {
  104.         if (x < a) {
  105.             return 0;
  106.         }
  107.         if (x < c) {
  108.             final double divident = 2 * (x - a);
  109.             return divident / divisor1;
  110.         }
  111.         if (x == c) {
  112.             return 2 / (b - a);
  113.         }
  114.         if (x <= b) {
  115.             final double divident = 2 * (b - x);
  116.             return divident / divisor2;
  117.         }
  118.         return 0;
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     public double cumulativeProbability(double x)  {
  123.         if (x <= a) {
  124.             return 0;
  125.         }
  126.         if (x < c) {
  127.             final double divident = (x - a) * (x - a);
  128.             return divident / divisor1;
  129.         }
  130.         if (x == c) {
  131.             return cdfMode;
  132.         }
  133.         if (x < b) {
  134.             final double divident = (b - x) * (b - x);
  135.             return 1 - (divident / divisor2);
  136.         }
  137.         return 1;
  138.     }


  139.     /** {@inheritDoc} */
  140.     @Override
  141.     public double survivalProbability(double x)  {
  142.         // By symmetry:
  143.         if (x <= a) {
  144.             return 1;
  145.         }
  146.         if (x < c) {
  147.             final double divident = (x - a) * (x - a);
  148.             return 1 - (divident / divisor1);
  149.         }
  150.         if (x == c) {
  151.             return sfMode;
  152.         }
  153.         if (x < b) {
  154.             final double divident = (b - x) * (b - x);
  155.             return divident / divisor2;
  156.         }
  157.         return 0;
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public double inverseCumulativeProbability(double p) {
  162.         ArgumentUtils.checkProbability(p);
  163.         if (p == 0) {
  164.             return a;
  165.         }
  166.         if (p == 1) {
  167.             return b;
  168.         }
  169.         if (p < cdfMode) {
  170.             return a + Math.sqrt(p * divisor1);
  171.         }
  172.         return b - Math.sqrt((1 - p) * divisor2);
  173.     }

  174.     /** {@inheritDoc} */
  175.     @Override
  176.     public double inverseSurvivalProbability(double p) {
  177.         // By symmetry:
  178.         ArgumentUtils.checkProbability(p);
  179.         if (p == 1) {
  180.             return a;
  181.         }
  182.         if (p == 0) {
  183.             return b;
  184.         }
  185.         if (p >= sfMode) {
  186.             return a + Math.sqrt((1 - p) * divisor1);
  187.         }
  188.         return b - Math.sqrt(p * divisor2);
  189.     }

  190.     /**
  191.      * {@inheritDoc}
  192.      *
  193.      * <p>For lower limit \( a \), upper limit \( b \), and mode \( c \),
  194.      * the mean is \( (a + b + c) / 3 \).
  195.      */
  196.     @Override
  197.     public double getMean() {
  198.         return (a + b + c) / 3;
  199.     }

  200.     /**
  201.      * {@inheritDoc}
  202.      *
  203.      * <p>For lower limit \( a \), upper limit \( b \), and mode \( c \),
  204.      * the variance is \( (a^2 + b^2 + c^2 - ab - ac - bc) / 18 \).
  205.      */
  206.     @Override
  207.     public double getVariance() {
  208.         return (a * a + b * b + c * c - a * b - a * c - b * c) / 18;
  209.     }

  210.     /**
  211.      * {@inheritDoc}
  212.      *
  213.      * <p>The lower bound of the support is equal to the lower limit parameter
  214.      * {@code a} of the distribution.
  215.      */
  216.     @Override
  217.     public double getSupportLowerBound() {
  218.         return a;
  219.     }

  220.     /**
  221.      * {@inheritDoc}
  222.      *
  223.      * <p>The upper bound of the support is equal to the upper limit parameter
  224.      * {@code b} of the distribution.
  225.      */
  226.     @Override
  227.     public double getSupportUpperBound() {
  228.         return b;
  229.     }
  230. }