View Javadoc
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  
18  package org.apache.commons.statistics.distribution;
19  
20  /**
21   * Implementation of the triangular distribution.
22   *
23   * <p>The probability density function of \( X \) is:
24   *
25   * <p>\[ f(x; a, b, c) = \begin{cases}
26   *       \frac{2(x-a)}{(b-a)(c-a)} &amp; \text{for } a \le x \lt c \\
27   *       \frac{2}{b-a}             &amp; \text{for } x = c \\
28   *       \frac{2(b-x)}{(b-a)(b-c)} &amp; \text{for } c \lt x \le b \\
29   *       \end{cases} \]
30   *
31   * <p>for \( -\infty \lt a \le c \le b \lt \infty \) and
32   * \( x \in [a, b] \).
33   *
34   * @see <a href="https://en.wikipedia.org/wiki/Triangular_distribution">Triangular distribution (Wikipedia)</a>
35   * @see <a href="https://mathworld.wolfram.com/TriangularDistribution.html">Triangular distribution (MathWorld)</a>
36   */
37  public final class TriangularDistribution extends AbstractContinuousDistribution {
38      /** Lower limit of this distribution (inclusive). */
39      private final double a;
40      /** Upper limit of this distribution (inclusive). */
41      private final double b;
42      /** Mode of this distribution. */
43      private final double c;
44      /** Cached value ((b - a) * (c - a). */
45      private final double divisor1;
46      /** Cached value ((b - a) * (b - c)). */
47      private final double divisor2;
48      /** Cumulative probability at the mode. */
49      private final double cdfMode;
50      /** Survival probability at the mode. */
51      private final double sfMode;
52  
53      /**
54       * @param a Lower limit of this distribution (inclusive).
55       * @param c Mode of this distribution.
56       * @param b Upper limit of this distribution (inclusive).
57       */
58      private TriangularDistribution(double a,
59                                     double c,
60                                     double b) {
61          this.a = a;
62          this.c = c;
63          this.b = b;
64          divisor1 = (b - a) * (c - a);
65          divisor2 = (b - a) * (b - c);
66          cdfMode = (c - a) / (b - a);
67          sfMode = (b - c) / (b - a);
68      }
69  
70      /**
71       * Creates a triangular distribution.
72       *
73       * @param a Lower limit of this distribution (inclusive).
74       * @param c Mode of this distribution.
75       * @param b Upper limit of this distribution (inclusive).
76       * @return the distribution
77       * @throws IllegalArgumentException if {@code a >= b}, if {@code c > b} or if
78       * {@code c < a}.
79       */
80      public static TriangularDistribution of(double a,
81                                              double c,
82                                              double b) {
83          if (a >= b) {
84              throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GTE_HIGH,
85                                              a, b);
86          }
87          if (c < a) {
88              throw new DistributionException(DistributionException.TOO_SMALL,
89                                              c, a);
90          }
91          if (c > b) {
92              throw new DistributionException(DistributionException.TOO_LARGE,
93                                              c, b);
94          }
95          return new TriangularDistribution(a, c, b);
96      }
97  
98      /**
99       * Gets the mode parameter of this distribution.
100      *
101      * @return the mode.
102      */
103     public double getMode() {
104         return c;
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public double density(double x) {
110         if (x < a) {
111             return 0;
112         }
113         if (x < c) {
114             final double divident = 2 * (x - a);
115             return divident / divisor1;
116         }
117         if (x == c) {
118             return 2 / (b - a);
119         }
120         if (x <= b) {
121             final double divident = 2 * (b - x);
122             return divident / divisor2;
123         }
124         return 0;
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public double cumulativeProbability(double x)  {
130         if (x <= a) {
131             return 0;
132         }
133         if (x < c) {
134             final double divident = (x - a) * (x - a);
135             return divident / divisor1;
136         }
137         if (x == c) {
138             return cdfMode;
139         }
140         if (x < b) {
141             final double divident = (b - x) * (b - x);
142             return 1 - (divident / divisor2);
143         }
144         return 1;
145     }
146 
147 
148     /** {@inheritDoc} */
149     @Override
150     public double survivalProbability(double x)  {
151         // By symmetry:
152         if (x <= a) {
153             return 1;
154         }
155         if (x < c) {
156             final double divident = (x - a) * (x - a);
157             return 1 - (divident / divisor1);
158         }
159         if (x == c) {
160             return sfMode;
161         }
162         if (x < b) {
163             final double divident = (b - x) * (b - x);
164             return divident / divisor2;
165         }
166         return 0;
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     public double inverseCumulativeProbability(double p) {
172         ArgumentUtils.checkProbability(p);
173         if (p == 0) {
174             return a;
175         }
176         if (p == 1) {
177             return b;
178         }
179         if (p < cdfMode) {
180             return a + Math.sqrt(p * divisor1);
181         }
182         return b - Math.sqrt((1 - p) * divisor2);
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public double inverseSurvivalProbability(double p) {
188         // By symmetry:
189         ArgumentUtils.checkProbability(p);
190         if (p == 1) {
191             return a;
192         }
193         if (p == 0) {
194             return b;
195         }
196         if (p >= sfMode) {
197             return a + Math.sqrt((1 - p) * divisor1);
198         }
199         return b - Math.sqrt(p * divisor2);
200     }
201 
202     /**
203      * {@inheritDoc}
204      *
205      * <p>For lower limit \( a \), upper limit \( b \), and mode \( c \),
206      * the mean is \( (a + b + c) / 3 \).
207      */
208     @Override
209     public double getMean() {
210         return (a + b + c) / 3;
211     }
212 
213     /**
214      * {@inheritDoc}
215      *
216      * <p>For lower limit \( a \), upper limit \( b \), and mode \( c \),
217      * the variance is \( (a^2 + b^2 + c^2 - ab - ac - bc) / 18 \).
218      */
219     @Override
220     public double getVariance() {
221         return (a * a + b * b + c * c - a * b - a * c - b * c) / 18;
222     }
223 
224     /**
225      * {@inheritDoc}
226      *
227      * <p>The lower bound of the support is equal to the lower limit parameter
228      * {@code a} of the distribution.
229      */
230     @Override
231     public double getSupportLowerBound() {
232         return a;
233     }
234 
235     /**
236      * {@inheritDoc}
237      *
238      * <p>The upper bound of the support is equal to the upper limit parameter
239      * {@code b} of the distribution.
240      */
241     @Override
242     public double getSupportUpperBound() {
243         return b;
244     }
245 }