blob: 87979e28a632f827d29c01f22adeaea5e21840c4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.rng.sampling.distribution;
import org.apache.commons.rng.RandomProviderState;
import org.apache.commons.rng.RestorableUniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.Assert;
import org.junit.Test;
/**
* This test checks the {@link PoissonSamplerCache} functions exactly like the
* constructor of the {@link PoissonSampler}, irrespective of the range
* covered by the cache.
*/
public class PoissonSamplerCacheTest {
// Set a range so that the SmallMeanPoissonSampler is also required.
/** The minimum of the range of the mean */
private final int minRange = (int) Math.floor(PoissonSampler.PIVOT - 2);
/** The maximum of the range of the mean */
private final int maxRange = (int) Math.floor(PoissonSampler.PIVOT + 6);
/** The mid-point of the range of the mean */
private final int midRange = (minRange + maxRange) / 2;
/**
* Test the cache reports the minimum mean that uses an algorithm that supports caching.
* This mean is the same level as the algorithm switch point in the PoissonSampler.
*/
@Test
public void testMinimumCachedMean() {
Assert.assertEquals(PoissonSampler.PIVOT, PoissonSamplerCache.getMinimumCachedMean(), 0);
}
// Edge cases for construction
/**
* Test the cache can be created without a range that requires a cache.
* In this case the cache will be a pass through to the constructor
* of the SmallMeanPoissonSampler.
*/
@Test
public void testConstructorWithNoCache() {
final double min = 0;
final double max = PoissonSampler.PIVOT - 2;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
Assert.assertFalse(cache.isValidRange());
Assert.assertEquals(0, cache.getMinMean(), 0);
Assert.assertEquals(0, cache.getMaxMean(), 0);
}
/**
* Test the cache can be created with a range of 1.
* In this case the cache will be valid for all mean values
* in the range {@code n <= mean < n+1}.
*/
@Test
public void testConstructorWhenMaxEqualsMin() {
final double min = PoissonSampler.PIVOT + 2;
final double max = min;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(min, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache can be created with a range of 1.
* In this case the cache will be valid for all mean values
* in the range {@code n <= mean < n+1}.
*/
@Test
public void testConstructorWhenMaxAboveMin() {
final double min = PoissonSampler.PIVOT + 2;
final double max = min + 10;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(min, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache requires a range with {@code max >= min}.
*/
@Test(expected = IllegalArgumentException.class)
public void testConstructorThrowsWhenMaxIsLessThanMin() {
final double min = PoissonSampler.PIVOT;
final double max = Math.nextAfter(min, -1);
createPoissonSamplerCache(min, max);
}
/**
* Test the cache can be created with a min range below 0.
* In this case the range is truncated to 0.
*/
@Test
public void testConstructorWhenMinBelow0() {
final double min = -1;
final double max = PoissonSampler.PIVOT + 2;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(PoissonSampler.PIVOT, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache can be created with a max range below 0.
* In this case the range is truncated to 0, i.e. no cache.
*/
@Test
public void testConstructorWhenMaxBelow0() {
final double min = -10;
final double max = -1;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
Assert.assertFalse(cache.isValidRange());
Assert.assertEquals(0, cache.getMinMean(), 0);
Assert.assertEquals(0, cache.getMaxMean(), 0);
}
/**
* Test the cache can be created without a range that requires a cache.
* In this case the cache will be a pass through to the constructor
* of the SmallMeanPoissonSampler.
*/
@Test
public void testWithRangeConstructorWithNoCache() {
final double min = 0;
final double max = PoissonSampler.PIVOT - 2;
final PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
Assert.assertFalse(cache.isValidRange());
Assert.assertEquals(0, cache.getMinMean(), 0);
Assert.assertEquals(0, cache.getMaxMean(), 0);
}
/**
* Test the cache can be created with a range of 1.
* In this case the cache will be valid for all mean values
* in the range {@code n <= mean < n+1}.
*/
@Test
public void testWithRangeConstructorWhenMaxEqualsMin() {
final double min = PoissonSampler.PIVOT + 2;
final double max = min;
final PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(min, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache can be created with a range of 1.
* In this case the cache will be valid for all mean values
* in the range {@code n <= mean < n+1}.
*/
@Test
public void testWithRangeConstructorWhenMaxAboveMin() {
final double min = PoissonSampler.PIVOT + 2;
final double max = min + 10;
final PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(min, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache requires a range with {@code max >= min}.
*/
@Test(expected = IllegalArgumentException.class)
public void testWithRangeConstructorThrowsWhenMaxIsLessThanMin() {
final double min = PoissonSampler.PIVOT;
final double max = Math.nextAfter(min, -1);
createPoissonSamplerCache().withRange(min, max);
}
/**
* Test the cache can be created with a min range below 0.
* In this case the range is truncated to 0.
*/
@Test
public void testWithRangeConstructorWhenMinBelow0() {
final double min = -1;
final double max = PoissonSampler.PIVOT + 2;
final PoissonSamplerCache cache = createPoissonSamplerCache().withRange(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(PoissonSampler.PIVOT, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the cache can be created from a cache with no capacity.
*/
@Test
public void testWithRangeConstructorWhenCacheHasNoCapcity() {
final double min = PoissonSampler.PIVOT + 2;
final double max = min + 10;
final PoissonSamplerCache cache = createPoissonSamplerCache(0, 0).withRange(min, max);
Assert.assertTrue(cache.isValidRange());
Assert.assertEquals(min, cache.getMinMean(), 0);
Assert.assertEquals(Math.nextAfter(Math.floor(max) + 1, -1),
cache.getMaxMean(), 0);
}
/**
* Test the withinRange function of the cache signals when construction
* cost is minimal.
*/
@Test
public void testWithinRange() {
final double min = PoissonSampler.PIVOT + 10;
final double max = PoissonSampler.PIVOT + 20;
final PoissonSamplerCache cache = createPoissonSamplerCache(min, max);
// Under the pivot point is always within range
Assert.assertTrue(cache.withinRange(PoissonSampler.PIVOT - 1));
Assert.assertFalse(cache.withinRange(min - 1));
Assert.assertTrue(cache.withinRange(min));
Assert.assertTrue(cache.withinRange(max));
Assert.assertFalse(cache.withinRange(max + 10));
}
// Edge cases for creating a Poisson sampler
/**
* Test createSharedStateSampler() with a bad mean.
*
* <p>Note this test actually tests the SmallMeanPoissonSampler throws.
*/
@Test(expected = IllegalArgumentException.class)
public void testCreateSharedStateSamplerThrowsWithZeroMean() {
final RestorableUniformRandomProvider rng =
RandomSource.create(RandomSource.SPLIT_MIX_64);
final PoissonSamplerCache cache = createPoissonSamplerCache();
cache.createSharedStateSampler(rng, 0);
}
/**
* Test createSharedStateSampler() with a mean that is too large.
*/
@Test(expected = IllegalArgumentException.class)
public void testCreateSharedStateSamplerThrowsWithNonIntegerMean() {
final RestorableUniformRandomProvider rng =
RandomSource.create(RandomSource.SPLIT_MIX_64);
final PoissonSamplerCache cache = createPoissonSamplerCache();
final double mean = Integer.MAX_VALUE + 1.0;
cache.createSharedStateSampler(rng, mean);
}
// Sampling tests
/**
* Test the cache returns the same samples as the PoissonSampler when it
* covers the entire range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerWithFullRangeCache() {
checkComputeSameSamplesAsPoissonSampler(minRange,
maxRange);
}
/**
* Test the cache returns the same samples as the PoissonSampler
* with no cache.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerWithNoCache() {
checkComputeSameSamplesAsPoissonSampler(0,
minRange - 2);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* partial cache covering the lower range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerWithPartialCacheCoveringLowerRange() {
checkComputeSameSamplesAsPoissonSampler(minRange,
midRange);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* partial cache covering the upper range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerWithPartialCacheCoveringUpperRange() {
checkComputeSameSamplesAsPoissonSampler(midRange,
maxRange);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* cache above the upper range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerWithCacheAboveTheUpperRange() {
checkComputeSameSamplesAsPoissonSampler(maxRange + 10,
maxRange + 20);
}
/**
* Check poisson samples are the same from the {@link PoissonSampler}
* and {@link PoissonSamplerCache}.
*
* @param minMean the min mean of the cache
* @param maxMean the max mean of the cache
*/
private void checkComputeSameSamplesAsPoissonSampler(int minMean,
int maxMean) {
// Two identical RNGs
final RestorableUniformRandomProvider rng1 =
RandomSource.create(RandomSource.WELL_19937_C);
final RandomProviderState state = rng1.saveState();
final RestorableUniformRandomProvider rng2 =
RandomSource.create(RandomSource.WELL_19937_C);
rng2.restoreState(state);
// Create the cache with the given range
final PoissonSamplerCache cache =
createPoissonSamplerCache(minMean, maxMean);
// Test all means in the test range (which may be different
// from the cache range).
for (int i = minRange; i <= maxRange; i++) {
// Test integer mean (no SmallMeanPoissonSampler required)
testPoissonSamples(rng1, rng2, cache, i);
// Test non-integer mean (SmallMeanPoissonSampler required)
testPoissonSamples(rng1, rng2, cache, i + 0.5);
}
}
/**
* Creates the poisson sampler cache with the given range.
*
* @param minMean the min mean
* @param maxMean the max mean
* @return the poisson sampler cache
*/
private static PoissonSamplerCache createPoissonSamplerCache(double minMean,
double maxMean) {
return new PoissonSamplerCache(minMean, maxMean);
}
/**
* Creates a poisson sampler cache that will have a valid range for the cache.
*
* @return the poisson sampler cache
*/
private static PoissonSamplerCache createPoissonSamplerCache() {
return new PoissonSamplerCache(PoissonSampler.PIVOT,
PoissonSampler.PIVOT + 10);
}
/**
* Test poisson samples are the same from the {@link PoissonSampler}
* and {@link PoissonSamplerCache}. The random providers must be
* identical (including state).
*
* @param rng1 the first random provider
* @param rng2 the second random provider
* @param cache the cache
* @param mean the mean
*/
private static void testPoissonSamples(
final RestorableUniformRandomProvider rng1,
final RestorableUniformRandomProvider rng2,
PoissonSamplerCache cache,
double mean) {
final DiscreteSampler s1 = PoissonSampler.of(rng1, mean);
final DiscreteSampler s2 = cache.createSharedStateSampler(rng2, mean);
for (int j = 0; j < 10; j++) {
Assert.assertEquals(s1.sample(), s2.sample());
}
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* a new cache reusing the entire range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheEntireRange() {
checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
maxRange,
midRange,
maxRange);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* a new cache reusing none of the range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheNoRange() {
checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
maxRange,
maxRange + 10,
maxRange + 20);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* a new cache reusing some of the lower range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheLowerRange() {
checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
maxRange,
minRange,
midRange + 1);
}
/**
* Test the cache returns the same samples as the PoissonSampler with
* a new cache reusing some of the upper range.
*/
@Test
public void testCanComputeSameSamplesAsPoissonSamplerReusingCacheUpperRange() {
checkComputeSameSamplesAsPoissonSamplerReusingCache(midRange,
maxRange,
maxRange - 1,
maxRange + 5);
}
/**
* Check poisson samples are the same from the {@link PoissonSampler}
* and a {@link PoissonSamplerCache} created reusing values.
*
* <p>Note: This cannot check the cache values were reused but ensures
* that a new cache created with a range functions correctly.
*
* @param minMean the min mean of the cache
* @param maxMean the max mean of the cache
* @param minMean2 the min mean of the second cache
* @param maxMean2 the max mean of the second cache
*/
private void checkComputeSameSamplesAsPoissonSamplerReusingCache(int minMean,
int maxMean,
int minMean2,
int maxMean2) {
// Two identical RNGs
final RestorableUniformRandomProvider rng1 =
RandomSource.create(RandomSource.WELL_19937_C);
final RandomProviderState state = rng1.saveState();
final RestorableUniformRandomProvider rng2 =
RandomSource.create(RandomSource.WELL_19937_C);
// Create the cache with the given range and fill it
final PoissonSamplerCache cache =
createPoissonSamplerCache(minMean, maxMean);
// Test all means in the test range (which may be different
// from the cache range).
for (int i = minMean; i <= maxMean; i++) {
cache.createSharedStateSampler(rng1, i);
}
final PoissonSamplerCache cache2 = cache.withRange(minMean2, maxMean2);
Assert.assertTrue("WithRange cache is the same object", cache != cache2);
rng1.restoreState(state);
rng2.restoreState(state);
// Test all means in the test range (which may be different
// from the cache range).
for (int i = minRange; i <= maxRange; i++) {
// Test integer mean (no SmallMeanPoissonSampler required)
testPoissonSamples(rng1, rng2, cache2, i);
// Test non-integer mean (SmallMeanPoissonSampler required)
testPoissonSamples(rng1, rng2, cache2, i + 0.5);
}
}
/**
* Explicit test for the deprecated method createPoissonSampler().
* All other uses of this method have been removed.
*/
@SuppressWarnings("deprecation")
@Test
public void testCreatePoissonSampler() {
final PoissonSamplerCache cache = createPoissonSamplerCache(0, 100);
final DiscreteSampler s2 = cache.createPoissonSampler(null, 42);
Assert.assertTrue(s2 instanceof LargeMeanPoissonSampler);
}
}