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.math3.distribution.PoissonDistribution;
20  import org.apache.commons.rng.UniformRandomProvider;
21  import org.apache.commons.rng.sampling.RandomAssert;
22  import org.apache.commons.rng.simple.RandomSource;
23  import org.junit.Assert;
24  import org.junit.Test;
25  
26  /**
27   * Test for the {@link KempSmallMeanPoissonSampler}. The tests hit edge cases for the
28   * sampler and tests it functions at the supported upper bound on the mean.
29   */
30  public class KempSmallMeanPoissonSamplerTest {
31      /**
32       * The upper limit on the mean.
33       *
34       * <p>p0 = Math.exp(-mean) => mean = -Math.log(p0). When p0 is {@link Double#MIN_VALUE} the
35       * mean is approximately 744.4.</p>
36       */
37      private static final double SUPPORTED_UPPER_BOUND = -Math.log(Double.MIN_VALUE);
38  
39      /** The rng for construction tests. */
40      private final UniformRandomProvider dummyRng = new FixedRNG(0);
41  
42      /**
43       * Test the constructor with a bad mean.
44       */
45      @Test(expected = IllegalArgumentException.class)
46      public void testConstructorThrowsWithMeanLargerThanUpperBound() {
47          final double mean = SUPPORTED_UPPER_BOUND + 1;
48          @SuppressWarnings("unused")
49          SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
50      }
51  
52      /**
53       * Test the constructor with zero mean.
54       */
55      @Test(expected = IllegalArgumentException.class)
56      public void testConstructorThrowsWithZeroMean() {
57          final double mean = 0;
58          @SuppressWarnings("unused")
59          SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
60      }
61  
62      /**
63       * Test the constructor with a negative mean.
64       */
65      @Test(expected = IllegalArgumentException.class)
66      public void testConstructorThrowsWithNegativeMean() {
67          final double mean = -1;
68          @SuppressWarnings("unused")
69          SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
70      }
71  
72      /**
73       * Test the constructor with a NaN mean.
74       */
75      @Test(expected = IllegalArgumentException.class)
76      public void testConstructorWithNaNMean() {
77          final double mean = Double.NaN;
78          @SuppressWarnings("unused")
79          SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
80      }
81  
82      /**
83       * Test the cumulative summation at the upper bound on the mean is close to zero when
84       * starting from 1.
85       */
86      @Test
87      public void testSummationFrom1AtUpperBound() {
88          final double mean = SUPPORTED_UPPER_BOUND;
89          double u = 1;
90          int x = 0;
91          double p = Math.exp(-mean);
92          while (u > p && p != 0) {
93              u -= p;
94              x = x + 1;
95              p = p * mean / x;
96          }
97          Assert.assertEquals("Summation is not zero", 0, u, 1e-3);
98          Assert.assertTrue("Summation is not greater than zero", u > 0);
99      }
100 
101     /**
102      * Test the cumulative summation at the upper bound on the mean is close to one when
103      * starting from 0.
104      */
105     @Test
106     public void testSummationTo1AtUpperBound() {
107         final double mean = SUPPORTED_UPPER_BOUND;
108         double u = 0;
109         int x = 0;
110         double p = Math.exp(-mean);
111         while (p != 0) {
112             u += p;
113             x = x + 1;
114             p = p * mean / x;
115         }
116         Assert.assertEquals("Summation is not one", 1, u, 1e-3);
117         Assert.assertTrue("Summation is not less than one", u < 1);
118     }
119 
120     /**
121      * Test the sampler functions at the upper bound on the mean.
122      */
123     @Test
124     public void testSamplerAtUpperBounds() {
125         final double mean = SUPPORTED_UPPER_BOUND;
126 
127         // Test some ranges for the cumulative probability
128         final PoissonDistribution pd = new PoissonDistribution(null, mean,
129             PoissonDistribution.DEFAULT_EPSILON, PoissonDistribution.DEFAULT_MAX_ITERATIONS);
130 
131         final FixedRNG rng = new FixedRNG(0);
132         final SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(rng, mean);
133 
134         // Lower bound should be zero
135         testSample(rng, sampler, 0, 0, 0);
136 
137         // Upper bound should exceed 99.99% of the range
138         testSample(rng, sampler, 1, pd.inverseCumulativeProbability(0.9999), Integer.MAX_VALUE);
139 
140         // A sample from within the cumulative probability should be within the expected range
141         for (int i = 1; i < 10; i++) {
142             final double p = i * 0.1;
143             final int lower = pd.inverseCumulativeProbability(p - 0.01);
144             final int upper = pd.inverseCumulativeProbability(p + 0.01);
145             testSample(rng, sampler, p, lower, upper);
146         }
147     }
148 
149     /**
150      * Test the SharedStateSampler implementation.
151      */
152     @Test
153     public void testSharedStateSampler() {
154         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
155         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
156         final double mean = 1.23;
157         final SharedStateDiscreteSampler sampler1 =
158             KempSmallMeanPoissonSampler.of(rng1, mean);
159         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
160         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
161     }
162 
163     /**
164      * Test a sample from the Poisson distribution at the given cumulative probability.
165      *
166      * @param rng the fixed random generator backing the sampler
167      * @param sampler the sampler
168      * @param cumulativeProbability the cumulative probability
169      * @param lower the expected lower limit
170      * @param upper the expected upper limit
171      */
172     private static void testSample(FixedRNG rng, SharedStateDiscreteSampler sampler, double cumulativeProbability,
173         int lower, int upper) {
174         rng.setValue(cumulativeProbability);
175         final int sample = sampler.sample();
176         Assert.assertTrue(sample + " sample is not above realistic lower limit: " + lower, sample >= lower);
177         Assert.assertTrue(sample + " sample is not below realistic upper limit: " + upper, sample <= upper);
178     }
179 
180     /**
181      * A RNG returning a fixed value.
182      */
183     private static class FixedRNG implements UniformRandomProvider {
184         /** The value. */
185         private double value;
186 
187         /**
188          * @param value the value
189          */
190         FixedRNG(double value) {
191             this.value = value;
192         }
193 
194         @Override
195         public double nextDouble() {
196             return value;
197         }
198 
199         /**
200          * @param value the new value
201          */
202         void setValue(double value) {
203             this.value = value;
204         }
205 
206         // CHECKSTYLE: stop all
207         public long nextLong(long n) { return 0; }
208         public long nextLong() { return 0; }
209         public int nextInt(int n) { return 0; }
210         public int nextInt() { return 0; }
211         public float nextFloat() { return 0; }
212         public void nextBytes(byte[] bytes, int start, int len) {}
213         public void nextBytes(byte[] bytes) {}
214         public boolean nextBoolean() { return false; }
215         // CHECKSTYLE: resume all
216     }
217 }