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)} & \text{for } a \le x \lt c \\
27 * \frac{2}{b-a} & \text{for } x = c \\
28 * \frac{2(b-x)}{(b-a)(b-c)} & \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 }