| /* |
| * 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.LargeMeanPoissonSampler.LargeMeanPoissonSamplerState; |
| |
| /** |
| * Create a sampler for the |
| * <a href="http://mathworld.wolfram.com/PoissonDistribution.html">Poisson |
| * distribution</a> using a cache to minimise construction cost. |
| * |
| * <p>The cache will return a sampler equivalent to |
| * {@link PoissonSampler#PoissonSampler(UniformRandomProvider, double)}.</p> |
| * |
| * <p>The cache allows the {@link PoissonSampler} construction cost to be minimised |
| * for low size Poisson samples. The cache stores state for a range of integers where |
| * integer value {@code n} can be used to construct a sampler for the range |
| * {@code n <= mean < n+1}.</p> |
| * |
| * <p>The cache is advantageous under the following conditions:</p> |
| * |
| * <ul> |
| * <li>The mean of the Poisson distribution falls within a known range. |
| * <li>The sample size to be made with the <strong>same</strong> sampler is |
| * small. |
| * <li>The Poisson samples have different means with the same integer |
| * value(s) after rounding down. |
| * </ul> |
| * |
| * <p>If the sample size to be made with the <strong>same</strong> sampler is large |
| * then the construction cost is low compared to the sampling time and the cache |
| * has minimal benefit.</p> |
| * |
| * <p>Performance improvement is dependent on the speed of the |
| * {@link UniformRandomProvider}. A fast provider can obtain a two-fold speed |
| * improvement for a single-use Poisson sampler.</p> |
| * |
| * <p>The cache is thread safe. Note that concurrent threads using the cache |
| * must ensure a thread safe {@link UniformRandomProvider} is used when creating |
| * samplers, e.g. a unique sampler per thread.</p> |
| * |
| * <p>Sampling uses:</p> |
| * |
| * <ul> |
| * <li>{@link UniformRandomProvider#nextDouble()} |
| * <li>{@link UniformRandomProvider#nextLong()} (large means only) |
| * </ul> |
| * |
| * @since 1.2 |
| */ |
| public class PoissonSamplerCache { |
| |
| /** |
| * The minimum N covered by the cache where |
| * {@code N = (int)Math.floor(mean)}. |
| */ |
| private final int minN; |
| /** |
| * The maximum N covered by the cache where |
| * {@code N = (int)Math.floor(mean)}. |
| */ |
| private final int maxN; |
| /** The cache of states between {@link minN} and {@link maxN}. */ |
| private final LargeMeanPoissonSamplerState[] values; |
| |
| /** |
| * @param minMean The minimum mean covered by the cache. |
| * @param maxMean The maximum mean covered by the cache. |
| * @throws IllegalArgumentException if {@code maxMean < minMean} |
| */ |
| public PoissonSamplerCache(double minMean, |
| double maxMean) { |
| |
| checkMeanRange(minMean, maxMean); |
| |
| // The cache can only be used for the LargeMeanPoissonSampler. |
| if (maxMean < PoissonSampler.PIVOT) { |
| // The upper limit is too small so no cache will be used. |
| // This class will just construct new samplers. |
| minN = 0; |
| maxN = 0; |
| values = null; |
| } else { |
| // Convert the mean into integers. |
| // Note the minimum is clipped to the algorithm switch point. |
| this.minN = (int) Math.floor(Math.max(minMean, PoissonSampler.PIVOT)); |
| this.maxN = (int) Math.floor(Math.min(maxMean, Integer.MAX_VALUE)); |
| values = new LargeMeanPoissonSamplerState[maxN - minN + 1]; |
| } |
| } |
| |
| /** |
| * @param minN The minimum N covered by the cache where {@code N = (int)Math.floor(mean)}. |
| * @param maxN The maximum N covered by the cache where {@code N = (int)Math.floor(mean)}. |
| * @param states The precomputed states. |
| */ |
| private PoissonSamplerCache(int minN, |
| int maxN, |
| LargeMeanPoissonSamplerState[] states) { |
| this.minN = minN; |
| this.maxN = maxN; |
| // Stored directly as the states were newly created within this class. |
| this.values = states; |
| } |
| |
| /** |
| * Check the mean range. |
| * |
| * @param minMean The minimum mean covered by the cache. |
| * @param maxMean The maximum mean covered by the cache. |
| * @throws IllegalArgumentException if {@code maxMean < minMean} |
| */ |
| private static void checkMeanRange(double minMean, double maxMean) { |
| // Note: |
| // Although a mean of 0 is invalid for a Poisson sampler this case |
| // is handled to make the cache user friendly. Any low means will |
| // be handled by the SmallMeanPoissonSampler and not cached. |
| // For this reason it is also OK if the means are negative. |
| |
| // Allow minMean == maxMean so that the cache can be used |
| // to create samplers with distinct RNGs and the same mean. |
| if (maxMean < minMean) { |
| throw new IllegalArgumentException( |
| "Max mean: " + maxMean + " < " + minMean); |
| } |
| } |
| |
| /** |
| * Creates a new Poisson sampler. |
| * |
| * <p>The returned sampler will function exactly the |
| * same as {@link PoissonSampler#of(UniformRandomProvider, double)}. |
| * |
| * @param rng Generator of uniformly distributed random numbers. |
| * @param mean Mean. |
| * @return A Poisson sampler |
| * @throws IllegalArgumentException if {@code mean <= 0} or |
| * {@code mean >} {@link Integer#MAX_VALUE}. |
| */ |
| public DiscreteSampler createPoissonSampler(UniformRandomProvider rng, |
| double mean) { |
| // Ensure the same functionality as the PoissonSampler by |
| // using a SmallMeanPoissonSampler under the switch point. |
| if (mean < PoissonSampler.PIVOT) { |
| return SmallMeanPoissonSampler.of(rng, mean); |
| } |
| if (mean > maxN) { |
| // Outside the range of the cache. |
| // This avoids extra parameter checks and handles the case when |
| // the cache is empty or if Math.floor(mean) is not an integer. |
| return LargeMeanPoissonSampler.of(rng, mean); |
| } |
| |
| // Convert the mean into an integer. |
| final int n = (int) Math.floor(mean); |
| if (n < minN) { |
| // Outside the lower range of the cache. |
| return LargeMeanPoissonSampler.of(rng, mean); |
| } |
| |
| // Look in the cache for a state that can be reused. |
| // Note: The cache is offset by minN. |
| final int index = n - minN; |
| final LargeMeanPoissonSamplerState state = values[index]; |
| if (state == null) { |
| // Create a sampler and store the state for reuse. |
| // Do not worry about thread contention |
| // as the state is effectively immutable. |
| // If recomputed and replaced it will the same. |
| final LargeMeanPoissonSampler sampler = new LargeMeanPoissonSampler(rng, mean); |
| values[index] = sampler.getState(); |
| return sampler; |
| } |
| // Compute the remaining fraction of the mean |
| final double lambdaFractional = mean - n; |
| return new LargeMeanPoissonSampler(rng, state, lambdaFractional); |
| } |
| |
| /** |
| * Check if the mean is within the range where the cache can minimise the |
| * construction cost of the {@link PoissonSampler}. |
| * |
| * @param mean |
| * the mean |
| * @return true, if within the cache range |
| */ |
| public boolean withinRange(double mean) { |
| if (mean < PoissonSampler.PIVOT) { |
| // Construction is optimal |
| return true; |
| } |
| // Convert the mean into an integer. |
| final int n = (int) Math.floor(mean); |
| return n <= maxN && n >= minN; |
| } |
| |
| /** |
| * Checks if the cache covers a valid range of mean values. |
| * |
| * <p>Note that the cache is only valid for one of the Poisson sampling |
| * algorithms. In the instance that a range was requested that was too |
| * low then there is nothing to cache and this functions returns |
| * {@code false}. |
| * |
| * <p>The cache can still be used to create a {@link PoissonSampler} using |
| * {@link #createPoissonSampler(UniformRandomProvider, double)}. |
| * |
| * <p>This method can be used to determine if the cache has a potential |
| * performance benefit. |
| * |
| * @return true, if the cache covers a range of mean values |
| */ |
| public boolean isValidRange() { |
| return values != null; |
| } |
| |
| /** |
| * Gets the minimum mean covered by the cache. |
| * |
| * <p>This value is the inclusive lower bound and is equal to |
| * the lowest integer-valued mean that is covered by the cache. |
| * |
| * <p>Note that this value may not match the value passed to the constructor |
| * due to the following reasons: |
| * |
| * <ul> |
| * <li>At small mean values a different algorithm is used for Poisson |
| * sampling and the cache is unnecessary. |
| * <li>The minimum is always an integer so may be below the constructor |
| * minimum mean. |
| * </ul> |
| * |
| * <p>If {@link #isValidRange()} returns {@code true} the cache will store |
| * state to reduce construction cost of samplers in |
| * the range {@link #getMinMean()} inclusive to {@link #getMaxMean()} |
| * inclusive. Otherwise this method returns 0; |
| * |
| * @return The minimum mean covered by the cache. |
| */ |
| public double getMinMean() { |
| return minN; |
| } |
| |
| /** |
| * Gets the maximum mean covered by the cache. |
| * |
| * <p>This value is the inclusive upper bound and is equal to |
| * the double value below the first integer-valued mean that is |
| * above range covered by the cache. |
| * |
| * <p>Note that this value may not match the value passed to the constructor |
| * due to the following reasons: |
| * <ul> |
| * <li>At small mean values a different algorithm is used for Poisson |
| * sampling and the cache is unnecessary. |
| * <li>The maximum is always the double value below an integer so |
| * may be above the constructor maximum mean. |
| * </ul> |
| * |
| * <p>If {@link #isValidRange()} returns {@code true} the cache will store |
| * state to reduce construction cost of samplers in |
| * the range {@link #getMinMean()} inclusive to {@link #getMaxMean()} |
| * inclusive. Otherwise this method returns 0; |
| * |
| * @return The maximum mean covered by the cache. |
| */ |
| public double getMaxMean() { |
| if (isValidRange()) { |
| return Math.nextAfter(maxN + 1.0, -1); |
| } |
| return 0; |
| } |
| |
| /** |
| * Gets the minimum mean value that can be cached. |
| * |
| * <p>Any {@link PoissonSampler} created with a mean below this level will not |
| * have any state that can be cached. |
| * |
| * @return the minimum cached mean |
| */ |
| public static double getMinimumCachedMean() { |
| return PoissonSampler.PIVOT; |
| } |
| |
| /** |
| * Create a new {@link PoissonSamplerCache} with the given range |
| * reusing the current cache values. |
| * |
| * <p>This will create a new object even if the range is smaller or the |
| * same as the current cache. |
| * |
| * @param minMean The minimum mean covered by the cache. |
| * @param maxMean The maximum mean covered by the cache. |
| * @throws IllegalArgumentException if {@code maxMean < minMean} |
| * @return the poisson sampler cache |
| */ |
| public PoissonSamplerCache withRange(double minMean, |
| double maxMean) { |
| if (values == null) { |
| // Nothing to reuse |
| return new PoissonSamplerCache(minMean, maxMean); |
| } |
| checkMeanRange(minMean, maxMean); |
| |
| // The cache can only be used for the LargeMeanPoissonSampler. |
| if (maxMean < PoissonSampler.PIVOT) { |
| return new PoissonSamplerCache(0, 0); |
| } |
| |
| // Convert the mean into integers. |
| // Note the minimum is clipped to the algorithm switch point. |
| final int withMinN = (int) Math.floor(Math.max(minMean, PoissonSampler.PIVOT)); |
| final int withMaxN = (int) Math.floor(maxMean); |
| final LargeMeanPoissonSamplerState[] states = |
| new LargeMeanPoissonSamplerState[withMaxN - withMinN + 1]; |
| |
| // Preserve values from the current array to the next |
| int currentIndex; |
| int nextIndex; |
| if (this.minN <= withMinN) { |
| // The current array starts before the new array |
| currentIndex = withMinN - this.minN; |
| nextIndex = 0; |
| } else { |
| // The new array starts before the current array |
| currentIndex = 0; |
| nextIndex = this.minN - withMinN; |
| } |
| final int length = Math.min(values.length - currentIndex, states.length - nextIndex); |
| if (length > 0) { |
| System.arraycopy(values, currentIndex, states, nextIndex, length); |
| } |
| |
| return new PoissonSamplerCache(withMinN, withMaxN, states); |
| } |
| } |