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