This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-rng.git
The following commit(s) were added to refs/heads/master by this push:
new c6a5e9f6 RNG-194: Samplers to validate parameters are finite
c6a5e9f6 is described below
commit c6a5e9f686c53c0b9ff620497f82c29e72ba1c7c
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Aug 21 16:10:06 2026 +0100
RNG-194: Samplers to validate parameters are finite
---
.../AhrensDieterExponentialSampler.java | 8 +-
.../AhrensDieterMarsagliaTsangGammaSampler.java | 19 ++-
.../distribution/BoxMullerGaussianSampler.java | 7 +-
.../distribution/BoxMullerLogNormalSampler.java | 3 +-
.../sampling/distribution/ChengBetaSampler.java | 10 +-
.../distribution/ContinuousUniformSampler.java | 11 +-
.../rng/sampling/distribution/GaussianSampler.java | 4 +-
.../sampling/distribution/GeometricSampler.java | 65 +++++++---
.../rng/sampling/distribution/InternalUtils.java | 19 +--
.../InverseTransformParetoSampler.java | 8 +-
.../rng/sampling/distribution/LevySampler.java | 6 +-
.../sampling/distribution/LogNormalSampler.java | 9 +-
.../RejectionInversionZipfSampler.java | 6 +-
.../rng/sampling/distribution/ZigguratSampler.java | 5 +-
.../distribution/ContinuousUniformSamplerTest.java | 8 ++
.../distribution/GeometricSamplerTest.java | 21 +++
.../NonFiniteParameterValidationTest.java | 142 +++++++++++++++++++++
src/changes/changes.xml | 5 +
18 files changed, 286 insertions(+), 70 deletions(-)
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
index 844d50e4..2e1d6af6 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
@@ -77,12 +77,13 @@ public class AhrensDieterExponentialSampler
*
* @param rng Generator of uniformly distributed random numbers.
* @param mean Mean of this distribution.
- * @throws IllegalArgumentException if {@code mean <= 0}
+ * @throws IllegalArgumentException if {@code mean <= 0}, or if {@code
mean}
+ * is not finite
*/
public AhrensDieterExponentialSampler(UniformRandomProvider rng,
double mean) {
// Validation before java.lang.Object constructor exits prevents
partially initialized object
- this(InternalUtils.requireStrictlyPositive(mean, "mean"), rng);
+ this(InternalUtils.requireStrictlyPositiveFinite(mean, "mean"), rng);
}
/**
@@ -161,7 +162,8 @@ public class AhrensDieterExponentialSampler
* @param rng Generator of uniformly distributed random numbers.
* @param mean Mean of the distribution.
* @return the sampler
- * @throws IllegalArgumentException if {@code mean <= 0}
+ * @throws IllegalArgumentException if {@code mean <= 0}, or if {@code
mean}
+ * is not finite
* @since 1.3
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
index 3bb16cd3..e0788d1e 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
@@ -73,14 +73,15 @@ public class AhrensDieterMarsagliaTsangGammaSampler
* @param rng Generator of uniformly distributed random numbers.
* @param alpha Alpha parameter of the distribution.
* @param theta Theta parameter of the distribution.
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0},
+ * or if {@code alpha} or {@code theta} are not finite
*/
BaseGammaSampler(UniformRandomProvider rng,
double alpha,
double theta) {
// Validation before java.lang.Object constructor exits prevents
partially initialized object
- this(InternalUtils.requireStrictlyPositive(alpha, "alpha"),
- InternalUtils.requireStrictlyPositive(theta, "theta"),
+ this(InternalUtils.requireStrictlyPositiveFinite(alpha, "alpha"),
+ InternalUtils.requireStrictlyPositiveFinite(theta, "theta"),
rng);
}
@@ -136,7 +137,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
* @param rng Generator of uniformly distributed random numbers.
* @param alpha Alpha parameter of the distribution.
* @param theta Theta parameter of the distribution.
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0},
+ * or if {@code alpha} or {@code theta} are not finite
*/
AhrensDieterGammaSampler(UniformRandomProvider rng,
double alpha,
@@ -221,7 +223,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
* @param rng Generator of uniformly distributed random numbers.
* @param alpha Alpha parameter of the distribution.
* @param theta Theta parameter of the distribution.
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code
theta <= 0},
+ * or if {@code alpha} or {@code theta} are not finite
*/
MarsagliaTsangGammaSampler(UniformRandomProvider rng,
double alpha,
@@ -282,7 +285,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
* @param rng Generator of uniformly distributed random numbers.
* @param alpha Alpha parameter of the distribution (this is a shape
parameter).
* @param theta Theta parameter of the distribution (this is a scale
parameter).
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta
<= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta
<= 0},
+ * or if {@code alpha} or {@code theta} are not finite
*/
public AhrensDieterMarsagliaTsangGammaSampler(UniformRandomProvider rng,
double alpha,
@@ -321,7 +325,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
* @param alpha Alpha parameter of the distribution (this is a shape
parameter).
* @param theta Theta parameter of the distribution (this is a scale
parameter).
* @return the sampler
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta
<= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta
<= 0},
+ * or if {@code alpha} or {@code theta} are not finite
* @since 1.3
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
index 7831a01d..2361c3fd 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
@@ -53,12 +53,15 @@ public class BoxMullerGaussianSampler
* @param rng Generator of uniformly distributed random numbers.
* @param mean Mean of the Gaussian distribution.
* @param standardDeviation Standard deviation of the Gaussian
distribution.
- * @throws IllegalArgumentException if {@code standardDeviation <= 0}
+ * @throws IllegalArgumentException if {@code standardDeviation <= 0} or
is not finite;
+ * or {@code mean} is not finite
*/
public BoxMullerGaussianSampler(UniformRandomProvider rng,
double mean,
double standardDeviation) {
- this(mean,
InternalUtils.requireStrictlyPositiveFinite(standardDeviation,
"standardDeviation"), rng);
+ this(InternalUtils.requireFinite(mean, "mean"),
+ InternalUtils.requireStrictlyPositiveFinite(standardDeviation,
"standardDeviation"),
+ rng);
}
/**
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
index 3e23fde4..8d3e576e 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
@@ -47,7 +47,8 @@ public class BoxMullerLogNormalSampler
* @param rng Generator of uniformly distributed random numbers.
* @param mu Mean of the natural logarithm of the distribution values.
* @param sigma Standard deviation of the natural logarithm of the
distribution values.
- * @throws IllegalArgumentException if {@code sigma <= 0}.
+ * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+ * or {@code sigma} are not finite.
*/
public BoxMullerLogNormalSampler(UniformRandomProvider rng,
double mu,
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 67fd9156..4e42097a 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
@@ -309,7 +309,8 @@ public class ChengBetaSampler
* @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}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta
<= 0},
+ * or if {@code alpha} or {@code beta} are not finite
*/
public ChengBetaSampler(UniformRandomProvider rng,
double alpha,
@@ -354,14 +355,15 @@ public class ChengBetaSampler
* @param alpha Distribution first shape parameter.
* @param beta Distribution second shape parameter.
* @return the sampler
- * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta
<= 0}
+ * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta
<= 0},
+ * or if {@code alpha} or {@code beta} are not finite
* @since 1.3
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
double alpha,
double beta) {
- InternalUtils.requireStrictlyPositive(alpha, "alpha");
- InternalUtils.requireStrictlyPositive(beta, "beta");
+ InternalUtils.requireStrictlyPositiveFinite(alpha, "alpha");
+ InternalUtils.requireStrictlyPositiveFinite(beta, "beta");
// Choose the algorithm.
final double a = Math.min(alpha, beta);
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
index a5f3e463..52b4940e 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
@@ -105,14 +105,15 @@ public class ContinuousUniformSampler
* @param rng Generator of uniformly distributed random numbers.
* @param lo Lower bound.
* @param hi Higher bound.
+ * @throws IllegalArgumentException if {@code lo} or {@code hi} are not
finite
*/
public ContinuousUniformSampler(UniformRandomProvider rng,
double lo,
double hi) {
super(null);
this.rng = rng;
- this.lo = lo;
- this.hi = hi;
+ this.lo = InternalUtils.requireFinite(lo, "lower bound");
+ this.hi = InternalUtils.requireFinite(hi, "higher bound");
}
/** {@inheritDoc} */
@@ -163,6 +164,7 @@ public class ContinuousUniformSampler
* @param lo Lower bound.
* @param hi Higher bound.
* @return the sampler
+ * @throws IllegalArgumentException if {@code lo} or {@code hi} are not
finite
* @since 1.3
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
@@ -187,13 +189,16 @@ public class ContinuousUniformSampler
* @param excludeBounds Set to {@code true} to use the open interval
* {@code (lower, upper)}.
* @return the sampler
- * @throws IllegalArgumentException If the open interval is invalid.
+ * @throws IllegalArgumentException If the interval bounds are not finite,
+ * or the open interval is invalid.
* @since 1.4
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
double lo,
double hi,
boolean excludeBounds) {
+ InternalUtils.requireFinite(lo, "lo");
+ InternalUtils.requireFinite(hi, "hi");
if (excludeBounds) {
if (!validateOpenInterval(lo, hi)) {
throw new IllegalArgumentException("Invalid open interval (" +
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
index 3db9a875..8a4880d3 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
@@ -46,8 +46,8 @@ public class GaussianSampler implements
SharedStateContinuousSampler {
* @param normalized Generator of N(0,1) Gaussian distributed random
numbers.
* @param mean Mean of the Gaussian distribution.
* @param standardDeviation Standard deviation of the Gaussian
distribution.
- * @throws IllegalArgumentException if {@code standardDeviation <= 0} or
is infinite;
- * or {@code mean} is infinite
+ * @throws IllegalArgumentException if {@code standardDeviation <= 0} or
is not finite;
+ * or {@code mean} is not finite
*/
public GaussianSampler(NormalizedGaussianSampler normalized,
double mean,
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
index 27b63edd..33cf1a21 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
@@ -71,6 +71,32 @@ public final class GeometricSampler {
}
}
+ /**
+ * Sample from the geometric distribution when the probability of success
is effectively zero.
+ */
+ private static final class GeometricP0Sampler
+ implements SharedStateDiscreteSampler {
+ /** The single instance. */
+ static final GeometricP0Sampler INSTANCE = new GeometricP0Sampler();
+
+ @Override
+ public int sample() {
+ // When probability of success is effectively 0 the sample is
always the max value
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public String toString() {
+ return "Geometric(p~0) deviate";
+ }
+
+ @Override
+ public SharedStateDiscreteSampler
withUniformRandomProvider(UniformRandomProvider rng) {
+ // No requirement for a new instance
+ return this;
+ }
+ }
+
/**
* Sample from the geometric distribution by using a related exponential
distribution.
*/
@@ -83,22 +109,11 @@ public final class GeometricSampler {
/**
* @param rng Generator of uniformly distributed random numbers
- * @param probabilityOfSuccess The probability of success (must be in
the range
- * {@code [0 < probabilityOfSuccess < 1]})
+ * @param exponentialMean The mean of the related exponential
distribution (must be
+ * strictly positive finite)
*/
- GeometricExponentialSampler(UniformRandomProvider rng, double
probabilityOfSuccess) {
+ GeometricExponentialSampler(UniformRandomProvider rng, double
exponentialMean) {
this.rng = rng;
- // Use a related exponential distribution:
- // λ = −ln(1 − probabilityOfSuccess)
- // exponential mean = 1 / λ
- // --
- // Note on validation:
- // If probabilityOfSuccess == Math.nextDown(1.0) the exponential
mean is >0 (valid).
- // If probabilityOfSuccess == Double.MIN_VALUE the exponential
mean is +Infinity
- // and the sample will always be Integer.MAX_VALUE (the
distribution is truncated). It
- // is noted in the class Javadoc that the use of a small p leads
to truncation so
- // no checks are made for this case.
- final double exponentialMean = 1.0 /
(-Math.log1p(-probabilityOfSuccess));
exponentialSampler = ZigguratSampler.Exponential.of(rng,
exponentialMean);
}
@@ -149,8 +164,24 @@ public final class GeometricSampler {
"Probability of success (p) must be in the range [0 < p <= 1]:
" +
probabilityOfSuccess);
}
- return probabilityOfSuccess == 1 ?
- GeometricP1Sampler.INSTANCE :
- new GeometricExponentialSampler(rng, probabilityOfSuccess);
+ if (probabilityOfSuccess == 1) {
+ return GeometricP1Sampler.INSTANCE;
+ }
+ // Use a related exponential distribution:
+ // λ = −ln(1 − probabilityOfSuccess)
+ // exponential mean = 1 / λ
+ // --
+ // Note on validation:
+ // If probabilityOfSuccess == Math.nextDown(1.0) the exponential mean
is >0 (valid).
+ // If probabilityOfSuccess == Double.MIN_VALUE the exponential mean is
+Infinity
+ // and the sample will always be Integer.MAX_VALUE (the distribution
is truncated). It
+ // is noted in the class Javadoc that the use of a small p leads to
truncation.
+ // This special case requires a p=0 sampler as the infinite mean will
raise an exception
+ // from the exponential sampler.
+ final double exponentialMean = 1.0 /
(-Math.log1p(-probabilityOfSuccess));
+ if (Double.isInfinite(exponentialMean)) {
+ return GeometricP0Sampler.INSTANCE;
+ }
+ return new GeometricExponentialSampler(rng, exponentialMean);
}
}
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
index 8e150d09..de1e139c 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
@@ -134,30 +134,13 @@ final class InternalUtils {
return x;
}
- /**
- * Checks the value {@code x >= 0}.
- * Note: This method allows {@code x == -0.0}.
- *
- * @param x Value.
- * @param name Name of the value.
- * @return x
- * @throws IllegalArgumentException if {@code x < 0}
- */
- static double requirePositive(double x, String name) {
- // Logic inversion detects NaN
- if (!(x >= 0)) {
- throw new IllegalArgumentException(name + " is not positive: " +
x);
- }
- return x;
- }
-
/**
* Checks the value {@code x > 0}.
*
* @param x Value.
* @param name Name of the value.
* @return x
- * @throws IllegalArgumentException if {@code x <= 0}
+ * @throws IllegalArgumentException if {@code x <= 0} or is NaN
*/
static double requireStrictlyPositive(double x, String name) {
// Logic inversion detects NaN
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
index 08cfaedb..c078b9e6 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
@@ -44,13 +44,14 @@ public class InverseTransformParetoSampler
* @param rng Generator of uniformly distributed random numbers.
* @param scale Scale of the distribution.
* @param shape Shape of the distribution.
- * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape
<= 0}
+ * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape
<= 0},
+ * or if {@code scale} is not finite or {@code shape} is NaN
*/
public InverseTransformParetoSampler(UniformRandomProvider rng,
double scale,
double shape) {
// Validation before java.lang.Object constructor exits prevents
partially initialized object
- this(InternalUtils.requireStrictlyPositive(scale, "scale"),
+ this(InternalUtils.requireStrictlyPositiveFinite(scale, "scale"),
InternalUtils.requireStrictlyPositive(shape, "shape"),
rng);
}
@@ -118,7 +119,8 @@ public class InverseTransformParetoSampler
* @param scale Scale of the distribution.
* @param shape Shape of the distribution.
* @return the sampler
- * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape
<= 0}
+ * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape
<= 0},
+ * or if {@code scale} is not finite
* @since 1.3
*/
public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
index 28468f8b..91c274c7 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
@@ -86,12 +86,14 @@ public final class LevySampler implements
SharedStateContinuousSampler {
* @param location Location of the Lévy distribution.
* @param scale Scale of the Lévy distribution.
* @return the sampler
- * @throws IllegalArgumentException if {@code scale <= 0}
+ * @throws IllegalArgumentException if {@code scale <= 0}, or if
+ * {@code location} or {@code scale} are not finite
*/
public static LevySampler of(UniformRandomProvider rng,
double location,
double scale) {
- InternalUtils.requireStrictlyPositive(scale, "scale");
+ InternalUtils.requireFinite(location, "location");
+ InternalUtils.requireStrictlyPositiveFinite(scale, "scale");
return new LevySampler(rng, location, scale);
}
}
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
index 52f02708..3c97bc06 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
@@ -37,13 +37,15 @@ public class LogNormalSampler implements
SharedStateContinuousSampler {
* @param gaussian N(0,1) generator.
* @param mu Mean of the natural logarithm of the distribution values.
* @param sigma Standard deviation of the natural logarithm of the
distribution values.
- * @throws IllegalArgumentException if {@code sigma <= 0}.
+ * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+ * or {@code sigma} are not finite.
*/
public LogNormalSampler(NormalizedGaussianSampler gaussian,
double mu,
double sigma) {
// Validation before java.lang.Object constructor exits prevents
partially initialized object
- this(mu, InternalUtils.requireStrictlyPositive(sigma, "sigma"),
gaussian);
+ this(InternalUtils.requireFinite(mu, "mu"),
+ InternalUtils.requireStrictlyPositiveFinite(sigma, "sigma"),
gaussian);
}
/**
@@ -111,7 +113,8 @@ public class LogNormalSampler implements
SharedStateContinuousSampler {
* @param mu Mean of the natural logarithm of the distribution values.
* @param sigma Standard deviation of the natural logarithm of the
distribution values.
* @return the sampler
- * @throws IllegalArgumentException if {@code sigma <= 0}.
+ * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+ * or {@code sigma} are not finite.
* @see #withUniformRandomProvider(UniformRandomProvider)
* @since 1.3
*/
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
index 0aa97187..1c6dc97f 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
@@ -271,7 +271,7 @@ public class RejectionInversionZipfSampler
* @param numberOfElements Number of elements.
* @param exponent Exponent.
* @throws IllegalArgumentException if {@code numberOfElements <= 0}
- * or {@code exponent < 0}.
+ * or {@code exponent < 0} or is non-finite.
*/
public RejectionInversionZipfSampler(UniformRandomProvider rng,
int numberOfElements,
@@ -333,7 +333,7 @@ public class RejectionInversionZipfSampler
* @param exponent Exponent.
* @return the sampler
* @throws IllegalArgumentException if {@code numberOfElements <= 0} or
- * {@code exponent < 0}.
+ * {@code exponent < 0} or is non-finite.
* @since 1.3
*/
public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
@@ -342,7 +342,7 @@ public class RejectionInversionZipfSampler
if (numberOfElements <= 0) {
throw new IllegalArgumentException("number of elements is not
strictly positive: " + numberOfElements);
}
- InternalUtils.requirePositive(exponent, "exponent");
+ InternalUtils.requirePositiveFinite(exponent, "exponent");
// When the exponent is at the limit of 0 the distribution PMF reduces
to 1 / n
// and sampling can use a discrete uniform sampler.
diff --git
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
index f714d107..600e96f0 100644
---
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
+++
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
@@ -724,10 +724,11 @@ public abstract class ZigguratSampler implements
SharedStateContinuousSampler {
* @param rng Generator of uniformly distributed random numbers.
* @param mean Mean.
* @return the sampler
- * @throws IllegalArgumentException if the mean is not strictly
positive ({@code mean <= 0})
+ * @throws IllegalArgumentException if the mean is not strictly
positive
+ * and finite ({@code mean <= 0} or infinite)
*/
public static Exponential of(UniformRandomProvider rng, double mean) {
- return new ExponentialMean(rng,
InternalUtils.requireStrictlyPositive(mean, "mean"));
+ return new ExponentialMean(rng,
InternalUtils.requireStrictlyPositiveFinite(mean, "mean"));
}
}
diff --git
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
index 43db611e..1db9e73c 100644
---
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
+++
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
@@ -114,6 +114,14 @@ class ContinuousUniformSamplerTest {
{1.23, Math.nextUp(1.23)},
// Different exponent
{2.0, Math.nextDown(2.0)},
+ // Non-finite bounds. An infinite bound creates samples equal to
the bound
+ // (or NaN) and the resampling to exclude the bounds cannot
terminate.
+ {0.0, Double.POSITIVE_INFINITY},
+ {Double.NEGATIVE_INFINITY, 0.0},
+ {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY},
+ {0.0, Double.NaN},
+ {Double.NaN, 0.0},
+ {Double.NaN, Double.NaN},
}) {
final double low = interval[0];
final double high = interval[1];
diff --git
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
index ff69911b..9e5f5f31 100644
---
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
+++
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
@@ -70,6 +70,18 @@ class GeometricSamplerTest {
"Missing 'Geometric' from toString");
}
+ /**
+ * Test the edge case where the probability of success approaches 0 since
it uses a different
+ * {@link Object#toString()} method to the normal case tested elsewhere.
+ */
+ @Test
+ void testProbabilityOfSuccessIsZeroSamplerToString() {
+ final UniformRandomProvider unusedRng = RandomAssert.seededRNG();
+ final SharedStateDiscreteSampler sampler =
GeometricSampler.of(unusedRng, Double.MIN_VALUE);
+ Assertions.assertTrue(sampler.toString().contains("Geometric"),
+ "Missing 'Geometric' from toString");
+ }
+
/**
* Test the edge case where the probability of success is nearly 0. This
is a valid geometric
* distribution but the sample is clipped to max integer value because the
underlying
@@ -129,6 +141,15 @@ class GeometricSamplerTest {
testSharedStateSampler(1.0);
}
+ /**
+ * Test the SharedStateSampler implementation with the edge case when the
probability of
+ * success approaches {@code 0.0}.
+ */
+ @Test
+ void testSharedStateSamplerWithProbabilityOfSuccessEffectivelyZero() {
+ testSharedStateSampler(Double.MIN_VALUE);
+ }
+
/**
* Test the SharedStateSampler implementation.
*
diff --git
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
new file mode 100644
index 00000000..8823cff5
--- /dev/null
+++
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.RandomAssert;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+/**
+ * Test that public sampler factory entry points reject non-finite (infinite
or NaN)
+ * distribution parameters instead of silently creating a sampler that returns
+ * NaN, infinite or degenerate-constant samples forever.
+ *
+ * <p>This test collects all samplers identified for the 1.8 release that did
not
+ * verify finite arguments. Other samplers validating for finite arguments
+ * have tests in their respective test class.
+ *
+ * <p>See RNG-194.
+ */
+class NonFiniteParameterValidationTest {
+ /** Positive infinity. */
+ private static final double INF = Double.POSITIVE_INFINITY;
+ /** Not a number. */
+ private static final double NAN = Double.NaN;
+
+
+ @Test
+ void testBoxMullerGaussianSamplerThrowsWithNonFiniteParameters() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, INF, 1.0));
+ assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, 0.0, INF));
+ assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, NAN, 1.0));
+ assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, 0.0, NAN));
+ }
+
+ @Test
+ void testChengBetaSamplerThrowsWithNonFiniteShape() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> ChengBetaSampler.of(rng, INF, 2.0));
+ assertThrowsIAE(() -> ChengBetaSampler.of(rng, 2.0, INF));
+ assertThrowsIAE(() -> ChengBetaSampler.of(rng, NAN, 2.0));
+ assertThrowsIAE(() -> ChengBetaSampler.of(rng, 2.0, NAN));
+ }
+
+ @Test
+ void testGammaSamplerThrowsWithNonFiniteParameters() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
INF, 1.0));
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
0.5, INF));
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
1.5, INF));
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
NAN, 1.0));
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
0.5, NAN));
+ assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng,
1.5, NAN));
+ }
+
+ @Test
+ void testLogNormalSamplerThrowsWithNonFiniteParameters() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ final NormalizedGaussianSampler gaussian =
ZigguratSampler.NormalizedGaussian.of(rng);
+ assertThrowsIAE(() -> LogNormalSampler.of(gaussian, INF, 1.0));
+ assertThrowsIAE(() -> LogNormalSampler.of(gaussian, 0.0, INF));
+ assertThrowsIAE(() -> LogNormalSampler.of(gaussian, NAN, 1.0));
+ assertThrowsIAE(() -> LogNormalSampler.of(gaussian, 0.0, NAN));
+ }
+
+ @Test
+ void testLevySamplerThrowsWithNonFiniteParameters() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> LevySampler.of(rng, INF, 1.0));
+ assertThrowsIAE(() -> LevySampler.of(rng, 0.0, INF));
+ assertThrowsIAE(() -> LevySampler.of(rng, NAN, 1.0));
+ assertThrowsIAE(() -> LevySampler.of(rng, 0.0, NAN));
+ }
+
+ @Test
+ void testExponentialSamplersThrowWithNonFiniteParameters() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> AhrensDieterExponentialSampler.of(rng, INF));
+ assertThrowsIAE(() -> ZigguratSampler.Exponential.of(rng, INF));
+ assertThrowsIAE(() -> AhrensDieterExponentialSampler.of(rng, NAN));
+ assertThrowsIAE(() -> ZigguratSampler.Exponential.of(rng, NAN));
+ }
+
+ @Test
+ void testParetoSamplerThrowsWithNonFiniteScale() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, INF, 1.0));
+ assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, NAN, 1.0));
+ // Note: an infinite shape is a supported limit of the distribution
+ // (all samples equal the scale); it is deliberately not rejected.
+ assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, 1.0, NAN));
+ }
+
+ @Test
+ void testContinuousUniformSamplerThrowsWithNonFiniteBounds() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, INF, 1.0));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, INF));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, INF, 1.0,
true));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, INF,
false));
+ assertThrowsIAE(() -> new ContinuousUniformSampler(rng, INF, 1.0));
+ assertThrowsIAE(() -> new ContinuousUniformSampler(rng, 0.0, INF));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, NAN, 1.0));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, NAN));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, NAN, 1.0,
true));
+ assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, NAN,
false));
+ assertThrowsIAE(() -> new ContinuousUniformSampler(rng, NAN, 1.0));
+ assertThrowsIAE(() -> new ContinuousUniformSampler(rng, 0.0, NAN));
+ }
+
+ @Test
+ void testRejectionInversionZipfSamplerThrowsWithNonFiniteExponent() {
+ final UniformRandomProvider rng = RandomAssert.seededRNG();
+ assertThrowsIAE(() -> RejectionInversionZipfSampler.of(rng, 10, INF));
+ assertThrowsIAE(() -> RejectionInversionZipfSampler.of(rng, 10, NAN));
+ }
+
+ /**
+ * Assert the executable throws an {@link IllegalArgumentException}.
+ *
+ * @param executable the executable
+ */
+ private static void assertThrowsIAE(Executable executable) {
+ Assertions.assertThrows(IllegalArgumentException.class, executable);
+ }
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7cbd4abb..ad886f69 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,11 @@ If the output is not quite correct, check for invisible
trailing spaces!
<release version="1.8" date="TBD" description="
New features, updates and bug fixes (requires Java 8).
">
+ <action dev="aherbert" type="fix" due-to="Security scan, Alex Herbert"
issue="RNG-194">
+ Samplers to validate parameters are finite to avoid creating a sampler
that
+ returns NaN, infinite or degenerate-constant samples forever; or
enters an
+ infinite loop during sample generation.
+ </action>
</release>
<release version="1.7" date="2026-04-20" description="