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