Skip to content

Commit

Permalink
RNG-166: LogNormalSamplers to allow a negative mean.
Browse files Browse the repository at this point in the history
Changes:

- LogNormalSampler
- BoxNormalLogNormalSamplers

Also updated the constructor parameters from shape and scale to mu and
sigma. This matches the wikipedia reference and implementations in
Wolfram.
  • Loading branch information
aherbert committed Oct 1, 2021
1 parent dd67119 commit f46c001
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ public class BoxMullerLogNormalSampler

/**
* @param rng Generator of uniformly distributed random numbers.
* @param scale Scale of the log-normal distribution.
* @param shape Shape of the log-normal distribution.
* @throws IllegalArgumentException if {@code scale < 0} or {@code shape <= 0}.
* @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}.
*/
public BoxMullerLogNormalSampler(UniformRandomProvider rng,
double scale,
double shape) {
double mu,
double sigma) {
super(null);
sampler = LogNormalSampler.of(new BoxMullerNormalizedGaussianSampler(rng),
scale, shape);
mu, sigma);
}

/** {@inheritDoc} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,27 @@
* @since 1.1
*/
public class LogNormalSampler implements SharedStateContinuousSampler {
/** Scale. */
private final double scale;
/** Shape. */
private final double shape;
/** Mean of the natural logarithm of the distribution values. */
private final double mu;
/** Standard deviation of the natural logarithm of the distribution values. */
private final double sigma;
/** Gaussian sampling. */
private final NormalizedGaussianSampler gaussian;

/**
* @param gaussian N(0,1) generator.
* @param scale Scale of the log-normal distribution.
* @param shape Shape of the log-normal distribution.
* @throws IllegalArgumentException if {@code scale < 0} or {@code shape <= 0}.
* @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}.
*/
public LogNormalSampler(NormalizedGaussianSampler gaussian,
double scale,
double shape) {
if (scale < 0) {
throw new IllegalArgumentException("scale is not positive: " + scale);
double mu,
double sigma) {
if (sigma <= 0) {
throw new IllegalArgumentException("sigma is not strictly positive: " + sigma);
}
if (shape <= 0) {
throw new IllegalArgumentException("shape is not strictly positive: " + shape);
}
this.scale = scale;
this.shape = shape;
this.mu = mu;
this.sigma = sigma;
this.gaussian = gaussian;
}

Expand All @@ -57,15 +54,15 @@ public LogNormalSampler(NormalizedGaussianSampler gaussian,
*/
private LogNormalSampler(UniformRandomProvider rng,
LogNormalSampler source) {
this.scale = source.scale;
this.shape = source.shape;
this.mu = source.mu;
this.sigma = source.sigma;
this.gaussian = InternalUtils.newNormalizedGaussianSampler(source.gaussian, rng);
}

/** {@inheritDoc} */
@Override
public double sample() {
return Math.exp(scale + shape * gaussian.sample());
return Math.exp(mu + sigma * gaussian.sample());
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -100,16 +97,16 @@ public SharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvi
* Otherwise a run-time exception will be thrown when the sampler is used to share state.</p>
*
* @param gaussian N(0,1) generator.
* @param scale Scale of the log-normal distribution.
* @param shape Shape of the log-normal distribution.
* @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 scale < 0} or {@code shape <= 0}.
* @throws IllegalArgumentException if {@code sigma <= 0}.
* @see #withUniformRandomProvider(UniformRandomProvider)
* @since 1.3
*/
public static SharedStateContinuousSampler of(NormalizedGaussianSampler gaussian,
double scale,
double shape) {
return new LogNormalSampler(gaussian, scale, shape);
double mu,
double sigma) {
return new LogNormalSampler(gaussian, mu, sigma);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,16 @@
*/
class BoxMullerLogNormalSamplerTest {
/**
* Test the constructor with a bad scale.
*/
@SuppressWarnings({"deprecation"})
@Test
void testConstructorThrowsWithNegativeScale() {
final RestorableUniformRandomProvider rng =
RandomSource.SPLIT_MIX_64.create(0L);
final double scale = -1e-6;
final double shape = 1;
Assertions.assertThrows(IllegalArgumentException.class,
() -> new BoxMullerLogNormalSampler(rng, scale, shape));
}

/**
* Test the constructor with a bad shape.
* Test the constructor with a bad standard deviation.
*/
@SuppressWarnings({"deprecation"})
@Test
void testConstructorThrowsWithZeroShape() {
final RestorableUniformRandomProvider rng =
RandomSource.SPLIT_MIX_64.create(0L);
final double scale = 1;
final double shape = 0;
final double mu = 1;
final double sigma = 0;
Assertions.assertThrows(IllegalArgumentException.class,
() -> new BoxMullerLogNormalSampler(rng, scale, shape));
() -> new BoxMullerLogNormalSampler(rng, mu, sigma));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,25 +151,31 @@ public final class ContinuousSamplersList {
LevySampler.of(RandomSource.JSF_64.create(), 0.0, 1.0));

// Log normal ("inverse method").
final double scaleLogNormal = 2.345;
final double shapeLogNormal = 0.1234;
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, scaleLogNormal, shapeLogNormal),
final double muLogNormal = 2.345;
final double sigmaLogNormal = 0.1234;
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
RandomSource.KISS.create());
// Log-normal (DEPRECATED "Box-Muller").
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, scaleLogNormal, shapeLogNormal),
new BoxMullerLogNormalSampler(RandomSource.XOR_SHIFT_1024_S_PHI.create(), scaleLogNormal, shapeLogNormal));
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
new BoxMullerLogNormalSampler(RandomSource.XOR_SHIFT_1024_S_PHI.create(), muLogNormal, sigmaLogNormal));
// Log-normal ("Box-Muller").
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, scaleLogNormal, shapeLogNormal),
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
LogNormalSampler.of(new BoxMullerNormalizedGaussianSampler(RandomSource.XOR_SHIFT_1024_S_PHI.create()),
scaleLogNormal, shapeLogNormal));
muLogNormal, sigmaLogNormal));
// Log-normal ("Marsaglia").
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, scaleLogNormal, shapeLogNormal),
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
LogNormalSampler.of(new MarsagliaNormalizedGaussianSampler(RandomSource.MT_64.create()),
scaleLogNormal, shapeLogNormal));
muLogNormal, sigmaLogNormal));
// Log-normal ("Ziggurat").
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, scaleLogNormal, shapeLogNormal),
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
LogNormalSampler.of(new ZigguratNormalizedGaussianSampler(RandomSource.MWC_256.create()),
scaleLogNormal, shapeLogNormal));
muLogNormal, sigmaLogNormal));
// Log-normal negative mean
final double muLogNormal2 = -1.1;
final double sigmaLogNormal2 = 2.3;
add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal2, sigmaLogNormal2),
LogNormalSampler.of(new ZigguratNormalizedGaussianSampler(RandomSource.MWC_256.create()),
muLogNormal2, sigmaLogNormal2));

