Merge in master
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java
new file mode 100644
index 0000000..a729686
--- /dev/null
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.rng.sampling.distribution;
+
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.sampling.distribution.InternalUtils.FactorialLog;
+
+/**
+ * Sampler for the <a href="http://mathworld.wolfram.com/PoissonDistribution.html">Poisson distribution</a>.
+ *
+ * <ul>
+ *  <li>
+ *   For large means, we use the rejection algorithm described in
+ *   <blockquote>
+ *    Devroye, Luc. (1981).<i>The Computer Generation of Poisson Random Variables</i><br>
+ *    <strong>Computing</strong> vol. 26 pp. 197-207.
+ *   </blockquote>
+ *  </li>
+ * </ul>
+ *
+ * This sampler is suitable for {@code mean >= 40}.
+ */
+public class LargeMeanPoissonSampler
+    extends SamplerBase
+    implements DiscreteSampler {
+
+    /** Class to compute {@code log(n!)}. This has no cached values. */
+    private static final InternalUtils.FactorialLog NO_CACHE_FACTORIAL_LOG;
+
+    static {
+        // Create without a cache.
+        NO_CACHE_FACTORIAL_LOG = FactorialLog.create();
+    }
+
+    /** Exponential. */
+    private final ContinuousSampler exponential;
+    /** Gaussian. */
+    private final ContinuousSampler gaussian;
+    /** Local class to compute {@code log(n!)}. This may have cached values. */
+    private final InternalUtils.FactorialLog factorialLog;
+
+    // Working values
+
+    /** Algorithm constant: {@code Math.floor(mean)}. */
+    private final double lambda;
+    /** Algorithm constant: {@code mean - lambda}. */
+    private final double lambdaFractional;
+    /** Algorithm constant: {@code Math.log(lambda)}. */
+    private final double logLambda;
+    /** Algorithm constant: {@code factorialLog((int) lambda)}. */
+    private final double logLambdaFactorial;
+    /** Algorithm constant: {@code Math.sqrt(lambda * Math.log(32 * lambda / Math.PI + 1))}. */
+    private final double delta;
+    /** Algorithm constant: {@code delta / 2}. */
+    private final double halfDelta;
+    /** Algorithm constant: {@code 2 * lambda + delta}. */
+    private final double twolpd;
+    /**
+     * Algorithm constant: {@code a1 / aSum} with
+     * <ul>
+     *  <li>{@code a1 = Math.sqrt(Math.PI * twolpd) * Math.exp(c1)}</li>
+     *  <li>{@code aSum = a1 + a2 + 1}</li>
+     * </ul>
+     */
+    private final double p1;
+    /**
+     * Algorithm constant: {@code a1 / aSum} with
+     * <ul>
+     *  <li>{@code a2 = (twolpd / delta) * Math.exp(-delta * (1 + delta) / twolpd)}</li>
+     *  <li>{@code aSum = a1 + a2 + 1}</li>
+     * </ul>
+     */
+    private final double p2;
+    /** Algorithm constant: {@code 1 / (8 * lambda)}. */
+    private final double c1;
+
+    /** The internal Poisson sampler for the lambda fraction. */
+    private final DiscreteSampler smallMeanPoissonSampler;
+
+    /**
+     * @param rng  Generator of uniformly distributed random numbers.
+     * @param mean Mean.
+     * @throws IllegalArgumentException if {@code mean <= 0}.
+     */
+    public LargeMeanPoissonSampler(UniformRandomProvider rng,
+                                   double mean) {
+        super(rng);
+        if (mean <= 0) {
+            throw new IllegalArgumentException(mean + " <= " + 0);
+        }
+
+        gaussian = new ZigguratNormalizedGaussianSampler(rng);
+        exponential = new AhrensDieterExponentialSampler(rng, 1);
+        // Plain constructor uses the uncached function.
+        factorialLog = NO_CACHE_FACTORIAL_LOG;
+
+        // Cache values used in the algorithm
+        lambda = Math.floor(mean);
+        lambdaFractional = mean - lambda;
+        logLambda = Math.log(lambda);
+        logLambdaFactorial = factorialLog((int) lambda);
+        delta = Math.sqrt(lambda * Math.log(32 * lambda / Math.PI + 1));
+        halfDelta = delta / 2;
+        twolpd = 2 * lambda + delta;
+        c1 = 1 / (8 * lambda);
+        final double a1 = Math.sqrt(Math.PI * twolpd) * Math.exp(c1);
+        final double a2 = (twolpd / delta) * Math.exp(-delta * (1 + delta) / twolpd);
+        final double aSum = a1 + a2 + 1;
+        p1 = a1 / aSum;
+        p2 = a2 / aSum;
+
+        // The algorithm requires a Poisson sample from the remaining lambda fraction.
+        smallMeanPoissonSampler = (lambdaFractional < Double.MIN_VALUE) ?
+            null : // Not used.
+            new SmallMeanPoissonSampler(rng, lambdaFractional);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+
+        final int y2 = (smallMeanPoissonSampler == null) ?
+            0 : // No lambda fraction
+            smallMeanPoissonSampler.sample();
+
+        double x = 0;
+        double y = 0;
+        double v = 0;
+        int a = 0;
+        double t = 0;
+        double qr = 0;
+        double qa = 0;
+        while (true) {
+            final double u = nextDouble();
+            if (u <= p1) {
+                final double n = gaussian.sample();
+                x = n * Math.sqrt(lambda + halfDelta) - 0.5d;
+                if (x > delta || x < -lambda) {
+                    continue;
+                }
+                y = x < 0 ? Math.floor(x) : Math.ceil(x);
+                final double e = exponential.sample();
+                v = -e - 0.5 * n * n + c1;
+            } else {
+                if (u > p1 + p2) {
+                    y = lambda;
+                    break;
+                }
+                x = delta + (twolpd / delta) * exponential.sample();
+                y = Math.ceil(x);
+                v = -exponential.sample() - delta * (x + 1) / twolpd;
+            }
+            a = x < 0 ? 1 : 0;
+            t = y * (y + 1) / (2 * lambda);
+            if (v < -t && a == 0) {
+                y = lambda + y;
+                break;
+            }
+            qr = t * ((2 * y + 1) / (6 * lambda) - 1);
+            qa = qr - (t * t) / (3 * (lambda + a * (y + 1)));
+            if (v < qa) {
+                y = lambda + y;
+                break;
+            }
+            if (v > qr) {
+                continue;
+            }
+            if (v < y * logLambda - factorialLog((int) (y + lambda)) + logLambdaFactorial) {
+                y = lambda + y;
+                break;
+            }
+        }
+
+        return (int) Math.min(y2 + (long) y, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Compute the natural logarithm of the factorial of {@code n}.
+     *
+     * @param n Argument.
+     * @return {@code log(n!)}
+     * @throws IllegalArgumentException if {@code n < 0}.
+     */
+    private double factorialLog(int n) {
+        return factorialLog.value(n);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Large Mean Poisson deviate [" + super.toString() + "]";
+    }
+}
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/PoissonSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/PoissonSampler.java
index 195e725..cd9187a 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/PoissonSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/PoissonSampler.java
@@ -30,25 +30,19 @@
  *  <li>
  *   For large means, we use the rejection algorithm described in
  *   <blockquote>
- *    Devroye, Luc. (1981).<i>The Computer Generation of Poisson Random Variables</i><br>
+ *    Devroye, Luc. (1981). <i>The Computer Generation of Poisson Random Variables</i><br>
  *    <strong>Computing</strong> vol. 26 pp. 197-207.
  *   </blockquote>
  *  </li>
  * </ul>
  */
 public class PoissonSampler
-    extends SamplerBase
     implements DiscreteSampler {
+
     /** Value for switching sampling algorithm. */
     private static final double PIVOT = 40;
-    /** Mean of the distribution. */
-    private final double mean;
-    /** Exponential. */
-    private final ContinuousSampler exponential;
-    /** Gaussian. */
-    private final NormalizedGaussianSampler gaussian;
-    /** {@code log(n!)}. */
-    private final InternalUtils.FactorialLog factorialLog;
+    /** The internal Poisson sampler. */
+    private final DiscreteSampler poissonSampler;
 
     /**
      * @param rng Generator of uniformly distributed random numbers.
@@ -57,24 +51,17 @@
      */
     public PoissonSampler(UniformRandomProvider rng,
                           double mean) {
-        super(rng);
-        if (mean <= 0) {
-            throw new IllegalArgumentException(mean + " <= " + 0);
-        }
-
-        this.mean = mean;
-
-        gaussian = new ZigguratNormalizedGaussianSampler(rng);
-        exponential = new AhrensDieterExponentialSampler(rng, 1);
-        factorialLog = mean < PIVOT ?
-            null : // Not used.
-            InternalUtils.FactorialLog.create().withCache((int) Math.min(mean, 2 * PIVOT));
+        // Delegate all work to specialised samplers.
+        // These should check the input arguments.
+        poissonSampler = mean < PIVOT ?
+            new SmallMeanPoissonSampler(rng, mean) :
+            new LargeMeanPoissonSampler(rng, mean);
     }
 
     /** {@inheritDoc} */
     @Override
     public int sample() {
-        return (int) Math.min(nextPoisson(mean), Integer.MAX_VALUE);
+        return poissonSampler.sample();
     }
 
     /** {@inheritDoc} */
@@ -82,103 +69,4 @@
     public String toString() {
         return "Poisson deviate [" + super.toString() + "]";
     }
-
-    /**
-     * @param meanPoisson Mean.
-     * @return the next sample.
-     */
-    private long nextPoisson(double meanPoisson) {
-        if (meanPoisson < PIVOT) {
-            double p = Math.exp(-meanPoisson);
-            long n = 0;
-            double r = 1;
-
-            while (n < 1000 * meanPoisson) {
-                r *= nextDouble();
-                if (r >= p) {
-                    n++;
-                } else {
-                    break;
-                }
-            }
-            return n;
-        } else {
-            final double lambda = Math.floor(meanPoisson);
-            final double lambdaFractional = meanPoisson - lambda;
-            final double logLambda = Math.log(lambda);
-            final double logLambdaFactorial = factorialLog((int) lambda);
-            final long y2 = lambdaFractional < Double.MIN_VALUE ? 0 : nextPoisson(lambdaFractional);
-            final double delta = Math.sqrt(lambda * Math.log(32 * lambda / Math.PI + 1));
-            final double halfDelta = delta / 2;
-            final double twolpd = 2 * lambda + delta;
-            final double a1 = Math.sqrt(Math.PI * twolpd) * Math.exp(1 / (8 * lambda));
-            final double a2 = (twolpd / delta) * Math.exp(-delta * (1 + delta) / twolpd);
-            final double aSum = a1 + a2 + 1;
-            final double p1 = a1 / aSum;
-            final double p2 = a2 / aSum;
-            final double c1 = 1 / (8 * lambda);
-
-            double x;
-            double y;
-            double v;
-            int a;
-            double t;
-            double qr;
-            double qa;
-            while (true) {
-                final double u = nextDouble();
-                if (u <= p1) {
-                    final double n = gaussian.sample();
-                    x = n * Math.sqrt(lambda + halfDelta) - 0.5;
-                    if (x > delta ||
-                        x < -lambda) {
-                        continue;
-                    }
-                    y = x < 0 ? Math.floor(x) : Math.ceil(x);
-                    final double e = exponential.sample();
-                    v = -e - 0.5 * n * n + c1;
-                } else {
-                    if (u > p1 + p2) {
-                        y = lambda;
-                        break;
-                    } else {
-                        x = delta + (twolpd / delta) * exponential.sample();
-                        y = Math.ceil(x);
-                        v = -exponential.sample() - delta * (x + 1) / twolpd;
-                    }
-                }
-                a = x < 0 ? 1 : 0;
-                t = y * (y + 1) / (2 * lambda);
-                if (v < -t && a == 0) {
-                    y = lambda + y;
-                    break;
-                }
-                qr = t * ((2 * y + 1) / (6 * lambda) - 1);
-                qa = qr - (t * t) / (3 * (lambda + a * (y + 1)));
-                if (v < qa) {
-                    y = lambda + y;
-                    break;
-                }
-                if (v > qr) {
-                    continue;
-                }
-                if (v < y * logLambda - factorialLog((int) (y + lambda)) + logLambdaFactorial) {
-                    y = lambda + y;
-                    break;
-                }
-            }
-            return y2 + (long) y;
-        }
-    }
-
-    /**
-     * Compute the natural logarithm of the factorial of {@code n}.
-     *
-     * @param n Argument.
-     * @return {@code log(n!)}
-     * @throws IllegalArgumentException if {@code n < 0}.
-     */
-    private double factorialLog(int n) {
-        return factorialLog.value(n);
-    }
 }
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSampler.java
new file mode 100644
index 0000000..283594e
--- /dev/null
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSampler.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.rng.sampling.distribution;
+
+import org.apache.commons.rng.UniformRandomProvider;
+
+/**
+ * Sampler for the <a href="http://mathworld.wolfram.com/PoissonDistribution.html">Poisson distribution</a>.
+ *
+ * <ul>
+ *  <li>
+ *   For small means, a Poisson process is simulated using uniform deviates, as
+ *   described <a href="http://mathaa.epfl.ch/cours/PMMI2001/interactive/rng7.htm">here</a>.
+ *   The Poisson process (and hence, the returned value) is bounded by 1000 * mean.
+ *  </li>
+ * </ul>
+ *
+ * This sampler is suitable for {@code mean < 40}.
+ */
+public class SmallMeanPoissonSampler
+    extends SamplerBase
+    implements DiscreteSampler {
+
+    /**
+     * Pre-compute {@code Math.exp(-mean)}.
+     * Note: This is the probability of the Poisson sample {@code P(n=0)}.
+     */
+    private final double p0;
+    /** Pre-compute {@code 1000 * mean} as the upper limit of the sample. */
+    private final int limit;
+
+    /**
+     * @param rng  Generator of uniformly distributed random numbers.
+     * @param mean Mean.
+     * @throws IllegalArgumentException if {@code mean <= 0}.
+     */
+    public SmallMeanPoissonSampler(UniformRandomProvider rng,
+                                   double mean) {
+        super(rng);
+        if (mean <= 0) {
+            throw new IllegalArgumentException(mean + " <= " + 0);
+        }
+
+        p0 = Math.exp(-mean);
+        // The returned sample is bounded by 1000 * mean or Integer.MAX_VALUE
+        limit = (int) Math.ceil(Math.min(1000 * mean, Integer.MAX_VALUE));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+        int n = 0;
+        double r = 1;
+
+        while (n < limit) {
+            r *= nextDouble();
+            if (r >= p0) {
+                n++;
+            } else {
+                break;
+            }
+        }
+        return n;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Small Mean Poisson deviate [" + super.toString() + "]";
+    }
+}
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
index 2c02bdb..23122c7 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.rng.sampling.distribution;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -109,16 +108,28 @@
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(meanPoisson),
                 MathArrays.sequence(10, 0, 1),
                 new PoissonSampler(RandomSource.create(RandomSource.KISS), meanPoisson));
+            // Dedicated small mean poisson sampler
+            add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(meanPoisson),
+                MathArrays.sequence(10, 0, 1),
+                new SmallMeanPoissonSampler(RandomSource.create(RandomSource.KISS), meanPoisson));
             // Poisson (40 < mean < 80).
             final double largeMeanPoisson = 67.89;
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(largeMeanPoisson),
                 MathArrays.sequence(50, (int) (largeMeanPoisson - 25), 1),
                 new PoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), largeMeanPoisson));
