1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.statistics.inference;
18
19 import java.math.BigDecimal;
20 import java.math.MathContext;
21 import java.math.RoundingMode;
22 import org.apache.commons.numbers.core.DD;
23 import org.apache.commons.numbers.core.DDMath;
24 import org.apache.commons.statistics.inference.KolmogorovSmirnovDistribution.One.ScaledPower;
25 import org.junit.jupiter.api.Assertions;
26 import org.junit.jupiter.api.Assumptions;
27 import org.junit.jupiter.api.Test;
28 import org.junit.jupiter.params.ParameterizedTest;
29 import org.junit.jupiter.params.provider.CsvFileSource;
30 import org.junit.jupiter.params.provider.CsvSource;
31
32
33
34
35 class KolmogorovSmirnovDistributionTest {
36
37 private static final double EPS = Math.ulp(1.0);
38
39 private static final ScaledPower DEFAULT_POWER = null;
40
41 private static final MathContext DEFAULT_MC = null;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 @ParameterizedTest
62 @CsvSource({
63
64 "1, 10, 0",
65 "1, 100, 0",
66 "1.23, 10, 0",
67
68 "0.5, 1480, 0",
69 "0.1, 37000, 0",
70 "1e-3, 370000000, 0",
71
72 "0, 10, 1",
73 "0, 100, 1",
74 "-1, 10, 1",
75
76 "0.0125, 1, 1",
77 "0.75, 1, 0.5",
78 "0.789, 1, 0.42199999999999993",
79 "0.99999999999, 1, 2.000000165480742e-11",
80
81 "0.05, 10, 1",
82 "0.025, 20, 1",
83 "0.0125, 40, 1",
84
85 "0.075, 10, 0.999999645625",
86 "0.045, 20, 0.9999999997324996",
87 "0.025, 40, 0.9999999999999999",
88
89 "0.99, 10, 2.0000000000000176e-20",
90 "0.95, 10, 1.9531250000000172e-13",
91 "0.999, 100, 2.0000000000001778e-300",
92 "0.995, 100, 1.5777218104421636e-230",
93 })
94 void testTwoSFExact(double x, int n, double p) {
95 final double p2 = KolmogorovSmirnovDistribution.Two.sf(x, n);
96 TestUtils.assertProbability(p, p2, EPS, "sf");
97 }
98
99
100
101
102
103
104 @ParameterizedTest
105 @CsvSource({
106
107
108 "0.101, 10, 0.9995670338682896, 2e-16",
109 "0.15, 10, 0.95396527, 2e-16",
110 "0.22, 10, 0.6425444017073398, 4e-16",
111 "0.274, 10, 0.3715203845434957, 3e-16",
112 "0.02, 100, 0.999999999978262, 2e-16",
113 "0.04, 100, 0.995307510717411, 2e-16",
114 "0.06, 100, 0.8428847956274123, 3e-16",
115 "0.0868, 100, 0.4148814057122582, 2e-15",
116 "0.018, 140, 0.9999999997083645, 2e-16",
117 "0.04, 140, 0.9719726587618661, 2e-16",
118 "0.06, 140, 0.6719092248862638, 5e-16",
119 "0.0734, 140, 0.4177062319533004, 3e-15",
120
121 "0.008, 1000, 0.9999999133431808, 2e-16",
122 "0.009, 1000, 0.9999964462512184, 2e-16",
123 "0.01, 1000, 0.9999496745370611, 2e-16",
124 "0.0125, 1000, 0.9971528020597908, 2e-16",
125 "0.002, 10000, 0.9999999999991761, 2e-16",
126 "0.0024, 10000, 0.9999999930856749, 2e-16",
127 "0.00269, 10000, 0.9999995514082088, 2e-16",
128 "0.000922, 50000, 0.999999999996305, 2e-16",
129 })
130 void testDurbinMTW(double x, int n, double p, double eps) {
131 final double p2 = KolmogorovSmirnovDistribution.Two.sf(x, n);
132 TestUtils.assertProbability(p, p2, eps, "sf");
133 }
134
135
136
137
138
139
140
141 @ParameterizedTest
142 @CsvSource({
143
144
145
146 "0.125, 8",
147
148
149 "0.15625, 8",
150 "0.1875, 8",
151
152
153 "0.203125, 8",
154 "0.21875, 8",
155 })
156 void testPomeranzComputeA(double x, int n) {
157 final double t = n * x;
158 final double[] a = new double[2 * n + 3];
159 final int[] amt = new int[a.length];
160 final int[] apt = new int[a.length];
161 KolmogorovSmirnovDistribution.Two.computeA(n, t, amt, apt);
162
163
164 a[1] = 0;
165 a[2] = Math.min(t - Math.floor(t), Math.ceil(t) - t);
166 a[3] = 1 - a[2];
167 a[2 * n + 2] = n;
168 final int max = 2 * n + 2;
169 final double f = t - Math.floor(t);
170
171 if (f > 0.5) {
172
173
174 for (int i = 1; 2 * i < max; i++) {
175 a[2 * i] = i - f;
176 if (2 * i + 1 < max) {
177 a[2 * i + 1] = i - 1 + f;
178 }
179 }
180 } else if (f > 0) {
181
182
183 for (int i = 1; 2 * i < max; i++) {
184 a[2 * i] = i - 1 + f;
185 if (2 * i + 1 < max) {
186 a[2 * i + 1] = i - f;
187 }
188 }
189 } else {
190
191
192 for (int i = 1; 2 * i < max; i++) {
193 a[2 * i] = i - 1;
194 if (2 * i + 1 < max) {
195 a[2 * i + 1] = i;
196 }
197 }
198 }
199
200 for (int i = 2; i <= max; i++) {
201 final int im1 = i - 1;
202 Assertions.assertEquals(Math.floor(a[im1] - t), amt[im1], () ->
203 String.format("floor(A[%d] + t == floor(%s - %s)) = floor(%s)", im1, a[im1], t, a[im1] - t));
204 Assertions.assertEquals(Math.ceil(a[im1] + t), apt[im1], () ->
205 String.format("ceil(A[%d] + t) == ceil(%s + %s) = ceil(%s)", im1, a[im1], t, a[im1] + t));
206 }
207 }
208
209
210
211
212
213
214
215
216
217 @ParameterizedTest
218 @CsvSource({
219
220
221
222 "0.275, 10, 0.36721918274907195, 2e-16",
223 "0.3, 10, 0.27053557479999946, 5e-16",
224 "0.325, 10, 0.19329796645948394, 2e-15",
225 "0.5, 10, 0.00777741, 2e-13",
226 "0.55, 10, 0.0022805103214843725, 4e-13",
227 "0.632, 10, 0.00021216261775257054, 8e-14",
228
229 "0.0869, 100, 0.41345306880916205, 5e-16",
230 "0.12, 100, 0.10330374901819939, 9e-15",
231 "0.125, 100, 0.08050040280210224, 9e-15",
232 "0.1605, 100, 0.010204399956967765, 3e-14",
233 "0.199, 100, 0.0006024947156633567, 2e-12",
234
235 "0.0735, 140, 0.41601087723723057, 5e-16",
236 "0.09, 140, 0.1946946629845738, 2e-16",
237 "0.11, 140, 0.06252511429470399, 7e-15",
238 "0.15, 140, 0.0032495604415101703, 2e-13",
239 "0.169, 140, 0.0005778682806183945, 2e-13",
240 })
241 void testPomeranz(double x, int n, double p, double eps) {
242 final double p2 = KolmogorovSmirnovDistribution.Two.sf(x, n);
243 TestUtils.assertProbability(p, p2, eps, "sf");
244 }
245
246 @Test
247 void testPelzGoodApproximation() {
248
249
250
251
252 final double[] ds = {0.15, 0.20, 0.25, 0.3, 0.35, 0.4};
253 final int[] ns = {141, 150, 180, 220, 1000};
254 final double[] ref = {
255 0.9968940168727817, 0.9979326624184855, 0.9994677598604502, 0.9999128354780206, 1,
256 0.9999797514476229, 0.9999902122242085, 0.9999991327060904, 0.9999999657682075, 1,
257 0.9999999706445153, 0.9999999906571525, 0.9999999997949723, 0.9999999999987504, 1,
258 0.9999999999916627, 0.9999999999984474, 0.9999999999999944, 1, 1,
259 0.9999999999999996, 1, 1, 1, 1,
260 1, 1, 1, 1, 1,
261 };
262
263 final double tol = 1e-15;
264 int k = 0;
265 for (final double x : ds) {
266 for (final int n : ns) {
267 TestUtils.assertProbability(ref[k++],
268 1 - KolmogorovSmirnovDistribution.Two.pelzGood(x, n), tol, () -> String.format("%s %d", x, n));
269 }
270 }
271 }
272
273
274
275
276
277
278 @ParameterizedTest
279 @CsvSource({
280
281
282 "0.0126, 1000, 0.9968143322478163, 2e-16",
283 "0.02, 1000, 0.8108971656895577, 2e-16",
284 "0.03, 1000, 0.3226902143914636, 2e-15",
285 "0.0469, 1000, 0.023788979220138784, 2e-16",
286 "0.00270, 10000, 0.999999494161441, 2e-16",
287 "0.005, 10000, 0.9628778025204304, 2e-16",
288 "0.01, 10000, 0.2682191277029192, 2e-15",
289 "0.0148, 10000, 0.02478199615804111, 3e-14",
290 "0.000923, 50000, 0.9999999999960723, 2e-16",
291 "0.0025, 50000, 0.9126805535490892, 2e-16",
292 "0.004, 50000, 0.3994316646755731, 5e-16",
293 "0.00663, 50000, 0.02455135772431216, 9e-15",
294
295 "0.0006, 150000, 0.9999999985986439, 2e-16",
296 "0.001, 150000, 0.9982358422822605, 2e-16",
297 "0.002, 150000, 0.5852546878861703, 2e-16",
298 "0.00382, 150000, 0.025043795432396876, 4e-15",
299
300
301 "0.0001077, 150000, 1, 0",
302 "0.0001078, 150000, 1, 0",
303
304
305 "0.00048, 150000, 0.999999999999995, 0",
306 "0.00046, 150000, 0.9999999999999998, 0",
307 "0.00044, 150000, 1, 0",
308 })
309 void testPelzGood(double x, int n, double p, double eps) {
310 final double p2 = KolmogorovSmirnovDistribution.Two.sf(x, n);
311 TestUtils.assertProbability(p, p2, eps, "sf");
312 }
313
314
315
316
317
318
319 @ParameterizedTest
320 @CsvSource({
321
322
323 "0.64, 10, 0.00016378151748370424, 2e-16",
324 "0.65, 10, 0.00011761597734374991, 2e-16",
325 "0.66, 10, 8.372225302495224e-05, 2e-16",
326 "0.7, 10, 1.9544800000000034e-05, 2e-16",
327 "0.85, 10, 1.1566210937500017e-08, 2e-16",
328 "0.21, 100, 0.00023947345214651332, 2e-16",
329 "0.23, 100, 3.921924383021513e-05, 2e-16",
330 "0.25, 100, 5.408871776434847e-06, 2e-16",
331 "0.6, 100, 5.912822156396238e-35, 2e-16",
332 "0.17, 140, 0.0005246108687519814, 2e-16",
333 "0.18, 140, 0.0001932001834783552, 2e-16",
334 "0.19, 140, 6.711071724199073e-05, 2e-16",
335 "0.7, 140, 2.7611948816463573e-69, 2e-16",
336
337 "0.064, 1000, 0.0005275756095069879, 2e-16",
338 "0.07, 1000, 0.00010494206285958879, 2e-16",
339 "0.075, 1000, 2.445848323871376e-05, 2e-16",
340 "0.08, 1000, 5.154189384789824e-06, 2e-16",
341 "0.1, 1000, 3.703687096817711e-09, 2e-16",
342 "0.3, 1000, 2.590715705736845e-80, 2e-16",
343 "0.0201, 10000, 0.0006106415730821294, 2e-16",
344 "0.022, 10000, 0.00012312039864307816, 2e-16",
345 "0.025, 10000, 7.319405460271144e-06, 2e-16",
346 "0.03, 10000, 2.9761211950626197e-08, 2e-16",
347 "0.04, 10000, 2.4399624749525048e-14, 2e-16",
348 "0.1, 10000, 1.6633113315950355e-87, 2e-16",
349 })
350 void testMillerApproximation(double x, int n, double p, double eps) {
351 final double p2 = KolmogorovSmirnovDistribution.Two.sf(x, n);
352 TestUtils.assertProbability(p, p2, eps, "sf");
353 }
354
355
356
357
358 @ParameterizedTest
359 @CsvSource({
360
361 "1, 10, 0",
362 "1, 100, 0",
363 "1.23, 10, 0",
364
365 "0.5, 1490, 0",
366 "0.1, 37250, 0",
367 "1e-3, 372500000, 0",
368
369 "0, 10, 1",
370 "0, 100, 1",
371 "-1, 10, 1",
372
373 "0.012345, 1, 0.012345",
374 "0.025, 1, 0.025",
375 "0.99999999999, 1, 0.99999999999",
376
377 "0.05, 10, 0.9224335892010742",
378 "0.025, 20, 0.9600337453587708",
379 "0.0125, 40, 0.9797084016853456",
380 "0.075, 10, 0.8562071003140899",
381 "0.045, 20, 0.8961462860117863",
382 "0.025, 40, 0.9345106380880495",
383 "2.5e-6, 400000, 0.9999932043209127",
384 "1e-15, 400000, 0.999999999999999",
385
386 "0.99, 10, 1.0000000000000088e-20",
387 "0.95, 10, 9.765625000000086e-14",
388 "0.9999999, 10, 9.999999947364416e-71",
389 "0.999, 100, 1.0000000000000889e-300",
390 "0.995, 100, 7.888609052210818e-231",
391 })
392 void testOneSFExact(double x, int n, double p) {
393 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n);
394 final double p2 = onesf(x, n, DEFAULT_MC);
395 TestUtils.assertProbability(p, p1, EPS, "sf");
396 TestUtils.assertProbability(p, p2, EPS, "sf BigDecimal");
397
398 if (x > 0x1.0p-1000) {
399 final double p3 = onesf(x, n, DEFAULT_POWER);
400 TestUtils.assertProbability(p, p3, EPS, "sf double-double");
401 }
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 @ParameterizedTest
418 @CsvSource({
419
420
421 "1e-4, 1000001, 0.9801332548522448, 2e-16",
422 "1e-3, 1000001, 0.1352448117787778, 2e-16",
423 "1.5e-3, 1000001, 0.011097842537398537, 2e-16",
424 "1.75e-3, 1000001, 0.0021849270292376324, 2e-16",
425 "2e-3, 1000001, 0.0003350129437289286, 2e-16",
426 "2.5e-3, 1000001, 3.7204005445026563e-06, 2e-16",
427 "3e-3, 1000001, 1.5199275791041033e-08, 2e-16",
428 "3.5e-3, 1000001, 2.284342265345614e-11, 2e-16",
429 "4e-3, 1000001, 1.2630034559846114e-14, 2e-16",
430 "7e-3, 1000001, 2.7357189636541477e-43, 2e-16",
431 "1e-2, 1000001, 1.374426245809477e-87, 2e-16",
432
433 "1.0E-4, 1000001, 0.9801333136251243, 6e-8",
434 "0.001, 1000001, 0.13524481927494492, 6e-8",
435 "0.0015, 1000001, 0.011097829274951359, 2e-6",
436 "0.00175, 1000001, 0.0021849210146805925, 3e-6",
437 "0.002, 1000001, 3.3501117508103243E-4, 6e-6",
438 "0.0025, 1000001, 3.720346483702557E-6, 2e-5",
439 "0.003, 1000001, 1.519879017846374E-8, 4e-5",
440 "0.0035, 1000001, 2.2842024589556203E-11, 7e-5",
441 "0.004, 1000001, 1.2628687945778359E-14, 2e-4",
442 "0.007, 1000001, 2.7328605923747603E-43, 2e-3",
443 "0.01, 1000001, 1.3683915090193192E-87, 5e-3",
444
445 "1.0E-4, 1000001, 0.9801333136251243, 6e-8",
446 "0.001, 1000001, 0.13524481927494492, 6e-8",
447 "0.0015, 1000001, 0.011097829274951359, 2e-6",
448 "0.00175, 1000001, 0.0021849210146805925, 3e-6",
449 "0.002, 1000001, 3.350111750810325E-4, 6e-6",
450 "0.0025, 1000001, 3.720346483702557E-6, 2e-5",
451 "0.003, 1000001, 1.519879017846374E-8, 4e-5",
452 "0.0035, 1000001, 2.2842024589556203E-11, 7e-5",
453 "0.004, 1000001, 1.2628687945778359E-14, 2e-4",
454 "0.007, 1000001, 2.7328605923747603E-43, 2e-3",
455 "0.01, 1000001, 1.3683915090193192E-87, 5e-3",
456
457
458 "1e-6, 10000000, 0.9999793279914467, 2e-16",
459 "2e-6, 10000000, 0.9999186644190289, 2e-16",
460 "2e-4, 10000000, 0.44926905508659115, 2e-16",
461 "8e-4, 10000000, 2.7593005372427517e-06, 2e-16",
462 "1e-3, 10000000, 2.059779966512744e-09, 2e-16",
463
464 "1.0E-6, 10000000, 0.9999793335473409, 1e-8",
465 "2.0E-6, 10000000, 0.9999186699759279, 1e-8",
466 "2.0E-4, 10000000, 0.4492690623747514, 2e-8",
467 "8.0E-4, 10000000, 2.7592963140010787E-6, 2e-6",
468 "0.001, 10000000, 2.059771738419037E-9, 5e-6",
469
470 "1.0E-6, 10000000, 0.9999793335473409, 1e-8",
471 "2.0E-6, 10000000, 0.9999186699759279, 1e-8",
472 "2.0E-4, 10000000, 0.4492690623747514, 2e-8",
473 "8.0E-4, 10000000, 2.7592963140010787E-6, 2e-6",
474 "0.001, 10000000, 2.059771738419037E-9, 5e-6",
475
476
477
478
479 "1e-6, 1073741824, 0.9978541552573539, 2e-16",
480 "1e-5, 1073741824, 0.8067390416113425, 2e-16",
481 "1e-4, 1073741824, 4.715937751790102e-10, 2e-16",
482 "2e-4, 1073741824, 4.94686617627386e-38, 2e-16",
483 "3e-4, 1073741824, 1.1542138829781214e-84, 2e-16",
484 "5e-4, 1073741824, 6.914816492890092e-234, 2e-16",
485
486 "1.0E-6, 1073741824, 0.9978541553094261, 6e-11",
487 "1.0E-5, 1073741824, 0.806739041685089, 1e-10",
488 "1.0E-4, 1073741824, 4.715937547939542E-10, 5e-8",
489 "2.0E-4, 1073741824, 4.946862487288558E-38, 8e-7",
490 "3.0E-4, 1073741824, 1.1542094676281237E-84, 4e-6",
491 "5.0E-4, 1073741824, 6.914611021963405E-234, 3e-5",
492 })
493 void testOneSFAsymptotic(double x, int n, double p, double eps) {
494 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n);
495
496
497
498
499 TestUtils.assertProbability(p, p1, eps, "sf");
500 }
501
502
503
504
505
506
507
508
509 @ParameterizedTest
510 @CsvSource({
511
512
513 "0.17, 6, 0.6307293944351234, 2e-16",
514 "0.3, 6, 0.28207240740740747, 2e-16",
515 "0.5, 6, 0.03279320987654321, 2e-16",
516 "0.7, 6, 0.0009059876543209886, 2e-16",
517 "0.2, 10, 0.39676169159999997, 2e-16",
518 "0.4, 10, 0.02949469039999999, 2e-16",
519 "0.6, 10, 0.0002840836000000002, 2e-16",
520 "0.8, 10, 1.1039999999999974e-07, 2e-16",
521 "0.02, 60, 0.940517651518819, 2e-16",
522 "0.04, 60, 0.8041968460900994, 2e-16",
523 "0.2, 60, 0.007011703368333355, 2e-16",
524 "0.4, 60, 1.7743971854364937e-09, 2e-16",
525 "0.02, 100, 0.9109784815556481, 2e-16",
526 "0.03, 100, 0.8190484857999243, 2e-16",
527 "0.3, 100, 8.859934946331458e-09, 2e-16",
528 "0.5, 100, 6.065717185908929e-24, 2e-16",
529 "0.7, 100, 6.105702490608996e-50, 2e-16",
530
531 "1e-2, 1000, 0.8133237754763678, 2e-16",
532 "2e-2, 1000, 0.44342498843949424, 2e-16",
533 "3e-2, 1000, 0.16203171395455085, 2e-16",
534 "5e-2, 1000, 0.006506037390545166, 2e-16",
535 "8e-2, 1000, 2.577094692394912e-06, 2e-16",
536 "1e-1, 1000, 1.8518435484088554e-09, 2e-16",
537
538 "2e-2, 5000, 0.01806981293722266, 2e-16",
539
540 "1.5e-2, 10000, 0.01099707871209626, 2e-16",
541 })
542 void testOneSFSmallN(double x, int n, double p, double eps) {
543 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n);
544 final double p2 = onesf(x, n, DEFAULT_MC);
545 TestUtils.assertProbability(p, p1, eps, "sf");
546 TestUtils.assertProbability(p, p2, eps, "sf BigDecimal");
547
548 final double p3 = onesf(x, n, DD::pow);
549 final double p4 = onesf(x, n, DDMath::pow);
550
551 Assertions.assertTrue(p1 == p3 || p1 == p4, "Default implementation differs");
552 TestUtils.assertProbability(p, p3, eps, "sf DD.pow");
553 TestUtils.assertProbability(p, p4, eps, "sf DDMath.pow");
554
555 final double p5 = onesf(x, n, KolmogorovSmirnovDistributionTest::simplePowScaled);
556 TestUtils.assertProbability(p, p5, eps, "sf simplePow");
557 }
558
559
560
561
562
563 @ParameterizedTest
564 @CsvSource({
565
566
567 "1e-5, 12345, 0.9999886861857871, 2e-16",
568 "1e-3, 12345, 0.9749625482896954, 2e-16",
569 "1e-2, 12345, 0.08410601145926598, 2e-16",
570 "1e-1, 12345, 3.2064615455964645e-108, 2e-16",
571 "0.15, 12345, 3.007486476486515e-243, 2e-16",
572 "1e-6, 123456, 0.9999988686009794, 2e-16",
573 "1e-4, 123456, 0.9974674302237663, 2e-16",
574 "1.5e-3, 123456, 0.5731824062390436, 2e-16",
575 "5e-3, 123456, 0.002078400774523863, 2e-16",
576 "6e-3, 123456, 0.00013736249820594647, 2e-16",
577 "8e-3, 123456, 1.3636949699766825e-07, 2e-16",
578 "3e-2, 123456, 2.9034013257947777e-97, 2e-16",
579
580
581 "1e-6, 999000, 0.9999972844391667, 2e-16",
582 "1e-5, 999000, 0.9997935546914299, 2e-15",
583 "1e-3, 999000, 0.13551585067528177, 2e-15",
584 "1.5e-3, 999000, 0.011147932231639623, 2e-15",
585 "1e-2, 999000, 1.671698905805492e-87, 2e-15",
586
587
588
589
590
591
592
593
594 "0.8e-5, 500000, 0.9999306695974379, 2e-16",
595 "1.0e-5, 500000, 0.9998933391142139, 4e-16",
596 "1.4e-5, 500000, 0.9997946878340761, 2e-15",
597 })
598 void testOneSFLargeN(double x, int n, double p, double eps) {
599 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n);
600
601 TestUtils.assertProbability(p, p1, eps, "sf");
602 }
603
604
605
606
607
608
609
610
611
612
613
614
615 @ParameterizedTest
616 @CsvSource({
617
618 "0.0390625, 128",
619
620
621 "0.2e-5, 500000",
622
623 "0.4e-5, 500000",
624
625 "0.8e-5, 500000",
626
627 "0.6e-6, 5000000",
628
629 "0.6e-7, 50000000",
630
631
632 "0.6e-8, 500000000",
633 })
634 void testSplitX(double x, int n) {
635 final double[] alpha = {0};
636 final BigDecimal bn = bd(n);
637 for (final double x1 : new double[] {x, Math.nextDown(x), Math.nextUp(x)}) {
638 final int k = KolmogorovSmirnovDistribution.One.splitX(n, x1, alpha);
639 final BigDecimal nx = bn.multiply(bd(x1));
640 int ek = nx.round(new MathContext(16, RoundingMode.FLOOR)).intValue();
641 double ealpha = nx.subtract(bd(ek)).doubleValue();
642 if (ealpha == 1) {
643 ek++;
644 ealpha = 0;
645 }
646 Assertions.assertEquals(ek, k, () -> String.format("n=%d, x=%s : k", n, x1));
647 Assertions.assertEquals(ealpha, alpha[0], () -> String.format("n=%d, x=%s : alpha", n, x1));
648 }
649 }
650
651
652
653
654
655
656
657
658 @ParameterizedTest
659 @CsvFileSource(resources = {"ks.onesample.small.txt"}, delimiter = ' ')
660 void testTwoSmall(double x, int n, double p2, double ignored) {
661 final double p = KolmogorovSmirnovDistribution.Two.sf(x, n);
662
663
664 TestUtils.assertProbability(p2, p, 3e-12, "sf");
665 }
666
667
668
669
670
671
672
673
674 @ParameterizedTest
675 @CsvFileSource(resources = {"ks.onesample.large.txt"}, delimiter = ' ')
676 void testTwoLarge(double x, int n, double p2, double ignored) {
677 final double p = KolmogorovSmirnovDistribution.Two.sf(x, n);
678
679
680
681
682 if (p2 < Double.MIN_NORMAL) {
683 assertSubNormalP(p2, p);
684 } else {
685 TestUtils.assertProbability(p2, p, 3e-14, "sf");
686 }
687 }
688
689
690
691
692
693 @ParameterizedTest
694 @CsvFileSource(resources = {"ks.onesample.small.txt"}, delimiter = ' ')
695 void testOneSmall(double x, int n, double ignored, double p1) {
696 final double p = KolmogorovSmirnovDistribution.One.sf(x, n);
697 TestUtils.assertProbability(p1, p, 5e-15, "sf");
698 }
699
700
701
702
703
704 @ParameterizedTest
705 @CsvFileSource(resources = {"ks.onesample.large.txt"}, delimiter = ' ')
706 void testOneLarge(double x, int n, double ignored, double p1) {
707 final double p = KolmogorovSmirnovDistribution.One.sf(x, n);
708 if (p1 < Double.MIN_NORMAL) {
709 assertSubNormalP(p1, p);
710 } else {
711 TestUtils.assertProbability(p1, p, 5e-15, "sf");
712 }
713 }
714
715
716
717
718
719
720
721 private static void assertSubNormalP(double expected, double actual) {
722
723
724
725
726
727 if (expected > 1e-317) {
728
729 TestUtils.assertProbability(expected, actual, 1e-6, "sf");
730 } else if (expected > 1e-321) {
731
732 TestUtils.assertProbability(expected, actual, 1e-2, "sf");
733 } else {
734
735 Assertions.assertEquals(expected, actual, 1e-317);
736 }
737 }
738
739
740
741
742 @ParameterizedTest
743 @CsvSource({
744
745
746
747
748 "0.0, 1.0, 0",
749 "0.050505050505050504, 1.0, 0",
750 "0.10101010101010101, 1.0, 0",
751 "0.15151515151515152, 1.0, 0",
752 "0.20202020202020202, 0.9999999999990763, 0",
753 "0.25252525252525254, 0.9999999606675511, 0",
754 "0.30303030303030304, 0.9999878979777268, 0",
755 "0.35353535353535354, 0.9996336425939295, 0",
756 "0.40404040404040403, 0.9967594363660204, 0",
757 "0.45454545454545453, 0.9859300613624326, 0",
758 "0.5050505050505051, 0.9606226404614318, 0",
759 "0.5555555555555556, 0.9171285431502623, 0",
760 "0.6060606060606061, 0.8561574422672542, 0",
761 "0.6565656565656566, 0.7817735868286759, 0",
762
763 "0.7070707070707071, 0.6994345460544549, 2e-16",
764 "0.7575757575757576, 0.6144288489362741, 2e-16",
765 "0.8080808080808081, 0.5310526193469937, 3e-16",
766 "0.8585858585858586, 0.45236990476511224, 5e-16",
767 "0.9090909090909091, 0.3803016446753137, 0",
768 "0.9595959595959596, 0.3158476576930847, 3e-16",
769 "1.0101010101010102, 0.2593290139179131, 0",
770 "1.0606060606060606, 0.2105998042008422, 0",
771 "1.1111111111111112, 0.16921324662940662, 0",
772 "1.1616161616161615, 0.13454429534854942, 0",
773 "1.2121212121212122, 0.10587703327541544, 0",
774 "1.2626262626262625, 0.0824656779812758, 0",
775 "1.3131313131313131, 0.06357641976599795, 0",
776 "1.3636363636363635, 0.048515334358901395, 0",
777 "1.4141414141414141, 0.03664600540696065, 3e-16",
778 "1.4646464646464645, 0.027399406580958276, 0",
779 "1.5151515151515151, 0.020277931764730677, 0",
780 "1.5656565656565655, 0.014855068001826222, 0",
781 "1.6161616161616161, 0.010771950333051937, 0",
782 "1.6666666666666667, 0.00773183983221932, 0",
783 "1.7171717171717171, 0.005493387116065559, 0",
784 "1.7676767676767677, 0.00386337109251029, 0",
785 "1.8181818181818181, 0.002689437890602136, 0",
786 "1.8686868686868687, 0.0018532136354893044, 0",
787 "1.9191919191919191, 0.0012640327610063933, 0",
788 "1.9696969696969697, 0.0008534145615397466, 0",
789 "2.0202020202020203, 0.0005703358133877681, 0",
790 "2.0707070707070705, 0.00037728549920393333, 0",
791 "2.121212121212121, 0.00024704636020855906, 0",
792 "2.1717171717171717, 0.0001601237238799859, 0",
793 "2.2222222222222223, 0.00010273106237950482, 3e-16",
794 "2.2727272727272725, 6.524042069332053e-05, 0",
795 "2.323232323232323, 4.101102295378547e-05, 0",
796 "2.3737373737373737, 2.551839333925589e-05, 0",
797 "2.4242424242424243, 1.571719090670116e-05, 0",
798 "2.474747474747475, 9.582203835505282e-06, 0",
799 "2.525252525252525, 5.782621380645785e-06, 0",
800 "2.5757575757575757, 3.4542437941846216e-06, 3e-16",
801 "2.6262626262626263, 2.0424436820343616e-06, 0",
802 "2.676767676767677, 1.1954077585773137e-06, 0",
803 "2.727272727272727, 6.925496689847333e-07, 0",
804 "2.7777777777777777, 3.9715008300241957e-07, 3e-16",
805 "2.8282828282828283, 2.254380738121127e-07, 0",
806 "2.878787878787879, 1.2666853518376852e-07, 0",
807 "2.929292929292929, 7.044969339878468e-08, 0",
808 "2.9797979797979797, 3.8784512970011195e-08, 0",
809 "3.0303030303030303, 2.113520443054742e-08, 0",
810 "3.080808080808081, 1.1400487936839214e-08, 0",
811 "3.131313131313131, 6.087084094473165e-09, 0",
812 "3.1818181818181817, 3.2170961413269956e-09, 0",
813 "3.2323232323232323, 1.6830137095460802e-09, 0",
814 "3.282828282828283, 8.715255897542022e-10, 0",
815 "3.3333333333333335, 4.4672628724063165e-10, 0",
816 "3.3838383838383836, 2.2665836427043994e-10, 0",
817 "3.4343434343434343, 1.1383370398779754e-10, 0",
818 "3.484848484848485, 5.658989138309737e-11, 0",
819 "3.5353535353535355, 2.7846827793907893e-11, 0",
820 "3.5858585858585856, 1.3563803007031639e-11, 0",
821 "3.6363636363636362, 6.539673919045773e-12, 3e-16",
822 "3.686868686868687, 3.121041836580626e-12, 0",
823 "3.7373737373737375, 1.4743885933384083e-12, 0",
824 "3.7878787878787876, 6.894348142441467e-13, 0",
825 "3.8383838383838382, 3.191121453858006e-13, 0",
826 "3.888888888888889, 1.4620503637090137e-13, 0",
827 "3.9393939393939394, 6.630559985567193e-14, 0",
828 "3.9898989898989896, 2.976507350985772e-14, 0",
829 "4.040404040404041, 1.3226123885765453e-14, 0",
830 "4.090909090909091, 5.8173753853130996e-15, 0",
831 "4.141414141414141, 2.532739172792785e-15, 0",
832 "4.191919191919192, 1.0914974407649878e-15, 0",
833 "4.242424242424242, 4.656116646449293e-16, 0",
834 "4.292929292929293, 1.9660468278444803e-16, 0",
835 "4.343434343434343, 8.217368056381308e-17, 0",
836 "4.393939393939394, 3.39969923098359e-17, 0",
837 "4.444444444444445, 1.3922496915677958e-17, 0",
838 "4.494949494949495, 5.643683358858976e-18, 0",
839 "4.545454545454545, 2.264524503951638e-18, 0",
840 "4.595959595959596, 8.994153292453066e-19, 0",
841 "4.646464646464646, 3.536001347144667e-19, 3e-16",
842 "4.696969696969697, 1.376047527079819e-19, 0",
843 "4.747474747474747, 5.300579131166869e-20, 0",
844 "4.797979797979798, 2.0210734007684716e-20, 3e-16",
845 "4.848484848484849, 7.627983170533337e-21, 0",
846 "4.898989898989899, 2.8497465845482713e-21, 0",
847 "4.94949494949495, 1.0538326098079213e-21, 0",
848 "5.0, 3.8574996959278356e-22, 0",
849
850 "0.1754243674345323, 1.0, 0",
851 "0.17542436743453232, 0.9999999999999999, 0",
852
853
854
855 "0.825, 0.5040541803991673, 7e-15",
856 "0.8255555555555555, 0.5031777730824698, 7e-15",
857 "0.826111111111111, 0.5023020394782121, 6e-15",
858 "0.8266666666666667, 0.5014269823143391, 6e-15",
859 "0.8272222222222222, 0.500552604302936, 6e-15",
860 "0.8277777777777777, 0.49967890814026517, 6e-15",
861 "0.8283333333333333, 0.49880589650680446, 6e-15",
862 "0.8288888888888889, 0.49793357206728556, 6e-15",
863 "0.8294444444444444, 0.4970619374707335, 6e-15",
864 "0.83, 0.4961909953505036, 6e-15",
865 })
866 void testKSSum(double x, double p, double eps) {
867 final double p1 = KolmogorovSmirnovDistribution.ksSum(x);
868 TestUtils.assertProbability(p, p1, eps, "sf");
869 }
870
871
872
873
874 @Test
875 void testKSSumHalf() {
876 final double x = 0.8275735551899077;
877 TestUtils.assertProbability(0.5, KolmogorovSmirnovDistribution.ksSum(x), EPS, "sf ~ 0.5");
878 final double u = Math.ulp(x);
879 int i = 5;
880 double p = KolmogorovSmirnovDistribution.ksSum(x + i * u);
881 Assertions.assertTrue(0.5 * (1 - 10 * EPS) < p && p < 0.5, "Expected within 10 ulps below 0.5");
882 for (; i >= -5; i--) {
883 final double pn = KolmogorovSmirnovDistribution.ksSum(x + i * u);
884 Assertions.assertTrue(pn >= p, "Not monototic through sf ~ 0.5");
885 p = pn;
886 }
887 Assertions.assertTrue(0.5 * (1 + 10 * EPS) > p && p > 0.5, "Expected within 10 ulps above 0.5");
888 }
889
890
891
892
893
894
895
896
897
898
899
900
901 @ParameterizedTest
902 @CsvSource({
903
904 "4, 7, false",
905
906
907 })
908 void testSpeed(int minP, int maxP, boolean output) {
909 Assumptions.assumeTrue(minP <= maxP, "Skipping speed test");
910
911
912
913 Assertions.assertTrue(4 <= minP, () -> "Test requires n >= 16: found 2^" + minP);
914 Assertions.assertTrue(maxP <= 30, () -> "Test requires integer n = 2^p: found 2^" + maxP);
915
916 final int limit3 = 1000000;
917 final int limit4 = 100000;
918
919 final long start = System.currentTimeMillis();
920 if (output) {
921
922 final double x = 0.05;
923 final int n = 100;
924 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n, KolmogorovSmirnovDistributionTest::simplePowScaled);
925 final double p2 = KolmogorovSmirnovDistribution.One.sf(x, n, DD::pow);
926 final double p3 = KolmogorovSmirnovDistribution.One.sf(x, n, DDMath::pow);
927 final double p4 = onesf(x, n, DEFAULT_MC);
928 Assertions.assertTrue(p1 < p2 + p3 + p4, "Trivial use of the return values");
929
930 TestUtils.printf("||x||n||p||simplePow||pow||Relative||DDMath||Relative||BigDecimal||Relative||%n");
931 }
932
933 for (int p = minP; p <= maxP; p++) {
934 final int n = 1 << p;
935
936
937 final double rn = Math.sqrt(n);
938 for (final double x : new double[] {5.0 / n, 7.0 / n, 11.0 / n, 1.0 / rn, 2.0 / rn}) {
939 final long t1 = System.nanoTime();
940 final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n, KolmogorovSmirnovDistributionTest::simplePowScaled);
941 final long t2 = System.nanoTime();
942 final double p2 = KolmogorovSmirnovDistribution.One.sf(x, n, DD::pow);
943 final long t3 = System.nanoTime();
944
945 final double p3 = n < limit3 ? KolmogorovSmirnovDistribution.One.sf(x, n, DDMath::pow) : 0;
946 final long t4 = System.nanoTime();
947 final double p4 = n < limit4 ? onesf(x, n, DEFAULT_MC) : 0;
948 final long t5 = System.nanoTime();
949 final double time1 = (t2 - t1) * 1e-9;
950 final double time2 = (t3 - t2) * 1e-9;
951 final double time3 = (t4 - t3) * 1e-9;
952 final double time4 = (t5 - t4) * 1e-9;
953
954
955 TestUtils.assertProbability(p2, p1, 3e-15, () -> String.format("pow vs simplePow: %s %d", x, n));
956 if (n < limit3) {
957 TestUtils.assertProbability(p2, p3, 2e-16, () -> String.format("pow vs DDMath: %s %d", x, n));
958 }
959 if (n < limit4) {
960 TestUtils.assertProbability(p2, p4, 2e-16, () -> String.format("pow vs BigDecimal: %s %d", x, n));
961 }
962 if (output) {
963 TestUtils.printf("|%12.6g|%10d|%25s|%10.6f|%10.6f|%.3f|%10.6f|%.3f|%10.6f|%.3f|%n",
964 x, n, p2,
965 time1,
966 time2, time2 / time1,
967 time3, time3 / time1,
968 time4, time4 / time1);
969 }
970 }
971 }
972 if (output) {
973 TestUtils.printf("Finished in %.3f seconds%n", (System.currentTimeMillis() - start) * 1e-3);
974 }
975 }
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997 private static double onesf(double x, int n, ScaledPower power) {
998
999 if (n * x * x >= 372.5 || x >= 1) {
1000
1001 return 0;
1002 }
1003 if (x <= 0) {
1004
1005 return 1;
1006 }
1007 if (n == 1) {
1008 return x;
1009 }
1010
1011 final long start = System.nanoTime();
1012 final double p = KolmogorovSmirnovDistribution.One.sf(x, n, power);
1013
1014 return p;
1015 }
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032 private static double onesf(double x, int n, MathContext mc) {
1033
1034 if (n * x * x >= 372.5 || x >= 1) {
1035
1036 return 0;
1037 }
1038 if (x <= 0) {
1039
1040 return 1;
1041 }
1042 if (n == 1) {
1043 return x;
1044 }
1045
1046 final long start = System.nanoTime();
1047 final double p = sf(x, n, mc);
1048
1049 return p;
1050 }
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069 private static double sf(double x, int n, MathContext mathContext) {
1070
1071
1072
1073 final BigDecimal bx = bd(x);
1074 final BigDecimal nbx = bx.negate();
1075
1076
1077
1078
1079 final int k = bx.multiply(bd(n)).round(
1080 new MathContext(MathContext.DECIMAL128.getPrecision(), RoundingMode.FLOOR)).intValue();
1081
1082 int maxN;
1083 BigDecimal a;
1084
1085
1086
1087 boolean sd = false;
1088 if (k < n - k - 1) {
1089 sd = k <= 1;
1090 sd |= k <= 3 && n >= 8;
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100 MathContext mc;
1101 if (mathContext != null) {
1102 mc = mathContext;
1103 } else {
1104
1105
1106 final int guardDigits = 5 + (int) Math.ceil(Math.log10(n + 1));
1107 final int precision = sd ? MathContext.DECIMAL128.getPrecision() :
1108 MathContext.DECIMAL64.getPrecision();
1109 mc = new MathContext(precision + guardDigits);
1110 }
1111
1112
1113 final int sumPrecision = mc.getPrecision() + 2;
1114
1115
1116 if (sd) {
1117 maxN = k;
1118
1119 a = BigDecimal.ONE.add(bx).pow(n - 1, mc);
1120 } else {
1121 maxN = n - k - 1;
1122
1123 a = BigDecimal.ONE.subtract(bx).pow(n).divide(bx, mc);
1124 }
1125
1126
1127
1128 BigDecimal c = BigDecimal.ONE;
1129 BigDecimal sum = a;
1130
1131
1132 final int mask = -1;
1133 for (int i = 1; i <= maxN; i++) {
1134
1135
1136 c = c.multiply(bd(n - i + 1)).divide(bd(i), mc);
1137
1138 final int j = sd ? n - i : i;
1139
1140
1141
1142 final BigDecimal p = addDD(div22(j, n, mc), bx, mc);
1143 final BigDecimal q = addDD(div22(n - j, n, mc), nbx, mc);
1144 final BigDecimal s = p.pow(j - 1, mc);
1145 final BigDecimal t = q.pow(n - j, mc);
1146 a = mulDD(mulDD(c, t, mc), s, mc);
1147
1148
1149
1150
1151
1152
1153 if ((a.scale() - sum.scale()) < sumPrecision ||
1154 (sum.precision() - sum.scale()) - (a.precision() - a.scale()) < sumPrecision) {
1155 if ((i & mask) == 0) {
1156 TestUtils.printf("%s + A[%d] %s%n", sum.doubleValue(), j, a.doubleValue());
1157 }
1158 sum = sum.add(a, mc);
1159 } else {
1160
1161 if (mask >= 0) {
1162 TestUtils.printf("%s + A[%d] %s%n", sum.doubleValue(), j, a.doubleValue());
1163 }
1164 break;
1165 }
1166 }
1167
1168
1169
1170 BigDecimal p = bx.multiply(sum);
1171 if (sd) {
1172
1173 p = BigDecimal.ONE.subtract(p);
1174 }
1175
1176 return Math.max(0, Math.min(1, p.doubleValue()));
1177 }
1178
1179
1180
1181
1182
1183
1184
1185 private static BigDecimal bd(double v) {
1186 return new BigDecimal(v);
1187 }
1188
1189
1190
1191
1192
1193
1194
1195 private static BigDecimal bd(int v) {
1196 return BigDecimal.valueOf(v);
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207 private static BigDecimal addDD(BigDecimal a, BigDecimal b, MathContext mc) {
1208 return a.add(b, mc);
1209 }
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219 private static BigDecimal mulDD(BigDecimal a, BigDecimal b, MathContext mc) {
1220 return a.multiply(b, mc);
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 private static BigDecimal divDD(BigDecimal a, BigDecimal b, MathContext mc) {
1232 return a.divide(b, mc);
1233 }
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243 private static BigDecimal div22(int a, int b, MathContext mc) {
1244 return divDD(bd(a), bd(b), mc);
1245 }
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261 private static DD simplePowScaled(DD x, int n, long[] exp) {
1262 return org.apache.commons.numbers.core.DDExt.simplePowScaled(x.hi(), x.lo(), n, exp);
1263 }
1264 }