// Logistic ("inverse method").
final double muLogistic = -123.456;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,17 @@
*/
class LogNormalSamplerTest {
/**
* Test the constructor with a bad shape.
*/
@Test
void testConstructorThrowsWithNegativeScale() {
final RestorableUniformRandomProvider rng =
RandomSource.SPLIT_MIX_64.create(0L);
final NormalizedGaussianSampler gauss = ZigguratSampler.NormalizedGaussian.of(rng);
final double scale = -1e-6;
final double shape = 1;
Assertions.assertThrows(IllegalArgumentException.class,
() -> LogNormalSampler.of(gauss, scale, shape));
}

/**
* Test the constructor with a bad shape.
* Test the constructor with a bad standard deviation.
*/
@Test
void testConstructorThrowsWithZeroShape() {
final RestorableUniformRandomProvider rng =
RandomSource.SPLIT_MIX_64.create(0L);
final NormalizedGaussianSampler gauss = ZigguratSampler.NormalizedGaussian.of(rng);
final double scale = 1;
final double shape = 0;
final double mu = 1;
final double sigma = 0;
Assertions.assertThrows(IllegalArgumentException.class,
() -> LogNormalSampler.of(gauss, scale, shape));
() -> LogNormalSampler.of(gauss, mu, sigma));
}

/**
Expand All @@ -64,10 +50,11 @@ void testSharedStateSampler() {
final UniformRandomProvider rng1 = RandomSource.SPLIT_MIX_64.create(0L);
final UniformRandomProvider rng2 = RandomSource.SPLIT_MIX_64.create(0L);
final NormalizedGaussianSampler gauss = ZigguratSampler.NormalizedGaussian.of(rng1);
final double scale = 1.23;
final double shape = 4.56;
// Test a negative mean is allowed (see RNG-166)
final double mu = -1.23;
final double sigma = 4.56;
final SharedStateContinuousSampler sampler1 =
LogNormalSampler.of(gauss, scale, shape);
LogNormalSampler.of(gauss, mu, sigma);
final SharedStateContinuousSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
RandomAssert.assertProduceSameSequence(sampler1, sampler2);
}
Expand All @@ -85,10 +72,10 @@ public double sample() {
return 0;
}
};
final double scale = 1.23;
final double shape = 4.56;
final double mu = 1.23;
final double sigma = 4.56;
final SharedStateContinuousSampler sampler1 =
LogNormalSampler.of(gauss, scale, shape);
LogNormalSampler.of(gauss, mu, sigma);
Assertions.assertThrows(UnsupportedOperationException.class,
() -> sampler1.withUniformRandomProvider(rng2));
}
Expand All @@ -101,10 +88,10 @@ public double sample() {
void testSharedStateSamplerThrowsIfUnderlyingSamplerReturnsWrongSharedState() {
final UniformRandomProvider rng2 = RandomSource.SPLIT_MIX_64.create(0L);
final NormalizedGaussianSampler gauss = new BadSharedStateNormalizedGaussianSampler();
final double scale = 1.23;
final double shape = 4.56;
final double mu = 1.23;
final double sigma = 4.56;
final SharedStateContinuousSampler sampler1 =
LogNormalSampler.of(gauss, scale, shape);
LogNormalSampler.of(gauss, mu, sigma);
Assertions.assertThrows(UnsupportedOperationException.class,
() -> sampler1.withUniformRandomProvider(rng2));
}
Expand Down
4 changes: 4 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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="fix" issue="166">
Update "LogNormalSampler" and "BoxMullerLogNormalSampler" to allow a negative mean for
the natural logarithm of the distribution values.
</action>
<action dev="aherbert" type="fix" issue="165">
"RejectionInversionZipfSampler": Allow a zero exponent in the Zipf sampler.
</action>
Expand Down

0 comments on commit f46c001

Please sign in to comment.