Repository: commons-rng Updated Branches: refs/heads/RNG-30__sampling [created] 74be2678f
RNG-30: Unit tests. Project: http://git-wip-us.apache.org/repos/asf/commons-rng/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-rng/commit/86c64bfb Tree: http://git-wip-us.apache.org/repos/asf/commons-rng/tree/86c64bfb Diff: http://git-wip-us.apache.org/repos/asf/commons-rng/diff/86c64bfb Branch: refs/heads/RNG-30__sampling Commit: 86c64bfbe9c2d7e77bfd0945e04b9f69c6bfd2cf Parents: 362789f Author: Gilles <er...@apache.org> Authored: Thu Nov 10 16:48:03 2016 +0100 Committer: Gilles <er...@apache.org> Committed: Thu Nov 10 16:54:08 2016 +0100 ---------------------------------------------------------------------- .../distribution/ContinuousSamplerTestData.java | 48 +++++++ .../distribution/ContinuousSamplersList.java | 119 +++++++++++++++ ...seMethodContinuousSamplerParametricTest.java | 143 +++++++++++++++++++ 3 files changed, 310 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-rng/blob/86c64bfb/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplerTestData.java ---------------------------------------------------------------------- diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplerTestData.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplerTestData.java new file mode 100644 index 0000000..a9754b4 --- /dev/null +++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplerTestData.java @@ -0,0 +1,48 @@ +/* + * 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 java.util.Arrays; + +import org.apache.commons.rng.sampling.ContinuousSampler; + +/** + * Data store for {@link InverseMethodContinuousParametricTest}. + */ +class ContinuousSamplerTestData { + private final ContinuousSampler sampler; + private final double[] deciles; + + public ContinuousSamplerTestData(ContinuousSampler sampler, + double[] deciles) { + this.sampler = sampler; + this.deciles = deciles.clone(); + } + + public ContinuousSampler getSampler() { + return sampler; + } + + public double[] getDeciles() { + return deciles.clone(); + } + + @Override + public String toString() { + return sampler.toString() + ": deciles=" + Arrays.toString(deciles); + } +} http://git-wip-us.apache.org/repos/asf/commons-rng/blob/86c64bfb/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplersList.java ---------------------------------------------------------------------- diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplersList.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplersList.java new file mode 100644 index 0000000..125e9c9 --- /dev/null +++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousSamplersList.java @@ -0,0 +1,119 @@ +/* + * 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 java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.sampling.ContinuousSampler; +import org.apache.commons.rng.simple.RandomSource; + +/** + * The purpose of this class is to provide a list of samplers + * that use the "inverse method" to sampler from distributions. + */ +public class ContinuousSamplersList { + /** List of all RNGs implemented in the library. */ + private static final List<ContinuousSamplerTestData[]> LIST = + new ArrayList<ContinuousSamplerTestData[]>(); + + static { + try { + // List of distributions to test. + + // 1. Gaussian + // 1.a Using the "inverse method". + final double mean = -123.45; + final double sigma = 6.789; + add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(mean, sigma), + RandomSource.create(RandomSource.KISS)); + // 1.b Using "Box-Muller". + add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(mean, sigma), + new BoxMullerGaussianSampler(mean, sigma, RandomSource.create(RandomSource.MT))); + + // 2. + } catch (Exception e) { + System.err.println("Unexpected exception while creating the list of samplers: " + e); + e.printStackTrace(System.err); + throw new RuntimeException(e); + } + } + + /** + * Class contains only static methods. + */ + private ContinuousSamplersList() {} + + /** + * @param list List of data (one the "parameters" tested by the Junit parametric test). + * @param dist Distribution to which the samples are supposed to conform. + * @param rng Generator of uniformly distributed sequences. + */ + private static void add(List<ContinuousSamplerTestData[]> list, + final org.apache.commons.math3.distribution.RealDistribution dist, + UniformRandomProvider rng) { + final ContinuousSampler inverseMethodSampler = + new InverseMethodContinuousSampler(rng, + new ContinuousInverseCumulativeProbabilityFunction() { + @Override + public double inverseCumulativeProbability(double p) { + return dist.inverseCumulativeProbability(p); + } + }); + list.add(new ContinuousSamplerTestData[] { new ContinuousSamplerTestData(inverseMethodSampler, + getDeciles(dist)) }); + } + + /** + * @param list List of data (one the "parameters" tested by the Junit parametric test). + * @param dist Distribution to which the samples are supposed to conform. + * @param sampler Sampler. + */ + private static void add(List<ContinuousSamplerTestData[]> list, + final org.apache.commons.math3.distribution.RealDistribution dist, + final ContinuousSampler sampler) { + list.add(new ContinuousSamplerTestData[] { new ContinuousSamplerTestData(sampler, + getDeciles(dist)) }); + } + + /** + * Subclasses that are "parametric" tests can forward the call to + * the "@Parameters"-annotated method to this method. + * + * @return the list of all generators. + */ + public static Iterable<ContinuousSamplerTestData[]> list() { + return Collections.unmodifiableList(LIST); + } + + /** + * @param dist Distribution. + * @return the deciles of the given distribution. + */ + private static double[] getDeciles(org.apache.commons.math3.distribution.RealDistribution dist) { + final int last = 9; + final double[] deciles = new double[last]; + final double ten = 10; + for (int i = 0; i < last; i++) { + deciles[i] = dist.inverseCumulativeProbability((i + 1) / ten); + } + return deciles; + } +} http://git-wip-us.apache.org/repos/asf/commons-rng/blob/86c64bfb/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/InverseMethodContinuousSamplerParametricTest.java ---------------------------------------------------------------------- diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/InverseMethodContinuousSamplerParametricTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/InverseMethodContinuousSamplerParametricTest.java new file mode 100644 index 0000000..5296033 --- /dev/null +++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/InverseMethodContinuousSamplerParametricTest.java @@ -0,0 +1,143 @@ +/* + * 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 java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.util.concurrent.Callable; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.Assume; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.RandomProviderState; +import org.apache.commons.rng.RestorableUniformRandomProvider; +import org.apache.commons.rng.core.RandomProviderDefaultState; +import org.apache.commons.rng.sampling.ContinuousSampler; + +/** + * Tests for random deviates generators using the "inverse method". + */ +@RunWith(value=Parameterized.class) +public class InverseMethodContinuousSamplerParametricTest { + /** Sampler under test. */ + private final ContinuousSamplerTestData sampler; + + /** + * Initializes generator instance. + * + * @param rng RNG to be tested. + */ + public InverseMethodContinuousSamplerParametricTest(ContinuousSamplerTestData data) { + sampler = data; + } + + @Parameters(name = "{index}: data={0}") + public static Iterable<ContinuousSamplerTestData[]> getList() { + return ContinuousSamplersList.list(); + } + + @Test + public void testSampling() { + check(10000, sampler.getSampler(), sampler.getDeciles()); + } + + /** + * Performs a chi-square test of homogeneity of the observed + * distribution with the expected distribution. + * Tests are performed at the 1% level and an average failure rate + * higher than 2% (i.e. more than 20 null hypothesis rejections) + * causes the test case to fail. + * + * @param sampler Sampler. + * @param sampleSize Number of random values to generate. + * @param deciles Deciles. + */ + private void check(long sampleSize, + ContinuousSampler sampler, + double[] deciles) { + final int numTests = 500; + + // Do not change (statistical test assumes that dof = 10). + final int numBins = 10; + + // Run the tests. + int numFailures = 0; + + final double[] expected = new double[numBins]; + for (int k = 0; k < numBins; k++) { + expected[k] = sampleSize / (double) numBins; + } + + final long[] observed = new long[numBins]; + // Chi-square critical value with 10 degrees of freedom + // and 1% significance level. + final double chi2CriticalValue = 23.209; + + try { + final int lastDecileIndex = numBins - 1; + for (int i = 0; i < numTests; i++) { + Arrays.fill(observed, 0); + SAMPLE: for (long j = 0; j < sampleSize; j++) { + final double value = sampler.sample(); + + for (int k = 0; k < lastDecileIndex; k++) { + if (value < deciles[k]) { + ++observed[k]; + continue SAMPLE; + } + } + ++observed[lastDecileIndex]; + } + + // Compute chi-square. + double chi2 = 0; + for (int k = 0; k < numBins; k++) { + final double diff = observed[k] - expected[k]; + chi2 += diff * diff / expected[k]; + // System.out.println("bin[" + k + "]" + + // " obs=" + observed[k] + + // " exp=" + expected[k]); + } + + // Statistics check. + if (chi2 > chi2CriticalValue) { + ++numFailures; + } + } + } catch (Exception e) { + // Should never happen. + throw new RuntimeException("Unexpected", e); + } + + if ((double) numFailures / (double) numTests > 0.02) { + Assert.fail(sampler + ": Too many failures for sample size = " + sampleSize + + " (" + numFailures + " out of " + numTests + " tests failed)"); + } + } +}