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