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  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   * Test cases for {@link KolmogorovSmirnovDistribution}.
34   */
35  class KolmogorovSmirnovDistributionTest {
36      /** Minimum relative epsilon between double values. */
37      private static final double EPS = Math.ulp(1.0);
38      /** Proxy to trigger the default power function for the double-double One.sf computation. */
39      private static final ScaledPower DEFAULT_POWER = null;
40      /** Proxy to trigger the default MathContext for the BigDecimal One.sf computation. */
41      private static final MathContext DEFAULT_MC = null;
42  
43      // Unless otherwise stated the test data is from scipy (1.9.3):
44      // from scipy.stats import kstwo, ksone
45      // import numpy as np
46      // np.set_printoptions(precision=20)
47      // kstwo.sf(x, n)
48      // ksone.sf(x, n)
49      //
50      // Can be timed in seconds using e.g:
51      // from time import time
52      // t1 = time(); ksone.sf(1e-2, 1000000); time() - t1
53      // 1.3686652207740602e-87
54      // 1.1550960540771484
55      // Here the scipy implementation takes approximately 1 second at the n used
56      // for the asymptotic limit.
57  
58      /**
59       * Test cases of the two-sided survival function where there is an exact representation.
60       */
61      @ParameterizedTest
62      @CsvSource({
63          // Lower limit
64          "1, 10, 0",
65          "1, 100, 0",
66          "1.23, 10, 0", // invalid x
67          // nxx >= 370
68          "0.5, 1480, 0",
69          "0.1, 37000, 0",
70          "1e-3, 370000000, 0",
71          // Upper limit
72          "0, 10, 1",
73          "0, 100, 1",
74          "-1, 10, 1", // invalid x
75          // n = 1
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          // x <= 1/(2n)
81          "0.05, 10, 1",
82          "0.025, 20, 1",
83          "0.0125, 40, 1",
84          // 1/(2n) < x <= 1/n
85          "0.075, 10, 0.999999645625",
86          "0.045, 20, 0.9999999997324996",
87          "0.025, 40, 0.9999999999999999",
88          // 1 - 1/n <= x < 1
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      * Test cases of the two-sided survival function where it uses the Durbin MTW algorithm.
101      * This has been verified to invoke the correct function by throwing an exception from
102      * within the method.
103      */
104     @ParameterizedTest
105     @CsvSource({
106         // n <= 140. Expected precision is 10-digits
107         // 1/n < n*x < 1 - 1/n; n*x*x < 0.754693
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         // 140 < n <= 100000 && n * Math.pow(x, 1.5) < 1.4. Expected precision is 5 digits.
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      * Test the computation of the A factors for the Pomeranz algorithm is correct for the 3 cases.
137      * The parameters have been chosen so that the use of floor(A - t) and ceil(A - t)
138      * does not suffer rounding error. This verifies the implementation, which avoids
139      * using floor/ceil, is correct.
140      */
141     @ParameterizedTest
142     @CsvSource({
143         // t = n*x; f = t - floor(t)
144 
145         // f = 0
146         "0.125, 8",
147 
148         // 0 < f <= 1/2
149         "0.15625, 8",
150         "0.1875, 8",
151 
152         // 1/2 < f
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         // Create A
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         // 3-cases (see Simard and L’Ecuyer (2011))
171         if (f > 0.5) {
172             // Case (iii): 1/2 < f < 1
173             // for i = 1, 2, ...
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             // Case (ii): 0 < f <= 1/2
182             // for i = 1, 2, ...
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             // Case (i): f = 0
191             // for i = 1, 2, ...
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         // Check all floor/ceil elements for A[i-1] for i in [2, 2n+2]
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      * Test cases of the two-sided survival function where it uses the Pomeranz algorithm.
211      * This has been verified to invoke the correct function by throwing an exception from
212      * within the method.
213      *
214      * <p>Note: Here Simard and L’Ecuyer expect 10 digits of precision so
215      * eps less than 1e-10 is within the expected range.
216      */
217     @ParameterizedTest
218     @CsvSource({
219         // Note: f = n*x - floor(n*x). 3 cases are {f = 0, 0 < f <= 0.5, 0.5 < f}.
220         // 1/n < n*x < 1 - 1/n; 0.754693 < n*x*x < 4
221         // Values of x for n=10 create f of {0.75, 0, 0.25, 0, 0.5, 0.320}
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         // Values of x for n=100 create f of {0.690, 0, 0.5, 0.050, 0.90}
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         // Values of x for n=140 create f of {0.2899, 0.599, 0.40, 0, 0.660}
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         // Test ported from o.a.c.math where the Pelz-Good algorithm computed the CDF.
249         // Note: These values are not really appropriate as the Pelz-Good method
250         // is not used when n*x*x > 2.2. Reference values have been updated using
251         // scipy.stats.kstwo.cdf to verify the method is valid for this range.
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      * Test cases of the two-sided survival function where it uses the Pelz-Good algorithm.
275      * This has been verified to invoke the correct function by throwing an exception from
276      * within the method.
277      */
278     @ParameterizedTest
279     @CsvSource({
280         // n * x^2 < 2.2
281         // 140 < n <= 100000 && n * Math.pow(x, 1.5) >= 1.4. Expected precision is 5 digits.
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         // 100000 < n
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         // Threshold CDF ~ min_normal
300         // z = x * sqrt(n) ~ 0.0417  [ sqrt(-pi^2 / 8 / ln(2^-1023)) ]
301         "0.0001077, 150000, 1, 0",
302         "0.0001078, 150000, 1, 0",
303         // Threshold SF ~ 1
304         // z = x * sqrt(n) ~ 0.18325  [ sqrt(-pi^2 / 8 / ln(2^-53)) ]
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      * Test cases of the two-sided survival function where it uses the Miller approximation.
316      * This has been verified to invoke the correct function by throwing an exception from
317      * within the method.
318      */
319     @ParameterizedTest
320     @CsvSource({
321         // n <= 140. Expected precision is 10-digits
322         // n*x*x > 4
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         // n > 140 && n*x*x > 2.2
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      * Test cases of the one-sided survival function where there is an exact representation.
357      */
358     @ParameterizedTest
359     @CsvSource({
360         // Lower limit
361         "1, 10, 0",
362         "1, 100, 0",
363         "1.23, 10, 0", // invalid x
364         // 2 * nxx >= 745
365         "0.5, 1490, 0",
366         "0.1, 37250, 0",
367         "1e-3, 372500000, 0",
368         // Upper limit
369         "0, 10, 1",
370         "0, 100, 1",
371         "-1, 10, 1", // invalid x
372         // n = 1
373         "0.012345, 1, 0.012345",
374         "0.025, 1, 0.025",
375         "0.99999999999, 1, 0.99999999999",
376         // x <= 1/n
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         // 1 - 1/n <= x < 1
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         // The double-double computation should be correct when x does not approach sub-normal
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      * Test cases of the asymptotic approximation to the one-sided survival function.
406      * Cases must use {@code 1/n < x < 1 - 1/n} to avoid the exact computation
407      * and a large n to trigger the asymptotic computation.
408      *
409      * <p>Notes:
410      * <ul>
411      * <li>At the switch point the asymptotic approximation agrees to ~ 6 digits
412      * until p is much smaller than a realistic alpha for significance testing.
413      * <li>Use {@link DDMath#pow(DD, int, long[])} is with 1 ulp of {@link DD#pow(int, long[])}
414      * and ~ 2-3x slower. Thus the power function is not changing the precision of the result.
415      * </ul>
416      */
417     @ParameterizedTest
418     @CsvSource({
419         // n = 10^6 + 1 (threshold to switch to the asymptotic
420         // Computed using: numpy.exp(-pow(6.0*n*x+1.0, 2)/(18.0*n)):
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", // Requires 6.0*n*x not 6.0*(n*x)
430         "7e-3, 1000001, 2.7357189636541477e-43, 2e-16",
431         "1e-2, 1000001, 1.374426245809477e-87, 2e-16",
432         // Computed using the double-double implementation (DD.pow, with timings)
433         "1.0E-4, 1000001, 0.9801333136251243, 6e-8", // 0.724783s
434         "0.001, 1000001, 0.13524481927494492, 6e-8", // 0.709156s
435         "0.0015, 1000001, 0.011097829274951359, 2e-6", // 0.699217s
436         "0.00175, 1000001, 0.0021849210146805925, 3e-6", // 0.696271s
437         "0.002, 1000001, 3.3501117508103243E-4, 6e-6", // 0.679680s
438         "0.0025, 1000001, 3.720346483702557E-6, 2e-5", // 0.658114s
439         "0.003, 1000001, 1.519879017846374E-8, 4e-5", // 0.640645s
440         "0.0035, 1000001, 2.2842024589556203E-11, 7e-5", // 0.629275s
441         "0.004, 1000001, 1.2628687945778359E-14, 2e-4", // 0.613186s
442         "0.007, 1000001, 2.7328605923747603E-43, 2e-3", // 0.534130s
443         "0.01, 1000001, 1.3683915090193192E-87, 5e-3", // 0.487097s
444         // Computed using the double-double implementation (DDMath.pow, with timings)
445         "1.0E-4, 1000001, 0.9801333136251243, 6e-8", // 2.119189s
446         "0.001, 1000001, 0.13524481927494492, 6e-8", // 1.933861s
447         "0.0015, 1000001, 0.011097829274951359, 2e-6", // 1.914388s
448         "0.00175, 1000001, 0.0021849210146805925, 3e-6", // 1.876002s
449         "0.002, 1000001, 3.350111750810325E-4, 6e-6", // 1.811108s
450         "0.0025, 1000001, 3.720346483702557E-6, 2e-5", // 1.781407s
451         "0.003, 1000001, 1.519879017846374E-8, 4e-5", // 1.739311s
452         "0.0035, 1000001, 2.2842024589556203E-11, 7e-5", // 1.697893s
453         "0.004, 1000001, 1.2628687945778359E-14, 2e-4", // 1.659005s
454         "0.007, 1000001, 2.7328605923747603E-43, 2e-3", // 1.452124s
455         "0.01, 1000001, 1.3683915090193192E-87, 5e-3", // 1.338291s
456         // n = 10^7
457         // Computed using: numpy.exp(-pow(6.0*n*x+1.0, 2)/(18.0*n)):
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         // Computed using the double-double implementation (DD.pow, with timings)
464         "1.0E-6, 10000000, 0.9999793335473409, 1e-8", // 8.119499s
465         "2.0E-6, 10000000, 0.9999186699759279, 1e-8", // 8.073493s
466         "2.0E-4, 10000000, 0.4492690623747514, 2e-8", // 8.014497s
467         "8.0E-4, 10000000, 2.7592963140010787E-6, 2e-6", // 7.610442s
468         "0.001, 10000000, 2.059771738419037E-9, 5e-6", // 7.383041s
469         // Computed using the double-double implementation (DDMath.pow, with timings)
470         "1.0E-6, 10000000, 0.9999793335473409, 1e-8", // 22.379404s
471         "2.0E-6, 10000000, 0.9999186699759279, 1e-8", // 22.142838s
472         "2.0E-4, 10000000, 0.4492690623747514, 2e-8", // 22.130956s
473         "8.0E-4, 10000000, 2.7592963140010787E-6, 2e-6", // 20.772157s
474         "0.001, 10000000, 2.059771738419037E-9, 5e-6", // 20.374969s
475         // n = 2^30
476         // Using scipy ksone.sf found an intermediate overflow bug in v1.9.3:
477         // https://github.com/scipy/scipy/issues/17775
478         // Computed using: numpy.exp(-pow(6.0*n*x+1.0, 2)/(18.0*n)):
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         // Computed using the double-double implementation (DD.pow, with timings)
486         "1.0E-6, 1073741824, 0.9978541553094261, 6e-11", // 1067.231777s
487         "1.0E-5, 1073741824, 0.806739041685089, 1e-10", // 1066.338235s
488         "1.0E-4, 1073741824, 4.715937547939542E-10, 5e-8", // 974.019864s
489         "2.0E-4, 1073741824, 4.946862487288558E-38, 8e-7", // 841.211851s
490         "3.0E-4, 1073741824, 1.1542094676281237E-84, 4e-6", // 761.320554s
491         "5.0E-4, 1073741824, 6.914611021963405E-234, 3e-5", // 681.080892s
492     })
493     void testOneSFAsymptotic(double x, int n, double p, double eps) {
494         final double p1 = KolmogorovSmirnovDistribution.One.sf(x, n);
495         // Use to obtain double-double result with timing
496         //final double p1 = onesf(x, n, DEFAULT_POWER);
497         //final double p1 = onesf(x, n, DDMath::pow);
498         //final double p1 = onesf(x, n, DEFAULT_MC);
499         TestUtils.assertProbability(p, p1, eps, "sf");
500     }
501 
502     /**
503      * Test cases of the one-sided survival function with small n.
504      * Test the BigDecimal implementation and the double-double implementation
505      * with their default configuration. Test the double-double implementation
506      * with each available power function. This test verifies the implementations
507      * all agree with the scipy reference.
508      */
509     @ParameterizedTest
510     @CsvSource({
511         // Values from scipy ksone.sf
512         // 1/n < x < n - 1/n; small n
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         // BigDecimal computation is < 0.1 seconds
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         // BigDecimal computation is < 0.3 seconds
538         "2e-2, 5000, 0.01806981293722266, 2e-16",
539         // BigDecimal computation is < 0.6 seconds
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         // For small N we can use either double-double power function.
548         final double p3 = onesf(x, n, DD::pow);
549         final double p4 = onesf(x, n, DDMath::pow);
550         // Check at least one computation matched the default
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         // simplePow also works
555         final double p5 = onesf(x, n, KolmogorovSmirnovDistributionTest::simplePowScaled);
556         TestUtils.assertProbability(p, p5, eps, "sf simplePow");
557     }
558 
559     /**
560      * Test cases of the one-sided survival function with large n.
561      * Test the double-double implementation with the default configuration.
562      */
563     @ParameterizedTest
564     @CsvSource({
565         // Values from scipy ksone.sf
566         // 1/n < x < n - 1/n; large n; n < 1e6
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         // Close to the asymptotic limit (1e6).
580         // Timings for the DD.pow.
581         "1e-6, 999000, 0.9999972844391667, 2e-16", // 0.000114s
582         "1e-5, 999000, 0.9997935546914299, 2e-15", // 0.701445s
583         "1e-3, 999000, 0.13551585067528177, 2e-15", // 0.684693s
584         "1.5e-3, 999000, 0.011147932231639623, 2e-15", // 0.680678s
585         "1e-2, 999000, 1.671698905805492e-87, 2e-15", // 0.485230s
586         // Test cases requiring careful computation of
587         // k = floor(n*x), alpha = nx - k; x = (k+alpha)/n with 0 <= alpha < 1
588         // 0.8e-5 * 5e5 = 4.0, actually 3.99999999999999897195950...
589         // 1.0e-5 * 5e5 = 5.0, actually 4.99999999999999956198232...
590         // 1.4e-5 * 5e5 = 7.0, actually 6.99999999999999989499501...
591         // First two cases use Smirnov-Dwass.
592         // Final case uses regular computation where this implementation
593         // has a more accurate sum and the scipy p-value is 11 ULP lower.
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         //final double p1 = onesf(x, n, DEFAULT_POWER);
601         TestUtils.assertProbability(p, p1, eps, "sf");
602     }
603 
604     /**
605      * Test cases of splitting x into {@code x = (k + alpha) / n},
606      * where {@code k} is integer and {@code alpha} is in [0, 1).
607      *
608      * <p>Some cases (*) required to hit execution code paths
609      * use {@code n} above the threshold for the asymptotic
610      * approximation, or {@code n*x <= 1}. In practice the
611      * code path where {@code alpha = (1.0, -eps)} *may* not occur
612      * with the configured One.sf computation but is included for
613      * completeness.
614      */
615     @ParameterizedTest
616     @CsvSource({
617         // 5.0/n * n = 5.0 (exact)
618         "0.0390625, 128",
619         // 0.2e-5 * 5e5 = 1.0, actually 0.99999999999999995474811...
620         // (*) Creates k=0 alpha=(1.0,-4.525188817411374E-17)
621         "0.2e-5, 500000",
622         // 0.4e-5 * 5e5 = 2.0, actually 1.99999999999999990949622...
623         "0.4e-5, 500000",
624         // 0.8e-5 * 5e5 = 4.0, actually 3.99999999999999981899244...
625         "0.8e-5, 500000",
626         // 0.6e-6 * 5e6 = 3.0, actually 2.99999999999999986424433...
627         "0.6e-6, 5000000",
628         // 0.6e-7 * 5e7 = 3.0, actually 2.99999999999999973189543...
629         "0.6e-7, 50000000",
630         // 0.6e-8 * 5e8 = 3.0, actually 2.99999999999999998004962...
631         // (*) Creates k=2 alpha=(1.0,-1.995037876491735E-17)
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      * Test the two-sided one-sample distribution for small n. Resource files are used to
653      * include a range of values uniformly for x in the interval [0, 1].
654      *
655      * <p>Note: Executing this test alone will hit all branches of the
656      * {@link KolmogorovSmirnovDistribution.Two#sf(double, int)} with {@code n <= 140};
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         // Uses n <= 140 where there are ~ 10 digits of precision.
663         // Passes at 3e-12
664         TestUtils.assertProbability(p2, p, 3e-12, "sf");
665     }
666 
667     /**
668      * Test the two-sided one-sample distribution for large n. Resource files are used to
669      * include a range of values uniformly for x in the interval [0, 1].
670      *
671      * <p>Note: Executing this test alone will hit all branches of the
672      * {@link KolmogorovSmirnovDistribution.Two#sf(double, int)} with {@code n > 140};
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         // Uses n > 140 where there are ~ 6 digits of precision.
679         // Passes at 3e-14 unless p is sub-normal.
680         // Note: The two-sided distribution uses the one-sided distribution when
681         // the p-value is small.
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      * Test the one-sided one-sample distribution for small n. Resource files are used to
691      * include a range of values uniformly for x in the interval [0, 1].
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      * Test the one-sided one-sample distribution for large n. Resource files are used to
702      * include a range of values uniformly for x in the interval [0, 1].
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      * Assert the sub-normal p-values are approximately the same.
717      *
718      * @param expected Expected p-value
719      * @param actual Actual p-value
720      */
721     private static void assertSubNormalP(double expected, double actual) {
722         // Note: The SciPy implementation of ksone does not use a scaled
723         // sum and thus when term Aj is zero the algorithm stops. This can make the ksone
724         // sf result lower than the correct result and it cannot accurately sum sub-normal
725         // numbers.
726         // Test the values are approximately the same
727         if (expected > 1e-317) {
728             // Close
729             TestUtils.assertProbability(expected, actual, 1e-6, "sf");
730         } else if (expected > 1e-321) {
731             // Quite close
732             TestUtils.assertProbability(expected, actual, 1e-2, "sf");
733         } else {
734             // Very small numbers have issues. Just check p1 is also very small.
735             Assertions.assertEquals(expected, actual, 1e-317);
736         }
737     }
738 
739     /**
740      * Test cases of the limiting form for the distribution of Kolmogorov's D_n.
741      */
742     @ParameterizedTest
743     @CsvSource({
744         // Values from scipy 1.9.3
745         // from scipy.stats import kstwobign
746         // import numpy as np
747         // kstwobign.sf(np.linspace(0, 5, 100))
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         // p approaches 0.5
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         // Cases for switchover points
850         "0.1754243674345323, 1.0, 0",
851         "0.17542436743453232, 0.9999999999999999, 0",
852         // Here scipy disagrees a bit more.
853         // It may use a different number of iterations for convergence.
854         // A test for a monototic function is in testKSSumHalf.
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      * Test the KS sum is monototic at the p ~ 0.5 crossover.
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      * Simple speed test to provide an approximate run-time for the computation for different
892      * {@code n}. Uses {@code 1/n < x < n - 1/n} where exact computations do not exist.
893      * When x > 1/sqrt(n) terms peak in magnitude around (n-nx)/2.
894      * Worst case is when Smirnov-Dwass does not apply and x is small.
895      * This uses powers of two and the speed approximately halves at each increasing n.
896      * The asymptotic limit is at 1e7 (approximately 2^23.25).
897      *
898      * <p>Note: This is not a substitute for a JMH benchmark but does provide useful
899      * information. Disable by setting {@code maxP < minP}.
900      */
901     @ParameterizedTest
902     @CsvSource({
903         // Exercises all the methods and checks they compute the same result (takes 0.2 seconds)
904         "4, 7, false",
905         // For useful information on speed (takes 5+ minutes to run)
906         //"7, 24, true",
907     })
908     void testSpeed(int minP, int maxP, boolean output) {
909         Assumptions.assumeTrue(minP <= maxP, "Skipping speed test");
910         // At small n the Smirnov-Dwass option is ignored.
911         // Since we directly call the computation keep x within [0, 1]
912         // by limiting n to 16 (thus 11/n is always valid).
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         // Limit for the slow computations to avoid long run-time
916         final int limit3 = 1000000;
917         final int limit4 = 100000;
918 
919         final long start = System.currentTimeMillis();
920         if (output) {
921             // Do a trivial warm-up and use the p-values
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             // Add a header to allow to be pasted into a Jira ticket as a table
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             // Small x where Smirnov-Dwass does not apply (n * x > 4)
936             // n * x * x < 372.5 or else p = 0
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                 // Avoid really long computation
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                 // Check (the pow computation is the reference).
954                 // This limit supports the entire test range for p [4, 24].
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      * Calculates complementary probability {@code P[D_n^+ >= x]}, or survival
979      * function (SF), for the one-sided one-sample Kolmogorov-Smirnov distribution.
980      *
981      * <p>Computes the result using double-double arithmetic. The power function
982      * can use a fast approximation or a full power computation.
983      *
984      * <p>This function is safe for {@code x > 1/n}. When {@code x} approaches
985      * sub-normal then division or multiplication by x can under/overflow.
986      *
987      * <p>This method is here to allow the result of the computation to be tested
988      * without the use of the exact SF when {@code x < 1/n} or {@code x > 1 - 1/n}.
989      *
990      * @param x Statistic (typically in (1/n, 1 - 1/n)).
991      * @param n Sample size (assumed to be positive).
992      * @param power Function to compute the scaled power (can be null).
993      * @return \(P(D_n^+ &ge; x)\)
994      * @see DD#pow(int, long[])
995      * @see DDMath#pow(int, long[])
996      */
997     private static double onesf(double x, int n, ScaledPower power) {
998         // Guard input to allow all test cases.
999         if (n * x * x >= 372.5 || x >= 1) {
1000             // p would underflow, or x is out of the domain
1001             return 0;
1002         }
1003         if (x <= 0) {
1004             // edge-of, or out-of, the domain
1005             return 1;
1006         }
1007         if (n == 1) {
1008             return x;
1009         }
1010         // Debugging
1011         final long start = System.nanoTime();
1012         final double p = KolmogorovSmirnovDistribution.One.sf(x, n, power);
1013         //TestUtils.printf("\"%s, %d, %s\", // %.6fs%n", x, n, p, (System.nanoTime() - start) * 1e-9);
1014         return p;
1015     }
1016 
1017     /**
1018      * Calculates complementary probability {@code P[D_n^+ >= x]}, or survival
1019      * function (SF), for the one-sided one-sample Kolmogorov-Smirnov distribution.
1020      *
1021      * <p>Computes the result using BigDecimal to the precision specified by the
1022      * {@link MathContext}.
1023      *
1024      * <p>This method is here to allow the result of the computation to be output for
1025      * spot test cases including the time of the computation.
1026      *
1027      * @param x Statistic.
1028      * @param n Sample size (assumed to be positive).
1029      * @param mathContext MathContext for precision (can be null).
1030      * @return \(P(D_n^+ &ge; x)\)
1031      */
1032     private static double onesf(double x, int n, MathContext mc) {
1033         // Guard input to allow all test cases.
1034         if (n * x * x >= 372.5 || x >= 1) {
1035             // p would underflow, or x is out of the domain
1036             return 0;
1037         }
1038         if (x <= 0) {
1039             // edge-of, or out-of, the domain
1040             return 1;
1041         }
1042         if (n == 1) {
1043             return x;
1044         }
1045         // Debugging
1046         final long start = System.nanoTime();
1047         final double p = sf(x, n, mc);
1048         //TestUtils.printf("\"%s, %d, %s\", // %.6fs%n", x, n, p, (System.nanoTime() - start) * 1e-9);
1049         return p;
1050     }
1051 
1052     /**
1053      * Calculates complementary probability {@code P[D_n^+ >= x]}, or survival
1054      * function (SF), for the one-sided one-sample Kolmogorov-Smirnov distribution.
1055      *
1056      * <p>Computes the result using BigDecimal to the precision specified by the
1057      * {@link MathContext}. This is safe for {@code n < 1e9} which is the limit of BigDecimal
1058      * power computations.
1059      *
1060      * <p>This implementation was developed first closely following the description
1061      * in van Mulbregt (2018). It has been moved here for reference to allow testing
1062      * the against the double-double implementation. At large n it is prohibitively slow.
1063      *
1064      * @param x Statistic.
1065      * @param n Sample size (assumed to be positive).
1066      * @param mathContext MathContext for precision (can be null).
1067      * @return \(P(D_n^+ &ge; x)\)
1068      */
1069     private static double sf(double x, int n, MathContext mathContext) {
1070         // Compute only the SF using Algorithm 1 pp 12.
1071         // This omits computation of D for the PDF.
1072 
1073         final BigDecimal bx = bd(x);
1074         final BigDecimal nbx = bx.negate();
1075 
1076         // Compute x = (k + alpha) / n ; 0 <= alpha < 1
1077         // Round-off issues with alpha -> 1 are ignored as a zero term for Aj
1078         // will be computed as zero. Here we only require floor(nx).
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         // Eq (13) Smirnov/Birnbaum-Tingey; or Smirnov/Dwass Eq (31)
1086         // Use the same decision criteria as the double-double implementation.
1087         boolean sd = false;
1088         if (k < n - k - 1) {
1089             sd = k <= 1;
1090             sd |= k <= 3 && n >= 8;
1091         }
1092 
1093         // Configure working precision
1094         // When using BigDecimal add and multiply are correctly rounded.
1095         // Pow is accurate to 2 ulp of the precision.
1096         // For SD the terms may cancel so we use a precision at least 2x of a double.
1097         // For the regular computation we can use 1x a double and maintain guard digits.
1098         // Since more precision is required for SD only choose it when the number
1099         // of terms is 1/4 of the regular computation
1100         MathContext mc;
1101         if (mathContext != null) {
1102             mc = mathContext;
1103         } else {
1104             // Add some guard digits.
1105             // Use more for larger n (log10(n+1) is the number of digits).
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         // Use to determine if term Aj will change the sum. Include guard digits.
1113         final int sumPrecision = mc.getPrecision() + 2;
1114 
1115         // Compute A0
1116         if (sd) {
1117             maxN = k;
1118             // A0 = (1+x)^(n-1)
1119             a = BigDecimal.ONE.add(bx).pow(n - 1, mc);
1120         } else {
1121             maxN = n - k - 1;
1122             // A0 = (1-x)^n / x
1123             a = BigDecimal.ONE.subtract(bx).pow(n).divide(bx, mc);
1124         }
1125 
1126         // Binomial coefficient c(n, j)
1127         // This value is integral but maintained to limited precision
1128         BigDecimal c = BigDecimal.ONE;
1129         BigDecimal sum = a;
1130         // Use this to print out progress (which may be very slow when N is large).
1131         // Report at < 1/128 intervals (or every i if n < 128). Set to -1 to disable.
1132         final int mask = -1; // (Integer.highestOneBit(maxN) - 1) >>> 7;
1133         for (int i = 1; i <= maxN; i++) {
1134             // c(n, j) = c(n, j-1) * (n-j+1) / j
1135             // Exact multiply then round on divide
1136             c = c.multiply(bd(n - i + 1)).divide(bd(i), mc);
1137             // Compute Aj
1138             final int j = sd ? n - i : i;
1139             // Algorithm 4 pp. 27
1140             // S = ((j/n) + x)^(j-1)
1141             // T = ((n-j)/n - x)^(n-j)
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             // Only add to the sum when the value would change the sum.
1148             // Approximate size of the BigDecimal is (precision - scale).
1149             // scale is a property but precision must be computed and
1150             // should be the same as the working precision.
1151             // So neglect the precision for a first check.
1152             // Allow print debugging
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                 // Effectively Aj -> eps * sum, and most of the computation is done.
1161                 if (mask >= 0) {
1162                     TestUtils.printf("%s + A[%d] %s%n", sum.doubleValue(), j, a.doubleValue());
1163                 }
1164                 break;
1165             }
1166         }
1167 
1168         // This ignores the working precision
1169         // p = sum(Aj) * x
1170         BigDecimal p = bx.multiply(sum);
1171         if (sd) {
1172             // SF = 1 - CDF
1173             p = BigDecimal.ONE.subtract(p);
1174         }
1175         // Clip to [0, 1]
1176         return Math.max(0, Math.min(1, p.doubleValue()));
1177     }
1178 
1179     /**
1180      * Create a BigDecimal for the given value.
1181      *
1182      * @param v Value
1183      * @return the BigDecimal
1184      */
1185     private static BigDecimal bd(double v) {
1186         return new BigDecimal(v);
1187     }
1188 
1189     /**
1190      * Create a BigDecimal for the given value.
1191      *
1192      * @param v Value
1193      * @return the BigDecimal
1194      */
1195     private static BigDecimal bd(int v) {
1196         return BigDecimal.valueOf(v);
1197     }
1198 
1199     /**
1200      * Returns an approximation of the number {@code a+b}.
1201      *
1202      * @param a Addend.
1203      * @param b Addend.
1204      * @param mc Math context (for rounding)
1205      * @return {@code a+b}
1206      */
1207     private static BigDecimal addDD(BigDecimal a, BigDecimal b, MathContext mc) {
1208         return a.add(b, mc);
1209     }
1210 
1211     /**
1212      * Returns an approximation of the number {@code a*b}.
1213      *
1214      * @param a Factor.
1215      * @param b Factor.
1216      * @param mc Math context (for rounding)
1217      * @return {@code a*b}
1218      */
1219     private static BigDecimal mulDD(BigDecimal a, BigDecimal b, MathContext mc) {
1220         return a.multiply(b, mc);
1221     }
1222 
1223     /**
1224      * Returns an approximation of the real number {@code a/b}.
1225      *
1226      * @param a Dividend.
1227      * @param b Divisor.
1228      * @param mc Math context (for rounding)
1229      * @return {@code a/b}
1230      */
1231     private static BigDecimal divDD(BigDecimal a, BigDecimal b, MathContext mc) {
1232         return a.divide(b, mc);
1233     }
1234 
1235     /**
1236      * Returns an approximation of the real number {@code a/b}.
1237      *
1238      * @param a Dividend.
1239      * @param b Divisor.
1240      * @param mc Math context (for rounding)
1241      * @return {@code a/b}
1242      */
1243     private static BigDecimal div22(int a, int b, MathContext mc) {
1244         return divDD(bd(a), bd(b), mc);
1245     }
1246 
1247     /**
1248      * Compute the number {@code x} raised to the power {@code n}.
1249      *
1250      * <p>Note: This is a wrapper around the simple power function from Commons Numbers
1251      * to implement the correct method signature. The method originated in this project
1252      * and was moved to Commons Numbers when the DD class was moved. The class exists
1253      * only for testing in the test-jar artifact of Numbers core.
1254      *
1255      * @param x High part of x.
1256      * @param xx Low part of x.
1257      * @param n Power.
1258      * @param exp Power of two scale factor (integral exponent).
1259      * @return Fraction part.
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 }