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.math.random;
18  
19  import junit.framework.Test;
20  import junit.framework.TestSuite;
21  import java.security.NoSuchProviderException;
22  import java.security.NoSuchAlgorithmException;
23  import java.util.HashSet;
24  
25  import org.apache.commons.math.RetryTestCase;
26  import org.apache.commons.math.stat.Frequency;
27  import org.apache.commons.math.stat.inference.ChiSquareTestImpl;
28  import org.apache.commons.math.stat.descriptive.SummaryStatistics;
29  
30  /**
31   * Test cases for the RandomData class.
32   *
33   * @version $Revision: 610789 $ $Date: 2008-01-10 06:46:49 -0700 (Thu, 10 Jan 2008) $
34   */
35  
36  public class RandomDataTest extends RetryTestCase {
37  
38      public RandomDataTest(String name) {
39          super(name);
40          randomData = new RandomDataImpl();
41      }
42  
43      protected long smallSampleSize = 1000;
44      protected double[] expected = {250,250,250,250};
45      protected int largeSampleSize = 10000;
46      private String[] hex = 
47          {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; 
48      protected RandomDataImpl randomData = null; 
49      protected ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
50      
51      public void setUp() { 
52      }
53  
54      public static Test suite() {
55          TestSuite suite = new TestSuite(RandomDataTest.class);
56          suite.setName("RandomData Tests");
57          return suite;
58      }
59  
60      public void testNextIntExtremeValues() {
61          int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
62          int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
63          assertFalse(x == y);
64      }
65  
66      public void testNextLongExtremeValues() {
67          long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
68          long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
69          assertFalse(x == y);
70      }
71      
72      /** test dispersion and failure modes for nextInt() */
73      public void testNextInt() {
74          try {
75              randomData.nextInt(4,3);
76              fail("IllegalArgumentException expected");
77          } catch (IllegalArgumentException ex) {
78              ;
79          }
80          Frequency freq = new Frequency();
81          int value = 0;
82          for (int i=0;i<smallSampleSize;i++) {
83              value = randomData.nextInt(0,3);
84              assertTrue("nextInt range",(value >= 0) && (value <= 3));
85              freq.addValue(value);  
86          }
87          long[] observed = new long[4];
88          for (int i=0; i<4; i++) {
89              observed[i] = freq.getCount(i);
90          } 
91          
92          /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
93           * Change to 11.34 for alpha = .01
94           */
95          assertTrue("chi-square test -- will fail about 1 in 1000 times",
96              testStatistic.chiSquare(expected,observed) < 16.27);    
97      }
98      
99      /** test dispersion and failure modes for nextLong() */
100     public void testNextLong() {
101        try {
102             randomData.nextLong(4,3);
103             fail("IllegalArgumentException expected");
104         } catch (IllegalArgumentException ex) {
105             ;
106         }
107        Frequency freq = new Frequency();
108        long value = 0;
109         for (int i=0;i<smallSampleSize;i++) {
110             value = randomData.nextLong(0,3);
111             assertTrue("nextInt range",(value >= 0) && (value <= 3));
112             freq.addValue(value);  
113         }
114         long[] observed = new long[4];
115         for (int i=0; i<4; i++) {
116             observed[i] = freq.getCount(i);
117         } 
118         
119         /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
120          * Change to 11.34 for alpha = .01
121          */
122         assertTrue("chi-square test -- will fail about 1 in 1000 times",
123             testStatistic.chiSquare(expected,observed) < 16.27);    
124     }
125     
126     /** test dispersion and failure modes for nextSecureLong() */
127     public void testNextSecureLong() {
128         try {
129             randomData.nextSecureLong(4,3);
130             fail("IllegalArgumentException expected");
131         } catch (IllegalArgumentException ex) {
132             ;
133         }
134         Frequency freq = new Frequency();
135         long value = 0;
136         for (int i=0;i<smallSampleSize;i++) {
137             value = randomData.nextSecureLong(0,3);
138             assertTrue("nextInt range",(value >= 0) && (value <= 3));
139             freq.addValue(value);  
140         }
141         long[] observed = new long[4];
142         for (int i=0; i<4; i++) {
143             observed[i] = freq.getCount(i);
144         } 
145         
146         /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
147          * Change to 11.34 for alpha = .01
148          */
149         assertTrue("chi-square test -- will fail about 1 in 1000 times",
150             testStatistic.chiSquare(expected,observed) < 16.27);    
151     }
152     
153     /** test dispersion and failure modes for nextSecureInt() */
154     public void testNextSecureInt() {
155         try {
156             randomData.nextSecureInt(4,3);
157             fail("IllegalArgumentException expected");
158         } catch (IllegalArgumentException ex) {
159             ;
160         }
161         Frequency freq = new Frequency();
162         int value = 0;
163         for (int i=0;i<smallSampleSize;i++) {
164             value = randomData.nextSecureInt(0,3);
165             assertTrue("nextInt range",(value >= 0) && (value <= 3));
166             freq.addValue(value);  
167         }
168         long[] observed = new long[4];
169         for (int i=0; i<4; i++) {
170             observed[i] = freq.getCount(i);
171         } 
172         
173         /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
174          * Change to 11.34 for alpha = .01
175          */
176         assertTrue("chi-square test -- will fail about 1 in 1000 times",
177             testStatistic.chiSquare(expected,observed) < 16.27);    
178     }
179     
180     /** 
181      * Make sure that empirical distribution of random Poisson(4)'s 
182      * has P(X <= 5) close to actual cumulative Poisson probablity
183      * and that nextPoisson fails when mean is non-positive
184      * TODO: replace with statistical test, adding test stat to TestStatistic
185      */
186     public void testNextPoisson() {
187         try {
188             randomData.nextPoisson(0);
189             fail("zero mean -- expecting IllegalArgumentException");
190         } catch (IllegalArgumentException ex) {
191             ;
192         }
193         Frequency f = new Frequency();
194         for (int i = 0; i<largeSampleSize; i++) {
195             try {
196                 f.addValue(randomData.nextPoisson(4.0d));
197             } catch (Exception ex) {
198                 fail(ex.getMessage());
199             }
200         }
201         long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2) + 
202                         f.getCount(3) + f.getCount(4) + f.getCount(5);
203         long sumFreq = f.getSumFreq();
204         double cumPct = 
205             new Double(cumFreq).doubleValue()/new Double(sumFreq).doubleValue();
206         assertEquals("cum Poisson(4)",cumPct,0.7851,0.2);
207         try {
208             randomData.nextPoisson(-1);
209             fail("negative mean supplied -- IllegalArgumentException expected");
210         } catch (IllegalArgumentException ex) {
211             ;
212         }
213         try {
214             randomData.nextPoisson(0);
215             fail("0 mean supplied -- IllegalArgumentException expected");
216         } catch (IllegalArgumentException ex) {
217             ;
218         }
219         
220     }
221     
222     /** test dispersion and failute modes for nextHex() */
223     public void testNextHex() {
224         try {
225             randomData.nextHexString(-1);
226             fail("negative length supplied -- IllegalArgumentException expected");
227         } catch (IllegalArgumentException ex) {
228             ;
229         }
230         try {
231             randomData.nextHexString(0);
232             fail("zero length supplied -- IllegalArgumentException expected");
233         } catch (IllegalArgumentException ex) {
234             ;
235         }
236         String hexString = randomData.nextHexString(3);
237         if (hexString.length() != 3) {
238                 fail("incorrect length for generated string");
239         }
240         hexString = randomData.nextHexString(1);
241         if (hexString.length() != 1) {
242                 fail("incorrect length for generated string");
243         }
244         try {
245             hexString = randomData.nextHexString(0);
246             fail("zero length requested -- expecting IllegalArgumentException");
247         } catch (IllegalArgumentException ex) {
248             ;
249         }
250         if (hexString.length() != 1) {
251                 fail("incorrect length for generated string");
252         }      
253         Frequency f = new Frequency();
254         for (int i = 0; i < smallSampleSize; i++) {
255             hexString = randomData.nextHexString(100);
256             if (hexString.length() != 100) {
257                 fail("incorrect length for generated string");
258             }
259             for (int j = 0; j < hexString.length(); j++) {
260                 f.addValue(hexString.substring(j,j+1));
261             }
262         }
263         double[] expected = new double[16];
264         long[] observed = new long[16];
265         for (int i = 0; i < 16; i++) {
266             expected[i] = (double)smallSampleSize*100/(double)16;
267             observed[i] = f.getCount(hex[i]);
268         }
269         /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
270          * Change to 30.58 for alpha = .01
271          */
272         assertTrue("chi-square test -- will fail about 1 in 1000 times",
273             testStatistic.chiSquare(expected,observed) < 37.70);    
274     }
275     
276     /** test dispersion and failute modes for nextHex() */
277     public void testNextSecureHex() {
278         try {
279             randomData.nextSecureHexString(-1);
280             fail("negative length -- IllegalArgumentException expected");
281         } catch (IllegalArgumentException ex) {
282             ;
283         }
284         try {
285             randomData.nextSecureHexString(0);
286             fail("zero length -- IllegalArgumentException expected");
287         } catch (IllegalArgumentException ex) {
288             ;
289         }
290         String hexString = randomData.nextSecureHexString(3);
291         if (hexString.length() != 3) {
292                 fail("incorrect length for generated string");
293         }
294         hexString = randomData.nextSecureHexString(1);
295         if (hexString.length() != 1) {
296                 fail("incorrect length for generated string");
297         }
298         try {
299             hexString = randomData.nextSecureHexString(0);
300             fail("zero length requested -- expecting IllegalArgumentException");
301         } catch (IllegalArgumentException ex) {
302             ;
303         }
304         if (hexString.length() != 1) {
305                 fail("incorrect length for generated string");
306         }      
307         Frequency f = new Frequency();
308         for (int i = 0; i < smallSampleSize; i++) {
309             hexString = randomData.nextSecureHexString(100);
310             if (hexString.length() != 100) {
311                 fail("incorrect length for generated string");
312             }
313             for (int j = 0; j < hexString.length(); j++) {
314                 f.addValue(hexString.substring(j,j+1));
315             }
316         }
317         double[] expected = new double[16];
318         long[] observed = new long[16];
319         for (int i = 0; i < 16; i++) {
320             expected[i] = (double)smallSampleSize*100/(double)16;
321             observed[i] = f.getCount(hex[i]);
322         }
323         /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
324          * Change to 30.58 for alpha = .01
325          */
326         assertTrue("chi-square test -- will fail about 1 in 1000 times",
327             testStatistic.chiSquare(expected,observed) < 37.70);    
328     }
329     
330     /** test failure modes and dispersion of nextUniform() */  
331     public void testNextUniform() {    
332         try {
333             randomData.nextUniform(4,3);
334             fail("IllegalArgumentException expected");
335         } catch (IllegalArgumentException ex) {
336             ;
337         }
338         try {
339             randomData.nextUniform(3,3);
340             fail("IllegalArgumentException expected");
341         } catch (IllegalArgumentException ex) {
342             ;
343         }
344         double[] expected = {500,500};
345         long[] observed = {0,0};
346         double lower = -1d;
347         double upper = 20d;
348         double midpoint = (lower + upper)/2d;
349         double result = 0;
350         for (int i = 0; i < 1000; i++) {
351             result = randomData.nextUniform(lower,upper);
352             if ((result == lower) || (result == upper)) {
353                 fail("generated value equal to an endpoint: " + result);
354             } 
355             if (result < midpoint) {
356                 observed[0]++;
357             } else {
358                 observed[1]++;
359             }
360         }
361         /* Use ChiSquare dist with df = 2-1 = 1, alpha = .001
362          * Change to 6.64 for alpha = .01
363          */
364         assertTrue("chi-square test -- will fail about 1 in 1000 times",
365             testStatistic.chiSquare(expected,observed) < 10.83);  
366     }
367     
368     /** test exclusive endpoints of nextUniform **/
369     public void testNextUniformExclusiveEndpoints() {
370         for (int i = 0; i < 1000; i++) {
371             double u = randomData.nextUniform(0.99, 1);
372             assertTrue(u > 0.99 && u < 1);
373         }
374     }
375     
376     /** test failure modes and distribution of nextGaussian() */  
377     public void testNextGaussian() { 
378         try {
379             randomData.nextGaussian(0,0);
380             fail("zero sigma -- IllegalArgumentException expected");
381         } catch (IllegalArgumentException ex) {
382             ;
383         }
384         SummaryStatistics u = new SummaryStatistics();
385         for (int i = 0; i<largeSampleSize; i++) {
386             u.addValue(randomData.nextGaussian(0,1));
387         }
388         double xbar = u.getMean();
389         double s = u.getStandardDeviation();
390         double n = (double) u.getN(); 
391         /* t-test at .001-level TODO: replace with externalized t-test, with
392          * test statistic defined in TestStatistic
393          */
394         assertTrue(Math.abs(xbar)/(s/Math.sqrt(n))< 3.29);
395     }
396     
397     /** test failure modes and distribution of nextExponential() */  
398     public void testNextExponential() {
399         try {
400             randomData.nextExponential(-1);
401             fail("negative mean -- expecting IllegalArgumentException");
402         } catch (IllegalArgumentException ex) {
403             ;
404         }
405         assertEquals("0 mean", 0,randomData.nextExponential(0),10E-8); 
406         long cumFreq = 0;
407         double v = 0;
408         for (int i = 0; i < largeSampleSize; i++) {
409             v = randomData.nextExponential(1);
410             assertTrue("exponential deviate postive", v > 0);
411             if (v < 2) cumFreq++;
412         }
413         /* TODO: Replace with a statistical test, with statistic added to
414          * TestStatistic.  Check below compares observed cumulative distribution
415          * evaluated at 2 with exponential CDF 
416          */
417         assertEquals("exponential cumulative distribution",
418             (double)cumFreq/(double)largeSampleSize,0.8646647167633873,.2);
419     } 
420     
421     /** test reseeding, algorithm/provider games */
422     public void testConfig() throws NoSuchProviderException, 
423       NoSuchAlgorithmException {
424         randomData.reSeed(1000);
425         double v = randomData.nextUniform(0,1);
426         randomData.reSeed();
427         assertTrue("different seeds", 
428             Math.abs(v - randomData.nextUniform(0,1)) > 10E-12);
429         randomData.reSeed(1000);
430         assertEquals("same seeds",v,randomData.nextUniform(0,1),10E-12);
431         randomData.reSeedSecure(1000);
432         String hex = randomData.nextSecureHexString(40);
433         randomData.reSeedSecure();
434         assertTrue("different seeds",
435             !hex.equals(randomData.nextSecureHexString(40)));
436         randomData.reSeedSecure(1000);
437         assertTrue("same seeds",
438             !hex.equals(randomData.nextSecureHexString(40))); 
439         
440         /* remove this test back soon,
441          * since it takes about 4 seconds 
442 
443         try {
444             randomData.setSecureAlgorithm("SHA1PRNG","SUN");
445         } catch (NoSuchProviderException ex) {
446             ;
447         }
448         assertTrue("different seeds",
449             !hex.equals(randomData.nextSecureHexString(40)));
450         try {
451             randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
452             fail("expecting NoSuchAlgorithmException");
453         } catch (NoSuchProviderException ex) {
454             ;
455         } catch (NoSuchAlgorithmException ex) {
456             ;
457         }
458         
459         try {
460             randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
461             fail("expecting NoSuchProviderException");
462         } catch (NoSuchProviderException ex) {
463             ;
464         } 
465         */
466         
467         // test reseeding without first using the generators
468         RandomDataImpl rd = new RandomDataImpl();
469         rd.reSeed(100);
470         rd.nextLong(1,2);
471         RandomDataImpl rd2 = new RandomDataImpl();
472         rd2.reSeedSecure(2000);
473         rd2.nextSecureLong(1,2);
474         rd = new RandomDataImpl();
475         rd.reSeed();
476         rd.nextLong(1,2);
477         rd2 = new RandomDataImpl();
478         rd2.reSeedSecure();
479         rd2.nextSecureLong(1,2);
480     }
481     
482     /** tests for nextSample() sampling from Collection */
483     public void testNextSample() {
484        Object[][] c = {{"0","1"},{"0","2"},{"0","3"},{"0","4"},{"1","2"},
485                         {"1","3"},{"1","4"},{"2","3"},{"2","4"},{"3","4"}};
486        long[] observed = {0,0,0,0,0,0,0,0,0,0};
487        double[] expected = {100,100,100,100,100,100,100,100,100,100};
488        
489        HashSet cPop = new HashSet();  //{0,1,2,3,4}
490        for (int i = 0; i < 5; i++) {
491            cPop.add(Integer.toString(i));
492        }
493        
494        Object[] sets = new Object[10]; // 2-sets from 5
495        for (int i = 0; i < 10; i ++) {
496            HashSet hs = new HashSet();
497            hs.add(c[i][0]);
498            hs.add(c[i][1]);
499            sets[i] = hs;
500        }
501        
502        for (int i = 0; i < 1000; i ++) {
503            Object[] cSamp = randomData.nextSample(cPop,2);
504            observed[findSample(sets,cSamp)]++;
505        }
506        
507         /* Use ChiSquare dist with df = 10-1 = 9, alpha = .001
508          * Change to 21.67 for alpha = .01
509          */
510         assertTrue("chi-square test -- will fail about 1 in 1000 times",
511             testStatistic.chiSquare(expected,observed) < 27.88);  
512        
513        // Make sure sample of size = size of collection returns same collection
514        HashSet hs = new HashSet();
515        hs.add("one");
516        Object[] one = randomData.nextSample(hs,1);
517        String oneString = (String) one[0];
518        if ((one.length != 1) || !oneString.equals("one")){
519            fail("bad sample for set size = 1, sample size = 1");
520        }
521        
522        // Make sure we fail for sample size > collection size
523        try {
524            one = randomData.nextSample(hs,2);
525            fail("sample size > set size, expecting IllegalArgumentException");
526        } catch (IllegalArgumentException ex) {
527            ;
528        }
529        
530        // Make sure we fail for empty collection
531        try {
532            hs = new HashSet();
533            one = randomData.nextSample(hs,0);
534            fail("n = k = 0, expecting IllegalArgumentException");
535        } catch (IllegalArgumentException ex) {
536            ;
537        }
538     }
539     
540     private int findSample(Object[] u, Object[] samp) {
541         for (int i = 0; i < u.length; i++) {
542             HashSet set = (HashSet) u[i];
543             HashSet sampSet = new HashSet();
544             for (int j = 0; j < samp.length; j++) {
545                 sampSet.add(samp[j]);
546             }
547             if (set.equals(sampSet)) {                 
548                return i;
549            }
550         }
551         fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
552         return -1;
553     }
554     
555     /** tests for nextPermutation */
556     public void testNextPermutation() {
557         int[][] p = {{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}};
558         long[] observed = {0,0,0,0,0,0};
559         double[] expected = {100,100,100,100,100,100};
560         
561         for (int i = 0; i < 600; i++) {
562             int[] perm = randomData.nextPermutation(3,3);
563             observed[findPerm(p,perm)]++;
564         }  
565         
566         /* Use ChiSquare dist with df = 6-1 = 5, alpha = .001
567          * Change to 15.09 for alpha = .01
568          */
569         assertTrue("chi-square test -- will fail about 1 in 1000 times",
570                 testStatistic.chiSquare(expected,observed) < 20.52); 
571         
572         // Check size = 1 boundary case
573         int[] perm = randomData.nextPermutation(1,1);
574         if ((perm.length != 1) || (perm[0] != 0)){
575             fail("bad permutation for n = 1, sample k = 1");
576             
577             // Make sure we fail for k size > n 
578             try {
579                 perm = randomData.nextPermutation(2,3);
580                 fail("permutation k > n, expecting IllegalArgumentException");
581             } catch (IllegalArgumentException ex) {
582                 ;
583             }
584             
585             // Make sure we fail for n = 0
586             try {
587                 perm = randomData.nextPermutation(0,0);
588                 fail("permutation k = n = 0, expecting IllegalArgumentException");
589             } catch (IllegalArgumentException ex) {
590                 ;
591             }               
592         }       
593     }
594     
595     private int findPerm(int[][] p, int[] samp) {
596         for (int i = 0; i < p.length; i++) {
597             boolean good = true;
598             for (int j = 0; j < samp.length; j++) {
599                 if (samp[j] != p[i][j]) {
600                     good = false;
601                 }
602             }
603             if (good)  {
604                 return i;
605             }
606         }        
607         fail("permutation not found");
608         return -1;
609     }   
610 }
611