View Javadoc
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.rng.sampling.distribution;
18  
19  import org.apache.commons.rng.RandomProviderState;
20  import org.apache.commons.rng.RestorableUniformRandomProvider;
21  import org.apache.commons.rng.simple.RandomSource;
22  import org.junit.Assert;
23  import org.junit.Test;
24  
25  /**
26   * This test checks the {@link PoissonSamplerCache} functions exactly like the
27   * constructor of the {@link PoissonSampler}, irrespective of the range
28   * covered by the cache.
29   */
30  public class PoissonSamplerCacheTest {
31  
32      // Set a range so that the SmallMeanPoissonSampler is also required.
33  
34      /** The minimum of the range of the mean */
35      private final int minRange = (int) Math.floor(PoissonSampler.PIVOT - 2);
36      /** The maximum of the range of the mean */
37      private final int maxRange = (int) Math.floor(PoissonSampler.PIVOT + 6);
38      /** The mid-point of the range of the mean */
39      private final int midRange = (minRange + maxRange) / 2;
40  
41      // Edge cases for construction
42  
43      /**
44       * Test the cache can be created without a range that requires a cache.
45       * In this case the cache will be a pass through to the constructor
46       * of the SmallMeanPoissonSampler.
47       */
48      @Test
49      public void testConstructorWithNoCache() {
50          final double min = 0;
51          final double max = PoissonSampler.PIVOT - 2;
52          PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
53          Assert.assertFalse(cache.isValidRange());
54          Assert.assertEquals(0, cache.getMinMean(), 0);
55          Assert.assertEquals(0, cache.getMaxMean(), 0);
56      }
57  
58      /**
59       * Test the cache can be created with a range of 1.
60       * In this case the cache will be valid for all mean values
61       * in the range {@code n <= mean < n+1}.
62       */
63      @Test
64      public void testConstructorWhenMaxEqualsMin() {
65          final double min = PoissonSampler.PIVOT + 2;
66          final double max = min;
67          PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
68          Assert.assertTrue(cache.isValidRange());
69          Assert.assertEquals(min, cache.getMinMean(), 0);
70          Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
71                              cache.getMaxMean(), 0);
72      }
73  
74      /**
75       * Test the cache can be created with a range of 1.
76       * In this case the cache will be valid for all mean values
77       * in the range {@code n <= mean < n+1}.
78       */
79      @Test
80      public void testConstructorWhenMaxAboveMin() {
81          final double min = PoissonSampler.PIVOT + 2;
82          final double max = min + 10;
83          PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
84          Assert.assertTrue(cache.isValidRange());
85          Assert.assertEquals(min, cache.getMinMean(), 0);
86          Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
87                              cache.getMaxMean(), 0);
88      }
89  
90      /**
91       * Test the cache requires a range with {@code max >= min}.
92       */
93      @Test(expected=IllegalArgumentException.class)
94      public void testConstructorThrowsWhenMaxIsLessThanMin() {
95          final double min = PoissonSampler.PIVOT;
96          final double max = Math.nextAfter(min, -1);
97          createPoissonSamplerCache(min, max);
98      }
99  
100     /**
101      * Test the cache can be created with a min range below 0.
102      * In this case the range is truncated to 0.
103      */
104     @Test
105     public void testConstructorWhenMinBelow0() {
106         final double min = -1;
107         final double max = PoissonSampler.PIVOT + 2;
108         PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
109         Assert.assertTrue(cache.isValidRange());
110         Assert.assertEquals(PoissonSampler.PIVOT, cache.getMinMean(), 0);
111         Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
112                             cache.getMaxMean(), 0);
113     }
114 
115     /**
116      * Test the cache can be created with a max range below 0.
117      * In this case the range is truncated to 0, i.e. no cache.
118      */
119     @Test
120     public void testConstructorWhenMaxBelow0() {
121         final double min = -10;
122         final double max = -1;
123         PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
124         Assert.assertFalse(cache.isValidRange());
125         Assert.assertEquals(0, cache.getMinMean(), 0);
126         Assert.assertEquals(0, cache.getMaxMean(), 0);
127     }
128 
129     /**
130      * Test the cache can be created without a range that requires a cache.
131      * In this case the cache will be a pass through to the constructor
132      * of the SmallMeanPoissonSampler.
133      */
134     @Test
135     public void testWithRangeConstructorWithNoCache() {
136         final double min = 0;
137         final double max = PoissonSampler.PIVOT - 2;
138         PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
139         Assert.assertFalse(cache.isValidRange());
140         Assert.assertEquals(0, cache.getMinMean(), 0);
141         Assert.assertEquals(0, cache.getMaxMean(), 0);
142     }
143 
144     /**
145      * Test the cache can be created with a range of 1.
146      * In this case the cache will be valid for all mean values
147      * in the range {@code n <= mean < n+1}.
148      */
149     @Test
150     public void testWithRangeConstructorWhenMaxEqualsMin() {
151         final double min = PoissonSampler.PIVOT + 2;
152         final double max = min;
153         PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
154         Assert.assertTrue(cache.isValidRange());
155         Assert.assertEquals(min, cache.getMinMean(), 0);
156         Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
157                             cache.getMaxMean(), 0);
158     }
159 
160     /**
161      * Test the cache can be created with a range of 1.
162      * In this case the cache will be valid for all mean values
163      * in the range {@code n <= mean < n+1}.
164      */
165     @Test
166     public void testWithRangeConstructorWhenMaxAboveMin() {
167         final double min = PoissonSampler.PIVOT + 2;
168         final double max = min + 10;
169         PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
170         Assert.assertTrue(cache.isValidRange());
171         Assert.assertEquals(min, cache.getMinMean(), 0);
172         Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
173                             cache.getMaxMean(), 0);
174     }
175 
176     /**
177      * Test the cache requires a range with {@code max >= min}.
178      */
179     @Test(expected=IllegalArgumentException.class)
180     public void testWithRangeConstructorThrowsWhenMaxIsLessThanMin() {
181         final double min = PoissonSampler.PIVOT;
182         final double max = Math.nextAfter(min, -1);
183         createPoissonSamplerCache().withRange(min, max);
184     }
185 
186     /**
187      * Test the cache can be created with a min range below 0.
188      * In this case the range is truncated to 0.
189      */
190     @Test
191     public void testWithRangeConstructorWhenMinBelow0() {
192         final double min = -1;
193         final double max = PoissonSampler.PIVOT + 2;
194         PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
195         Assert.assertTrue(cache.isValidRange());
196         Assert.assertEquals(PoissonSampler.PIVOT, cache.getMinMean(), 0);
197         Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
198                             cache.getMaxMean(), 0);
199     }
200 
201     /**
202      * Test the cache can be created from a cache with no capacity.
203      */
204     @Test
205     public void testWithRangeConstructorWhenCacheHasNoCapcity() {
206         final double min = PoissonSampler.PIVOT + 2;
207         final double max = min + 10;
208         PoissonSamplerCache cache = createPoissonSamplerCache(0, 0).withRange(min, max);
209         Assert.assertTrue(cache.isValidRange());
210         Assert.assertEquals(min, cache.getMinMean(), 0);
211         Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
212                             cache.getMaxMean(), 0);
213     }
214 
215     /**
216      * Test the withinRange function of the cache signals when construction
217      * cost is minimal.
218      */
219     @Test
220     public void testWithinRange() {
221         final double min = PoissonSampler.PIVOT + 10;
222         final double max = PoissonSampler.PIVOT + 20;
223         PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
224         // Under the pivot point is always within range
225         Assert.assertTrue(cache.withinRange(PoissonSampler.PIVOT - 1));
226         Assert.assertFalse(cache.withinRange(min - 1));
227         Assert.assertTrue(cache.withinRange(min));
228         Assert.assertTrue(cache.withinRange(max));
229         Assert.assertFalse(cache.withinRange(max + 10));
230     }
231 
232     // Edge cases for creating a Poisson sampler
233 
234     /**
235      * Test createPoissonSampler() with a bad mean.
236      *
237      * <p>Note this test actually tests the SmallMeanPoissonSampler throws.
238      */
239     @Test(expected=IllegalArgumentException.class)
240     public void testCreatePoissonSamplerThrowsWithZeroMean() {
241         final RestorableUniformRandomProvider rng =
242                 RandomSource.create(RandomSource.SPLIT_MIX_64);
243         final PoissonSamplerCache cache = createPoissonSamplerCache();
244         cache.createPoissonSampler(rng, 0);
245     }
246 
247     /**
248      * Test createPoissonSampler() with a mean that is too large.
249      */
250     @Test(expected=IllegalArgumentException.class)
251     public void testCreatePoissonSamplerThrowsWithNonIntegerMean() {
252         final RestorableUniformRandomProvider rng =
253                 RandomSource.create(RandomSource.SPLIT_MIX_64);
254         final PoissonSamplerCache cache = createPoissonSamplerCache();
255         final double mean = Integer.MAX_VALUE + 1.0;
256         cache.createPoissonSampler(rng, mean);
257     }
258 
259     // Sampling tests
260 
261     /**
262      * Test the cache returns the same samples as the PoissonSampler when it
263      * covers the entire range.
264      */
265     @Test
266     public void testCanComputeSameSamplesAsPoissonSamplerWithFullRangeCache() {
267         checkComputeSameSamplesAsPoissonSampler(minRange,
268                                                 maxRange);
269     }
270 
271     /**
272      * Test the cache returns the same samples as the PoissonSampler
273      * with no cache.
274      */
275     @Test
276     public void testCanComputeSameSamplesAsPoissonSamplerWithNoCache() {
277         checkComputeSameSamplesAsPoissonSampler(0,
278                                                 minRange - 2);
279     }
280 
281     /**
282      * Test the cache returns the same samples as the PoissonSampler with
283      * partial cache covering the lower range.
284      */
285     @Test
286     public void testCanComputeSameSamplesAsPoissonSamplerWithPartialCacheCoveringLowerRange() {
287         checkComputeSameSamplesAsPoissonSampler(minRange,
288                                                 midRange);
289     }
290 
291     /**
292      * Test the cache returns the same samples as the PoissonSampler with
293      * partial cache covering the upper range.
294      */
295     @Test
296     public void testCanComputeSameSamplesAsPoissonSamplerWithPartialCacheCoveringUpperRange() {
297         checkComputeSameSamplesAsPoissonSampler(midRange,
298                                                 maxRange);
299     }
300 
301     /**
302      * Test the cache returns the same samples as the PoissonSampler with
303      * cache above the upper range.
304      */
305     @Test
306     public void testCanComputeSameSamplesAsPoissonSamplerWithCacheAboveTheUpperRange() {
307         checkComputeSameSamplesAsPoissonSampler(maxRange + 10,
308                                                 maxRange + 20);
309     }
310 
311     /**
312      * Check poisson samples are the same from the {@link PoissonSampler}
313      * and {@link PoissonSamplerCache}.
314      *
315      * @param minMean the min mean of the cache
316      * @param maxMean the max mean of the cache
317      */
318     private void checkComputeSameSamplesAsPoissonSampler(int minMean,
319                                                          int maxMean) {
320         // Two identical RNGs
321         final RestorableUniformRandomProvider rng1 =
322                 RandomSource.create(RandomSource.WELL_19937_C);
323         final RandomProviderState state = rng1.saveState();
324         final RestorableUniformRandomProvider rng2 =
325                 RandomSource.create(RandomSource.WELL_19937_C);
326         rng2.restoreState(state);
327 
328         // Create the cache with the given range
329         final PoissonSamplerCache cache =
330                 createPoissonSamplerCache(minMean, maxMean);
331         // Test all means in the test range (which may be different
332         // from the cache range).
333         for (int i = minRange; i <= maxRange; i++) {
334             // Test integer mean (no SmallMeanPoissonSampler required)
335             testPoissonSamples(rng1, rng2, cache, i);
336             // Test non-integer mean (SmallMeanPoissonSampler required)
337             testPoissonSamples(rng1, rng2, cache, i + 0.5);
338         }
339     }
340 
341     /**
342      * Creates the poisson sampler cache with the given range.
343      *
344      * @param minMean the min mean
345      * @param maxMean the max mean
346      * @return the poisson sampler cache
347      */
348     private static PoissonSamplerCache createPoissonSamplerCache(double minMean,
349                                                           double maxMean)
350     {
351         return new PoissonSamplerCache(minMean, maxMean);
352     }
353 
354     /**
355      * Creates a poisson sampler cache that will have a valid range for the cache.
356      *
357      * @return the poisson sampler cache
358      */
359     private static PoissonSamplerCache createPoissonSamplerCache()
360     {
361         return new PoissonSamplerCache(PoissonSampler.PIVOT,
362                                        PoissonSampler.PIVOT + 10);
363     }
364 
365     /**
366      * Test poisson samples are the same from the {@link PoissonSampler}
367      * and {@link PoissonSamplerCache}. The random providers must be
368      * identical (including state).
369      *
370      * @param rng1  the first random provider
371      * @param rng2  the second random provider
372      * @param cache the cache
373      * @param mean  the mean
374      */
375     private static void testPoissonSamples(
376             final RestorableUniformRandomProvider rng1,
377             final RestorableUniformRandomProvider rng2,
378             PoissonSamplerCache cache,
379             double mean) {
380         final DiscreteSampler s1 = new PoissonSampler(rng1, mean);
381         final DiscreteSampler s2 = cache.createPoissonSampler(rng2, mean);
382         for (int j = 0; j < 10; j++)
383             Assert.assertEquals(s1.sample(), s2.sample());
384     }
385 
386     /**
387      * Test the cache returns the same samples as the PoissonSampler with
388      * a new cache reusing the entire range.
389      */
390     @Test
391     public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheEntireRange() {
392         checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
393                                                             maxRange,
394                                                             midRange,
395                                                             maxRange);
396     }
397 
398     /**
399      * Test the cache returns the same samples as the PoissonSampler with
400      * a new cache reusing none of the range.
401      */
402     @Test
403     public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheNoRange() {
404         checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
405                                                             maxRange,
406                                                             maxRange + 10,
407                                                             maxRange + 20);
408     }
409 
410     /**
411      * Test the cache returns the same samples as the PoissonSampler with
412      * a new cache reusing some of the lower range.
413      */
414     @Test
415     public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheLowerRange() {
416         checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
417                                                             maxRange,
418                                                             minRange,
419                                                             midRange + 1);
420     }
421 
422     /**
423      * Test the cache returns the same samples as the PoissonSampler with
424      * a new cache reusing some of the upper range.
425      */
426     @Test
427     public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheUpperRange() {
428         checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
429                                                             maxRange,
430                                                             maxRange - 1,
431                                                             maxRange + 5);
432     }
433 
434     /**
435      * Check poisson samples are the same from the {@link PoissonSampler}
436      * and a {@link PoissonSamplerCache} created reusing values.
437      *
438      * <p>Note: This cannot check the cache values were reused but ensures
439      * that a new cache created with a range functions correctly.
440      *
441      * @param minMean  the min mean of the cache
442      * @param maxMean  the max mean of the cache
443      * @param minMean2 the min mean of the second cache
444      * @param maxMean2 the max mean of the second cache
445      */
446     private void checkComputeSameSamplesAsPoissonSamplerReusingCache(int minMean,
447                                                                      int maxMean,
448                                                                      int minMean2,
449                                                                      int maxMean2) {
450         // Two identical RNGs
451         final RestorableUniformRandomProvider rng1 =
452                 RandomSource.create(RandomSource.WELL_19937_C);
453         final RandomProviderState state = rng1.saveState();
454         final RestorableUniformRandomProvider rng2 =
455                 RandomSource.create(RandomSource.WELL_19937_C);
456 
457         // Create the cache with the given range and fill it
458         final PoissonSamplerCache cache =
459                 createPoissonSamplerCache(minMean, maxMean);
460         // Test all means in the test range (which may be different
461         // from the cache range).
462         for (int i = minMean; i <= maxMean; i++) {
463             cache.createPoissonSampler(rng1, i);
464         }
465 
466         final PoissonSamplerCache cache2 = cache.withRange(minMean2, maxMean2);
467         Assert.assertTrue("WithRange cache is the same object", cache != cache2);
468 
469         rng1.restoreState(state);
470         rng2.restoreState(state);
471 
472         // Test all means in the test range (which may be different
473         // from the cache range).
474         for (int i = minRange; i <= maxRange; i++) {
475             // Test integer mean (no SmallMeanPoissonSampler required)
476             testPoissonSamples(rng1, rng2, cache2, i);
477             // Test non-integer mean (SmallMeanPoissonSampler required)
478             testPoissonSamples(rng1, rng2, cache2, i + 0.5);
479         }
480     }
481 }