View Javadoc
1   /*
2    * Licensed to the Hipparchus project 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 Hipparchus project 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    *      https://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.hipparchus.stat.descriptive.rank;
18  
19  import org.hipparchus.UnitTestUtils;
20  import org.hipparchus.distribution.RealDistribution;
21  import org.hipparchus.distribution.continuous.ExponentialDistribution;
22  import org.hipparchus.distribution.continuous.GammaDistribution;
23  import org.hipparchus.distribution.continuous.LogNormalDistribution;
24  import org.hipparchus.distribution.continuous.NormalDistribution;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.random.MersenneTwister;
27  import org.hipparchus.random.RandomDataGenerator;
28  import org.hipparchus.random.RandomGenerator;
29  import org.hipparchus.random.Well19937c;
30  import org.hipparchus.stat.StatUtils;
31  import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
32  import org.hipparchus.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
33  import org.hipparchus.stat.inference.AlternativeHypothesis;
34  import org.hipparchus.stat.inference.BinomialTest;
35  import org.hipparchus.util.FastMath;
36  import org.junit.jupiter.api.Test;
37  
38  import java.lang.reflect.Constructor;
39  import java.lang.reflect.Method;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.List;
43  import java.util.SplittableRandom;
44  
45  import static org.junit.jupiter.api.Assertions.assertEquals;
46  import static org.junit.jupiter.api.Assertions.assertFalse;
47  import static org.junit.jupiter.api.Assertions.assertThrows;
48  import static org.junit.jupiter.api.Assertions.assertTrue;
49  
50  /**
51   * Test cases for the {@link RandomPercentileTest} class.
52   * Some tests are adapted from PSquarePercentileTest.
53   */
54  public class RandomPercentileTest extends
55          StorelessUnivariateStatisticAbstractTest {
56  
57      protected double tolerance = 10E-12;
58  
59      private final RandomGenerator randomGenerator = new Well19937c(1000);
60  
61      @Override
62      public RandomPercentile getUnivariateStatistic() {
63          return new RandomPercentile();  // Median with default epsilon and PRNG
64      }
65  
66      @Override
67      public double expectedValue() {
68          return this.median;
69      }
70  
71      /**
72       * Verifies that copied statistics remain equal to originals when
73       * incremented the same way by making the copy after a majority of elements
74       * are incremented
75       */
76      @Test
77      void testCopyConsistencyWithInitialMostElements() {
78  
79          StorelessUnivariateStatistic master = getUnivariateStatistic();
80          StorelessUnivariateStatistic replica = null;
81  
82          // select a portion of testArray till 75 % of the length to load first
83          long index = FastMath.round(0.75 * testArray.length);
84  
85          // Put first half in master and copy master to replica
86          master.incrementAll(testArray, 0, (int) index);
87          replica = master.copy();
88  
89          // Check same
90          assertEquals(replica, master);
91          assertEquals(master, replica);
92  
93          // Now add second part to both and check again
94          master.incrementAll(testArray, (int) index, (int) (testArray.length - index));
95          replica.incrementAll(testArray, (int) index, (int) (testArray.length - index));
96          assertEquals(replica, master);
97          assertEquals(master, replica);
98      }
99  
100     /**
101      * Verifies that copied statistics remain equal to originals when
102      * incremented the same way by way of copying original after just a few
103      * elements are incremented
104      */
105     @Test
106     void testCopyConsistencyWithInitialFirstFewElements() {
107 
108         StorelessUnivariateStatistic master = getUnivariateStatistic();
109         StorelessUnivariateStatistic replica = null;
110 
111         // select a portion of testArray which is 10% of the length to load
112         // first
113         long index = FastMath.round(0.1 * testArray.length);
114 
115         // Put first half in master and copy master to replica
116         master.incrementAll(testArray, 0, (int) index);
117         replica = master.copy();
118 
119         // Check same
120         assertEquals(replica, master);
121         assertEquals(master, replica);
122         // Now add second part to both and check again
123         master.incrementAll(testArray, (int) index, (int) (testArray.length - index));
124         replica.incrementAll(testArray, (int) index, (int) (testArray.length - index));
125         assertEquals(master, master);
126         assertEquals(replica, replica);
127         assertEquals(replica, master);
128         assertEquals(master, replica);
129     }
130 
131     @Test
132     void testPercentileSmallSample() {
133         double[] d = new double[] { 1, 3, 2, 4 };
134         final RandomPercentile randomPercentile = new RandomPercentile();
135         randomPercentile.incrementAll(d);
136         Percentile p = new Percentile(30d);
137         assertEquals(p.evaluate(d), randomPercentile.getResult(30d), 1.0e-5);
138         p = new Percentile(25);
139         assertEquals(p.evaluate(d), randomPercentile.getResult(25d), 1.0e-5);
140         p = new Percentile(75);
141         assertEquals(p.evaluate(d),randomPercentile.getResult(75d), 1.0e-5);
142         p = new Percentile(50);
143         assertEquals(p.evaluate(d),randomPercentile.getResult(50d), 1.0e-5);
144     }
145 
146     @Test
147     void testNonPositiveEpsilon() {
148         assertThrows(MathIllegalArgumentException.class, () -> {
149             double[] d =
150                 new double[]{95.1772, 95.1567, 95.1937, 95.1959, 95.1442,
151                     95.0610, 95.1591, 95.1195, 95.1772, 95.0925, 95.1990,
152                     95.1682};
153             RandomPercentile p = new RandomPercentile(0);
154             p.evaluate(d, 0, d.length);
155         });
156     }
157 
158     @Test
159     void testNISTExample() {
160         double[] d =
161                 new double[] { 95.1772, 95.1567, 95.1937, 95.1959, 95.1442,
162                         95.0610, 95.1591, 95.1195, 95.1772, 95.0925, 95.1990,
163                         95.1682 };
164         assertEquals(95.1981, new RandomPercentile().evaluate(90d, d), 1.0e-4);
165         assertEquals(95.061, new RandomPercentile().evaluate(0d, d), 0);
166         assertEquals(95.1990, new RandomPercentile().evaluate(100d, d, 0, d.length), 0);
167     }
168 
169     @Test
170     void test5() {
171         RandomPercentile percentile = new RandomPercentile();
172         assertEquals(this.percentile5, percentile.evaluate(5, testArray), 0.0001);
173     }
174 
175     @Test
176     void testSingleton() {
177         RandomPercentile percentile = new RandomPercentile();
178         double[] singletonArray = new double[] { 1d };
179         assertEquals(1d, percentile.evaluate(singletonArray), 0);
180         assertEquals(1d, percentile.evaluate(singletonArray, 0, 1), 0);
181         percentile = new RandomPercentile();
182         assertEquals(1d, percentile.evaluate(5, singletonArray, 0, 1), 0);
183         percentile = new RandomPercentile();
184         assertEquals(1d, percentile.evaluate(100, singletonArray, 0, 1), 0);
185         percentile = new RandomPercentile();
186         assertTrue(Double.isNaN(percentile.evaluate(100, singletonArray, 0, 0)));
187     }
188 
189     @Test
190     void testSpecialValues() {
191         RandomPercentile percentile = new RandomPercentile();
192         double[] specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN };
193         assertEquals(2d, percentile.evaluate(specialValues), 0);
194         specialValues =
195             new double[] { Double.NEGATIVE_INFINITY, 1d, 2d, 3d, Double.NaN, Double.POSITIVE_INFINITY };
196         assertEquals(2d, percentile.evaluate(specialValues), 0);
197         specialValues =
198             new double[] { 1d, 1d, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
199         assertTrue(Double.isInfinite(percentile.evaluate(specialValues)));
200         specialValues = new double[] { 1d, 1d, Double.NaN, Double.NaN };
201         assertFalse(Double.isNaN(percentile.evaluate(specialValues)));
202         specialValues =
203             new double[] { 1d, 1d, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
204         percentile = new RandomPercentile();
205         assertTrue(Double.isNaN(percentile.evaluate(specialValues)));
206     }
207 
208     @Test
209     void testAggregateSmallSamplesA() {
210         doTestAggregateSmallSamples(0.5, 11.0);
211     }
212 
213     @Test
214     void testAggregateSmallSamplesB() {
215         doTestAggregateSmallSamples(0.1, 7.0);
216     }
217 
218     @Test
219     void testAggregateSmallSamplesC() {
220         doTestAggregateSmallSamples(0.01, 7.0);
221     }
222 
223     private void doTestAggregateSmallSamples(double epsilon, double expected) {
224         SplittableRandom seeds = new SplittableRandom(0x218560e08c8df220l);
225 
226         RandomPercentile rp1   = new RandomPercentile(epsilon, new Well19937c(seeds.nextLong()));
227         double[]         data1 = new double[] { 3, 5, -2, 7, 14, 6 };
228         for (double d : data1 ) {
229             rp1.accept(d);
230         }
231         RandomPercentile rp2   = new RandomPercentile(epsilon, new Well19937c(seeds.nextLong()));
232         double[]         data2 = new double[] { 9, 12, 15 };
233         for (double d : data2 ) {
234             rp2.accept(d);
235         }
236 
237         rp1.aggregate(rp2);
238 
239         assertEquals(expected, rp1.getResult(), 1.0e-10);
240 
241     }
242 
243     @Test
244     void testBufferConsumeLevel0() throws Exception {
245         final int len = testArray.length;
246         final double[] sorted = Arrays.copyOf(testArray, len);
247         Arrays.sort(sorted);
248         BufferMock buffer = new BufferMock(len, 0, randomGenerator);
249         for (int i = 0; i < len; i++) {
250             buffer.consume(testArray[i]);
251         }
252         UnitTestUtils.customAssertEquals(sorted, buffer.getData(), Double.MIN_VALUE);
253         assertFalse(buffer.hasCapacity());
254         assertEquals(StatUtils.min(testArray),buffer.min(), Double.MIN_VALUE);
255         assertEquals(StatUtils.max(testArray),buffer.max(), Double.MIN_VALUE);
256     }
257 
258     @Test
259     void testBufferSampling() throws Exception {
260         // Capacity = 10, level = 2 - should take 1 out of each block of 4 values from the stream
261         BufferMock buffer = new BufferMock(10, 2, randomGenerator);
262         for (int i = 0; i < 40; i++) {
263             buffer.consume(i);
264         }
265         // Buffer should be full, including one value from each consecutive sequence of 4
266         assertFalse(buffer.hasCapacity());
267         final double[] data = buffer.getData();
268         for (int i = 0; i < 10; i++) {
269            assertTrue(data[i] < 4 * (i + 1));
270            assertTrue(data[i] >= 4 * i);
271            // Rank of 4n should be n for n = 1, ..., 10.
272            assertEquals(i + 1, buffer.rankOf((i + 1) * 4));
273            // Check boundary ranks
274            assertEquals(0, buffer.rankOf(-1));
275            assertEquals(10, buffer.rankOf(100));
276         }
277     }
278 
279     @Test
280     void testBufferMergeWith() throws Exception {
281         // Create 2 level 0 buffers of size 20
282         BufferMock buffer1 = new BufferMock(20, 0, randomGenerator);
283         BufferMock buffer2 = new BufferMock(20, 0, randomGenerator);
284 
285         // fill buffer1 with 0, ..., 19 and buffer2 with 20, ..., 39
286         for (int i = 0; i < 20; i++) {
287             buffer1.consume(i);
288             buffer2.consume(i + 20);
289         }
290         assertEquals(0, buffer1.getLevel());
291         assertEquals(0, buffer2.getLevel());
292 
293         // Merge 1 with 2
294         buffer1.mergeWith(buffer2.getInstance()); // Need to pass the real thing for buffer2
295 
296         // Both should now have level 1, buffer1 should be full, buffer2 should be free
297         assertEquals(1,buffer1.getLevel());
298         assertFalse(buffer1.hasCapacity());
299         assertEquals(1,buffer2.getLevel());
300         assertTrue(buffer2.hasCapacity());
301 
302         // Check the contents of buffer1 - should be the merged contents of both
303         final double[] data = buffer1.getData();
304         int nSmall = 0;
305         for (double value : data) {
306             assertTrue(value >=0 && value < 40);
307             if (value < 20) {
308                 nSmall++;
309             }
310         }
311 
312         // Verify merge selection was close to fair
313         BinomialTest bTest = new BinomialTest();
314         assertFalse(bTest.binomialTest(20, nSmall, 0.5, AlternativeHypothesis.TWO_SIDED, 0.01));
315 
316         // Check sort on merged buffer
317         buffer1.checkSorted();
318     }
319 
320     @Test
321     void testBufferMergeInto() throws Exception {
322         BufferMock buffer1 = new BufferMock(20, 0, randomGenerator);
323         BufferMock buffer2 = new BufferMock(20, 2, randomGenerator);
324 
325         // fill buffer1 with 0, ..., 19
326         for (int i = 0; i < 20; i++) {
327             buffer1.consume(i);
328         }
329 
330         // fill buffer 2 - level 2 means it will take 80 values to fill it
331         for (int i = 0; i < 80; i++) {
332             buffer2.consume(i + 20);
333         }
334         assertFalse(buffer1.hasCapacity());
335         assertFalse(buffer2.hasCapacity());
336         buffer1.mergeInto(buffer2.getInstance());
337 
338         // levels should be unchanged
339         assertEquals(0, buffer1.getLevel());
340         assertEquals(2, buffer2.getLevel());
341 
342         // buffer2 should have about 1/4 values under 20
343         final double[] data = buffer2.getData();
344         int nSmall = 0;
345         for (double value : data) {
346             assertTrue(value >=0 && value < 100);
347             if (value < 20) {
348                 nSmall++;
349             }
350         }
351         BinomialTest bTest = new BinomialTest();
352         assertFalse(bTest.binomialTest(20, nSmall, 0.25, AlternativeHypothesis.TWO_SIDED, 0.01));
353 
354         // Check sort on merged buffer
355         buffer2.checkSorted();
356     }
357 
358     static class BufferMock {
359         private Object instance;
360         private Method consumeMethod;
361         private Method getDataMethod;
362         private Method hasCapacityMethod;
363         private Method minMethod;
364         private Method maxMethod;
365         private Method rankOfMethod;
366         private Method getLevelMethod;
367         private Method mergeWithMethod;
368         private Method mergeIntoMethod;
369         @SuppressWarnings({ "unchecked", "rawtypes" })
370         public BufferMock(int size, int level, RandomGenerator randomGenerator) throws Exception {
371             final Class[] classes = RandomPercentile.class.getDeclaredClasses();
372             for (Class cls : classes) {
373                 if (cls.getName().endsWith("$Buffer")) {
374                    Constructor constructor = cls.getDeclaredConstructor(Integer.TYPE, Integer.TYPE, RandomGenerator.class);
375                    instance = constructor.newInstance(size, level, randomGenerator);
376                    consumeMethod = cls.getDeclaredMethod("consume", Double.TYPE);
377                    getDataMethod = cls.getDeclaredMethod("getData");
378                    hasCapacityMethod = cls.getDeclaredMethod("hasCapacity");
379                    minMethod = cls.getDeclaredMethod("min");
380                    maxMethod = cls.getDeclaredMethod("max");
381                    rankOfMethod = cls.getDeclaredMethod("rankOf", Double.TYPE);
382                    getLevelMethod = cls.getDeclaredMethod("getLevel");
383                    mergeWithMethod = cls.getDeclaredMethod("mergeWith", cls);
384                    mergeIntoMethod = cls.getDeclaredMethod("mergeInto", cls);
385                 }
386             }
387         }
388 
389         public void consume(double value) throws Exception {
390             consumeMethod.invoke(instance, value);
391         }
392 
393         public boolean hasCapacity() throws Exception {
394             return (boolean) hasCapacityMethod.invoke(instance);
395         }
396 
397         public double[] getData() throws Exception {
398             return (double[]) getDataMethod.invoke(instance);
399         }
400 
401         public double min() throws Exception {
402             return (double) minMethod.invoke(instance);
403         }
404 
405         public double max() throws Exception {
406             return (double) maxMethod.invoke(instance);
407         }
408 
409         public int rankOf(double value) throws Exception {
410             return (int) rankOfMethod.invoke(instance, value);
411         }
412 
413         public int getLevel() throws Exception {
414             return (int) getLevelMethod.invoke(instance);
415         }
416 
417         public void mergeWith(Object other) throws Exception {
418             mergeWithMethod.invoke(instance, other);
419         }
420 
421         public void mergeInto(Object other) throws Exception {
422             mergeIntoMethod.invoke(instance, other);
423         }
424 
425         public Object getInstance() {
426             return instance;
427         }
428 
429         public void checkSorted() throws Exception {
430             final double[] data = getData();
431             final double[] copy = Arrays.copyOf(data, data.length);
432             Arrays.sort(copy);
433             UnitTestUtils.customAssertEquals(data, copy, Double.MIN_VALUE);
434         }
435 
436     }
437 
438     @Test
439     void testArrayExample() {
440         assertEquals(this.percentile95, new RandomPercentile().evaluate(95d,testArray), getTolerance());
441     }
442 
443     @Test
444     void testReduceSmallDataSet() {
445         final RandomDataGenerator random = new RandomDataGenerator(1000);
446         final long n = 1000;
447         double[] combined = new double[10000];
448         int i = 0;
449         final List<RandomPercentile> aggregates = new ArrayList<RandomPercentile>();
450         for (int j = 0; j < 10; j++) {
451             final RandomPercentile randomPercentile = new RandomPercentile();
452             for (int k = 0; k < n; k++) {
453                 final double value = random.nextGaussian();
454                 randomPercentile.accept(value);
455                 combined[i++] = value;
456             }
457             aggregates.add(randomPercentile);
458         }
459         // Check some quantiles
460         final Percentile master = new Percentile();
461         final RandomPercentile randomMaster = new RandomPercentile();
462         for (int l = 0; l < 5; l++) {
463             final double percentile = l * 15 + 1;
464             assertEquals(master.evaluate(combined, percentile),
465                     randomMaster.reduce(percentile, aggregates), Double.MIN_VALUE);
466         }
467     }
468 
469     @Test
470     void testReduceLargeDataSet() {
471         final RandomDataGenerator random = new RandomDataGenerator(1000);
472         final long n = 1000000;
473         final RandomGenerator randomGenerator = new RandomDataGenerator(1000);
474         final RandomPercentile randomMaster = new RandomPercentile(randomGenerator);
475         final PSquarePercentile pSquare = new PSquarePercentile(1);
476         final List<RandomPercentile> aggregates = new ArrayList<RandomPercentile>();
477         for (int j = 0; j < 5; j++) {
478             final RandomPercentile randomPercentile = new RandomPercentile(randomGenerator);
479             for (int k = 0; k < n; k++) {
480                 final double value = random.nextGaussian();
481                 randomPercentile.accept(value);
482                 randomMaster.accept(value);
483                 pSquare.increment(value);
484             }
485             aggregates.add(randomPercentile);
486         }
487         // Check some quantiles
488         for (int l = 0; l < 5; l++) {
489             final double percentile = l * 13 + 1;
490             assertEquals(randomMaster.getResult(percentile),
491                     randomMaster.reduce(percentile, aggregates), 5E-3, "percentile = " + percentile);
492         }
493     }
494 
495     private Double[] randomTestData(int factor, int values) {
496         Double[] test = new Double[values];
497         for (int i = 0; i < test.length; i++) {
498             test[i] = Math.abs(randomGenerator.nextDouble() * factor);
499         }
500         return test;
501     }
502 
503     // @Test
504     public void testAccept() {
505         final RandomPercentile randomPercentile = new RandomPercentile();
506         assertTrue(Double.isNaN(randomPercentile.getResult()));
507         Double[] test = randomTestData(100, 10000);
508 
509         for (Double value : test) {
510             randomPercentile.increment(value);
511             assertTrue(randomPercentile.getResult() >= 0);
512         }
513     }
514 
515     private void customAssertValues(Double a, Double b, double delta) {
516         if (Double.isNaN(a)) {
517             assertTrue(Double.isNaN(a), "" + b + " is not NaN.");
518         } else if (Double.isInfinite(a)) {
519             assertEquals(a, b);
520         } else {
521             double max = FastMath.max(a, b);
522             double percentage = FastMath.abs(a - b) / max;
523             double deviation = delta;
524             assertTrue(percentage < deviation, String.format("Deviated = %f and is beyond %f as a=%f,  b=%f",
525                                      percentage, deviation, a, b));
526         }
527     }
528 
529     /**
530      * Checks to make sure that the actual quantile position (normalized rank) of value
531      * is within tolerance of quantile
532      *
533      * @param data data array
534      * @param value value to test
535      * @param quantile purported quantile
536      * @param tolerance max difference between actual quantile of value and quantile
537      */
538     private void checkQuantileError(double[] data, double value, double quantile, double tolerance,
539                                     double referenceValue) {
540          final double n = (double) data.length;
541          int nLess = 0;
542          for (double val : data) {
543              if (val < value) {
544                  nLess++;
545              }
546          }
547          if (Double.isNaN(referenceValue)) {
548              assertTrue(Double.isNaN(value));
549          } else if (Double.isInfinite(value)) {
550              assertEquals(value, referenceValue);
551          } else {
552              assertTrue(FastMath.abs(quantile - (double) nLess / n) < tolerance,
553                         "Quantile error exceeded: value returned = " + value +
554                         " Reference value = " + referenceValue +
555                         " quantile = " + quantile + " n = " + n +
556                         " error = " + (quantile - (double) nLess / n));
557          }
558     }
559 
560 
561     private void doCalculatePercentile(Double percentile, Number[] test) {
562         doCalculatePercentile(percentile, test, Double.MAX_VALUE);
563     }
564 
565 
566     private void doCalculatePercentile(Double percentile, Number[] test, double delta) {
567         RandomPercentile random = new RandomPercentile(new Well19937c(200));
568         for (Number value : test) {
569             random.increment(value.doubleValue());
570         }
571 
572         Percentile p2 = new Percentile(percentile * 100);
573 
574         double[] dall = new double[test.length];
575         for (int i = 0; i < test.length; i++) {
576             dall[i] = test[i].doubleValue();
577         }
578 
579         Double referenceValue = p2.evaluate(dall);
580         customAssertValues(random.getResult(percentile), referenceValue, delta);
581     }
582 
583     private void doCalculatePercentile(double percentile, double[] test, double delta) {
584         RandomPercentile randomEstimated = new RandomPercentile(new Well19937c(200));
585         for (double value : test) {
586             randomEstimated.increment(value);
587         }
588 
589         Percentile p2 = new Percentile(percentile < 1 ? percentile * 100 : percentile);
590         /*
591          * double[] dall = new double[test.length]; for (int i = 0; i <
592          * test.length; i++) dall[i] = test[i];
593          */
594         Double referenceValue = p2.evaluate(test);
595         if (test.length < LARGE) {
596             customAssertValues(randomEstimated.getResult(percentile), referenceValue, delta);
597         } else {
598             checkQuantileError(test,randomEstimated.getResult(percentile), percentile / 100, delta, referenceValue);
599         }
600     }
601 
602     @Test
603     void testCannedDataSet() {
604         Integer[] seedInput =
605                 new Integer[] { 283, 285, 298, 304, 310, 31, 319, 32, 33, 339,
606                         342, 348, 350, 354, 354, 357, 36, 36, 369, 37, 37, 375,
607                         378, 383, 390, 396, 405, 408, 41, 414, 419, 416, 42,
608                         420, 430, 430, 432, 444, 447, 447, 449, 45, 451, 456,
609                         468, 470, 471, 474, 600, 695, 70, 83, 97, 109, 113, 128 };
610         Integer[] input = new Integer[seedInput.length * 100];
611         for (int i = 0; i < input.length; i++) {
612             input[i] = seedInput[i % seedInput.length] + i;
613         }
614         doCalculatePercentile(0.50d, input);
615         doCalculatePercentile(0.95d, input);
616     }
617 
618     @Test
619     void test99Percentile() {
620         Double[] test = randomTestData(100, 10000);
621         doCalculatePercentile(0.99d, test);
622     }
623 
624     @Test
625     void test90Percentile() {
626         Double[] test = randomTestData(100, 10000);
627         doCalculatePercentile(0.90d, test);
628     }
629 
630     @Test
631     void test20Percentile() {
632         Double[] test = randomTestData(100, 100000);
633         doCalculatePercentile(0.20d, test);
634     }
635 
636     @Test
637     void test5Percentile() {
638         Double[] test = randomTestData(50, 990000);
639         doCalculatePercentile(0.50d, test);
640     }
641 
642     @Test
643     void test99PercentileHighValues() {
644         Double[] test = randomTestData(100000, 10000);
645         doCalculatePercentile(0.99d, test);
646     }
647 
648     @Test
649     void test90PercentileHighValues() {
650         Double[] test = randomTestData(100000, 100000);
651         doCalculatePercentile(0.90d, test);
652     }
653 
654     @Test
655     void test20PercentileHighValues() {
656         Double[] test = randomTestData(100000, 100000);
657         doCalculatePercentile(0.20d, test);
658     }
659 
660     @Test
661     void test5PercentileHighValues() {
662         Double[] test = randomTestData(100000, 100000);
663         doCalculatePercentile(0.05d, test);
664     }
665 
666     @Test
667     void test0PercentileValuesWithFewerThan5Values() {
668         double[] test = { 1d, 2d, 3d, 4d };
669         RandomPercentile p = new RandomPercentile();
670         assertEquals(1d, p.evaluate(0d,test), 0);
671     }
672 
673     @Test
674     void testMaxValuesRetained() {
675         assertEquals(546795, RandomPercentile.maxValuesRetained(RandomPercentile.DEFAULT_EPSILON));
676         assertEquals(34727, RandomPercentile.maxValuesRetained(1E-3));
677         assertEquals(2064, RandomPercentile.maxValuesRetained(1E-2));
678     }
679 
680     @Test
681     void testMaxValuesRetained0Epsilon() {
682         assertThrows(MathIllegalArgumentException.class, () -> {
683             RandomPercentile.maxValuesRetained(0);
684         });
685     }
686 
687     @Test
688     void testMaxValuesRetained1Epsilon() {
689         assertThrows(MathIllegalArgumentException.class, () -> {
690             RandomPercentile.maxValuesRetained(1);
691         });
692     }
693 
694 
695     final int TINY = 10, SMALL = 50, NOMINAL = 100, MEDIUM = 500,
696               STANDARD = 1000, BIG = 10000, VERY_BIG = 50000, LARGE = 1000000,
697               VERY_LARGE = 10000000;
698 
699     private void doDistributionTest(RealDistribution distribution) {
700         double[] data;
701 
702         final RandomDataGenerator randomDataGenerator = new RandomDataGenerator(100);
703         data = randomDataGenerator.nextDeviates(distribution, LARGE);
704         doCalculatePercentile(50, data, 0.0005);
705         doCalculatePercentile(95, data, 0.0005);
706 
707         data = randomDataGenerator.nextDeviates(distribution, VERY_BIG);
708         doCalculatePercentile(50, data, 0.0001);
709         doCalculatePercentile(95, data, 0.0001);
710 
711         data = randomDataGenerator.nextDeviates(distribution, BIG);
712         doCalculatePercentile(50, data, 0.0001);
713         doCalculatePercentile(95, data, 0.0001);
714 
715         data = randomDataGenerator.nextDeviates(distribution, STANDARD);
716         doCalculatePercentile(50, data, 0.0001);
717         doCalculatePercentile(95, data, 0.0001);
718 
719         data = randomDataGenerator.nextDeviates(distribution, MEDIUM);
720         doCalculatePercentile(50, data, 0.0001);
721         doCalculatePercentile(95, data, 0.0001);
722 
723         data = randomDataGenerator.nextDeviates(distribution, NOMINAL);
724         doCalculatePercentile(50, data, 0.0001);
725         doCalculatePercentile(95, data, 0.0001);
726 
727         data = randomDataGenerator.nextDeviates(distribution, SMALL);
728         doCalculatePercentile(50, data, 0.0001);
729         doCalculatePercentile(95, data, 0.0001);
730 
731         data = randomDataGenerator.nextDeviates(distribution, TINY);
732         doCalculatePercentile(50, data, 0.0001);
733         doCalculatePercentile(95, data, 0.0001);
734     }
735 
736     /**
737      * Test Various Dist
738      */
739     @Test
740     void testDistribution() {
741         doDistributionTest(new NormalDistribution(4000, 50));
742         doDistributionTest(new LogNormalDistribution(4000, 50));
743         doDistributionTest(new ExponentialDistribution(4000));
744         doDistributionTest(new GammaDistribution(5d,1d));
745     }
746 
747     /**
748      * Large sample streaming tests.
749      */
750     @Test
751     void testDistributionStreaming() {
752         checkQuartiles(new NormalDistribution(), 1000000, 5E-4);
753         checkQuartiles(new ExponentialDistribution(1), 600000, 5E-4);
754         checkQuartiles(new GammaDistribution(4d,2d), 600000, 5E-4);
755     }
756 
757     /**
758      * Verify no sequential bias even when buffer size is small.
759      */
760     @Test
761     void testSequentialData() {
762         final long seed = 1000;
763         double epsilon = 1e-4;
764         for (int j = 0; j < 3; j++) {
765             epsilon *= 10;
766             final RandomPercentile randomPercentile = new RandomPercentile(epsilon,
767                     new MersenneTwister(seed));
768             final int n = 5000000;
769             for (int i = 1; i <= n; i++) {
770                 randomPercentile.accept(i);
771             }
772             for (int i = 1; i < 5; i++) {
773                 assertEquals(0.2 * i, randomPercentile.getResult(i * 20) / n, 2 * epsilon);
774             }
775         }
776     }
777 
778     /**
779      * Verify the quantile-accuracy contract using a large sample from dist.
780      *
781      * @param dist distribution to generate sample data from
782      * @param sampleSize size of the generated sample
783      * @param tolerance normalized rank error tolerance (epsilon in contract)
784      */
785     private void checkQuartiles(RealDistribution dist, int sampleSize, double tolerance) {
786         final long seed = 1000;
787         RandomDataGenerator randomDataGenerator = RandomDataGenerator.of(new MersenneTwister(seed));
788         final RandomPercentile randomPercentile = new RandomPercentile(RandomPercentile.DEFAULT_EPSILON,
789                 randomGenerator);
790         for (int i = 0; i < sampleSize; i++) {
791             randomPercentile.increment(randomDataGenerator.nextDeviate(dist));
792         }
793         final double q1 = randomPercentile.getResult(25);
794         final double q2 = randomPercentile.getResult();
795         final double q3 = randomPercentile.getResult(75);
796 
797         // Respin the sample and (brutally) count to compute actual ranks
798         randomDataGenerator.setSeed(seed);
799         double v;
800         double ct1 = 0;
801         double ct2 = 0;
802         double ct3 = 0;
803         for (int i = 0; i < sampleSize; i++) {
804             v = randomDataGenerator.nextDeviate(dist);
805             if (v < q1) {
806                 ct1++;
807                 ct2++;
808                 ct3++;
809             } else if (v < q2) {
810                 ct2++;
811                 ct3++;
812             } else if (v < q3) {
813                 ct3++;
814             }
815         }
816         assertEquals(0.25, ct1/sampleSize, tolerance);
817         assertEquals(0.5, ct2/sampleSize, tolerance);
818         assertEquals(0.75, ct3/sampleSize, tolerance);
819     }
820 }