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