Hi,
I've now made a commit. I'm now writing to be sure that I did it
correctly. First of all, I of course committed the code. I also added
the following to changes.xml. I did this in the same commit (this is
best, right?):
<action dev="mikl" type="fix" issue="MATH-597">
Implemented faster generation of random exponential
distributed values with
algorithm from Ahrens and Dieter (1972): Computer methods for sampling
from the exponential and normal distributions.
</action>
As the commit message, I used:
Added fix for MATH-597: Implemented faster generation of random
exponential distributed values with algorithm from Ahrens and Dieter
(1972): Computer methods for sampling from the exponential and normal
distributions. Test case was improved, too.
Is this okay? Did I miss something or should I done something different?
Cheers, Mikkel.
2011/6/20 <[email protected]>:
> Author: mikl
> Date: Mon Jun 20 21:42:48 2011
> New Revision: 1137795
>
> URL: http://svn.apache.org/viewvc?rev=1137795&view=rev
> Log:
> Added fix for MATH-597: Implemented faster generation of random exponential
> distributed values with algorithm from Ahrens and Dieter (1972): Computer
> methods for sampling from the exponential and normal distributions. Test case
> was improved, too.
>
> Modified:
>
> commons/proper/math/trunk/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
> commons/proper/math/trunk/src/site/xdoc/changes.xml
>
> commons/proper/math/trunk/src/test/java/org/apache/commons/math/random/RandomDataTest.java
>
> Modified:
> commons/proper/math/trunk/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
> URL:
> http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math/random/RandomDataImpl.java?rev=1137795&r1=1137794&r2=1137795&view=diff
> ==============================================================================
> ---
> commons/proper/math/trunk/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
> (original)
> +++
> commons/proper/math/trunk/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
> Mon Jun 20 21:42:48 2011
> @@ -44,6 +44,7 @@ import org.apache.commons.math.exception
> import org.apache.commons.math.exception.util.LocalizedFormats;
> import org.apache.commons.math.util.FastMath;
> import org.apache.commons.math.util.MathUtils;
> +import org.apache.commons.math.util.ResizableDoubleArray;
>
> /**
> * Implements the {@link RandomData} interface using a {@link RandomGenerator}
> @@ -107,6 +108,21 @@ public class RandomDataImpl implements R
> /** Serializable version identifier */
> private static final long serialVersionUID = -626730818244969716L;
>
> + /** Used when generating Exponential samples
> + * [1] writes:
> + * One table containing the constants
> + * q_i = sum_{j=1}^i (ln 2)^j/j! = ln 2 + (ln 2)^2/2 + ... + (ln 2)^i/i!
> + * until the largest representable fraction below 1 is exceeded.
> + *
> + * Note that
> + * 1 = 2 - 1 = exp(ln 2) - 1 = sum_{n=1}^infty (ln 2)^n / n!
> + * thus q_i -> 1 as i -> infty,
> + * so the higher 1, the closer to one we get (the series is not
> alternating).
> + *
> + * By trying, n = 16 in Java is enough to reach 1.0.
> + */
> + private static double[] EXPONENTIAL_SA_QI = null;
> +
> /** underlying random number generator */
> private RandomGenerator rand = null;
>
> @@ -114,6 +130,35 @@ public class RandomDataImpl implements R
> private SecureRandom secRand = null;
>
> /**
> + * Initialize tables
> + */
> + static {
> + /**
> + * Filling EXPONENTIAL_SA_QI table.
> + * Note that we don't want qi = 0 in the table.
> + */
> + final double LN2 = FastMath.log(2);
> + double qi = 0;
> + int i = 1;
> +
> + /**
> + * MathUtils provides factorials up to 20, so let's use that limit
> together
> + * with MathUtils.EPSILON to generate the following code (a priori,
> we know that
> + * there will be 16 elements, but instead of hardcoding that, this is
> + * prettier):
> + */
> + final ResizableDoubleArray ra = new ResizableDoubleArray(20);
> +
> + while (qi < 1) {
> + qi += FastMath.pow(LN2, i) / MathUtils.factorial(i);
> + ra.addElement(qi);
> + ++i;
> + }
> +
> + EXPONENTIAL_SA_QI = ra.getElements();
> + }
> +
> + /**
> * Construct a RandomDataImpl.
> */
> public RandomDataImpl() {
> @@ -469,10 +514,11 @@ public class RandomDataImpl implements R
> * 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.
> + * <strong>Algorithm Description</strong>: Uses the Algorithm SA (Ahrens)
> + * from p. 876 in:
> + * [1]: Ahrens, J. H. and Dieter, U. (1972). Computer methods for
> + * sampling from the exponential and normal distributions.
> + * Communications of the ACM, 15, 873-882.
> * </p>
> *
> * @param mean the mean of the distribution
> @@ -483,12 +529,43 @@ public class RandomDataImpl implements R
> if (mean <= 0.0) {
> throw new NotStrictlyPositiveException(LocalizedFormats.MEAN,
> mean);
> }
> - final RandomGenerator generator = getRan();
> - double unif = generator.nextDouble();
> - while (unif == 0.0d) {
> - unif = generator.nextDouble();
> +
> + // Step 1:
> + double a = 0;
> + double u = this.nextUniform(0, 1);
> +
> + // Step 2 and 3:
> + while (u < 0.5) {
> + a += EXPONENTIAL_SA_QI[0];
> + u *= 2;
> }
> - return -mean * FastMath.log(unif);
> +
> + // Step 4 (now u >= 0.5):
> + u += u - 1;
> +
> + // Step 5:
> + if (u <= EXPONENTIAL_SA_QI[0]) {
> + return mean * (a + u);
> + }
> +
> + // Step 6:
> + int i = 0; // Should be 1, be we iterate before it in while using 0
> + double u2 = this.nextUniform(0, 1);
> + double umin = u2;
> +
> + // Step 7 and 8:
> + do {
> + ++i;
> + u2 = this.nextUniform(0, 1);
> +
> + if (u2 < umin) {
> + umin = u2;
> + }
> +
> + // Step 8:
> + } while (u > EXPONENTIAL_SA_QI[i]); // Ensured to exit since
> EXPONENTIAL_SA_QI[MAX] = 1
> +
> + return mean * (a + umin * EXPONENTIAL_SA_QI[0]);
> }
>
> /**
>
> Modified: commons/proper/math/trunk/src/site/xdoc/changes.xml
> URL:
> http://svn.apache.org/viewvc/commons/proper/math/trunk/src/site/xdoc/changes.xml?rev=1137795&r1=1137794&r2=1137795&view=diff
> ==============================================================================
> --- commons/proper/math/trunk/src/site/xdoc/changes.xml (original)
> +++ commons/proper/math/trunk/src/site/xdoc/changes.xml Mon Jun 20 21:42:48
> 2011
> @@ -52,6 +52,11 @@ The <action> type attribute can be add,u
> If the output is not quite correct, check for invisible trailing spaces!
> -->
> <release version="3.0" date="TBD" description="TBD">
> + <action dev="mikl" type="fix" issue="MATH-597">
> + Implemented faster generation of random exponential distributed
> values with
> + algorithm from Ahrens and Dieter (1972): Computer methods for
> sampling
> + from the exponential and normal distributions.
> + </action>
> <action dev="luc" type="add" issue="MATH-548">
> K-means++ clustering can now run multiple trials
> </action>
>
> Modified:
> commons/proper/math/trunk/src/test/java/org/apache/commons/math/random/RandomDataTest.java
> URL:
> http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math/random/RandomDataTest.java?rev=1137795&r1=1137794&r2=1137795&view=diff
> ==============================================================================
> ---
> commons/proper/math/trunk/src/test/java/org/apache/commons/math/random/RandomDataTest.java
> (original)
> +++
> commons/proper/math/trunk/src/test/java/org/apache/commons/math/random/RandomDataTest.java
> Mon Jun 20 21:42:48 2011
> @@ -30,6 +30,7 @@ import org.apache.commons.math.distribut
> import org.apache.commons.math.distribution.BinomialDistributionTest;
> import org.apache.commons.math.distribution.CauchyDistributionImpl;
> import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
> +import org.apache.commons.math.distribution.ExponentialDistributionImpl;
> import org.apache.commons.math.distribution.FDistributionImpl;
> import org.apache.commons.math.distribution.GammaDistributionImpl;
> import org.apache.commons.math.distribution.HypergeometricDistributionImpl;
> @@ -245,10 +246,10 @@ public class RandomDataTest {
>
> @Test
> public void testNextPoissonConsistency() throws Exception {
> -
> +
> // Reseed randomGenerator to get fixed sequence
> - randomData.reSeed(1000);
> -
> + randomData.reSeed(1000);
> +
> // Small integral means
> for (int i = 1; i < 100; i++) {
> checkNextPoissonConsistency(i);
> @@ -581,7 +582,7 @@ public class RandomDataTest {
>
> /** test failure modes and distribution of nextExponential() */
> @Test
> - public void testNextExponential() {
> + public void testNextExponential() throws Exception {
> try {
> randomData.nextExponential(-1);
> Assert.fail("negative mean -- expecting
> MathIllegalArgumentException");
> @@ -609,6 +610,32 @@ public class RandomDataTest {
> */
> Assert.assertEquals("exponential cumulative distribution", (double)
> cumFreq
> / (double) largeSampleSize, 0.8646647167633873, .2);
> +
> + /**
> + * Proposal on improving the test of generating exponentials
> + */
> + double[] quartiles;
> + long[] counts;
> +
> + // Mean 1
> + quartiles = TestUtils.getDistributionQuartiles(new
> ExponentialDistributionImpl(1));
> + counts = new long[4];
> + randomData.reSeed(1000);
> + for (int i = 0; i < 1000; i++) {
> + double value = randomData.nextExponential(1);
> + TestUtils.updateCounts(value, counts, quartiles);
> + }
> + TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> +
> + // Mean 5
> + quartiles = TestUtils.getDistributionQuartiles(new
> ExponentialDistributionImpl(5));
> + counts = new long[4];
> + randomData.reSeed(1000);
> + for (int i = 0; i < 1000; i++) {
> + double value = randomData.nextExponential(5);
> + TestUtils.updateCounts(value, counts, quartiles);
> + }
> + TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
>
> /** test reseeding, algorithm/provider games */
> @@ -810,7 +837,7 @@ public class RandomDataTest {
> Assert.fail("permutation not found");
> return -1;
> }
> -
> +
> @Test
> public void testNextInversionDeviate() throws Exception {
> // Set the seed for the default random generator
> @@ -830,9 +857,9 @@ public class RandomDataTest {
> for (int i = 0; i < 10; i++) {
> double value = randomData.nextInversionDeviate(betaDistribution);
>
> Assert.assertEquals(betaDistribution.cumulativeProbability(value),
> quantiles[i], 10E-9);
> - }
> + }
> }
> -
> +
> @Test
> public void testNextBeta() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> BetaDistributionImpl(2,5));
> @@ -844,7 +871,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextCauchy() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> CauchyDistributionImpl(1.2, 2.1));
> @@ -856,7 +883,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextChiSquare() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> ChiSquaredDistributionImpl(12));
> @@ -868,7 +895,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextF() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> FDistributionImpl(12, 5));
> @@ -880,7 +907,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextGamma() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> GammaDistributionImpl(4, 2));
> @@ -892,7 +919,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextT() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> TDistributionImpl(10));
> @@ -904,7 +931,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextWeibull() throws Exception {
> double[] quartiles = TestUtils.getDistributionQuartiles(new
> WeibullDistributionImpl(1.2, 2.1));
> @@ -916,7 +943,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(expected, counts, 0.001);
> }
> -
> +
> @Test
> public void testNextBinomial() throws Exception {
> BinomialDistributionTest testInstance = new
> BinomialDistributionTest();
> @@ -942,7 +969,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(densityPoints, expectedCounts,
> observedCounts, .001);
> }
> -
> +
> @Test
> public void testNextHypergeometric() throws Exception {
> HypergeometricDistributionTest testInstance = new
> HypergeometricDistributionTest();
> @@ -968,7 +995,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(densityPoints, expectedCounts,
> observedCounts, .001);
> }
> -
> +
> @Test
> public void testNextPascal() throws Exception {
> PascalDistributionTest testInstance = new PascalDistributionTest();
> @@ -993,7 +1020,7 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(densityPoints, expectedCounts,
> observedCounts, .001);
> }
> -
> +
> @Test
> public void testNextZipf() throws Exception {
> ZipfDistributionTest testInstance = new ZipfDistributionTest();
> @@ -1018,5 +1045,5 @@ public class RandomDataTest {
> }
> TestUtils.assertChiSquareAccept(densityPoints, expectedCounts,
> observedCounts, .001);
> }
> -
> +
> }
>
>
>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]