This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-rng.git

commit 7d0a4aa42d65acdc28d818ed4e6c8d44d81425f3
Author: Alex Herbert <aherb...@apache.org>
AuthorDate: Sun Oct 6 14:57:12 2019 +0100

    RNG-121: ChengBetaSampler algorithms delegated to specialised classes.
    
    Allows precomputation of factors including one Math.log() constant used
    each loop iteration.
---
 .../sampling/distribution/ChengBetaSampler.java    | 387 +++++++++++++--------
 .../distribution/ChengBetaSamplerTest.java         |  53 ++-
 src/changes/changes.xml                            |   4 +
 3 files changed, 299 insertions(+), 145 deletions(-)

diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
index f96e198..0e82f79 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
@@ -19,7 +19,7 @@ package org.apache.commons.rng.sampling.distribution;
 import org.apache.commons.rng.UniformRandomProvider;
 
 /**
- * Sampling from a <a 
href="http://en.wikipedia.org/wiki/Beta_distribution";>Beta distribution</a>.
+ * Sampling from a <a 
href="http://en.wikipedia.org/wiki/Beta_distribution";>beta distribution</a>.
  *
  * <p>Uses Cheng's algorithms for beta distribution sampling:</p>
  *
@@ -38,181 +38,268 @@ import org.apache.commons.rng.UniformRandomProvider;
 public class ChengBetaSampler
     extends SamplerBase
     implements SharedStateContinuousSampler {
-    /** 1/2. */
-    private static final double ONE_HALF = 1d / 2;
-    /** 1/4. */
-    private static final double ONE_QUARTER = 1d / 4;
-
-    /** First shape parameter. */
-    private final double alphaShape;
-    /** Second shape parameter. */
-    private final double betaShape;
-    /** Underlying source of randomness. */
-    private final UniformRandomProvider rng;
+    /** The appropriate beta sampler for the parameters. */
+    private final SharedStateContinuousSampler delegate;
 
     /**
-     * Creates a sampler instance.
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param alpha Distribution first shape parameter.
-     * @param beta Distribution second shape parameter.
-     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0}
+     * Base class to implement Cheng's algorithms for the beta distribution.
      */
