Skip to content

Commit b45a01e

Browse files
committed
Avoid reuse of spare random variables when using different RNG sources
1 parent 042818e commit b45a01e

File tree

2 files changed

+44
-20
lines changed

2 files changed

+44
-20
lines changed

src/SimSharp/Core/Environment.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -376,30 +376,16 @@ public TimeSpan RandExponential(TimeSpan mean) {
376376
/// from two uniform random distributed values.
377377
/// </summary>
378378
/// <remarks>
379-
/// A spare random variable is generated from the second uniformly
380-
/// distributed value. Thus, the two calls to the uniform random number
381-
/// generator will be made only every second call.
379+
/// Unlike <see cref="RandNormal(double, double)"/> this method does not
380+
/// make use of a spare random variable. It discards the spare and thus
381+
/// requires twice the number of calls to the underlying IRandom instance.
382382
/// </remarks>
383383
/// <param name="random">The random number generator to use.</param>
384384
/// <param name="mu">The mean of the normal distribution.</param>
385385
/// <param name="sigma">The standard deviation of the normal distribution.</param>
386386
/// <returns>A number that is normal distributed.</returns>
387387
public virtual double RandNormal(IRandom random, double mu, double sigma) {
388-
if (useSpareNormal) {
389-
useSpareNormal = false;
390-
return spareNormal * sigma + mu;
391-
} else {
392-
double u, v, s;
393-
do {
394-
u = random.NextDouble() * 2 - 1;
395-
v = random.NextDouble() * 2 - 1;
396-
s = u * u + v * v;
397-
} while (s >= 1 || s == 0);
398-
var mul = Math.Sqrt(-2.0 * Math.Log(s) / s);
399-
spareNormal = v * mul;
400-
useSpareNormal = true;
401-
return mu + sigma * u * mul;
402-
}
388+
return MarsagliaPolar(random, mu, sigma, out _); // do not reuse the spare normal in this case, because it could be from a different RNG
403389
}
404390
/// <summary>
405391
/// Uses the Marsaglia polar method to generate a random variable
@@ -413,8 +399,25 @@ public virtual double RandNormal(IRandom random, double mu, double sigma) {
413399
/// <param name="mu">The mean of the normal distribution.</param>
414400
/// <param name="sigma">The standard deviation of the normal distribution.</param>
415401
/// <returns>A number that is normal distributed.</returns>
416-
public double RandNormal(double mu, double sigma) {
417-
return RandNormal(Random, mu, sigma);
402+
public virtual double RandNormal(double mu, double sigma) {
403+
if (useSpareNormal) {
404+
useSpareNormal = false;
405+
return spareNormal * sigma + mu;
406+
} else {
407+
useSpareNormal = true;
408+
return MarsagliaPolar(Random, mu, sigma, out spareNormal);
409+
}
410+
}
411+
private double MarsagliaPolar(IRandom random, double mu, double sigma, out double spare) {
412+
double u, v, s;
413+
do {
414+
u = random.NextDouble() * 2 - 1;
415+
v = random.NextDouble() * 2 - 1;
416+
s = u * u + v * v;
417+
} while (s > 1 || s == 0);
418+
var mul = Math.Sqrt(-2.0 * Math.Log(s) / s);
419+
spare = v * mul;
420+
return mu + sigma * u * mul;
418421
}
419422

420423
/// <summary>
@@ -879,6 +882,9 @@ public Environment(DateTime initialDateTime, int randomSeed, TimeSpan? defaultSt
879882
}
880883

881884
protected static readonly double NormalMagicConst = 4 * Math.Exp(-0.5) / Math.Sqrt(2.0);
885+
public override double RandNormal(double mu, double sigma) {
886+
return RandNormal(Random, mu, sigma);
887+
}
882888
public override double RandNormal(IRandom random, double mu, double sigma) {
883889
double z, zz, u1, u2;
884890
do {

src/Tests/RandomTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using Xunit;
4+
5+
namespace SimSharp.Tests {
6+
public class RandomTest {
7+
8+
[Fact]
9+
public void PcgReproducibilityTest() {
10+
var pcg = new PcgRandom(15);
11+
var seq = Enumerable.Range(0, 1000).Select(x => pcg.Next()).ToArray();
12+
Thread.Sleep(100);
13+
pcg = new PcgRandom(15);
14+
var seq2 = Enumerable.Range(0, 1000).Select(x => pcg.Next()).ToArray();
15+
Assert.Equal(seq, seq2);
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)