+            // Dedicated large mean poisson sampler
+            add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(largeMeanPoisson),
+                MathArrays.sequence(50, (int) (largeMeanPoisson - 25), 1),
+                new LargeMeanPoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), largeMeanPoisson));
             // Poisson (mean >> 40).
             final double veryLargeMeanPoisson = 543.21;
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(veryLargeMeanPoisson),
                 MathArrays.sequence(100, (int) (veryLargeMeanPoisson - 50), 1),
                 new PoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), veryLargeMeanPoisson));
+            // Dedicated large mean poisson sampler
+            add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(veryLargeMeanPoisson),
+                MathArrays.sequence(100, (int) (veryLargeMeanPoisson - 50), 1),
+                new LargeMeanPoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), veryLargeMeanPoisson));
         } catch (Exception e) {
             System.err.println("Unexpected exception while creating the list of samplers: " + e);
             e.printStackTrace(System.err);
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8901f17..8fba90c 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -51,7 +51,7 @@
   </properties>
   <body>
 
-    <release version="1.1" date="2018-08-05" description="
+    <release version="1.1" date="2018-08-06" description="
 This is a minor release of Apache Commons RNG, containing a
 few new features and performance improvements.
 
@@ -76,6 +76,12 @@
 to re-run tests that fail and pass the build if they succeed (the test will
 be marked as 'flaky' in the report).
 ">
+      <action dev="erans" type="update" issue="RNG-50" due-to="Alex D. Herbert">
+	"PoissonSampler": Algorithms for small mean and large mean have
+	been separated into dedicated classes.  Cache precomputation has
+	been disabled as it is only marginally used and is a performance
+	hit for small sampling sets.
+      </action>
       <action dev="erans" type="add" issue="RNG-37">
         Implementation of the "Ziggurat" algorithm for Gaussian sampling.
       </action>