1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.statistics.distribution;
18
19 import org.apache.commons.math3.analysis.UnivariateFunction;
20 import org.apache.commons.math3.analysis.integration.RombergIntegrator;
21 import org.apache.commons.math3.analysis.integration.UnivariateIntegrator;
22 import org.junit.jupiter.api.Assertions;
23 import org.junit.jupiter.api.Test;
24
25
26
27
28 class AbstractContinuousDistributionTest {
29
30
31 @Test
32 void testContinuous() {
33 final double x0 = 0.0;
34 final double x1 = 1.0;
35 final double x2 = 2.0;
36 final double x3 = 3.0;
37 final double p12 = 0.5;
38 final AbstractContinuousDistribution distribution;
39 distribution = new AbstractContinuousDistribution() {
40 @Override
41 public double cumulativeProbability(final double x) {
42 if (x < x0 ||
43 x > x3) {
44 throw new DistributionException(DistributionException.OUT_OF_RANGE, x, x0, x3);
45 }
46 if (x <= x1) {
47 return p12 * (x - x0) / (x1 - x0);
48 } else if (x <= x2) {
49 return p12;
50 } else if (x <= x3) {
51 return p12 + (1.0 - p12) * (x - x2) / (x3 - x2);
52 }
53 return 0.0;
54 }
55
56 @Override
57 public double density(final double x) {
58 if (x < x0 ||
59 x > x3) {
60 throw new DistributionException(DistributionException.OUT_OF_RANGE, x, x0, x3);
61 }
62 if (x <= x1) {
63 return p12 / (x1 - x0);
64 } else if (x <= x2) {
65 return 0.0;
66 } else if (x <= x3) {
67 return (1.0 - p12) / (x3 - x2);
68 }
69 return 0.0;
70 }
71
72 @Override
73 public double getMean() {
74 return ((x0 + x1) * p12 + (x2 + x3) * (1.0 - p12)) / 2.0;
75 }
76
77 @Override
78 public double getVariance() {
79 final double meanX = getMean();
80 final double meanX2;
81 meanX2 = ((x0 * x0 + x0 * x1 + x1 * x1) * p12 +
82 (x2 * x2 + x2 * x3 + x3 * x3) * (1.0 - p12)) / 3.0;
83 return meanX2 - meanX * meanX;
84 }
85
86 @Override
87 public double getSupportLowerBound() {
88 return x0;
89 }
90
91 @Override
92 public double getSupportUpperBound() {
93 return x3;
94 }
95
96 @Override
97 boolean isSupportConnected() {
98
99 return false;
100 }
101 };
102
103
104
105 double expected = x1;
106 double actual = distribution.inverseCumulativeProbability(p12);
107 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
108
109 actual = distribution.inverseSurvivalProbability(1 - p12);
110 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
111
112
113 expected = 0.5 * (x1 - x0);
114 actual = distribution.inverseCumulativeProbability(p12 * 0.5);
115 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
116
117 actual = distribution.inverseSurvivalProbability(1 - p12 * 0.5);
118 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
119
120
121 expected = x0;
122 actual = distribution.inverseCumulativeProbability(Double.MIN_VALUE);
123 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
124
125 actual = distribution.inverseSurvivalProbability(Math.nextDown(1.0));
126 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
127 }
128
129
130 @Test
131 void testDiscontinuous() {
132 final double x0 = 0.0;
133 final double x1 = 0.25;
134 final double x2 = 0.5;
135 final double x3 = 0.75;
136 final double x4 = 1.0;
137 final double p12 = 1.0 / 3.0;
138 final double p23 = 2.0 / 3.0;
139 final AbstractContinuousDistribution distribution;
140 distribution = new AbstractContinuousDistribution() {
141 @Override
142 public double cumulativeProbability(final double x) {
143 if (x < x0 ||
144 x > x4) {
145 throw new DistributionException(DistributionException.OUT_OF_RANGE, x, x0, x4);
146 }
147 if (x <= x1) {
148 return p12 * (x - x0) / (x1 - x0);
149 } else if (x <= x2) {
150 return p12;
151 } else if (x <= x3) {
152 return p23;
153 } else {
154 return (1.0 - p23) * (x - x3) / (x4 - x3) + p23;
155 }
156 }
157
158 @Override
159 public double density(final double x) {
160 if (x < x0 ||
161 x > x4) {
162 throw new DistributionException(DistributionException.OUT_OF_RANGE, x, x0, x4);
163 }
164 if (x <= x1) {
165 return p12 / (x1 - x0);
166 } else if (x <= x2) {
167 return 0.0;
168 } else if (x <= x3) {
169 return 0.0;
170 } else {
171 return (1.0 - p23) / (x4 - x3);
172 }
173 }
174
175 @Override
176 public double getMean() {
177 final UnivariateFunction f = x -> x * density(x);
178 final UnivariateIntegrator integrator = new RombergIntegrator();
179 return integrator.integrate(Integer.MAX_VALUE, f, x0, x4);
180 }
181
182 @Override
183 public double getVariance() {
184 final double meanX = getMean();
185 final UnivariateFunction f = x -> x * x * density(x);
186 final UnivariateIntegrator integrator = new RombergIntegrator();
187 final double meanX2 = integrator.integrate(Integer.MAX_VALUE,
188 f, x0, x4);
189 return meanX2 - meanX * meanX;
190 }
191
192 @Override
193 public double getSupportLowerBound() {
194 return x0;
195 }
196
197 @Override
198 public double getSupportUpperBound() {
199 return x4;
200 }
201
202 @Override
203 boolean isSupportConnected() {
204
205 return false;
206 }
207 };
208
209
210
211 double expected = x2;
212 double actual = distribution.inverseCumulativeProbability(p23);
213 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
214
215 actual = distribution.inverseSurvivalProbability(1 - p23);
216 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
217
218
219 expected = 0.5 * (x1 - x0);
220 actual = distribution.inverseCumulativeProbability(p12 * 0.5);
221 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
222
223 actual = distribution.inverseSurvivalProbability(1 - p12 * 0.5);
224 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
225
226
227 expected = x0;
228 actual = distribution.inverseCumulativeProbability(Double.MIN_VALUE);
229 Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
230
231 actual = distribution.inverseSurvivalProbability(Math.nextDown(1.0));
232 Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
233 }
234
235
236
237
238
239
240
241
242
243
244 @Test
245 void testZeroVariance() {
246
247
248
249
250
251 final double x0 = 10.0;
252 final AbstractContinuousDistribution distribution;
253 distribution = new AbstractContinuousDistribution() {
254 @Override
255 public double cumulativeProbability(final double x) {
256 return x <= x0 ? 0.0 : 1.0;
257 }
258
259 @Override
260 public double density(final double x) {
261 throw new AssertionError();
262 }
263
264 @Override
265 public double getMean() {
266 return x0;
267 }
268
269 @Override
270 public double getVariance() {
271 return 0.0;
272 }
273
274 @Override
275 public double getSupportLowerBound() {
276 return x0;
277 }
278
279 @Override
280 public double getSupportUpperBound() {
281 return Double.POSITIVE_INFINITY;
282 }
283 };
284 double x = distribution.inverseCumulativeProbability(0.5);
285
286 Assertions.assertNotEquals(x0, x, "Inverse CDF");
287
288
289 Assertions.assertEquals(x0, x, 1e-8, "Inverse CDF");
290
291
292 x = distribution.inverseSurvivalProbability(0.5);
293 Assertions.assertNotEquals(x0, x, "Inverse SF");
294 Assertions.assertEquals(x0, x, 1e-8, "Inverse SF");
295 }
296
297
298
299
300
301
302 @Test
303 void testInfiniteVariance() {
304
305
306
307
308
309
310
311
312
313
314 final AbstractContinuousDistribution distribution;
315 distribution = new AbstractContinuousDistribution() {
316 @Override
317 public double cumulativeProbability(final double x) {
318 if (x > 0) {
319
320 return 1 - cumulativeProbability(-x);
321 }
322 if (x < -10) {
323 return 0;
324 }
325 return Math.pow(x + 10, 2) / 200;
326 }
327
328 @Override
329 public double density(final double x) {
330 throw new AssertionError();
331 }
332
333 @Override
334 public double getMean() {
335 return 0;
336 }
337
338 @Override
339 public double getVariance() {
340
341 return Double.POSITIVE_INFINITY;
342 }
343
344 @Override
345 public double getSupportLowerBound() {
346
347 return Double.NEGATIVE_INFINITY;
348 }
349
350 @Override
351 public double getSupportUpperBound() {
352
353 return Double.POSITIVE_INFINITY;
354 }
355 };
356
357
358 final double tolerance = 1e-8;
359
360 final double x = Math.sqrt(50);
361 Assertions.assertEquals(Double.NEGATIVE_INFINITY, distribution.inverseCumulativeProbability(0), "Inverse CDF");
362 Assertions.assertEquals(x - 10, distribution.inverseCumulativeProbability(0.25), tolerance, "Inverse CDF");
363 Assertions.assertEquals(0, distribution.inverseCumulativeProbability(0.5), tolerance, "Inverse CDF");
364 Assertions.assertEquals(10 - x, distribution.inverseCumulativeProbability(0.75), tolerance, "Inverse CDF");
365 Assertions.assertEquals(Double.POSITIVE_INFINITY, distribution.inverseCumulativeProbability(1), "Inverse CDF");
366
367
368 Assertions.assertEquals(Double.NEGATIVE_INFINITY, distribution.inverseSurvivalProbability(1), "Inverse CDF");
369 Assertions.assertEquals(x - 10, distribution.inverseSurvivalProbability(0.75), tolerance, "Inverse SF");
370 Assertions.assertEquals(0, distribution.inverseSurvivalProbability(0.5), tolerance, "Inverse SF");
371 Assertions.assertEquals(10 - x, distribution.inverseSurvivalProbability(0.25), tolerance, "Inverse SF");
372 Assertions.assertEquals(Double.POSITIVE_INFINITY, distribution.inverseSurvivalProbability(0), "Inverse CDF");
373 }
374
375
376
377
378
379 @Test
380 void testTruncatedDistributionAtPositiveInfinity() {
381 assertTruncatedDistributionAtPositiveInfinity(false);
382 }
383
384
385
386
387
388 @Test
389 void testTruncatedDistributionAtPositiveInfinity2() {
390 assertTruncatedDistributionAtPositiveInfinity(true);
391 }
392
393 private static void assertTruncatedDistributionAtPositiveInfinity(boolean finiteBound) {
394 final double mean = Double.MAX_VALUE;
395 final double width = Double.MAX_VALUE / 2;
396 final double bound = finiteBound ? Double.MAX_VALUE : Double.POSITIVE_INFINITY;
397 final CentredUniformDistribution dist = new CentredUniformDistribution(mean, width) {
398 @Override
399 public double getSupportUpperBound() {
400 return bound;
401 }
402 };
403 final double x = mean - width / 2;
404 Assertions.assertEquals(0, dist.cumulativeProbability(x));
405 Assertions.assertTrue(dist.cumulativeProbability(Math.nextUp(x)) > 0);
406 Assertions.assertEquals(0.25, dist.cumulativeProbability(mean - width / 4));
407 Assertions.assertEquals(0.5, dist.cumulativeProbability(mean));
408
409
410 Assertions.assertEquals(1.0, dist.cumulativeProbability(Math.nextUp(mean)));
411
412
413 Assertions.assertEquals(mean, dist.inverseCumulativeProbability(0.5));
414 Assertions.assertEquals(mean, dist.inverseSurvivalProbability(0.5));
415 Assertions.assertEquals(bound, dist.inverseCumulativeProbability(0.75));
416 Assertions.assertEquals(bound, dist.inverseSurvivalProbability(0.25));
417 }
418
419
420
421
422
423 @Test
424 void testTruncatedDistributionAtNegativeInfinity() {
425 assertTruncatedDistributionAtNegativeInfinity(false);
426 }
427
428
429
430
431
432 @Test
433 void testTruncatedDistributionAtNegativeInfinity2() {
434 assertTruncatedDistributionAtNegativeInfinity(true);
435 }
436
437 private static void assertTruncatedDistributionAtNegativeInfinity(boolean finiteBound) {
438 final double mean = -Double.MAX_VALUE;
439 final double width = Double.MAX_VALUE / 2;
440 final double bound = finiteBound ? -Double.MAX_VALUE : Double.NEGATIVE_INFINITY;
441 final CentredUniformDistribution dist = new CentredUniformDistribution(mean, width) {
442 @Override
443 public double getSupportLowerBound() {
444 return bound;
445 }
446 };
447 final double x = mean + width / 2;
448 Assertions.assertEquals(1, dist.cumulativeProbability(x));
449 Assertions.assertTrue(dist.cumulativeProbability(Math.nextDown(x)) < 1);
450 Assertions.assertEquals(0.75, dist.cumulativeProbability(mean + width / 4));
451 Assertions.assertEquals(0.5, dist.cumulativeProbability(mean));
452
453
454 Assertions.assertEquals(0.0, dist.cumulativeProbability(Math.nextDown(mean)));
455
456
457 Assertions.assertEquals(mean, dist.inverseCumulativeProbability(0.5));
458 Assertions.assertEquals(mean, dist.inverseSurvivalProbability(0.5));
459 Assertions.assertEquals(bound, dist.inverseCumulativeProbability(0.25));
460 Assertions.assertEquals(bound, dist.inverseSurvivalProbability(0.75));
461 }
462
463
464
465
466
467
468 static class CentredUniformDistribution extends AbstractContinuousDistribution {
469 private final double mean;
470 private final double width;
471 private final double density;
472 private final double lo;
473 private final double hi;
474
475
476
477
478
479 CentredUniformDistribution(double mean, double width) {
480 this.mean = mean;
481 this.width = width;
482 density = 1 / width;
483 lo = mean - width / 2;
484 hi = mean + width / 2;
485 }
486
487 @Override
488 public double density(double x) {
489 return density;
490 }
491
492 @Override
493 public double cumulativeProbability(double x) {
494 if (x <= lo) {
495 return 0;
496 }
497 if (x >= hi) {
498 return 1;
499 }
500 return 0.5 + density * (x - mean);
501 }
502
503 @Override
504 public double getMean() {
505 return mean;
506 }
507
508 @Override
509 public double getVariance() {
510 return width * width / 12;
511 }
512
513 @Override
514 public double getSupportLowerBound() {
515 return lo;
516 }
517
518 @Override
519 public double getSupportUpperBound() {
520 return hi;
521 }
522 }
523 }