-    public ChengBetaSampler(UniformRandomProvider rng,
-                            double alpha,
-                            double beta) {
-        super(null);
-        if (alpha <= 0) {
-            throw new IllegalArgumentException("alpha is not strictly 
positive: " + alpha);
+    private abstract static class BaseChengBetaSampler
+            implements SharedStateContinuousSampler {
+        /**
+         * Flag set to true if {@code a} is the beta distribution alpha shape 
parameter.
+         * Otherwise {@code a} is the beta distribution beta shape parameter.
+         */
+        protected final boolean aIsAlphaShape;
+        /**
+         * First shape parameter.
+         * The meaning of this is dependent on the {@code aIsAlphaShape} flag.
+         */
+        protected final double a;
+        /**
+         * Second shape parameter.
+         * The meaning of this is dependent on the {@code aIsAlphaShape} flag.
+         */
+        protected final double b;
+        /** Underlying source of randomness. */
+        protected final UniformRandomProvider rng;
+        /**
+         * The algorithm alpha factor. This is not the beta distribution alpha 
shape parameter.
+         * It is the sum of the two shape parameters ({@code a + b}.
+         */
+        protected final double alpha;
+        /** The logarithm of the alpha factor. */
+        protected final double logAlpha;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param aIsAlphaShape true if {@code a} is the beta distribution 
alpha shape parameter.
+         * @param a Distribution first shape parameter.
+         * @param b Distribution second shape parameter.
+         */
+        BaseChengBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, 
double a, double b) {
+            this.rng = rng;
+            this.aIsAlphaShape = aIsAlphaShape;
+            this.a = a;
+            this.b = b;
+            alpha = a + b;
+            logAlpha = Math.log(alpha);
         }
-        if (beta <= 0) {
-            throw new IllegalArgumentException("beta is not strictly positive: 
" + beta);
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param source Source to copy.
+         */
+        private BaseChengBetaSampler(UniformRandomProvider rng,
+                                     BaseChengBetaSampler source) {
+            this.rng = rng;
+            aIsAlphaShape = source.aIsAlphaShape;
+            a = source.a;
+            b = source.b;
+            // Compute algorithm factors.
+            alpha = source.alpha;
+            logAlpha = source.logAlpha;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String toString() {
+            return "Cheng Beta deviate [" + rng.toString() + "]";
         }
-        this.rng = rng;
-        alphaShape = alpha;
-        betaShape = beta;
     }
 
     /**
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param source Source to copy.
+     * Computes one sample using Cheng's BB algorithm, when beta distribution 
{@code alpha} and
+     * {@code beta} shape parameters are both larger than 1.
      */
-    private ChengBetaSampler(UniformRandomProvider rng,
-                             ChengBetaSampler source) {
-        super(null);
-        this.rng = rng;
-        alphaShape = source.alphaShape;
-        betaShape = source.betaShape;
-    }
+    private static class ChengBBBetaSampler extends BaseChengBetaSampler {
+        /** The algorithm beta factor. This is not the beta distribution beta 
shape parameter. */
+        private final double beta;
+        /** The algorithm gamma factor. */
+        private final double gamma;
 
-    /** {@inheritDoc} */
-    @Override
-    public double sample() {
-        final double a = Math.min(alphaShape, betaShape);
-        final double b = Math.max(alphaShape, betaShape);
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param aIsAlphaShape true if {@code a} is the beta distribution 
alpha shape parameter.
+         * @param a min(alpha, beta) shape parameter.
+         * @param b max(alpha, beta) shape parameter.
+         */
+        ChengBBBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, 
double a, double b) {
+            super(rng, aIsAlphaShape, a, b);
+            beta = Math.sqrt((alpha - 2) / (2 * a * b - alpha));
+            gamma = a + 1 / beta;
+        }
 
-        if (a > 1) {
-            return algorithmBB(a, b);
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param source Source to copy.
+         */
+        private ChengBBBetaSampler(UniformRandomProvider rng,
+                                   ChengBBBetaSampler source) {
+            super(rng, source);
+            // Compute algorithm factors.
+            beta = source.beta;
+            gamma = source.gamma;
         }
-        // The algorithm is deliberately invoked with reversed parameters
-        // as the argument order is: max(alpha,beta), min(alpha,beta).
-        return algorithmBC(b, a);
-    }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "Cheng Beta deviate [" + rng.toString() + "]";
-    }
+        @Override
+        public double sample() {
+            double r;
+            double w;
+            double t;
+            do {
+                final double u1 = rng.nextDouble();
+                final double u2 = rng.nextDouble();
+                final double v = beta * (Math.log(u1) - Math.log1p(-u1));
+                w = a * Math.exp(v);
+                final double z = u1 * u1 * u2;
+                r = gamma * v - 1.3862944;
+                final double s = a + r - w;
+                if (s + 2.609438 >= 5 * z) {
+                    break;
+                }
 
-    /** {@inheritDoc} */
-    @Override
-    public SharedStateContinuousSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
-        return new ChengBetaSampler(rng, this);
+                t = Math.log(z);
+                if (s >= t) {
+                    break;
+                }
+            } while (r + alpha * (logAlpha - Math.log(b + w)) < t);
+
+            w = Math.min(w, Double.MAX_VALUE);
+
+            return aIsAlphaShape ? w / (b + w) : b / (b + w);
+        }
+
+        @Override
+        public SharedStateContinuousSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new ChengBBBetaSampler(rng, this);
+        }
     }
 
     /**
-     * Computes one sample using Cheng's BB algorithm, when \( \alpha \) and
-     * \( \beta \) are both larger than 1.
-     *
-     * @param a \( \min(\alpha, \beta) \).
-     * @param b \( \max(\alpha, \beta) \).
-     * @return a random sample.
+     * Computes one sample using Cheng's BC algorithm, when at least one of 
beta distribution
+     * {@code alpha} or {@code beta} shape parameters is smaller than 1.
      */
-    private double algorithmBB(double a,
-                               double b) {
-        final double alpha = a + b;
-        final double beta = Math.sqrt((alpha - 2) / (2 * a * b - alpha));
-        final double gamma = a + 1 / beta;
-
-        double r;
-        double w;
-        double t;
-        do {
-            final double u1 = rng.nextDouble();
-            final double u2 = rng.nextDouble();
-            final double v = beta * (Math.log(u1) - Math.log1p(-u1));
-            w = a * Math.exp(v);
-            final double z = u1 * u1 * u2;
-            r = gamma * v - 1.3862944;
-            final double s = a + r - w;
-            if (s + 2.609438 >= 5 * z) {
-                break;
-            }
+    private static class ChengBCBetaSampler extends BaseChengBetaSampler {
+        /** 1/2. */
+        private static final double ONE_HALF = 1d / 2;
+        /** 1/4. */
+        private static final double ONE_QUARTER = 1d / 4;
 
-            t = Math.log(z);
-            if (s >= t) {
-                break;
-            }
-        } while (r + alpha * (Math.log(alpha) - Math.log(b + w)) < t);
+        /** The algorithm beta factor. This is not the beta distribution beta 
shape parameter. */
+        private final double beta;
+        /** The algorithm delta factor. */
+        private final double delta;
+        /** The algorithm k1 factor. */
+        private final double k1;
+        /** The algorithm k2 factor. */
+        private final double k2;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param aIsAlphaShape true if {@code a} is the beta distribution 
alpha shape parameter.
+         * @param a max(alpha, beta) shape parameter.
+         * @param b min(alpha, beta) shape parameter.
+         */
+        ChengBCBetaSampler(UniformRandomProvider rng, boolean aIsAlphaShape, 
double a, double b) {
+            super(rng, aIsAlphaShape, a, b);
+            // Compute algorithm factors.
+            beta = 1 / b;
+            delta = 1 + a - b;
+            k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 0.777778);
+            k2 = 0.25 + (0.5 + 0.25 / delta) * b;
+        }
 
-        w = Math.min(w, Double.MAX_VALUE);
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param source Source to copy.
+         */
+        private ChengBCBetaSampler(UniformRandomProvider rng,
+                                   ChengBCBetaSampler source) {
+            super(rng, source);
+            beta = source.beta;
+            delta = source.delta;
+            k1 = source.k1;
+            k2 = source.k2;
+        }
 
-        return equals(a, alphaShape) ? w / (b + w) : b / (b + w);
-    }
+        @Override
+        public double sample() {
+            double w;
+            while (true) {
+                final double u1 = rng.nextDouble();
+                final double u2 = rng.nextDouble();
+                final double y = u1 * u2;
+                final double z = u1 * y;
+                if (u1 < ONE_HALF) {
+                    if (0.25 * u2 + z - y >= k1) {
+                        continue;
+                    }
+                } else {
+                    if (z <= ONE_QUARTER) {
+                        final double v = beta * (Math.log(u1) - 
Math.log1p(-u1));
+                        w = a * Math.exp(v);
+                        break;
+                    }
 
-    /**
-     * Computes one sample using Cheng's BC algorithm, when at least one of
-     * \( \alpha \) or \( \beta \) is smaller than 1.
-     *
-     * @param a \( \max(\alpha, \beta) \).
-     * @param b \( \min(\alpha, \beta) \).
-     * @return a random sample.
-     */
-    private double algorithmBC(double a,
-                               double b) {
-        final double alpha = a + b;
-        final double beta = 1 / b;
-        final double delta = 1 + a - b;
-        final double k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 
0.777778);
-        final double k2 = 0.25 + (0.5 + 0.25 / delta) * b;
-
-        double w;
-        while (true) {
-            final double u1 = rng.nextDouble();
-            final double u2 = rng.nextDouble();
-            final double y = u1 * u2;
-            final double z = u1 * y;
-            if (u1 < ONE_HALF) {
-                if (0.25 * u2 + z - y >= k1) {
-                    continue;
-                }
-            } else {
-                if (z <= ONE_QUARTER) {
-                    final double v = beta * (Math.log(u1) - Math.log1p(-u1));
-                    w = a * Math.exp(v);
-                    break;
+                    if (z >= k2) {
+                        continue;
+                    }
                 }
 
-                if (z >= k2) {
-                    continue;
+                final double v = beta * (Math.log(u1) - Math.log1p(-u1));
+                w = a * Math.exp(v);
+                if (alpha * (logAlpha - Math.log(b + w) + v) - 1.3862944 >= 
Math.log(z)) {
+                    break;
                 }
             }
 
-            final double v = beta * (Math.log(u1) - Math.log1p(-u1));
-            w = a * Math.exp(v);
-            if (alpha * (Math.log(alpha) - Math.log(b + w) + v) - 1.3862944 >= 
Math.log(z)) {
-                break;
-            }
-        }
+            w = Math.min(w, Double.MAX_VALUE);
 
-        w = Math.min(w, Double.MAX_VALUE);
+            return aIsAlphaShape ? w / (b + w) : b / (b + w);
+        }
 
-        return equals(a, alphaShape) ? w / (b + w) : b / (b + w);
+        @Override
+        public SharedStateContinuousSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new ChengBCBetaSampler(rng, this);
+        }
     }
 
     /**
-     * @param a Value.
-     * @param b Value.
-     * @return {@code true} if {@code a} is equal to {@code b}.
+     * Creates a sampler instance.
+     *
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param alpha Distribution first shape parameter.
+     * @param beta Distribution second shape parameter.
+     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0}
      */
-    private boolean equals(double a,
-                           double b) {
-        return Math.abs(a - b) <= Double.MIN_VALUE;
+    public ChengBetaSampler(UniformRandomProvider rng,
+                            double alpha,
+                            double beta) {
+        super(null);
+        delegate = of(rng, alpha, beta);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        return delegate.sample();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SharedStateContinuousSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+        return delegate.withUniformRandomProvider(rng);
     }
 
     /**
-     * Creates a new Beta distribution sampler.
+     * Creates a new beta distribution sampler.
      *
      * @param rng Generator of uniformly distributed random numbers.
      * @param alpha Distribution first shape parameter.
@@ -223,6 +310,24 @@ public class ChengBetaSampler
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
                                                   double alpha,
                                                   double beta) {
-        return new ChengBetaSampler(rng, alpha, beta);
+        if (alpha <= 0) {
+            throw new IllegalArgumentException("alpha is not strictly 
positive: " + alpha);
+        }
+        if (beta <= 0) {
+            throw new IllegalArgumentException("beta is not strictly positive: 
" + beta);
+        }
+
+        // Choose the algorithm.
+        final double a = Math.min(alpha, beta);
+        final double b = Math.max(alpha, beta);
+        final boolean aIsAlphaShape = a == alpha;
+
+        return a > 1 ?
+            // BB algorithm
+            new ChengBBBetaSampler(rng, aIsAlphaShape, a, b) :
+            // The BC algorithm is deliberately invoked with reversed 
parameters
+            // as the argument order is: max(alpha,beta), min(alpha,beta).
+            // Also invert the 'a is alpha' flag.
+            new ChengBCBetaSampler(rng, !aIsAlphaShape, b, a);
     }
 }
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ChengBetaSamplerTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ChengBetaSamplerTest.java
index 8325244..0af2887 100644
--- 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ChengBetaSamplerTest.java
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ChengBetaSamplerTest.java
@@ -20,6 +20,7 @@ import org.apache.commons.rng.RestorableUniformRandomProvider;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.sampling.RandomAssert;
 import org.apache.commons.rng.simple.RandomSource;
+import org.junit.Assert;
 import org.junit.Test;
 
 /**
@@ -54,14 +55,58 @@ public class ChengBetaSamplerTest {
      * Test the SharedStateSampler implementation.
      */
     @Test
-    public void testSharedStateSampler() {
+    public void 
testSharedStateSamplerWithAlphaAndBetaAbove1AndAlphaBelowBeta() {
+        testSharedStateSampler(1.23, 4.56);
+    }
+
+    /**
+     * Test the SharedStateSampler implementation.
+     */
+    @Test
+    public void 
testSharedStateSamplerWithAlphaAndBetaAbove1AndAlphaAboveBeta() {
+        testSharedStateSampler(4.56, 1.23);
+    }
+
+    /**
+     * Test the SharedStateSampler implementation.
+     */
+    @Test
+    public void testSharedStateSamplerWithAlphaOrBetaBelow1AndAlphaBelowBeta() 
{
+        testSharedStateSampler(0.23, 4.56);
+    }
+
+    /**
+     * Test the SharedStateSampler implementation.
+     */
+    @Test
+    public void testSharedStateSamplerWithAlphaOrBetaBelow1AndAlphaAboveBeta() 
{
+        testSharedStateSampler(4.56, 0.23);
+    }
+
+    /**
+     * Test the SharedStateSampler implementation.
+     *
+     * @param alpha Alpha.
+     * @param beta Beta.
+     */
+    private static void testSharedStateSampler(double alpha, double beta) {
         final UniformRandomProvider rng1 = 
RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = 
RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
-        final double alpha = 1.23;
-        final double beta = 4.56;
+        // Use instance constructor not factory constructor to exercise 1.X 
public API
         final SharedStateContinuousSampler sampler1 =
-            ChengBetaSampler.of(rng1, alpha, beta);
+            new ChengBetaSampler(rng1, alpha, beta);
         final SharedStateContinuousSampler sampler2 = 
sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
+
+    /**
+     * Test the toString method. This is added to ensure coverage as the 
factory constructor
+     * used in other tests does not create an instance of the wrapper class.
+     */
+    @Test
+    public void testToString() {
+        final UniformRandomProvider rng = 
RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
+        Assert.assertTrue(new ChengBetaSampler(rng, 1.0, 2.0).toString()
+                .toLowerCase().contains("beta"));
+    }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 795e256..ae40c29 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -75,6 +75,10 @@ re-run tests that fail, and pass the build if they succeed
 within the allotted number of reruns (the test will be marked
 as 'flaky' in the report).
 ">
+      <action dev="aherbert" type="update" issue="RNG-121">
+        "ChengBetaSampler": Algorithms for different distribution parameters 
have
+        been delegated to specialised classes.
+      </action>
       <action dev="aherbert" type="update" issue="RNG-120">
         Update security of serialization code for java.util.Random instances. 
Implement
         look-ahead deserialization or remove the use of 
ObjectInputStream.readObject().

Reply via email to