1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.numbers.gamma;
18
19 import java.io.IOException;
20 import java.math.BigDecimal;
21 import java.util.Arrays;
22 import java.util.function.DoubleBinaryOperator;
23 import org.junit.jupiter.api.Assertions;
24 import org.junit.jupiter.api.MethodOrderer;
25 import org.junit.jupiter.api.Test;
26 import org.junit.jupiter.api.TestMethodOrder;
27 import org.junit.jupiter.params.ParameterizedTest;
28 import org.junit.jupiter.params.provider.CsvSource;
29 import org.junit.jupiter.params.provider.EnumSource;
30 import org.junit.jupiter.params.provider.EnumSource.Mode;
31
32
33
34
35
36
37
38 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
39 class BoostBetaTest {
40
41
42
43
44 interface DoubleTernaryOperator {
45
46
47
48
49
50
51
52
53 double applyAsDouble(double x, double y, double z);
54 }
55
56
57 private interface TestError {
58
59
60
61 double getTolerance();
62
63
64
65
66 double getRmsTolerance();
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 private enum BiTestCase implements TestError {
86
87
88
89
90
91 BETA_SMALL(BoostBeta::beta, "beta_small_data.csv", 4, 1.7),
92
93 BETA_MED(BoostBeta::beta, "beta_med_data.csv", 200, 35),
94
95 BETA_EXP(BoostBeta::beta, "beta_exp_data.csv", 17, 3.6),
96
97
98 BETA1_SMALL(BoostBetaTest::beta, "beta_small_data.csv", 110, 28),
99
100 BETA1_MED(BoostBetaTest::beta, "beta_med_data.csv", 280, 46),
101
102 BETA1_EXP(BoostBetaTest::beta, "beta_exp_data.csv", 28, 4.5),
103
104 BINOMIAL_SMALL(BoostBetaTest::binomialCoefficient, "binomial_small_data.csv", -2, 0.5),
105
106 BINOMIAL_LARGE(BoostBetaTest::binomialCoefficient, "binomial_large_data.csv", 5, 1.1),
107
108 BINOMIAL_XLARGE(BoostBetaTest::binomialCoefficient, "binomial_extra_large_data.csv", 9, 2),
109
110 BINOMIAL_HUGE(BoostBetaTest::binomialCoefficient, "binomial_huge_data.csv", 9, 2),
111
112
113 BINOMIAL1_LARGE(BoostBetaTest::binomialCoefficient1, "binomial_large_data.csv", 31, 9),
114
115 BINOMIAL1_HUGE(BoostBetaTest::binomialCoefficient1, "binomial_huge_data.csv", 70, 19);
116
117
118 private final DoubleBinaryOperator fun;
119
120
121 private final String filename;
122
123
124 private final int expected;
125
126
127 private final double maxUlp;
128
129
130 private final double rmsUlp;
131
132
133
134
135
136
137
138
139
140 BiTestCase(DoubleBinaryOperator fun, String filename, double maxUlp, double rmsUlp) {
141 this(fun, filename, 2, maxUlp, rmsUlp);
142 }
143
144
145
146
147
148
149
150
151
152
153 BiTestCase(DoubleBinaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
154 this.fun = fun;
155 this.filename = filename;
156 this.expected = expected;
157 this.maxUlp = maxUlp;
158 this.rmsUlp = rmsUlp;
159 }
160
161
162
163
164 DoubleBinaryOperator getFunction() {
165 return fun;
166 }
167
168
169
170
171 String getFilename() {
172 return filename;
173 }
174
175
176
177
178 int getExpectedField() {
179 return expected;
180 }
181
182 @Override
183 public double getTolerance() {
184 return maxUlp;
185 }
186
187 @Override
188 public double getRmsTolerance() {
189 return rmsUlp;
190 }
191 }
192
193
194
195
196
197
198 private enum TriTestCase implements TestError {
199
200 IBETA_DERIV_SMALL_INT(BoostBeta::ibetaDerivative, "ibeta_derivative_small_int_data.csv", 60, 13),
201
202 IBETA_DERIV_SMALL(BoostBeta::ibetaDerivative, "ibeta_derivative_small_data.csv", 22, 4),
203
204 IBETA_DERIV_MED(BoostBeta::ibetaDerivative, "ibeta_derivative_med_data.csv", 150, 33),
205
206 IBETA_DERIV_LARGE(BoostBeta::ibetaDerivative, "ibeta_derivative_large_data.csv", 3900, 260),
207
208
209 IBETA_DERIV1_SMALL_INT(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_small_int_data.csv", 220, 55),
210
211 IBETA_DERIV1_SMALL(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_small_data.csv", 75, 10.5),
212
213 IBETA_DERIV1_MED(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_med_data.csv", 1500, 300),
214
215 IBETA_DERIV1_LARGE(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_large_data.csv", 9e7, 250000),
216
217
218 IBETA_DERIV2_SMALL_INT(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_small_int_data.csv", 180, 31),
219
220 IBETA_DERIV2_SMALL(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_small_data.csv", 75, 8.5),
221
222 IBETA_DERIV2_MED(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_med_data.csv", 500, 85),
223
224 IBETA_DERIV2_LARGE(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_large_data.csv", 28000, 1200),
225
226
227 IBETA_SMALL_INT(BoostBeta::beta, "ibeta_small_int_data.csv", 48, 11),
228
229 IBETA_SMALL(BoostBeta::beta, "ibeta_small_data.csv", 17, 3.3),
230
231 IBETA_MED(BoostBeta::beta, "ibeta_med_data.csv", 190, 20),
232
233 IBETA_LARGE(BoostBeta::beta, "ibeta_large_data.csv", 1300, 50),
234
235 IBETAC_SMALL_INT(BoostBeta::betac, "ibeta_small_int_data.csv", 4, 57, 11),
236
237 IBETAC_SMALL(BoostBeta::betac, "ibeta_small_data.csv", 4, 14, 3.2),
238
239 IBETAC_MED(BoostBeta::betac, "ibeta_med_data.csv", 4, 130, 24),
240
241 IBETAC_LARGE(BoostBeta::betac, "ibeta_large_data.csv", 4, 7000, 220),
242
243 RBETA_SMALL_INT(BoostBeta::ibeta, "ibeta_small_int_data.csv", 5, 7.5, 1.2),
244
245 RBETA_SMALL(BoostBeta::ibeta, "ibeta_small_data.csv", 5, 14, 3.3),
246
247 RBETA_MED(BoostBeta::ibeta, "ibeta_med_data.csv", 5, 200, 28),
248
249 RBETA_LARGE(BoostBeta::ibeta, "ibeta_large_data.csv", 5, 2400, 100),
250
251 RBETAC_SMALL_INT(BoostBeta::ibetac, "ibeta_small_int_data.csv", 6, 8, 1.6),
252
253 RBETAC_SMALL(BoostBeta::ibetac, "ibeta_small_data.csv", 6, 11, 2.75),
254
255 RBETAC_MED(BoostBeta::ibetac, "ibeta_med_data.csv", 6, 105, 23),
256
257 RBETAC_LARGE(BoostBeta::ibetac, "ibeta_large_data.csv", 6, 4000, 180),
258
259
260
261
262
263
264 RBETA1_SMALL(BoostBetaTest::ibeta, "ibeta_small_data.csv", 5, 45, 5),
265
266 RBETA1_MED(BoostBetaTest::ibeta, "ibeta_med_data.csv", 5, 200, 26),
267
268 RBETA1_LARGE(BoostBetaTest::ibeta, "ibeta_large_data.csv", 5, 150000, 7500),
269
270 RBETAC1_SMALL(BoostBetaTest::ibetac, "ibeta_small_data.csv", 6, 30, 4.5),
271
272 RBETAC1_MED(BoostBetaTest::ibetac, "ibeta_med_data.csv", 6, 100, 22),
273
274 RBETAC1_LARGE(BoostBetaTest::ibetac, "ibeta_large_data.csv", 6, 370000, 11000);
275
276
277 private final DoubleTernaryOperator fun;
278
279
280 private final String filename;
281
282
283 private final int expected;
284
285
286 private final double maxUlp;
287
288
289 private final double rmsUlp;
290
291
292
293
294
295
296
297
298
299 TriTestCase(DoubleTernaryOperator fun, String filename, double maxUlp, double rmsUlp) {
300 this(fun, filename, 3, maxUlp, rmsUlp);
301 }
302
303
304
305
306
307
308
309
310
311
312 TriTestCase(DoubleTernaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
313 this.fun = fun;
314 this.filename = filename;
315 this.expected = expected;
316 this.maxUlp = maxUlp;
317 this.rmsUlp = rmsUlp;
318 }
319
320
321
322
323 DoubleTernaryOperator getFunction() {
324 return fun;
325 }
326
327
328
329
330 String getFilename() {
331 return filename;
332 }
333
334
335
336
337 int getExpectedField() {
338 return expected;
339 }
340
341 @Override
342 public double getTolerance() {
343 return maxUlp;
344 }
345
346 @Override
347 public double getRmsTolerance() {
348 return rmsUlp;
349 }
350 }
351
352 @ParameterizedTest
353 @CsvSource({
354
355 "NaN, 1, NaN",
356 "0, 1, NaN",
357 "-1, 1, NaN",
358
359 "1, NaN, NaN",
360 "1, 0, NaN",
361 "1, -1, NaN",
362 })
363 void testBetaEdgeCases(double a, double b, double expected) {
364 Assertions.assertEquals(expected, BoostBeta.beta(a, b), "beta");
365 }
366
367
368
369
370
371 @Test
372 void testBetaSpotTests() {
373 final int tolerance = 20;
374
375 final double small = Math.ulp(1.0) / 1024;
376 assertClose(BoostBeta::beta, 1, 1, 1, 0);
377 assertClose(BoostBeta::beta, 1, 4, 0.25, 0);
378 assertClose(BoostBeta::beta, 4, 1, 0.25, 0);
379 assertClose(BoostBeta::beta, small, 4, 1 / small, 0);
380 assertClose(BoostBeta::beta, 4, small, 1 / small, 0);
381 assertClose(BoostBeta::beta, 4, 20, 0.00002823263692828910220214568040654997176736, tolerance);
382 assertClose(BoostBeta::beta, 0.0125, 0.000023, 43558.24045647538375006349016083320744662, tolerance * 2);
383
384
385
386
387 assertClose(BoostBeta::beta, 1e6, 1e6, 0, 0);
388 assertClose(BoostBeta::beta, 1e10, 100, 0, 0);
389 assertClose(BoostBeta::beta, 1e11, 1, 1e-11, 0);
390 assertClose(BoostBeta::beta, 1e11, 2, 1 / (1e11 * (1e11 + 1)), 5);
391
392
393 assertClose(BoostBeta::beta, 5, 0x1.0p-51, 2.2517998136852459167e15, 5);
394
395 assertClose(BoostBeta::beta, 0x1.0p-50, 11, 1.125899906842621071e15, 5);
396 }
397
398 @ParameterizedTest
399 @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"BETA_.*", "BETA1_.*"})
400 void testBeta(BiTestCase tc) {
401 assertFunction(tc);
402 }
403
404 @ParameterizedTest
405 @CsvSource({
406
407 "1, 0, 1",
408 "42, 0, 1",
409 "1, 1, 1",
410 "42, 42, 1",
411
412 "6, 1, 6",
413 "42, 1, 42",
414 "6, 5, 6",
415 "42, 41, 42",
416 })
417 void testBinomialCoefficientEdgeCases(int n, int k, double expected) {
418 Assertions.assertEquals(expected, BoostBeta.binomialCoefficient(n, k));
419 }
420
421
422
423
424
425 @Test
426 void testBinomialCoefficientSpotTests() {
427 Assertions.assertEquals(1, BoostBeta.binomialCoefficient(20, 0));
428 Assertions.assertEquals(20, BoostBeta.binomialCoefficient(20, 1));
429 Assertions.assertEquals(190, BoostBeta.binomialCoefficient(20, 2));
430 Assertions.assertEquals(1140, BoostBeta.binomialCoefficient(20, 3));
431 Assertions.assertEquals(1, BoostBeta.binomialCoefficient(20, 20));
432 Assertions.assertEquals(20, BoostBeta.binomialCoefficient(20, 19));
433 Assertions.assertEquals(190, BoostBeta.binomialCoefficient(20, 18));
434 Assertions.assertEquals(1140, BoostBeta.binomialCoefficient(20, 17));
435 Assertions.assertEquals(184756, BoostBeta.binomialCoefficient(20, 10));
436
437
438
439
440 int tolerance = 2;
441
442
443 assertClose(BoostBetaTest::binomialCoefficient, 100, 5, 7.528752e7, tolerance);
444
445 assertClose(BoostBetaTest::binomialCoefficient, 100, 81, 1.323415729392122674e20, tolerance);
446
447
448 assertClose(BoostBetaTest::binomialCoefficient, 300, 3, 4.45510e6, tolerance);
449
450 assertClose(BoostBetaTest::binomialCoefficient, 300, 7, 4.04385595614e13, tolerance);
451
452 assertClose(BoostBetaTest::binomialCoefficient, 300, 290, 1.39832023324170177e18, tolerance);
453
454 assertClose(BoostBetaTest::binomialCoefficient, 300, 275, 1.953265141442868389822364184842211512e36, tolerance);
455
456
457
458
459
460
461
462
463
464 tolerance = 5;
465 assertClose(BoostBetaTest::binomialCoefficient, 1786388282, 38, 7.187239013254065384599502085053593e306, tolerance);
466 assertClose(BoostBetaTest::binomialCoefficient, 1914878305, 38, 100.6570419073661447979173868523364e306, tolerance);
467 assertClose(BoostBetaTest::binomialCoefficient, 1179067476, 39, 30.22890249420109200962786203300876e306, tolerance);
468
469 assertClose(BoostBetaTest::binomialCoefficient, 2147483647, 37, 1.388890512412231479281222156415993e302, tolerance);
470 }
471
472 @ParameterizedTest
473 @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"BINOM.*"})
474 void testBinomialCoefficient(BiTestCase tc) {
475 assertFunction(tc);
476 }
477
478
479
480
481 @ParameterizedTest
482 @CsvSource({
483 "500, 225, 9.5909622148251792594e147",
484 "500, 250, 1.1674431578827768292e149",
485 "600, 300, 1.3510794199619426851e179",
486 "700, 350, 1.5857433585316795488e209",
487 "800, 400, 1.8804244186835312701e239",
488 "1000, 500, 2.7028824094543656952e299",
489 "100000, 50, 3.2479111644852887358e185",
490 "100000, 70, 8.1490000781382598363e249",
491 "100000, 80, 1.3537701492763430639e281",
492 "100000, 85, 3.4252195975122556458e296",
493 "1030, 515, 2.8596413729978081638e308",
494 })
495 void testBinomialCoefficientLargeK(int n, int k, double nCk) {
496 final double bc1 = BoostBeta.binomialCoefficient(n, k);
497 final double bc2 = BoostBetaTest.binomialCoefficient1(n, k);
498 assertCloser("nCk", nCk, bc1, bc2);
499 }
500
501 @ParameterizedTest
502 @CsvSource({
503
504 "1040, 450, 2.3101613255412135615e307",
505 "1029, 514, 1.4298206864989040819e308",
506 })
507 void testBinomialCoefficientOverflowAtLargeK(int n, int k, double nCk) {
508
509
510 Assertions.assertEquals(Double.POSITIVE_INFINITY, BoostBeta.binomialCoefficient(n, k));
511
512
513 Assertions.assertEquals(nCk, BoostBetaTest.binomialCoefficient1(n, k), nCk * 1e-12);
514 }
515
516 @ParameterizedTest
517 @CsvSource({
518
519 "NaN, 1, 0.5, NaN",
520 "0, 1, 0.5, NaN",
521 "-1, 1, 0.5, NaN",
522
523 "1, NaN, 0.5, NaN",
524 "1, 0, 0.5, NaN",
525 "1, -1, 0.5, NaN",
526
527 "1, 1, NaN, NaN",
528 "1, 1, -1, NaN",
529 "1, 1, 2, NaN",
530 })
531 void testIBetaEdgeCases(double a, double b, double x, double expected) {
532
533 Assertions.assertEquals(expected, BoostBeta.beta(a, b, x), "beta");
534 Assertions.assertEquals(expected, BoostBeta.betac(a, b, x), "betac");
535 final Policy pol = Policy.getDefault();
536 Assertions.assertEquals(expected, BoostBeta.beta(a, b, x, pol), "beta");
537 Assertions.assertEquals(expected, BoostBeta.betac(a, b, x, pol), "betac");
538
539
540
541
542
543 Assertions.assertEquals(expected, BoostBeta.ibetaDerivative(a, b, x), "ibetaDerivative");
544 }
545
546 @ParameterizedTest
547 @CsvSource({
548
549 "NaN, 1, 0.5, NaN",
550 "-1, 1, 0.5, NaN",
551
552 "1, NaN, 0.5, NaN",
553 "1, -1, 0.5, NaN",
554
555 "0, 0, 0.5, NaN",
556
557 "0, 1, 0.25, 1",
558 "0, 1, 0.5, 1",
559 "0, 2, 0.5, 1",
560 "1, 0, 0.25, 0",
561 "1, 0, 0.5, 0",
562 "2, 0, 0.5, 0",
563
564 "1, 1, NaN, NaN",
565 "1, 1, -1, NaN",
566 "1, 1, 2, NaN",
567 })
568 void testRegularisedIBetaEdgeCases(double a, double b, double x, double expected) {
569 Assertions.assertEquals(expected, BoostBeta.ibeta(a, b, x), "ibeta");
570 Assertions.assertEquals(1 - expected, BoostBeta.ibetac(a, b, x), "ibetac");
571 final Policy pol = Policy.getDefault();
572 Assertions.assertEquals(expected, BoostBeta.ibeta(a, b, x, pol), "ibeta");
573 Assertions.assertEquals(1 - expected, BoostBeta.ibetac(a, b, x, pol), "ibetac");
574 }
575
576
577
578
579
580 @Test
581 void testIBetaDerivativeSpotTests() {
582 final int tolerance = 400;
583 assertClose(BoostBeta::ibetaDerivative, 2, 4, Math.scalb(1.0, -557), 4.23957586190238472641508753637420672781472122471791800210e-167, tolerance * 4);
584 assertClose(BoostBeta::ibetaDerivative, 2, 4.5, Math.scalb(1.0, -557), 5.24647512910420109893867082626308082567071751558842352760e-167, tolerance * 4);
585
586
587 for (final double p : new double[] {3, 13, 42}) {
588
589 assertClose(BoostBeta::ibetaDerivative, 2, p, 0, 0.0, 0);
590 assertClose(BoostBeta::ibetaDerivative, 1, p, 0, p, 0);
591 assertClose(BoostBeta::ibetaDerivative, 0.5, p, 0, Double.POSITIVE_INFINITY, 0);
592
593 assertClose(BoostBeta::ibetaDerivative, p, 2, 1, 0.0, 0);
594 assertClose(BoostBeta::ibetaDerivative, p, 1, 1, p, 0);
595 assertClose(BoostBeta::ibetaDerivative, p, 0.5, 1, Double.POSITIVE_INFINITY, 0);
596 }
597 }
598
599 @ParameterizedTest
600 @EnumSource(value = TriTestCase.class, mode = Mode.MATCH_ANY, names = {"IBETA_DERIV.*"})
601 void testIBetaDerivative(TriTestCase tc) {
602 assertFunction(tc);
603 }
604
605
606
607
608
609 @Test
610 void testIBetaSpotTests() {
611 int tolerance = 30;
612
613
614
615 assertClose(BoostBeta::ibeta, 159.0 / 10000, 1184.0 / 1000000000L, 6917.0 / 10000,
616 0.000075393541456247525676062058821484095548666733251733, tolerance);
617 assertClose(BoostBeta::ibeta, 4243.0 / 100, 3001.0 / 10000, 9157.0 / 10000,
618 0.0028387319012616013434124297160711532419664289474798, tolerance);
619 assertClose(BoostBeta::ibeta, 9713.0 / 1000, 9940.0 / 100, 8391.0 / 100000,
620 0.46116895440368248909937863372410093344466819447476, tolerance * 2);
621
622 assertClose(BoostBeta::ibeta, 72.5, 1.125, 0.75, 1.3423066982487051710597194786268004978931316494920e-9,
623 tolerance * 3);
624 assertClose(BoostBeta::ibeta, 4985.0 / 1000, 1066.0 / 1000, 7599.0 / 10000,
625 0.27533431334486812211032939156910472371928659321347, tolerance);
626 assertClose(BoostBeta::ibeta, 6813.0 / 1000, 1056.0 / 1000, 1741.0 / 10000,
627 7.6736128722762245852815040810349072461658078840945e-6, tolerance);
628 assertClose(BoostBeta::ibeta, 4898.0 / 10000, 2251.0 / 10000, 2003.0 / 10000,
629 0.17089223868046209692215231702890838878342349377008, tolerance);
630 assertClose(BoostBeta::ibeta, 4049.0 / 1000, 1540.0 / 10000, 6537.0 / 10000,
631 0.017273988301528087878279199511703371301647583919670, tolerance);
632 assertClose(BoostBeta::ibeta, 7269.0 / 1000, 1190.0 / 10000, 8003.0 / 10000,
633 0.013334694467796052900138431733772122625376753696347, tolerance);
634 assertClose(BoostBeta::ibeta, 2726.0 / 1000, 1151.0 / 100000, 8665.0 / 100000,
635 5.8218877068298586420691288375690562915515260230173e-6, tolerance);
636 assertClose(BoostBeta::ibeta, 3431.0 / 10000, 4634.0 / 100000, 7582.0 / 10000,
637 0.15132819929418661038699397753916091907278005695387, tolerance);
638
639 assertClose(BoostBeta::ibeta, 0.34317314624786377, 0.046342257410287857, 0, 0, tolerance);
640 assertClose(BoostBeta::ibetac, 0.34317314624786377, 0.046342257410287857, 0, 1, tolerance);
641 assertClose(BoostBeta::ibeta, 0.34317314624786377, 0.046342257410287857, 1, 1, tolerance);
642 assertClose(BoostBeta::ibetac, 0.34317314624786377, 0.046342257410287857, 1, 0, tolerance);
643 assertClose(BoostBeta::ibeta, 1, 4634.0 / 100000,
644 32.0 / 100, 0.017712849440718489999419956301675684844663359595318, tolerance);
645 assertClose(BoostBeta::ibeta, 4634.0 / 100000,
646 1, 32.0 / 100, 0.94856839398626914764591440181367780660208493234722, tolerance);
647
648
649 assertClose(BoostBeta::ibeta, 3, 8, 0.25, 0.474407196044921875, tolerance);
650 assertClose(BoostBeta::ibeta, 6, 8, 0.25, 0.08021259307861328125, tolerance);
651 assertClose(BoostBeta::ibeta, 12, 1, 0.25, 5.9604644775390625e-8, tolerance);
652 assertClose(BoostBeta::ibeta, 1, 8, 0.25, 0.8998870849609375, tolerance);
653
654
655 tolerance = 100;
656 assertClose(BoostBeta::ibetaDerivative, 2, 3, 0.5,
657 Math.pow(0.5, 2) * Math.pow(0.5, 1) / BoostBeta.beta(2, 3), tolerance);
658
659
660
661
662 Assertions.assertEquals(1, BoostBeta.ibeta(0, 2, 0.5));
663 Assertions.assertEquals(0, BoostBeta.ibeta(3, 0, 0.5));
664 Assertions.assertEquals(0, BoostBeta.ibetac(0, 2, 0.5));
665 Assertions.assertEquals(1, BoostBeta.ibetac(4, 0, 0.5));
666
667
668 Assertions.assertEquals(Double.NaN, BoostBeta.beta(0, 2, 0.5));
669 Assertions.assertEquals(Double.NaN, BoostBeta.beta(3, 0, 0.5));
670 Assertions.assertEquals(Double.NaN, BoostBeta.betac(0, 2, 0.5));
671 Assertions.assertEquals(Double.NaN, BoostBeta.betac(4, 0, 0.5));
672
673 Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(0, 0, 0.5));
674 Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(-1, 2, 0.5));
675 Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, -2, 0.5));
676 Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, 2, -0.5));
677 Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, 2, 1.5));
678
679
680
681
682 assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.25, 1.0 / 3, tolerance);
683 assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.25, 2.0 / 3, tolerance);
684 assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.125, 0.230053456162615885213780567705142893009911395270714102055874, tolerance);
685 assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.125, 0.769946543837384114786219432294857106990088604729285897944125, tolerance);
686 assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.825, 0.725231121519469565327291851560156562956885802608457839260161, tolerance);
687 assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.825, 0.274768878480530434672708148439843437043114197391542160739838, tolerance);
688
689 assertClose(BoostBeta::beta, 0.5, 0.5, 0.25, Math.PI / 3, tolerance);
690 assertClose(BoostBeta::betac, 0.5, 0.5, 0.25, Math.PI * 2 / 3, tolerance);
691
692
693
694 assertClose(BoostBeta::ibeta, 0.5, 1, 0.825, 0.908295106229247499626759842915458109758420750043003849691665, tolerance);
695 assertClose(BoostBeta::ibetac, 0.5, 1, 0.825, 0.091704893770752500373240157084541890241579249956996150308334, tolerance);
696 assertClose(BoostBeta::ibeta, 30, 1, 0.825, 0.003116150729395132012981654047222541793435357905008020740211, tolerance);
697 assertClose(BoostBeta::ibetac, 30, 1, 0.825, 0.996883849270604867987018345952777458206564642094991979259788, tolerance);
698
699
700
701
702 assertClose(BoostBeta::beta, 2, 24, Math.scalb(1.0, -52),
703 2.46519032881565349871772482100516780410072110983579277754743e-32, tolerance);
704 assertClose(BoostBeta::ibeta, 2, 24, Math.scalb(1.0, -52),
705 1.47911419728939209923063489260310068246043266590147566652846e-29, tolerance);
706 assertClose(BoostBeta::beta, 3, 2, Math.scalb(1.0, -270),
707 4.88182556606650701438035298707052523938789614661168065734809e-245, tolerance);
708 assertClose(BoostBeta::beta, 2, 31, Math.scalb(1.0, -373),
709 1.35080680244581673116149460571129957689952846520037541640260e-225, tolerance);
710 assertClose(BoostBeta::ibeta, 3, 2, Math.scalb(1.0, -270),
711 5.85819067927980841725642358448463028726547537593401678881771e-244, tolerance);
712 assertClose(BoostBeta::ibeta, 2, 31, Math.scalb(1.0, -373),
713 1.34000034802625019731220264886560918028433223747877241307138e-222, tolerance);
714
715
716
717 assertClose(BoostBeta::beta, 2, 4, Math.scalb(1 + 1.0 / 1024, -351),
718 2.381008060978474962211278613067275529112106932635520021e-212, tolerance);
719 assertClose(BoostBeta::beta, 2, 4, Math.scalb(1 + 1.0 / 2048, -351),
720 2.378685692854274898232669682422430136513931911501225435e-212, tolerance);
721 assertClose(BoostBeta::ibeta, 3, 5, Math.scalb(1 + 15.0 / 16, -268),
722 2.386034198603463687323052353589201848077110231388968865e-240, tolerance);
723 assertClose(BoostBeta::ibetaDerivative, 2, 4, Math.scalb(1.0, -557),
724 4.23957586190238472641508753637420672781472122471791800210e-167, tolerance * 4);
725 assertClose(BoostBeta::ibetaDerivative, 2, 4.5, Math.scalb(1.0, -557),
726 5.24647512910420109893867082626308082567071751558842352760e-167, tolerance * 20);
727
728
729
730
731
732
733 tolerance = 5;
734 assertClose(BoostBeta::ibeta, 20, 2, Math.scalb(1.0, -52),
735 1.78247646441082836775138741451452643650923455144048771440215391e-312, tolerance);
736 assertClose(BoostBeta::ibeta, 33, 2, Math.scalb(1.0, -32),
737 4.403555717560735620179800985566548469434721409459606451475482401e-317, tolerance);
738 assertClose(BoostBeta::ibeta, 759, 2, 0.375,
739 2.327049191912271223071639014409322663839431670313469640444508565e-321, tolerance);
740
741
742
743 tolerance = 1;
744 assertClose(BoostBeta::ibeta, 1, 2, 0.125, 0.234375, tolerance);
745 assertClose(BoostBeta::ibeta, 1, 2, 0.5, 0.75, tolerance);
746 assertClose(BoostBeta::ibeta, 1, 2, 0.75, 0.9375, tolerance);
747 assertClose(BoostBeta::ibeta, 2, 1, 0.125, 0.015625, tolerance);
748 assertClose(BoostBeta::ibeta, 2, 1, 0.5, 0.25, tolerance);
749 assertClose(BoostBeta::ibeta, 2, 1, 0.75, 0.5625, tolerance);
750 assertClose(BoostBeta::ibeta, 2, 2, 0.125, 0.04296875, tolerance);
751 assertClose(BoostBeta::ibeta, 2, 2, 0.5, 0.5, tolerance);
752 assertClose(BoostBeta::ibeta, 2, 2, 0.75, 0.84375, tolerance);
753 assertClose(BoostBeta::ibeta, 2, 3, 0.125, 0.078857421875, tolerance);
754 assertClose(BoostBeta::ibeta, 2, 3, 0.5, 0.6875, tolerance);
755 assertClose(BoostBeta::ibeta, 2, 3, 0.75, 0.94921875, tolerance);
756
757
758
759 Assertions.assertEquals(1, BoostBeta.ibeta(39, 2147483546, Math.nextDown(1.0)));
760 Assertions.assertEquals(0, BoostBeta.ibetac(39, 2147483546, Math.nextDown(1.0)));
761
762 Assertions.assertEquals(1, BoostBeta.ibeta(39, Integer.MAX_VALUE, Math.nextDown(1.0)));
763 Assertions.assertEquals(0, BoostBeta.ibetac(39, Integer.MAX_VALUE, Math.nextDown(1.0)));
764
765
766 Assertions.assertEquals(0, BoostBeta.ibeta(2, 4, 0));
767 Assertions.assertEquals(1, BoostBeta.ibeta(2, 4, 1));
768 Assertions.assertEquals(1, BoostBeta.ibetac(2, 4, 0));
769 Assertions.assertEquals(0, BoostBeta.ibetac(2, 4, 1));
770 Assertions.assertEquals(0, BoostBeta.beta(2, 4, 0));
771 TestUtils.assertEquals(0.05, BoostBeta.beta(2, 4, 1), 1);
772 TestUtils.assertEquals(0.05, BoostBeta.betac(2, 4, 0), 1);
773 Assertions.assertEquals(0, BoostBeta.betac(2, 4, 1));
774
775 tolerance = 30;
776
777
778 assertClose(BoostBeta::ibeta, 72.5, 2, 0.75, 1.673181444858556263e-8, tolerance * 2);
779
780
781 assertClose(BoostBeta::ibeta, 14.5, 2, 0.75, 7.1367429756558048437e-2, tolerance);
782 }
783
784 @ParameterizedTest
785 @EnumSource(value = TriTestCase.class, mode = Mode.MATCH_ANY,
786 names = {"IBETA_[SML].*", "IBETAC_[SML].*", "RBETA.*"})
787 void testIBeta(TriTestCase tc) {
788 assertFunction(tc);
789 }
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807 @ParameterizedTest
808 @CsvSource(value = {
809
810
811
812 "4.201121919322759E-5,2.1881177963223308E-4,0.6323960423469543,23803.707603529016,4569.595256369948,0.838947362634037,0.16105263736596298",
813 "0.22512593865394592,0.4898320138454437,0.7996731996536255,4.764013990849158,0.9820281524501243,0.8290948573018541,0.17090514269814597",
814 "4.623167842510156E-5,4.340034502092749E-5,0.135563462972641,21628.337679706918,23043.143905699715,0.48416432390665337,0.5158356760933467",
815 "2.9415024982881732E-5,4.1924233664758503E-4,0.3082362115383148,33995.42298781772,2386.0630988783787,0.9344154580933705,0.0655845419066295",
816 "1.184685606858693E-5,0.015964560210704803,0.3082362115383148,84409.7658651171,63.42758275377908,0.9992491395179357,7.508604820642986E-4",
817 "3.529437162796967E-5,2.2326681573758833E-5,0.6323960423469543,28333.671885777032,44788.91695876111,0.3874817937042091,0.6125182062957909",
818 "0.06715317070484161,2.306319236755371,0.9084427952766418,13.787272071732604,0.001859217475218142,0.999865167903893,1.3483209610697333E-4",
819 "0.3183284401893616,3.165504217147827,0.07764927297830582,1.3374998709679642,0.6794195418585712,0.6631399660602084,0.3368600339397915",
820 "0.15403440594673157,4.049813747406006,0.34629878401756287,4.872033861103044,0.08561968850485947,0.9827297959310547,0.01727020406894529",
821 "1.3317101001739502,0.7650398015975952,0.6445860862731934,0.47144799136487586,0.5594135526519237,0.4573339592510717,0.5426660407489283",
822 "0.11902070045471191,7.269547462463379,0.19963125884532928,6.225047194692518,0.08420413075357451,0.9866538632858122,0.013346136714187858",
823 "2.664715051651001,0.6914005279541016,0.8443243503570557,0.3338388990912521,0.3587830340198169,0.4819929648946269,0.518007035105373",
824 "1.0562920570373535,6.812713623046875,0.8258343935012817,0.12732498812245932,9.807107058749213E-7,0.9999922976379849,7.702362015088557E-6",
825 "1.7118667364120483,3.0191311836242676,0.07594671100378036,0.0064151684204504875,0.10850933283432233,0.05582072012850319,0.9441792798714969",
826
827 "0.04634225741028786,0.34317314624786377,0.24176712334156036,20.363670894714268,3.6307737402387885,0.8486827348798152,0.15131726512018484",
828 "2.113992923113983E-5,1.7535277947899885E-5,0.8350250720977783,47305.46963235176,57026.27394012006,0.4534139659948463,0.5465860340051537",
829 "4.005068331025541E-5,42.84983825683594,0.12707412242889404,24964.03974453176,4.764518849491958E-4,0.9999999809144722,1.9085527852527327E-8",
830 "67.90167999267578,0.8324270844459534,0.9676981568336487,0.002669338211636283,0.031098353139101195,0.0790500654578503,0.9209499345421497",
831 "0.395370751619339,0.004023698624223471,0.9058013558387756,4.307485901473997,246.2348442100959,0.017192647244702382,0.9828073527552976",
832 "3.444607973098755,66.36054992675781,0.09654488414525986,1.4741027361579568E-6,8.307589573110104E-8,0.9466497330301025,0.05335026696989754",
833 "1.0665277242660522,4.985442161560059,0.2400285303592682,0.12523918055824373,0.0476465037156954,0.7244045745268357,0.2755954254731643",
834
835 "1.319732904434204,4.903014659881592,0.33251503109931946,0.0837704419861451,0.021604794441302123,0.7949727547593459,0.20502724524065405",
836 "485.7690734863281,190.16734313964844,0.6323960423469543,7.8023253024461E-182,7.885435919806278E-176,9.894592590329194E-7,0.9999990105407409",
837 })
838 void testIBetaPolicy(double a, double b, double x, double beta, double betac, double ibeta, double ibetac) {
839
840 final Policy pol1 = new Policy(0x1.0p-52, 1);
841 Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.beta(a, b, x, pol1), "beta");
842 Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.betac(a, b, x, pol1), "betac");
843 Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.ibeta(a, b, x, pol1), "ibeta");
844 Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.ibetac(a, b, x, pol1), "ibetac");
845
846
847 final Policy pol2 = new Policy(1e-3, Integer.MAX_VALUE);
848
849
850 if (Double.isFinite(beta)) {
851 final double u1 = BoostBeta.beta(a, b, x);
852 final double u2 = BoostBeta.beta(a, b, x, pol2);
853 assertCloser("beta", beta, u1, u2);
854 }
855 if (Double.isFinite(betac)) {
856 final double l1 = BoostBeta.betac(a, b, x);
857 final double l2 = BoostBeta.betac(a, b, x, pol2);
858 assertCloser("betac", betac, l1, l2);
859 }
860
861
862 if ((int) ibeta != ibeta) {
863 final double p1 = BoostBeta.ibeta(a, b, x);
864 final double p2 = BoostBeta.ibeta(a, b, x, pol2);
865 assertCloser("ibeta", ibeta, p1, p2);
866 }
867 if ((int) ibetac != ibetac) {
868 final double q1 = BoostBeta.ibetac(a, b, x);
869 final double q2 = BoostBeta.ibetac(a, b, x, pol2);
870 assertCloser("ibetac", ibetac, q1, q2);
871 }
872 }
873
874
875
876
877 private static void assertCloser(String msg, double expected, double x, double y) {
878 if (!Double.isFinite(expected)) {
879
880 Assertions.assertEquals(expected, x);
881 Assertions.assertEquals(expected, y);
882 } else {
883 final double dx = Math.abs(expected - x);
884 final double dy = Math.abs(expected - y);
885 Assertions.assertTrue(dx < dy,
886 () -> String.format("%s %s : %s (%s) : %s (%s)", msg, expected,
887 x, dx / Math.ulp(expected), y, dy / Math.ulp(expected)));
888 }
889 }
890
891
892
893
894
895
896
897
898
899
900
901 private static double binomialCoefficient(double n, double k) {
902 return BoostBeta.binomialCoefficient((int) n, (int) k);
903 }
904
905
906
907
908
909
910
911
912
913
914 private static double binomialCoefficient1(double n1, double k1) {
915
916 int n = (int) n1;
917 int k = (int) k1;
918 final int m = Math.min(k, n - k);
919 if (m == 0) {
920 return 1;
921 }
922 if (m == 1) {
923 return n;
924 }
925 if (m == 2) {
926
927 return (n - 1L) * n / 2;
928 }
929
930 double result;
931 if (n <= 170) {
932
933
934 result = BoostGamma.uncheckedFactorial(n);
935
936 result /= BoostGamma.uncheckedFactorial(m);
937 result /= BoostGamma.uncheckedFactorial(n - m);
938 } else {
939 if (k < n - k) {
940 result = k * BoostBeta.beta(k, n - k + 1);
941 } else {
942 result = (n - k) * BoostBeta.beta(k + 1, n - k);
943 }
944 result = 1 / result;
945 }
946 return Math.ceil(result - 0.5f);
947 }
948
949
950
951
952
953
954
955
956
957
958
959 private static double beta(double p, double q) {
960 return Math.exp(LogBeta.value(p, q));
961 }
962
963
964
965
966
967
968
969
970
971
972
973
974 private static double ibetaDerivative1(double a, double b, double x) {
975 if (x < 0 || x > 1) {
976 return 0;
977 } else if (x == 0) {
978 if (a < 1) {
979
980
981
982 return Double.POSITIVE_INFINITY;
983 }
984
985 if (a == 1) {
986 return b;
987 }
988 return 0;
989 } else if (x == 1) {
990 if (b < 1) {
991
992
993
994 return Double.POSITIVE_INFINITY;
995 }
996
997 if (b == 1) {
998 return a;
999 }
1000 return 0;
1001 } else {
1002 final double z = LogGamma.value(a) + LogGamma.value(b) - LogGamma.value(a + b);
1003 final double logX = Math.log(x);
1004 final double log1mX = Math.log1p(-x);
1005 return Math.exp((a - 1) * logX + (b - 1) * log1mX - z);
1006 }
1007 }
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020 private static double ibetaDerivative2(double a, double b, double x) {
1021 if (x < 0 || x > 1) {
1022 return 0;
1023 } else if (x == 0) {
1024 if (a < 1) {
1025
1026
1027
1028 return Double.POSITIVE_INFINITY;
1029 }
1030
1031 if (a == 1) {
1032 return b;
1033 }
1034 return 0;
1035 } else if (x == 1) {
1036 if (b < 1) {
1037
1038
1039
1040 return Double.POSITIVE_INFINITY;
1041 }
1042
1043 if (b == 1) {
1044 return a;
1045 }
1046 return 0;
1047 } else {
1048 final double z = LogBeta.value(a, b);
1049 final double logX = Math.log(x);
1050 final double log1mX = Math.log1p(-x);
1051 return Math.exp((a - 1) * logX + (b - 1) * log1mX - z);
1052 }
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 private static double ibeta(double a, double b, double x) {
1065 return ibetaImp(a, b, x, false);
1066 }
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077 private static double ibetac(double a, double b, double x) {
1078 return ibetaImp(a, b, x, true);
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093 private static double ibetaImp(double a, double b, double x, boolean inv) {
1094
1095
1096 double result;
1097 if (x > (a + 1) / (2 + b + a)) {
1098 result = BoostBeta.ibetaFraction(b, a, 1 - x, x, Policy.getDefault(), true);
1099 inv = !inv;
1100 } else {
1101 result = BoostBeta.ibetaFraction(a, b, x, 1 - x, Policy.getDefault(), true);
1102 }
1103 return inv ? 1 - result : result;
1104 }
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115 private static void assertClose(DoubleBinaryOperator fun, double x, double y, double expected, int tolerance) {
1116 final double actual = fun.applyAsDouble(x, y);
1117 TestUtils.assertEquals(expected, actual, tolerance, null, () -> x + ", " + y);
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 private static void assertWithinEps(DoubleBinaryOperator fun, double x, double y, double expected, double eps) {
1130 final double actual = fun.applyAsDouble(x, y);
1131 Assertions.assertEquals(expected, actual, Math.abs(expected) * eps, () -> x + ", " + y);
1132 }
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144 private static void assertClose(DoubleTernaryOperator fun, double x, double y, double z, double expected, int tolerance) {
1145 final double actual = fun.applyAsDouble(x, y, z);
1146 TestUtils.assertEquals(expected, actual, tolerance, null, () -> x + ", " + y + ", " + z);
1147 }
1148
1149
1150
1151
1152
1153
1154 private static void assertFunction(BiTestCase tc) {
1155 final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1156 try (DataReader in = new DataReader(tc.getFilename())) {
1157 while (in.next()) {
1158 try {
1159 final double x = in.getDouble(0);
1160 final double y = in.getDouble(1);
1161 final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1162 final double actual = tc.getFunction().applyAsDouble(x, y);
1163 TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1164 () -> tc + " x=" + x + ", y=" + y);
1165 } catch (final NumberFormatException ex) {
1166 Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1167 }
1168 }
1169 } catch (final IOException ex) {
1170 Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1171 }
1172
1173 assertRms(tc, stats);
1174 }
1175
1176
1177
1178
1179
1180
1181 private static void assertFunction(TriTestCase tc) {
1182 final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1183 try (DataReader in = new DataReader(tc.getFilename())) {
1184 while (in.next()) {
1185 try {
1186 final double x = in.getDouble(0);
1187 final double y = in.getDouble(1);
1188 final double z = in.getDouble(2);
1189 final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1190 final double actual = tc.getFunction().applyAsDouble(x, y, z);
1191 TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1192 () -> tc + " x=" + x + ", y=" + y + ", z=" + z);
1193 } catch (final NumberFormatException ex) {
1194 Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1195 }
1196 }
1197 } catch (final IOException ex) {
1198 Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1199 }
1200
1201 assertRms(tc, stats);
1202 }
1203
1204
1205
1206
1207
1208
1209
1210
1211 private static void assertRms(TestError te, TestUtils.ErrorStatistics stats) {
1212 final double rms = stats.getRMS();
1213
1214 Assertions.assertTrue(rms <= te.getRmsTolerance(),
1215 () -> String.format("%s RMS %s < %s", te, rms, te.getRmsTolerance()));
1216 }
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 private static void debugRms(String name, double maxAbsUlp, double rmsUlp, double meanUlp, int size) {
1231
1232 System.out.printf("%-35s max %10.6g RMS %10.6g mean %14.6g n %4d%n",
1233 name, maxAbsUlp, rmsUlp, meanUlp, size);
1234 }
1235 }