You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by br...@apache.org on 2009/04/06 03:25:34 UTC
svn commit: r762194 - in /commons/proper/math/trunk/src:
java/org/apache/commons/math/random/RandomDataImpl.java
test/org/apache/commons/math/random/RandomDataTest.java
Author: brentworden
Date: Mon Apr 6 01:25:34 2009
New Revision: 762194
URL: http://svn.apache.org/viewvc?rev=762194&view=rev
Log:
MATH-197. added rejection method to poisson random variates to help with large lamda values.
Modified:
commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java
commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java
Modified: commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java?rev=762194&r1=762193&r2=762194&view=diff
==============================================================================
--- commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java (original)
+++ commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java Mon Apr 6 01:25:34 2009
@@ -24,32 +24,36 @@
import java.security.NoSuchProviderException;
import java.util.Collection;
+import org.apache.commons.math.util.MathUtils;
+
/**
* Implements the {@link RandomData} interface using a {@link RandomGenerator}
- * instance to generate non-secure data and a
- * {@link java.security.SecureRandom} instance to provide data for the
- * <code>nextSecureXxx</code> methods. If no <code>RandomGenerator</code>
- * is provided in the constructor, the default is to use a generator based on
- * {@link java.util.Random}. To plug in a different implementation,
- * either implement <code>RandomGenerator</code> directly or extend
- * {@link AbstractRandomGenerator}.
+ * instance to generate non-secure data and a {@link java.security.SecureRandom}
+ * instance to provide data for the <code>nextSecureXxx</code> methods. If no
+ * <code>RandomGenerator</code> is provided in the constructor, the default is
+ * to use a generator based on {@link java.util.Random}. To plug in a different
+ * implementation, either implement <code>RandomGenerator</code> directly or
+ * extend {@link AbstractRandomGenerator}.
* <p>
- * Supports reseeding the underlying pseudo-random number generator (PRNG).
- * The <code>SecurityProvider</code> and <code>Algorithm</code>
- * used by the <code>SecureRandom</code> instance can also be reset.</p>
+ * Supports reseeding the underlying pseudo-random number generator (PRNG). The
+ * <code>SecurityProvider</code> and <code>Algorithm</code> used by the
+ * <code>SecureRandom</code> instance can also be reset.
+ * </p>
* <p>
* For details on the default PRNGs, see {@link java.util.Random} and
- * {@link java.security.SecureRandom}.</p>
+ * {@link java.security.SecureRandom}.
+ * </p>
* <p>
- * <strong>Usage Notes</strong>: <ul>
+ * <strong>Usage Notes</strong>:
+ * <ul>
* <li>
* Instance variables are used to maintain <code>RandomGenerator</code> and
- * <code>SecureRandom</code> instances used in data generation. Therefore,
- * to generate a random sequence of values or strings, you should use just
+ * <code>SecureRandom</code> instances used in data generation. Therefore, to
+ * generate a random sequence of values or strings, you should use just
* <strong>one</strong> <code>RandomDataImpl</code> instance repeatedly.</li>
* <li>
- * The "secure" methods are *much* slower. These should be used only when a
- * cryptographically secure random sequence is required. A secure random
+ * The "secure" methods are *much* slower. These should be used only when a
+ * cryptographically secure random sequence is required. A secure random
* sequence is a sequence of pseudo-random values which, in addition to being
* well-dispersed (so no subsequence of values is an any more likely than other
* subsequence of the the same length), also has the additional property that
@@ -57,562 +61,689 @@
* it any easier to predict subsequent values.</li>
* <li>
* When a new <code>RandomDataImpl</code> is created, the underlying random
- * number generators are <strong>not</strong> intialized. If you do not
- * explicitly seed the default non-secure generator, it is seeded with the current time
- * in milliseconds on first use. The same holds for the secure generator.
- * If you provide a <code>RandomGenerator</code> to the constructor, however,
- * this generator is not reseeded by the constructor nor is it reseeded on
- * first use. </li>
+ * number generators are <strong>not</strong> intialized. If you do not
+ * explicitly seed the default non-secure generator, it is seeded with the
+ * current time in milliseconds on first use. The same holds for the secure
+ * generator. If you provide a <code>RandomGenerator</code> to the constructor,
+ * however, this generator is not reseeded by the constructor nor is it reseeded
+ * on first use.</li>
* <li>
- * The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate
- * to the corresponding methods on the underlying <code>RandomGenerator</code>
- * and<code>SecureRandom</code> instances. Therefore,
- * <code>reSeed(long)</code> fully resets the initial state of the non-secure
- * random number generator (so that reseeding with a specific value always
- * results in the same subsequent random sequence); whereas reSeedSecure(long)
- * does <strong>not</strong> reinitialize the secure random number generator
- * (so secure sequences started with calls to reseedSecure(long) won't be
- * identical).</li>
+ * The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate to the
+ * corresponding methods on the underlying <code>RandomGenerator</code> and
+ * <code>SecureRandom</code> instances. Therefore, <code>reSeed(long)</code>
+ * fully resets the initial state of the non-secure random number generator (so
+ * that reseeding with a specific value always results in the same subsequent
+ * random sequence); whereas reSeedSecure(long) does <strong>not</strong>
+ * reinitialize the secure random number generator (so secure sequences started
+ * with calls to reseedSecure(long) won't be identical).</li>
* <li>
* This implementation is not synchronized.
- * </ul></p>
- *
- * @version $Revision$ $Date$
+ * </ul>
+ * </p>
+ *
+ * @version $Revision$ $Date: 2008-11-23 08:27:09 -0600 (Sun, 23 Nov
+ * 2008) $
*/
public class RandomDataImpl implements RandomData, Serializable {
- /** Serializable version identifier */
- private static final long serialVersionUID = -626730818244969716L;
+ /** Serializable version identifier */
+ private static final long serialVersionUID = -626730818244969716L;
- /** underlying random number generator */
- private RandomGenerator rand = null;
+ /** underlying random number generator */
+ private RandomGenerator rand = null;
- /** underlying secure random number generator */
- private SecureRandom secRand = null;
+ /** underlying secure random number generator */
+ private SecureRandom secRand = null;
- /**
- * Construct a RandomDataImpl.
- */
- public RandomDataImpl() {
- }
-
- /**
- * Construct a RandomDataImpl using the supplied {@link RandomGenerator}
- * as the source of (non-secure) random data.
- *
- * @param rand the source of (non-secure) random data
- * @since 1.1
- */
- public RandomDataImpl(RandomGenerator rand) {
- super();
- this.rand = rand;
- }
-
- /**
- * {@inheritDoc}<p>
- * <strong>Algorithm Description:</strong> hex strings are generated
- * using a 2-step process. <ol>
- * <li>
- * len/2+1 binary bytes are generated using the underlying Random</li>
- * <li>
- * Each binary byte is translated into 2 hex digits</li></ol></p>
- *
- * @param len the desired string length.
- * @return the random string.
- */
- public String nextHexString(int len) {
- if (len <= 0) {
- throw new IllegalArgumentException("length must be positive");
- }
-
- //Get a random number generator
- RandomGenerator ran = getRan();
-
- //Initialize output buffer
- StringBuffer outBuffer = new StringBuffer();
-
- //Get int(len/2)+1 random bytes
- byte[] randomBytes = new byte[(len / 2) + 1];
- ran.nextBytes(randomBytes);
-
- //Convert each byte to 2 hex digits
- for (int i = 0; i < randomBytes.length; i++) {
- Integer c = Integer.valueOf(randomBytes[i]);
-
- /* Add 128 to byte value to make interval 0-255 before
- * doing hex conversion.
- * This guarantees <= 2 hex digits from toHexString()
- * toHexString would otherwise add 2^32 to negative arguments.
- */
- String hex = Integer.toHexString(c.intValue() + 128);
-
- // Make sure we add 2 hex digits for each byte
- if (hex.length() == 1) {
- hex = "0" + hex;
- }
- outBuffer.append(hex);
- }
- return outBuffer.toString().substring(0, len);
- }
-
- /**
- * Generate a random int value uniformly distributed between
- * <code>lower</code> and <code>upper</code>, inclusive.
- *
- * @param lower the lower bound.
- * @param upper the upper bound.
- * @return the random integer.
- */
- public int nextInt(int lower, int upper) {
- if (lower >= upper) {
- throw new IllegalArgumentException
- ("upper bound must be > lower bound");
- }
- RandomGenerator rand = getRan();
- double r = rand.nextDouble();
- return (int)((r * upper) + ((1.0 - r) * lower) + r);
- }
-
- /**
- * Generate a random long value uniformly distributed between
- * <code>lower</code> and <code>upper</code>, inclusive.
- *
- * @param lower the lower bound.
- * @param upper the upper bound.
- * @return the random integer.
- */
- public long nextLong(long lower, long upper) {
- if (lower >= upper) {
- throw new IllegalArgumentException
- ("upper bound must be > lower bound");
- }
- RandomGenerator rand = getRan();
- double r = rand.nextDouble();
- return (long)((r * upper) + ((1.0 - r) * lower) + r);
- }
-
- /**
- * {@inheritDoc}<p>
- * <strong>Algorithm Description:</strong> hex strings are generated in
- * 40-byte segments using a 3-step process. <ol>
- * <li>
- * 20 random bytes are generated using the underlying
- * <code>SecureRandom</code>.</li>
- * <li>
- * SHA-1 hash is applied to yield a 20-byte binary digest.</li>
- * <li>
- * Each byte of the binary digest is converted to 2 hex digits.</li></ol>
- * </p>
- *
- * @param len the length of the generated string
- * @return the random string
- */
- public String nextSecureHexString(int len) {
- if (len <= 0) {
- throw new IllegalArgumentException("length must be positive");
- }
-
- // Get SecureRandom and setup Digest provider
- SecureRandom secRan = getSecRan();
- MessageDigest alg = null;
- try {
- alg = MessageDigest.getInstance("SHA-1");
- } catch (NoSuchAlgorithmException ex) {
- return null; // gulp FIXME? -- this *should* never fail.
- }
- alg.reset();
-
- //Compute number of iterations required (40 bytes each)
- int numIter = (len / 40) + 1;
-
- StringBuffer outBuffer = new StringBuffer();
- for (int iter = 1; iter < numIter + 1; iter++) {
- byte[] randomBytes = new byte[40];
- secRan.nextBytes(randomBytes);
- alg.update(randomBytes);
-
- //Compute hash -- will create 20-byte binary hash
- byte hash[] = alg.digest();
-
- //Loop over the hash, converting each byte to 2 hex digits
- for (int i = 0; i < hash.length; i++) {
- Integer c = Integer.valueOf(hash[i]);
-
- /* Add 128 to byte value to make interval 0-255
- * This guarantees <= 2 hex digits from toHexString()
- * toHexString would otherwise add 2^32 to negative
- * arguments
- */
- String hex = Integer.toHexString(c.intValue() + 128);
-
- //Keep strings uniform length -- guarantees 40 bytes
- if (hex.length() == 1) {
- hex = "0" + hex;
- }
- outBuffer.append(hex);
- }
- }
- return outBuffer.toString().substring(0, len);
- }
-
- /**
- * Generate a random int value uniformly distributed between
- * <code>lower</code> and <code>upper</code>, inclusive. This algorithm
- * uses a secure random number generator.
- *
- * @param lower the lower bound.
- * @param upper the upper bound.
- * @return the random integer.
- */
- public int nextSecureInt(int lower, int upper) {
- if (lower >= upper) {
- throw new IllegalArgumentException
- ("lower bound must be < upper bound");
- }
- SecureRandom sec = getSecRan();
- return lower + (int) (sec.nextDouble() * (upper - lower + 1));
- }
-
- /**
- * Generate a random long value uniformly distributed between
- * <code>lower</code> and <code>upper</code>, inclusive. This algorithm
- * uses a secure random number generator.
- *
- * @param lower the lower bound.
- * @param upper the upper bound.
- * @return the random integer.
- */
- public long nextSecureLong(long lower, long upper) {
- if (lower >= upper) {
- throw new IllegalArgumentException
- ("lower bound must be < upper bound");
- }
- SecureRandom sec = getSecRan();
- return lower + (long) (sec.nextDouble() * (upper - lower + 1));
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * <strong>Algorithm Description</strong>:
- * Uses simulation of a Poisson process using Uniform deviates, as
- * described
- * <a href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm">
- * here.</a></p>
- * <p>
- * The Poisson process (and hence value returned) is bounded by
- * 1000 * mean.</p>
- *
- * @param mean mean of the Poisson distribution.
- * @return the random Poisson value.
- */
- public long nextPoisson(double mean) {
- if (mean <= 0) {
- throw new IllegalArgumentException("Poisson mean must be > 0");
- }
- double p = Math.exp(-mean);
- long n = 0;
- double r = 1.0d;
- double rnd = 1.0d;
- RandomGenerator rand = getRan();
- while (n < 1000 * mean) {
- rnd = rand.nextDouble();
- r = r * rnd;
- if (r >= p) {
- n++;
- } else {
- return n;
- }
- }
- return n;
- }
-
- /**
- * Generate a random value from a Normal (a.k.a. Gaussian) distribution
- * with the given mean, <code>mu</code> and the given standard deviation,
- * <code>sigma</code>.
- *
- * @param mu the mean of the distribution
- * @param sigma the standard deviation of the distribution
- * @return the random Normal value
- */
- public double nextGaussian(double mu, double sigma) {
- if (sigma <= 0) {
- throw new IllegalArgumentException("Gaussian std dev must be > 0");
- }
- RandomGenerator rand = getRan();
- return sigma * rand.nextGaussian() + mu;
- }
-
- /**
- * Returns a random value from an Exponential distribution with the given
- * mean.
- * <p>
- * <strong>Algorithm Description</strong>: Uses the
- * <a href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html">
- * Inversion Method</a> to generate exponentially distributed random values
- * from uniform deviates.</p>
- *
- * @param mean the mean of the distribution
- * @return the random Exponential value
- */
- public double nextExponential(double mean) {
- if (mean < 0.0) {
- throw new IllegalArgumentException
- ("Exponential mean must be >= 0");
- }
- RandomGenerator rand = getRan();
- double unif = rand.nextDouble();
- while (unif == 0.0d) {
- unif = rand.nextDouble();
- }
- return -mean * Math.log(unif);
- }
-
- /**
- * {@inheritDoc}<p>
- * <strong>Algorithm Description</strong>: scales the output of
- * Random.nextDouble(), but rejects 0 values (i.e., will generate another
- * random double if Random.nextDouble() returns 0).
- * This is necessary to provide a symmetric output interval
- * (both endpoints excluded).</p>
- *
- * @param lower the lower bound.
- * @param upper the upper bound.
- * @return a uniformly distributed random value from the interval (lower, upper)
- */
- public double nextUniform(double lower, double upper) {
- if (lower >= upper) {
- throw new IllegalArgumentException
- ("lower bound must be < upper bound");
- }
- RandomGenerator rand = getRan();
-
- // ensure nextDouble() isn't 0.0
- double u = rand.nextDouble();
- while(u <= 0.0){
- u = rand.nextDouble();
- }
-
- return lower + u * (upper - lower);
- }
-
- /**
- * Returns the RandomGenerator used to generate non-secure
- * random data.
- * <p>
- * Creates and initializes a default generator if null.</p>
- *
- * @return the Random used to generate random data
- * @since 1.1
- */
- private RandomGenerator getRan() {
- if (rand == null) {
- rand = new JDKRandomGenerator();
- rand.setSeed(System.currentTimeMillis());
- }
- return rand;
- }
-
- /**
- * Returns the SecureRandom used to generate secure random data.
- * <p>
- * Creates and initializes if null.</p>
- *
- * @return the SecureRandom used to generate secure random data
- */
- private SecureRandom getSecRan() {
- if (secRand == null) {
- secRand = new SecureRandom();
- secRand.setSeed(System.currentTimeMillis());
- }
- return secRand;
- }
-
- /**
- * Reseeds the random number generator with the supplied seed.
- * <p>
- * Will create and initialize if null.</p>
- *
- * @param seed the seed value to use
- */
- public void reSeed(long seed) {
- if (rand == null) {
- rand = new JDKRandomGenerator();
- }
- rand.setSeed(seed);
- }
-
- /**
- * Reseeds the secure random number generator with the current time
- * in milliseconds.
- * <p>
- * Will create and initialize if null.</p>
- */
- public void reSeedSecure() {
- if (secRand == null) {
- secRand = new SecureRandom();
- }
- secRand.setSeed(System.currentTimeMillis());
- }
-
- /**
- * Reseeds the secure random number generator with the supplied seed.
- * <p>
- * Will create and initialize if null.</p>
- *
- * @param seed the seed value to use
- */
- public void reSeedSecure(long seed) {
- if (secRand == null) {
- secRand = new SecureRandom();
- }
- secRand.setSeed(seed);
- }
-
- /**
- * Reseeds the random number generator with the current time
- * in milliseconds.
- */
- public void reSeed() {
- if (rand == null) {
- rand = new JDKRandomGenerator();
- }
- rand.setSeed(System.currentTimeMillis());
- }
-
- /**
- * Sets the PRNG algorithm for the underlying SecureRandom instance
- * using the Security Provider API. The Security Provider API is defined in
- * <a href="http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA">
- * Java Cryptography Architecture API Specification & Reference.</a>
- * <p>
- * <strong>USAGE NOTE:</strong> This method carries <i>significant</i>
- * overhead and may take several seconds to execute.
- * </p>
- *
- * @param algorithm the name of the PRNG algorithm
- * @param provider the name of the provider
- * @throws NoSuchAlgorithmException if the specified algorithm
- * is not available
- * @throws NoSuchProviderException if the specified provider
- * is not installed
- */
- public void setSecureAlgorithm(String algorithm, String provider)
- throws NoSuchAlgorithmException, NoSuchProviderException {
- secRand = SecureRandom.getInstance(algorithm, provider);
- }
-
- /**
- * Generates an integer array of length <code>k</code> whose entries
- * are selected randomly, without repetition, from the integers
- * <code>0 through n-1</code> (inclusive).
- * <p>
- * Generated arrays represent permutations
- * of <code>n</code> taken <code>k</code> at a time.</p>
- * <p>
- * <strong>Preconditions:</strong><ul>
- * <li> <code>k <= n</code></li>
- * <li> <code>n > 0</code> </li>
- * </ul>
- * If the preconditions are not met, an IllegalArgumentException is
- * thrown.</p>
- * <p>
- * Uses a 2-cycle permutation shuffle. The shuffling process is described
- * <a href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
- * here</a>.</p>
- *
- * @param n domain of the permutation (must be positive)
- * @param k size of the permutation (must satisfy 0 < k <= n).
- * @return the random permutation as an int array
- */
- public int[] nextPermutation(int n, int k) {
- if (k > n) {
- throw new IllegalArgumentException
- ("permutation k exceeds n");
- }
- if (k == 0) {
- throw new IllegalArgumentException
- ("permutation k must be > 0");
- }
-
- int[] index = getNatural(n);
- shuffle(index, n - k);
- int[] result = new int[k];
- for (int i = 0; i < k; i++) {
- result[i] = index[n - i - 1];
- }
-
- return result;
- }
-
- /**
- * Uses a 2-cycle permutation shuffle to generate a random permutation.
- * <strong>Algorithm Description</strong>: Uses a 2-cycle permutation
- * shuffle to generate a random permutation of <code>c.size()</code> and
- * then returns the elements whose indexes correspond to the elements of
- * the generated permutation.
- * This technique is described, and proven to generate random samples,
- * <a href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
- * here</a>
- * @param c Collection to sample from.
- * @param k sample size.
- * @return the random sample.
- */
- public Object[] nextSample(Collection<?> c, int k) {
- int len = c.size();
- if (k > len) {
- throw new IllegalArgumentException
- ("sample size exceeds collection size");
- }
- if (k == 0) {
- throw new IllegalArgumentException
- ("sample size must be > 0");
- }
-
- Object[] objects = c.toArray();
- int[] index = nextPermutation(len, k);
- Object[] result = new Object[k];
- for (int i = 0; i < k; i++) {
- result[i] = objects[index[i]];
- }
- return result;
- }
-
- //------------------------Private methods----------------------------------
-
- /**
- * Uses a 2-cycle permutation shuffle to randomly re-order the last elements
- * of list.
- *
- * @param list list to be shuffled
- * @param end element past which shuffling begins
- */
- private void shuffle(int[] list, int end) {
- int target = 0;
- for (int i = list.length - 1 ; i >= end; i--) {
- if (i == 0) {
- target = 0;
- } else {
- target = nextInt(0, i);
- }
- int temp = list[target];
- list[target] = list[i];
- list[i] = temp;
- }
- }
-
- /**
- * Returns an array representing n.
- *
- * @param n the natural number to represent
- * @return array with entries = elements of n
- */
- private int[] getNatural(int n) {
- int[] natural = new int[n];
- for (int i = 0; i < n; i++) {
- natural[i] = i;
- }
- return natural;
- }
+ /**
+ * Construct a RandomDataImpl.
+ */
+ public RandomDataImpl() {
+ }
+
+ /**
+ * Construct a RandomDataImpl using the supplied {@link RandomGenerator} as
+ * the source of (non-secure) random data.
+ *
+ * @param rand
+ * the source of (non-secure) random data
+ * @since 1.1
+ */
+ public RandomDataImpl(RandomGenerator rand) {
+ super();
+ this.rand = rand;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Algorithm Description:</strong> hex strings are generated using a
+ * 2-step process.
+ * <ol>
+ * <li>
+ * len/2+1 binary bytes are generated using the underlying Random</li>
+ * <li>
+ * Each binary byte is translated into 2 hex digits</li>
+ * </ol>
+ * </p>
+ *
+ * @param len
+ * the desired string length.
+ * @return the random string.
+ */
+ public String nextHexString(int len) {
+ if (len <= 0) {
+ throw new IllegalArgumentException("length must be positive");
+ }
+
+ // Get a random number generator
+ RandomGenerator ran = getRan();
+
+ // Initialize output buffer
+ StringBuffer outBuffer = new StringBuffer();
+
+ // Get int(len/2)+1 random bytes
+ byte[] randomBytes = new byte[(len / 2) + 1];
+ ran.nextBytes(randomBytes);
+
+ // Convert each byte to 2 hex digits
+ for (int i = 0; i < randomBytes.length; i++) {
+ Integer c = Integer.valueOf(randomBytes[i]);
+
+ /*
+ * Add 128 to byte value to make interval 0-255 before doing hex
+ * conversion. This guarantees <= 2 hex digits from toHexString()
+ * toHexString would otherwise add 2^32 to negative arguments.
+ */
+ String hex = Integer.toHexString(c.intValue() + 128);
+
+ // Make sure we add 2 hex digits for each byte
+ if (hex.length() == 1) {
+ hex = "0" + hex;
+ }
+ outBuffer.append(hex);
+ }
+ return outBuffer.toString().substring(0, len);
+ }
+
+ /**
+ * Generate a random int value uniformly distributed between
+ * <code>lower</code> and <code>upper</code>, inclusive.
+ *
+ * @param lower
+ * the lower bound.
+ * @param upper
+ * the upper bound.
+ * @return the random integer.
+ */
+ public int nextInt(int lower, int upper) {
+ if (lower >= upper) {
+ throw new IllegalArgumentException(
+ "upper bound must be > lower bound");
+ }
+ RandomGenerator rand = getRan();
+ double r = rand.nextDouble();
+ return (int) ((r * upper) + ((1.0 - r) * lower) + r);
+ }
+
+ /**
+ * Generate a random long value uniformly distributed between
+ * <code>lower</code> and <code>upper</code>, inclusive.
+ *
+ * @param lower
+ * the lower bound.
+ * @param upper
+ * the upper bound.
+ * @return the random integer.
+ */
+ public long nextLong(long lower, long upper) {
+ if (lower >= upper) {
+ throw new IllegalArgumentException(
+ "upper bound must be > lower bound");
+ }
+ RandomGenerator rand = getRan();
+ double r = rand.nextDouble();
+ return (long) ((r * upper) + ((1.0 - r) * lower) + r);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Algorithm Description:</strong> hex strings are generated in
+ * 40-byte segments using a 3-step process.
+ * <ol>
+ * <li>
+ * 20 random bytes are generated using the underlying
+ * <code>SecureRandom</code>.</li>
+ * <li>
+ * SHA-1 hash is applied to yield a 20-byte binary digest.</li>
+ * <li>
+ * Each byte of the binary digest is converted to 2 hex digits.</li>
+ * </ol>
+ * </p>
+ *
+ * @param len
+ * the length of the generated string
+ * @return the random string
+ */
+ public String nextSecureHexString(int len) {
+ if (len <= 0) {
+ throw new IllegalArgumentException("length must be positive");
+ }
+
+ // Get SecureRandom and setup Digest provider
+ SecureRandom secRan = getSecRan();
+ MessageDigest alg = null;
+ try {
+ alg = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException ex) {
+ return null; // gulp FIXME? -- this *should* never fail.
+ }
+ alg.reset();
+
+ // Compute number of iterations required (40 bytes each)
+ int numIter = (len / 40) + 1;
+
+ StringBuffer outBuffer = new StringBuffer();
+ for (int iter = 1; iter < numIter + 1; iter++) {
+ byte[] randomBytes = new byte[40];
+ secRan.nextBytes(randomBytes);
+ alg.update(randomBytes);
+
+ // Compute hash -- will create 20-byte binary hash
+ byte hash[] = alg.digest();
+
+ // Loop over the hash, converting each byte to 2 hex digits
+ for (int i = 0; i < hash.length; i++) {
+ Integer c = Integer.valueOf(hash[i]);
+
+ /*
+ * Add 128 to byte value to make interval 0-255 This guarantees
+ * <= 2 hex digits from toHexString() toHexString would
+ * otherwise add 2^32 to negative arguments
+ */
+ String hex = Integer.toHexString(c.intValue() + 128);
+
+ // Keep strings uniform length -- guarantees 40 bytes
+ if (hex.length() == 1) {
+ hex = "0" + hex;
+ }
+ outBuffer.append(hex);
+ }
+ }
+ return outBuffer.toString().substring(0, len);
+ }
+
+ /**
+ * Generate a random int value uniformly distributed between
+ * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+ * a secure random number generator.
+ *
+ * @param lower
+ * the lower bound.
+ * @param upper
+ * the upper bound.
+ * @return the random integer.
+ */
+ public int nextSecureInt(int lower, int upper) {
+ if (lower >= upper) {
+ throw new IllegalArgumentException(
+ "lower bound must be < upper bound");
+ }
+ SecureRandom sec = getSecRan();
+ return lower + (int) (sec.nextDouble() * (upper - lower + 1));
+ }
+
+ /**
+ * Generate a random long value uniformly distributed between
+ * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+ * a secure random number generator.
+ *
+ * @param lower
+ * the lower bound.
+ * @param upper
+ * the upper bound.
+ * @return the random integer.
+ */
+ public long nextSecureLong(long lower, long upper) {
+ if (lower >= upper) {
+ throw new IllegalArgumentException(
+ "lower bound must be < upper bound");
+ }
+ SecureRandom sec = getSecRan();
+ return lower + (long) (sec.nextDouble() * (upper - lower + 1));
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Algorithm Description</strong>: For small means, uses simulation
+ * of a Poisson process using Uniform deviates, as described <a
+ * href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm"> here.</a>
+ * </p>
+ * <p>
+ * The Poisson process (and hence value returned) is bounded by 1000 * mean.
+ * </p>
+ *
+ * <p>
+ * For large means, uses a reject method as described in <a
+ * href="http://cg.scs.carleton.ca/~luc/rnbookindex.html">Non-Uniform Random
+ * Variate Generation</a>
+ * </p>
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li>Devroye, Luc. (1986). <i>Non-Uniform Random Variate Generation</i>.
+ * New York, NY. Springer-Verlag</li>
+ * </ul>
+ * </p>
+ *
+ * @param mean
+ * mean of the Poisson distribution.
+ * @return the random Poisson value.
+ */
+ public long nextPoisson(double mean) {
+ if (mean <= 0) {
+ throw new IllegalArgumentException("Poisson mean must be > 0");
+ }
+
+ RandomGenerator rand = getRan();
+
+ double pivot = 6.0;
+ if (mean < pivot) {
+ double p = Math.exp(-mean);
+ long n = 0;
+ double r = 1.0d;
+ double rnd = 1.0d;
+
+ while (n < 1000 * mean) {
+ rnd = rand.nextDouble();
+ r = r * rnd;
+ if (r >= p) {
+ n++;
+ } else {
+ return n;
+ }
+ }
+ return n;
+ } else {
+ double mu = Math.floor(mean);
+ double delta = Math.floor(pivot + (mu - pivot) / 2.0); // integer
+ // between 6
+ // and mean
+ double mu2delta = 2.0 * mu + delta;
+ double muDeltaHalf = mu + delta / 2.0;
+ double logMeanMu = Math.log(mean / mu);
+
+ double muFactorialLog = MathUtils.factorialLog((int) mu);
+
+ double c1 = Math.sqrt(Math.PI * mu / 2.0);
+ double c2 = c1
+ + Math.sqrt(Math.PI * muDeltaHalf
+ / (2.0 * Math.exp(1.0 / mu2delta)));
+ double c3 = c2 + 2.0;
+ double c4 = c3 + Math.exp(1.0 / 78.0);
+ double c = c4 + 2.0 / delta * mu2delta
+ * Math.exp(-delta / mu2delta * (1.0 + delta / 2.0));
+
+ double y = 0.0;
+ double x = 0.0;
+ double w = Double.POSITIVE_INFINITY;
+
+ boolean accept = false;
+ while (!accept) {
+ double u = nextUniform(0.0, c);
+ double e = nextExponential(mean);
+
+ if (u <= c1) {
+ double z = nextGaussian(0.0, 1.0);
+ y = -Math.abs(z) * Math.sqrt(mu) - 1.0;
+ x = Math.floor(y);
+ w = -z * z / 2.0 - e - x * logMeanMu;
+ if (x < -mu) {
+ w = Double.POSITIVE_INFINITY;
+ }
+ } else if (c1 < u && u <= c2) {
+ double z = nextGaussian(0.0, 1.0);
+ y = 1.0 + Math.abs(z) * Math.sqrt(muDeltaHalf);
+ x = Math.ceil(y);
+ w = (-y * y + 2.0 * y) / mu2delta - e - x * logMeanMu;
+ if (x > delta) {
+ w = Double.POSITIVE_INFINITY;
+ }
+ } else if (c2 < u && u <= c3) {
+ x = 0.0;
+ w = -e;
+ } else if (c3 < u && u <= c4) {
+ x = 1.0;
+ w = -e - logMeanMu;
+ } else if (c4 < u) {
+ double v = nextExponential(mean);
+ y = delta + v * 2.0 / delta * mu2delta;
+ x = Math.ceil(y);
+ w = -delta / mu2delta * (1.0 + y / 2.0) - e - x * logMeanMu;
+ }
+ accept = (w <= x * Math.log(mu)
+ - MathUtils.factorialLog((int) (mu + x))
+ / muFactorialLog);
+ }
+ // cast to long is acceptable because both x and mu are whole
+ // numbers.
+ return (long) (x + mu);
+ }
+ }
+
+ /**
+ * Generate a random value from a Normal (a.k.a. Gaussian) distribution with
+ * the given mean, <code>mu</code> and the given standard deviation,
+ * <code>sigma</code>.
+ *
+ * @param mu
+ * the mean of the distribution
+ * @param sigma
+ * the standard deviation of the distribution
+ * @return the random Normal value
+ */
+ public double nextGaussian(double mu, double sigma) {
+ if (sigma <= 0) {
+ throw new IllegalArgumentException("Gaussian std dev must be > 0");
+ }
+ RandomGenerator rand = getRan();
+ return sigma * rand.nextGaussian() + mu;
+ }
+
+ /**
+ * Returns a random value from an Exponential distribution with the given
+ * mean.
+ * <p>
+ * <strong>Algorithm Description</strong>: Uses the <a
+ * href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html"> Inversion
+ * Method</a> to generate exponentially distributed random values from
+ * uniform deviates.
+ * </p>
+ *
+ * @param mean
+ * the mean of the distribution
+ * @return the random Exponential value
+ */
+ public double nextExponential(double mean) {
+ if (mean < 0.0) {
+ throw new IllegalArgumentException("Exponential mean must be >= 0");
+ }
+ RandomGenerator rand = getRan();
+ double unif = rand.nextDouble();
+ while (unif == 0.0d) {
+ unif = rand.nextDouble();
+ }
+ return -mean * Math.log(unif);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <strong>Algorithm Description</strong>: scales the output of
+ * Random.nextDouble(), but rejects 0 values (i.e., will generate another
+ * random double if Random.nextDouble() returns 0). This is necessary to
+ * provide a symmetric output interval (both endpoints excluded).
+ * </p>
+ *
+ * @param lower
+ * the lower bound.
+ * @param upper
+ * the upper bound.
+ * @return a uniformly distributed random value from the interval (lower,
+ * upper)
+ */
+ public double nextUniform(double lower, double upper) {
+ if (lower >= upper) {
+ throw new IllegalArgumentException(
+ "lower bound must be < upper bound");
+ }
+ RandomGenerator rand = getRan();
+
+ // ensure nextDouble() isn't 0.0
+ double u = rand.nextDouble();
+ while (u <= 0.0) {
+ u = rand.nextDouble();
+ }
+
+ return lower + u * (upper - lower);
+ }
+
+ /**
+ * Returns the RandomGenerator used to generate non-secure random data.
+ * <p>
+ * Creates and initializes a default generator if null.
+ * </p>
+ *
+ * @return the Random used to generate random data
+ * @since 1.1
+ */
+ private RandomGenerator getRan() {
+ if (rand == null) {
+ rand = new JDKRandomGenerator();
+ rand.setSeed(System.currentTimeMillis());
+ }
+ return rand;
+ }
+
+ /**
+ * Returns the SecureRandom used to generate secure random data.
+ * <p>
+ * Creates and initializes if null.
+ * </p>
+ *
+ * @return the SecureRandom used to generate secure random data
+ */
+ private SecureRandom getSecRan() {
+ if (secRand == null) {
+ secRand = new SecureRandom();
+ secRand.setSeed(System.currentTimeMillis());
+ }
+ return secRand;
+ }
+
+ /**
+ * Reseeds the random number generator with the supplied seed.
+ * <p>
+ * Will create and initialize if null.
+ * </p>
+ *
+ * @param seed
+ * the seed value to use
+ */
+ public void reSeed(long seed) {
+ if (rand == null) {
+ rand = new JDKRandomGenerator();
+ }
+ rand.setSeed(seed);
+ }
+
+ /**
+ * Reseeds the secure random number generator with the current time in
+ * milliseconds.
+ * <p>
+ * Will create and initialize if null.
+ * </p>
+ */
+ public void reSeedSecure() {
+ if (secRand == null) {
+ secRand = new SecureRandom();
+ }
+ secRand.setSeed(System.currentTimeMillis());
+ }
+
+ /**
+ * Reseeds the secure random number generator with the supplied seed.
+ * <p>
+ * Will create and initialize if null.
+ * </p>
+ *
+ * @param seed
+ * the seed value to use
+ */
+ public void reSeedSecure(long seed) {
+ if (secRand == null) {
+ secRand = new SecureRandom();
+ }
+ secRand.setSeed(seed);
+ }
+
+ /**
+ * Reseeds the random number generator with the current time in
+ * milliseconds.
+ */
+ public void reSeed() {
+ if (rand == null) {
+ rand = new JDKRandomGenerator();
+ }
+ rand.setSeed(System.currentTimeMillis());
+ }
+
+ /**
+ * Sets the PRNG algorithm for the underlying SecureRandom instance using
+ * the Security Provider API. The Security Provider API is defined in <a
+ * href =
+ * "http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA">
+ * Java Cryptography Architecture API Specification & Reference.</a>
+ * <p>
+ * <strong>USAGE NOTE:</strong> This method carries <i>significant</i>
+ * overhead and may take several seconds to execute.
+ * </p>
+ *
+ * @param algorithm
+ * the name of the PRNG algorithm
+ * @param provider
+ * the name of the provider
+ * @throws NoSuchAlgorithmException
+ * if the specified algorithm is not available
+ * @throws NoSuchProviderException
+ * if the specified provider is not installed
+ */
+ public void setSecureAlgorithm(String algorithm, String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException {
+ secRand = SecureRandom.getInstance(algorithm, provider);
+ }
+
+ /**
+ * Generates an integer array of length <code>k</code> whose entries are
+ * selected randomly, without repetition, from the integers
+ * <code>0 through n-1</code> (inclusive).
+ * <p>
+ * Generated arrays represent permutations of <code>n</code> taken
+ * <code>k</code> at a time.
+ * </p>
+ * <p>
+ * <strong>Preconditions:</strong>
+ * <ul>
+ * <li> <code>k <= n</code></li>
+ * <li> <code>n > 0</code></li>
+ * </ul>
+ * If the preconditions are not met, an IllegalArgumentException is thrown.
+ * </p>
+ * <p>
+ * Uses a 2-cycle permutation shuffle. The shuffling process is described <a
+ * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
+ * here</a>.
+ * </p>
+ *
+ * @param n
+ * domain of the permutation (must be positive)
+ * @param k
+ * size of the permutation (must satisfy 0 < k <= n).
+ * @return the random permutation as an int array
+ */
+ public int[] nextPermutation(int n, int k) {
+ if (k > n) {
+ throw new IllegalArgumentException("permutation k exceeds n");
+ }
+ if (k == 0) {
+ throw new IllegalArgumentException("permutation k must be > 0");
+ }
+
+ int[] index = getNatural(n);
+ shuffle(index, n - k);
+ int[] result = new int[k];
+ for (int i = 0; i < k; i++) {
+ result[i] = index[n - i - 1];
+ }
+
+ return result;
+ }
+
+ /**
+ * Uses a 2-cycle permutation shuffle to generate a random permutation.
+ * <strong>Algorithm Description</strong>: Uses a 2-cycle permutation
+ * shuffle to generate a random permutation of <code>c.size()</code> and
+ * then returns the elements whose indexes correspond to the elements of the
+ * generated permutation. This technique is described, and proven to
+ * generate random samples, <a
+ * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
+ * here</a>
+ *
+ * @param c
+ * Collection to sample from.
+ * @param k
+ * sample size.
+ * @return the random sample.
+ */
+ public Object[] nextSample(Collection<?> c, int k) {
+ int len = c.size();
+ if (k > len) {
+ throw new IllegalArgumentException(
+ "sample size exceeds collection size");
+ }
+ if (k == 0) {
+ throw new IllegalArgumentException("sample size must be > 0");
+ }
+
+ Object[] objects = c.toArray();
+ int[] index = nextPermutation(len, k);
+ Object[] result = new Object[k];
+ for (int i = 0; i < k; i++) {
+ result[i] = objects[index[i]];
+ }
+ return result;
+ }
+
+ // ------------------------Private methods----------------------------------
+
+ /**
+ * Uses a 2-cycle permutation shuffle to randomly re-order the last elements
+ * of list.
+ *
+ * @param list
+ * list to be shuffled
+ * @param end
+ * element past which shuffling begins
+ */
+ private void shuffle(int[] list, int end) {
+ int target = 0;
+ for (int i = list.length - 1; i >= end; i--) {
+ if (i == 0) {
+ target = 0;
+ } else {
+ target = nextInt(0, i);
+ }
+ int temp = list[target];
+ list[target] = list[i];
+ list[i] = temp;
+ }
+ }
+
+ /**
+ * Returns an array representing n.
+ *
+ * @param n
+ * the natural number to represent
+ * @return array with entries = elements of n
+ */
+ private int[] getNatural(int n) {
+ int[] natural = new int[n];
+ for (int i = 0; i < n; i++) {
+ natural[i] = i;
+ }
+ return natural;
+ }
}
Modified: commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java?rev=762194&r1=762193&r2=762194&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java (original)
+++ commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java Mon Apr 6 01:25:34 2009
@@ -19,6 +19,7 @@
import junit.framework.Test;
import junit.framework.TestSuite;
import java.util.HashSet;
+import java.util.Iterator;
import org.apache.commons.math.RetryTestCase;
import org.apache.commons.math.stat.Frequency;
@@ -27,589 +28,599 @@
/**
* Test cases for the RandomData class.
- *
- * @version $Revision$ $Date$
+ *
+ * @version $Revision$ $Date: 2009-04-05 11:55:59 -0500 (Sun, 05 Apr
+ * 2009) $
*/
public class RandomDataTest extends RetryTestCase {
- public RandomDataTest(String name) {
- super(name);
- randomData = new RandomDataImpl();
- }
-
- protected long smallSampleSize = 1000;
- protected double[] expected = {250,250,250,250};
- protected int largeSampleSize = 10000;
- private String[] hex =
- {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
- protected RandomDataImpl randomData = null;
- protected ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
-
- public static Test suite() {
- TestSuite suite = new TestSuite(RandomDataTest.class);
- suite.setName("RandomData Tests");
- return suite;
- }
-
- public void testNextIntExtremeValues() {
- int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
- int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
- assertFalse(x == y);
- }
-
- public void testNextLongExtremeValues() {
- long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
- long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
- assertFalse(x == y);
- }
-
- /** test dispersion and failure modes for nextInt() */
- public void testNextInt() {
- try {
- randomData.nextInt(4,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- Frequency freq = new Frequency();
- int value = 0;
- for (int i=0;i<smallSampleSize;i++) {
- value = randomData.nextInt(0,3);
- assertTrue("nextInt range",(value >= 0) && (value <= 3));
- freq.addValue(value);
- }
- long[] observed = new long[4];
- for (int i=0; i<4; i++) {
- observed[i] = freq.getCount(i);
- }
-
- /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
- * Change to 11.34 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 16.27);
- }
-
- /** test dispersion and failure modes for nextLong() */
- public void testNextLong() {
- try {
- randomData.nextLong(4,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- Frequency freq = new Frequency();
- long value = 0;
- for (int i=0;i<smallSampleSize;i++) {
- value = randomData.nextLong(0,3);
- assertTrue("nextInt range",(value >= 0) && (value <= 3));
- freq.addValue(value);
- }
- long[] observed = new long[4];
- for (int i=0; i<4; i++) {
- observed[i] = freq.getCount(i);
- }
-
- /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
- * Change to 11.34 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 16.27);
- }
-
- /** test dispersion and failure modes for nextSecureLong() */
- public void testNextSecureLong() {
- try {
- randomData.nextSecureLong(4,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- Frequency freq = new Frequency();
- long value = 0;
- for (int i=0;i<smallSampleSize;i++) {
- value = randomData.nextSecureLong(0,3);
- assertTrue("nextInt range",(value >= 0) && (value <= 3));
- freq.addValue(value);
- }
- long[] observed = new long[4];
- for (int i=0; i<4; i++) {
- observed[i] = freq.getCount(i);
- }
-
- /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
- * Change to 11.34 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 16.27);
- }
-
- /** test dispersion and failure modes for nextSecureInt() */
- public void testNextSecureInt() {
- try {
- randomData.nextSecureInt(4,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- Frequency freq = new Frequency();
- int value = 0;
- for (int i=0;i<smallSampleSize;i++) {
- value = randomData.nextSecureInt(0,3);
- assertTrue("nextInt range",(value >= 0) && (value <= 3));
- freq.addValue(value);
- }
- long[] observed = new long[4];
- for (int i=0; i<4; i++) {
- observed[i] = freq.getCount(i);
- }
-
- /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
- * Change to 11.34 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 16.27);
- }
-
- /**
- * Make sure that empirical distribution of random Poisson(4)'s
- * has P(X <= 5) close to actual cumulative Poisson probablity
- * and that nextPoisson fails when mean is non-positive
- * TODO: replace with statistical test, adding test stat to TestStatistic
- */
- public void testNextPoisson() {
- try {
- randomData.nextPoisson(0);
- fail("zero mean -- expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- Frequency f = new Frequency();
- for (int i = 0; i<largeSampleSize; i++) {
- try {
- f.addValue(randomData.nextPoisson(4.0d));
- } catch (Exception ex) {
- fail(ex.getMessage());
- }
- }
- long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2) +
- f.getCount(3) + f.getCount(4) + f.getCount(5);
- long sumFreq = f.getSumFreq();
- double cumPct =
- Double.valueOf(cumFreq).doubleValue()/Double.valueOf(sumFreq).doubleValue();
- assertEquals("cum Poisson(4)",cumPct,0.7851,0.2);
- try {
- randomData.nextPoisson(-1);
- fail("negative mean supplied -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- try {
- randomData.nextPoisson(0);
- fail("0 mean supplied -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
-
- }
-
- /** test dispersion and failute modes for nextHex() */
- public void testNextHex() {
- try {
- randomData.nextHexString(-1);
- fail("negative length supplied -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- try {
- randomData.nextHexString(0);
- fail("zero length supplied -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- String hexString = randomData.nextHexString(3);
- if (hexString.length() != 3) {
- fail("incorrect length for generated string");
- }
- hexString = randomData.nextHexString(1);
- if (hexString.length() != 1) {
- fail("incorrect length for generated string");
- }
- try {
- hexString = randomData.nextHexString(0);
- fail("zero length requested -- expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- if (hexString.length() != 1) {
- fail("incorrect length for generated string");
- }
- Frequency f = new Frequency();
- for (int i = 0; i < smallSampleSize; i++) {
- hexString = randomData.nextHexString(100);
- if (hexString.length() != 100) {
- fail("incorrect length for generated string");
- }
- for (int j = 0; j < hexString.length(); j++) {
- f.addValue(hexString.substring(j,j+1));
- }
- }
- double[] expected = new double[16];
- long[] observed = new long[16];
- for (int i = 0; i < 16; i++) {
- expected[i] = (double)smallSampleSize*100/16;
- observed[i] = f.getCount(hex[i]);
- }
- /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
- * Change to 30.58 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 37.70);
- }
-
- /** test dispersion and failute modes for nextHex() */
- public void testNextSecureHex() {
- try {
- randomData.nextSecureHexString(-1);
- fail("negative length -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- try {
- randomData.nextSecureHexString(0);
- fail("zero length -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- String hexString = randomData.nextSecureHexString(3);
- if (hexString.length() != 3) {
- fail("incorrect length for generated string");
- }
- hexString = randomData.nextSecureHexString(1);
- if (hexString.length() != 1) {
- fail("incorrect length for generated string");
- }
- try {
- hexString = randomData.nextSecureHexString(0);
- fail("zero length requested -- expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- if (hexString.length() != 1) {
- fail("incorrect length for generated string");
- }
- Frequency f = new Frequency();
- for (int i = 0; i < smallSampleSize; i++) {
- hexString = randomData.nextSecureHexString(100);
- if (hexString.length() != 100) {
- fail("incorrect length for generated string");
- }
- for (int j = 0; j < hexString.length(); j++) {
- f.addValue(hexString.substring(j,j+1));
- }
- }
- double[] expected = new double[16];
- long[] observed = new long[16];
- for (int i = 0; i < 16; i++) {
- expected[i] = (double)smallSampleSize*100/16;
- observed[i] = f.getCount(hex[i]);
- }
- /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
- * Change to 30.58 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 37.70);
- }
-
- /** test failure modes and dispersion of nextUniform() */
- public void testNextUniform() {
- try {
- randomData.nextUniform(4,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- try {
- randomData.nextUniform(3,3);
- fail("IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- double[] expected = {500,500};
- long[] observed = {0,0};
- double lower = -1d;
- double upper = 20d;
- double midpoint = (lower + upper)/2d;
- double result = 0;
- for (int i = 0; i < 1000; i++) {
- result = randomData.nextUniform(lower,upper);
- if ((result == lower) || (result == upper)) {
- fail("generated value equal to an endpoint: " + result);
- }
- if (result < midpoint) {
- observed[0]++;
- } else {
- observed[1]++;
- }
- }
- /* Use ChiSquare dist with df = 2-1 = 1, alpha = .001
- * Change to 6.64 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 10.83);
- }
-
- /** test exclusive endpoints of nextUniform **/
- public void testNextUniformExclusiveEndpoints() {
- for (int i = 0; i < 1000; i++) {
- double u = randomData.nextUniform(0.99, 1);
- assertTrue(u > 0.99 && u < 1);
- }
- }
-
- /** test failure modes and distribution of nextGaussian() */
- public void testNextGaussian() {
- try {
- randomData.nextGaussian(0,0);
- fail("zero sigma -- IllegalArgumentException expected");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- SummaryStatistics u = new SummaryStatistics();
- for (int i = 0; i<largeSampleSize; i++) {
- u.addValue(randomData.nextGaussian(0,1));
- }
- double xbar = u.getMean();
- double s = u.getStandardDeviation();
- double n = u.getN();
- /* t-test at .001-level TODO: replace with externalized t-test, with
- * test statistic defined in TestStatistic
- */
- assertTrue(Math.abs(xbar)/(s/Math.sqrt(n))< 3.29);
- }
-
- /** test failure modes and distribution of nextExponential() */
- public void testNextExponential() {
- try {
- randomData.nextExponential(-1);
- fail("negative mean -- expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- assertEquals("0 mean", 0,randomData.nextExponential(0),10E-8);
- long cumFreq = 0;
- double v = 0;
- for (int i = 0; i < largeSampleSize; i++) {
- v = randomData.nextExponential(1);
- assertTrue("exponential deviate postive", v > 0);
- if (v < 2) cumFreq++;
- }
- /* TODO: Replace with a statistical test, with statistic added to
- * TestStatistic. Check below compares observed cumulative distribution
- * evaluated at 2 with exponential CDF
- */
- assertEquals("exponential cumulative distribution",
- (double)cumFreq/(double)largeSampleSize,0.8646647167633873,.2);
- }
-
- /** test reseeding, algorithm/provider games */
- public void testConfig() {
- randomData.reSeed(1000);
- double v = randomData.nextUniform(0,1);
- randomData.reSeed();
- assertTrue("different seeds",
- Math.abs(v - randomData.nextUniform(0,1)) > 10E-12);
- randomData.reSeed(1000);
- assertEquals("same seeds",v,randomData.nextUniform(0,1),10E-12);
- randomData.reSeedSecure(1000);
- String hex = randomData.nextSecureHexString(40);
- randomData.reSeedSecure();
- assertTrue("different seeds",
- !hex.equals(randomData.nextSecureHexString(40)));
- randomData.reSeedSecure(1000);
- assertTrue("same seeds",
- !hex.equals(randomData.nextSecureHexString(40)));
-
- /* remove this test back soon,
- * since it takes about 4 seconds
-
- try {
- randomData.setSecureAlgorithm("SHA1PRNG","SUN");
- } catch (NoSuchProviderException ex) {
- ;
- }
- assertTrue("different seeds",
- !hex.equals(randomData.nextSecureHexString(40)));
- try {
- randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
- fail("expecting NoSuchAlgorithmException");
- } catch (NoSuchProviderException ex) {
- ;
- } catch (NoSuchAlgorithmException ex) {
- ;
- }
-
- try {
- randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
- fail("expecting NoSuchProviderException");
- } catch (NoSuchProviderException ex) {
- ;
- }
- */
-
- // test reseeding without first using the generators
- RandomDataImpl rd = new RandomDataImpl();
- rd.reSeed(100);
- rd.nextLong(1,2);
- RandomDataImpl rd2 = new RandomDataImpl();
- rd2.reSeedSecure(2000);
- rd2.nextSecureLong(1,2);
- rd = new RandomDataImpl();
- rd.reSeed();
- rd.nextLong(1,2);
- rd2 = new RandomDataImpl();
- rd2.reSeedSecure();
- rd2.nextSecureLong(1,2);
- }
-
- /** tests for nextSample() sampling from Collection */
- public void testNextSample() {
- Object[][] c = {{"0","1"},{"0","2"},{"0","3"},{"0","4"},{"1","2"},
- {"1","3"},{"1","4"},{"2","3"},{"2","4"},{"3","4"}};
- long[] observed = {0,0,0,0,0,0,0,0,0,0};
- double[] expected = {100,100,100,100,100,100,100,100,100,100};
-
- HashSet<Object> cPop = new HashSet<Object>(); //{0,1,2,3,4}
- for (int i = 0; i < 5; i++) {
- cPop.add(Integer.toString(i));
- }
-
- Object[] sets = new Object[10]; // 2-sets from 5
- for (int i = 0; i < 10; i ++) {
- HashSet<Object> hs = new HashSet<Object>();
- hs.add(c[i][0]);
- hs.add(c[i][1]);
- sets[i] = hs;
- }
-
- for (int i = 0; i < 1000; i ++) {
- Object[] cSamp = randomData.nextSample(cPop,2);
- observed[findSample(sets,cSamp)]++;
- }
-
- /* Use ChiSquare dist with df = 10-1 = 9, alpha = .001
- * Change to 21.67 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 27.88);
-
- // Make sure sample of size = size of collection returns same collection
- HashSet<Object> hs = new HashSet<Object>();
- hs.add("one");
- Object[] one = randomData.nextSample(hs,1);
- String oneString = (String) one[0];
- if ((one.length != 1) || !oneString.equals("one")){
- fail("bad sample for set size = 1, sample size = 1");
- }
-
- // Make sure we fail for sample size > collection size
- try {
- one = randomData.nextSample(hs,2);
- fail("sample size > set size, expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
-
- // Make sure we fail for empty collection
- try {
- hs = new HashSet<Object>();
- one = randomData.nextSample(hs,0);
- fail("n = k = 0, expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
- }
-
- @SuppressWarnings("unchecked")
- private int findSample(Object[] u, Object[] samp) {
- for (int i = 0; i < u.length; i++) {
- HashSet<Object> set = (HashSet<Object>) u[i];
- HashSet<Object> sampSet = new HashSet<Object>();
- for (int j = 0; j < samp.length; j++) {
- sampSet.add(samp[j]);
- }
- if (set.equals(sampSet)) {
- return i;
- }
- }
- fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
- return -1;
- }
-
- /** tests for nextPermutation */
- public void testNextPermutation() {
- int[][] p = {{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}};
- long[] observed = {0,0,0,0,0,0};
- double[] expected = {100,100,100,100,100,100};
-
- for (int i = 0; i < 600; i++) {
- int[] perm = randomData.nextPermutation(3,3);
- observed[findPerm(p,perm)]++;
- }
-
- /* Use ChiSquare dist with df = 6-1 = 5, alpha = .001
- * Change to 15.09 for alpha = .01
- */
- assertTrue("chi-square test -- will fail about 1 in 1000 times",
- testStatistic.chiSquare(expected,observed) < 20.52);
-
- // Check size = 1 boundary case
- int[] perm = randomData.nextPermutation(1,1);
- if ((perm.length != 1) || (perm[0] != 0)){
- fail("bad permutation for n = 1, sample k = 1");
-
- // Make sure we fail for k size > n
- try {
- perm = randomData.nextPermutation(2,3);
- fail("permutation k > n, expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
-
- // Make sure we fail for n = 0
- try {
- perm = randomData.nextPermutation(0,0);
- fail("permutation k = n = 0, expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
-
- // Make sure we fail for k < n < 0
- try {
- perm = randomData.nextPermutation(-1,-3);
- fail("permutation k < n < 0, expecting IllegalArgumentException");
- } catch (IllegalArgumentException ex) {
- // ignored
- }
-
- }
- }
-
- private int findPerm(int[][] p, int[] samp) {
- for (int i = 0; i < p.length; i++) {
- boolean good = true;
- for (int j = 0; j < samp.length; j++) {
- if (samp[j] != p[i][j]) {
- good = false;
- }
- }
- if (good) {
- return i;
- }
- }
- fail("permutation not found");
- return -1;
- }
-}
+ public RandomDataTest(String name) {
+ super(name);
+ randomData = new RandomDataImpl();
+ }
+
+ protected long smallSampleSize = 1000;
+ protected double[] expected = { 250, 250, 250, 250 };
+ protected int largeSampleSize = 10000;
+ private String[] hex = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ "a", "b", "c", "d", "e", "f" };
+ protected RandomDataImpl randomData = null;
+ protected ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite(RandomDataTest.class);
+ suite.setName("RandomData Tests");
+ return suite;
+ }
+
+ public void testNextIntExtremeValues() {
+ int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ assertFalse(x == y);
+ }
+
+ public void testNextLongExtremeValues() {
+ long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+ long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+ assertFalse(x == y);
+ }
+
+ /** test dispersion and failure modes for nextInt() */
+ public void testNextInt() {
+ try {
+ randomData.nextInt(4, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ Frequency freq = new Frequency();
+ int value = 0;
+ for (int i = 0; i < smallSampleSize; i++) {
+ value = randomData.nextInt(0, 3);
+ assertTrue("nextInt range", (value >= 0) && (value <= 3));
+ freq.addValue(value);
+ }
+ long[] observed = new long[4];
+ for (int i = 0; i < 4; i++) {
+ observed[i] = freq.getCount(i);
+ }
+
+ /*
+ * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 16.27);
+ }
+
+ /** test dispersion and failure modes for nextLong() */
+ public void testNextLong() {
+ try {
+ randomData.nextLong(4, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ Frequency freq = new Frequency();
+ long value = 0;
+ for (int i = 0; i < smallSampleSize; i++) {
+ value = randomData.nextLong(0, 3);
+ assertTrue("nextInt range", (value >= 0) && (value <= 3));
+ freq.addValue(value);
+ }
+ long[] observed = new long[4];
+ for (int i = 0; i < 4; i++) {
+ observed[i] = freq.getCount(i);
+ }
+
+ /*
+ * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 16.27);
+ }
+
+ /** test dispersion and failure modes for nextSecureLong() */
+ public void testNextSecureLong() {
+ try {
+ randomData.nextSecureLong(4, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ Frequency freq = new Frequency();
+ long value = 0;
+ for (int i = 0; i < smallSampleSize; i++) {
+ value = randomData.nextSecureLong(0, 3);
+ assertTrue("nextInt range", (value >= 0) && (value <= 3));
+ freq.addValue(value);
+ }
+ long[] observed = new long[4];
+ for (int i = 0; i < 4; i++) {
+ observed[i] = freq.getCount(i);
+ }
+
+ /*
+ * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 16.27);
+ }
+
+ /** test dispersion and failure modes for nextSecureInt() */
+ public void testNextSecureInt() {
+ try {
+ randomData.nextSecureInt(4, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ Frequency freq = new Frequency();
+ int value = 0;
+ for (int i = 0; i < smallSampleSize; i++) {
+ value = randomData.nextSecureInt(0, 3);
+ assertTrue("nextInt range", (value >= 0) && (value <= 3));
+ freq.addValue(value);
+ }
+ long[] observed = new long[4];
+ for (int i = 0; i < 4; i++) {
+ observed[i] = freq.getCount(i);
+ }
+
+ /*
+ * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 16.27);
+ }
+
+ /**
+ * Make sure that empirical distribution of random Poisson(4)'s has P(X <=
+ * 5) close to actual cumulative Poisson probablity and that nextPoisson
+ * fails when mean is non-positive TODO: replace with statistical test,
+ * adding test stat to TestStatistic
+ */
+ public void testNextPoisson() {
+ try {
+ randomData.nextPoisson(0);
+ fail("zero mean -- expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ Frequency f = new Frequency();
+ for (int i = 0; i < largeSampleSize; i++) {
+ try {
+ f.addValue(randomData.nextPoisson(4.0d));
+ } catch (Exception ex) {
+ fail(ex.getMessage());
+ }
+ }
+ long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2)
+ + f.getCount(3) + f.getCount(4) + f.getCount(5);
+ long sumFreq = f.getSumFreq();
+ double cumPct = Double.valueOf(cumFreq).doubleValue()
+ / Double.valueOf(sumFreq).doubleValue();
+ assertEquals("cum Poisson(4)", cumPct, 0.7851, 0.2);
+ try {
+ randomData.nextPoisson(-1);
+ fail("negative mean supplied -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ try {
+ randomData.nextPoisson(0);
+ fail("0 mean supplied -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+
+ }
+
+ public void testNextPoissonLargeMean() {
+ for (int i = 0; i < 1000; i++) {
+ long n = randomData.nextPoisson(1500.0);
+ assertTrue(0 <= n);
+ }
+ }
+
+ /** test dispersion and failute modes for nextHex() */
+ public void testNextHex() {
+ try {
+ randomData.nextHexString(-1);
+ fail("negative length supplied -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ try {
+ randomData.nextHexString(0);
+ fail("zero length supplied -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ String hexString = randomData.nextHexString(3);
+ if (hexString.length() != 3) {
+ fail("incorrect length for generated string");
+ }
+ hexString = randomData.nextHexString(1);
+ if (hexString.length() != 1) {
+ fail("incorrect length for generated string");
+ }
+ try {
+ hexString = randomData.nextHexString(0);
+ fail("zero length requested -- expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ if (hexString.length() != 1) {
+ fail("incorrect length for generated string");
+ }
+ Frequency f = new Frequency();
+ for (int i = 0; i < smallSampleSize; i++) {
+ hexString = randomData.nextHexString(100);
+ if (hexString.length() != 100) {
+ fail("incorrect length for generated string");
+ }
+ for (int j = 0; j < hexString.length(); j++) {
+ f.addValue(hexString.substring(j, j + 1));
+ }
+ }
+ double[] expected = new double[16];
+ long[] observed = new long[16];
+ for (int i = 0; i < 16; i++) {
+ expected[i] = (double) smallSampleSize * 100 / 16;
+ observed[i] = f.getCount(hex[i]);
+ }
+ /*
+ * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 37.70);
+ }
+
+ /** test dispersion and failute modes for nextHex() */
+ public void testNextSecureHex() {
+ try {
+ randomData.nextSecureHexString(-1);
+ fail("negative length -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ try {
+ randomData.nextSecureHexString(0);
+ fail("zero length -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ String hexString = randomData.nextSecureHexString(3);
+ if (hexString.length() != 3) {
+ fail("incorrect length for generated string");
+ }
+ hexString = randomData.nextSecureHexString(1);
+ if (hexString.length() != 1) {
+ fail("incorrect length for generated string");
+ }
+ try {
+ hexString = randomData.nextSecureHexString(0);
+ fail("zero length requested -- expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ if (hexString.length() != 1) {
+ fail("incorrect length for generated string");
+ }
+ Frequency f = new Frequency();
+ for (int i = 0; i < smallSampleSize; i++) {
+ hexString = randomData.nextSecureHexString(100);
+ if (hexString.length() != 100) {
+ fail("incorrect length for generated string");
+ }
+ for (int j = 0; j < hexString.length(); j++) {
+ f.addValue(hexString.substring(j, j + 1));
+ }
+ }
+ double[] expected = new double[16];
+ long[] observed = new long[16];
+ for (int i = 0; i < 16; i++) {
+ expected[i] = (double) smallSampleSize * 100 / 16;
+ observed[i] = f.getCount(hex[i]);
+ }
+ /*
+ * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 37.70);
+ }
+
+ /** test failure modes and dispersion of nextUniform() */
+ public void testNextUniform() {
+ try {
+ randomData.nextUniform(4, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ try {
+ randomData.nextUniform(3, 3);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ double[] expected = { 500, 500 };
+ long[] observed = { 0, 0 };
+ double lower = -1d;
+ double upper = 20d;
+ double midpoint = (lower + upper) / 2d;
+ double result = 0;
+ for (int i = 0; i < 1000; i++) {
+ result = randomData.nextUniform(lower, upper);
+ if ((result == lower) || (result == upper)) {
+ fail("generated value equal to an endpoint: " + result);
+ }
+ if (result < midpoint) {
+ observed[0]++;
+ } else {
+ observed[1]++;
+ }
+ }
+ /*
+ * Use ChiSquare dist with df = 2-1 = 1, alpha = .001 Change to 6.64 for
+ * alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 10.83);
+ }
+
+ /** test exclusive endpoints of nextUniform **/
+ public void testNextUniformExclusiveEndpoints() {
+ for (int i = 0; i < 1000; i++) {
+ double u = randomData.nextUniform(0.99, 1);
+ assertTrue(u > 0.99 && u < 1);
+ }
+ }
+
+ /** test failure modes and distribution of nextGaussian() */
+ public void testNextGaussian() {
+ try {
+ randomData.nextGaussian(0, 0);
+ fail("zero sigma -- IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ SummaryStatistics u = new SummaryStatistics();
+ for (int i = 0; i < largeSampleSize; i++) {
+ u.addValue(randomData.nextGaussian(0, 1));
+ }
+ double xbar = u.getMean();
+ double s = u.getStandardDeviation();
+ double n = u.getN();
+ /*
+ * t-test at .001-level TODO: replace with externalized t-test, with
+ * test statistic defined in TestStatistic
+ */
+ assertTrue(Math.abs(xbar) / (s / Math.sqrt(n)) < 3.29);
+ }
+
+ /** test failure modes and distribution of nextExponential() */
+ public void testNextExponential() {
+ try {
+ randomData.nextExponential(-1);
+ fail("negative mean -- expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ assertEquals("0 mean", 0, randomData.nextExponential(0), 10E-8);
+ long cumFreq = 0;
+ double v = 0;
+ for (int i = 0; i < largeSampleSize; i++) {
+ v = randomData.nextExponential(1);
+ assertTrue("exponential deviate postive", v > 0);
+ if (v < 2)
+ cumFreq++;
+ }
+ /*
+ * TODO: Replace with a statistical test, with statistic added to
+ * TestStatistic. Check below compares observed cumulative distribution
+ * evaluated at 2 with exponential CDF
+ */
+ assertEquals("exponential cumulative distribution", (double) cumFreq
+ / (double) largeSampleSize, 0.8646647167633873, .2);
+ }
+ /** test reseeding, algorithm/provider games */
+ public void testConfig() {
+ randomData.reSeed(1000);
+ double v = randomData.nextUniform(0, 1);
+ randomData.reSeed();
+ assertTrue("different seeds", Math
+ .abs(v - randomData.nextUniform(0, 1)) > 10E-12);
+ randomData.reSeed(1000);
+ assertEquals("same seeds", v, randomData.nextUniform(0, 1), 10E-12);
+ randomData.reSeedSecure(1000);
+ String hex = randomData.nextSecureHexString(40);
+ randomData.reSeedSecure();
+ assertTrue("different seeds", !hex.equals(randomData
+ .nextSecureHexString(40)));
+ randomData.reSeedSecure(1000);
+ assertTrue("same seeds", !hex
+ .equals(randomData.nextSecureHexString(40)));
+
+ /*
+ * remove this test back soon, since it takes about 4 seconds
+ *
+ * try { randomData.setSecureAlgorithm("SHA1PRNG","SUN"); } catch
+ * (NoSuchProviderException ex) { ; } assertTrue("different seeds",
+ * !hex.equals(randomData.nextSecureHexString(40))); try {
+ * randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
+ * fail("expecting NoSuchAlgorithmException"); } catch
+ * (NoSuchProviderException ex) { ; } catch (NoSuchAlgorithmException
+ * ex) { ; }
+ *
+ * try { randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
+ * fail("expecting NoSuchProviderException"); } catch
+ * (NoSuchProviderException ex) { ; }
+ */
+
+ // test reseeding without first using the generators
+ RandomDataImpl rd = new RandomDataImpl();
+ rd.reSeed(100);
+ rd.nextLong(1, 2);
+ RandomDataImpl rd2 = new RandomDataImpl();
+ rd2.reSeedSecure(2000);
+ rd2.nextSecureLong(1, 2);
+ rd = new RandomDataImpl();
+ rd.reSeed();
+ rd.nextLong(1, 2);
+ rd2 = new RandomDataImpl();
+ rd2.reSeedSecure();
+ rd2.nextSecureLong(1, 2);
+ }
+
+ /** tests for nextSample() sampling from Collection */
+ public void testNextSample() {
+ Object[][] c = { { "0", "1" }, { "0", "2" }, { "0", "3" },
+ { "0", "4" }, { "1", "2" }, { "1", "3" }, { "1", "4" },
+ { "2", "3" }, { "2", "4" }, { "3", "4" } };
+ long[] observed = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ double[] expected = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+
+ HashSet<Object> cPop = new HashSet<Object>(); // {0,1,2,3,4}
+ for (int i = 0; i < 5; i++) {
+ cPop.add(Integer.toString(i));
+ }
+
+ Object[] sets = new Object[10]; // 2-sets from 5
+ for (int i = 0; i < 10; i++) {
+ HashSet<Object> hs = new HashSet<Object>();
+ hs.add(c[i][0]);
+ hs.add(c[i][1]);
+ sets[i] = hs;
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ Object[] cSamp = randomData.nextSample(cPop, 2);
+ observed[findSample(sets, cSamp)]++;
+ }
+
+ /*
+ * Use ChiSquare dist with df = 10-1 = 9, alpha = .001 Change to 21.67
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 27.88);
+
+ // Make sure sample of size = size of collection returns same collection
+ HashSet<Object> hs = new HashSet<Object>();
+ hs.add("one");
+ Object[] one = randomData.nextSample(hs, 1);
+ String oneString = (String) one[0];
+ if ((one.length != 1) || !oneString.equals("one")) {
+ fail("bad sample for set size = 1, sample size = 1");
+ }
+
+ // Make sure we fail for sample size > collection size
+ try {
+ one = randomData.nextSample(hs, 2);
+ fail("sample size > set size, expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+
+ // Make sure we fail for empty collection
+ try {
+ hs = new HashSet<Object>();
+ one = randomData.nextSample(hs, 0);
+ fail("n = k = 0, expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private int findSample(Object[] u, Object[] samp) {
+ for (int i = 0; i < u.length; i++) {
+ HashSet<Object> set = (HashSet<Object>) u[i];
+ HashSet<Object> sampSet = new HashSet<Object>();
+ for (int j = 0; j < samp.length; j++) {
+ sampSet.add(samp[j]);
+ }
+ if (set.equals(sampSet)) {
+ return i;
+ }
+ }
+ fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
+ return -1;
+ }
+
+ /** tests for nextPermutation */
+ public void testNextPermutation() {
+ int[][] p = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 }, { 1, 2, 0 },
+ { 2, 0, 1 }, { 2, 1, 0 } };
+ long[] observed = { 0, 0, 0, 0, 0, 0 };
+ double[] expected = { 100, 100, 100, 100, 100, 100 };
+
+ for (int i = 0; i < 600; i++) {
+ int[] perm = randomData.nextPermutation(3, 3);
+ observed[findPerm(p, perm)]++;
+ }
+
+ /*
+ * Use ChiSquare dist with df = 6-1 = 5, alpha = .001 Change to 15.09
+ * for alpha = .01
+ */
+ assertTrue("chi-square test -- will fail about 1 in 1000 times",
+ testStatistic.chiSquare(expected, observed) < 20.52);
+
+ // Check size = 1 boundary case
+ int[] perm = randomData.nextPermutation(1, 1);
+ if ((perm.length != 1) || (perm[0] != 0)) {
+ fail("bad permutation for n = 1, sample k = 1");
+
+ // Make sure we fail for k size > n
+ try {
+ perm = randomData.nextPermutation(2, 3);
+ fail("permutation k > n, expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+
+ // Make sure we fail for n = 0
+ try {
+ perm = randomData.nextPermutation(0, 0);
+ fail("permutation k = n = 0, expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+
+ // Make sure we fail for k < n < 0
+ try {
+ perm = randomData.nextPermutation(-1, -3);
+ fail("permutation k < n < 0, expecting IllegalArgumentException");
+ } catch (IllegalArgumentException ex) {
+ // ignored
+ }
+
+ }
+ }
+
+ private int findPerm(int[][] p, int[] samp) {
+ for (int i = 0; i < p.length; i++) {
+ boolean good = true;
+ for (int j = 0; j < samp.length; j++) {
+ if (samp[j] != p[i][j]) {
+ good = false;
+ }
+ }
+ if (good) {
+ return i;
+ }
+ }
+ fail("permutation not found");
+ return -1;
+ }
+}