Fixed Rngs

- removed rngs
- added method for streams in Rng
- changed the number of streams possibles
- parallel and iterative run give now the same results
This commit is contained in:
2025-01-24 10:59:41 +01:00
parent 4601e76438
commit 00a09be756
4 changed files with 108 additions and 140 deletions

View File

@@ -1,47 +1,51 @@
package net.berack.upo.valpre.rand;
/**
* This is an Java library for random number generation. The use of this
* This class has been modified by Giacomo Bertolazzi in a way that doesn`t
* resemble the original. It still has the same role, but has been extended and
* modernized to java 23.
*
* This is an Java library for random number generation. The use of this
* library is recommended as a replacement for the Java class Random,
* particularly in simulation applications where the statistical
* 'goodness' of the random number generator is important.
*
* The generator used in this library is a so-called 'Lehmer random number
* generator' which returns a pseudo-random number uniformly distributed
* between 0.0 and 1.0. The period is (m - 1) where m = 2,147,483,647 and
* between 0.0 and 1.0. The period is (m - 1) where m = 2,147,483,647 and
* the smallest and largest possible values are (1 / m) and 1 - (1 / m)
* respectively. For more details see:
*
* "Random Number Generators: Good Ones Are Hard To Find"
* Steve Park and Keith Miller
* Communications of the ACM, October 1988
*
* Note that as of 7-11-90 the multiplier used in this library has changed
* from the previous "minimal standard" 16807 to a new value of 48271. To
* use this library in its old (16807) form change the constants MULTIPLIER
* and CHECK as indicated in the comments.
*
* Name : Rng.java (Random Number Generation - Single Stream)
* Authors : Steve Park & Dave Geyer
* Translated by : Jun Wang & Richard Dutton
* Language : Java
* Latest Revision : 6-10-04
*
* Program rng : Section 2.2
* respectively. For more details see:
* "Random Number Generators: Good Ones Are Hard To Find"
*/
public class Rng {
public final static long CHECK = 399268537L; /* use 1043616065 for the "minimal standard" */
public final static long DEFAULT = 123456789L; /* initial seed, use 0 < DEFAULT < MODULUS */
public final static long MODULUS = 2147483647; /* DON'T CHANGE THIS VALUE */
public final static long MULTIPLIER = 48271; /* use 16807 for the "minimal standard" */
// Streams multipliers values taken from the table at page 114 of
// L.M. Leemis, S.K. Park «Discrete event simulation: a first course»
public static final long MULT_128 = 40509L;
public static final long MULT_256 = 22925L;
public static final long MULT_512 = 44857L;
public static final long MULT_1024 = 97070L;
// Single Rng values
public final static long DEFAULT = 123456789L;
public final static long MODULUS = 2147483647;
public final static long MULTIPLIER = 48271;
private long seed = DEFAULT; /* seed is the state of the generator */
/**
* Default constructor, build the RNG with the default seed. {@link #DEFAULT}
*/
public Rng() {
}
/**
* Builde the RNG with a seed passed as paraemter, if negative or zero will be
* modified to a positive number by getting the system time.
*
* @param seed the seed to start the rng
*/
public Rng(long seed) {
this.putSeed(seed);
this.setSeed(seed);
}
/**
@@ -61,35 +65,66 @@ public class Rng {
* if x > 0 then x is the initial seed (unless too large)
* if x <= 0 then the initial seed is obtained from the system clock
*/
public void putSeed(long x) {
if (x > 0L) {
x = x % MODULUS; /* correct if x is too large */
} else {
x = System.currentTimeMillis();
// x = ((unsigned long) time((time_t *) NULL)) % MODULUS;
public void setSeed(long seed) {
if (seed <= 0L) {
seed = System.currentTimeMillis();
}
this.seed = x;
this.seed = seed % MODULUS; /* correct if x is too large */
}
/**
* Use this (optional) procedure to get the current state of the random
* number generator.
* Use this procedure to get the current state of the random number generator.
*/
public long getSeed() {
return this.seed;
}
/**
* Use this (optional) procedure to test for a correct implementation.
* Get multiple streams for the generation of random numbers. The streams
* generated will have the seeds spaced enough that the sequences will not
* overlap (if not after many calls)
* Note that for efficiency the total number of streams cannot suprass 1024 and
* will be casted to a pow of 2 no less than 128 giving the array only 4
* possible lengths: 128, 256, 512, 1024
*
* @param seed the initial seed of the rngs
* @param total the total number of streams
* @return the streams
*/
public static boolean testRandom() {
Rng rng = new Rng(1); /* set initial state to 1 */
for (var i = 0; i < 10000; i++)
rng.random();
return rng.getSeed() == CHECK;
public static Rng[] getMultipleStreams(long seed, int total) {
if (total > 1024)
throw new IllegalArgumentException("Cannot genrate more than 1024 streams");
// rounding to the highest pow2
total = Math.max(total, 128);
total = 1 << (32 - Integer.numberOfLeadingZeros(total - 1));
var mult = switch (total) {
case 128 -> MULT_128;
case 256 -> MULT_256;
case 512 -> MULT_512;
default -> MULT_1024;
};
// Building the streams
var streams = new Rng[total];
for (int i = 0; i < total; i++) {
streams[i] = new Rng(seed);
seed = (seed * mult) % MODULUS;
}
return streams;
}
/**
* This procedure is used for calculating a new seed starting from the one
* passed as input. The modulus and multiplier should be passed as well but it
* is advised to use the standard ones {@link #MODULUS} and {@link #MULTIPLIER}
*
* @param modulus the modulus used for the generation
* @param multiplier the multiplier used for the generation
* @param seed the seed where to start
* @return a new seed used for the calculation
*/
public static long newSeed(long modulus, long multiplier, long seed) {
var Q = modulus / multiplier;
var R = modulus % multiplier;

View File

@@ -1,95 +0,0 @@
package net.berack.upo.valpre.rand;
/**
* This is an Java library for multi-stream random number generation.
* The use of this library is recommended as a replacement for the Java
* class Random, particularly in simulation applications where the
* statistical 'goodness' of the random number generator is important.
* The library supplies 256 streams of random numbers; use
* selectStream(s) to switch between streams indexed s = 0,1,...,255.
*
* The streams must be initialized. The recommended way to do this is by
* using the function plantSeeds(x) with the value of x used to initialize
* the default stream and all other streams initialized automatically with
* values dependent on the value of x. The following convention is used
* to initialize the default stream:
* if x > 0 then x is the state
* if x < 0 then the state is obtained from the system clock
* if x = 0 then the state is to be supplied interactively.
*
* The generator used in this library is a so-called 'Lehmer random number
* generator' which returns a pseudo-random number uniformly distributed
* 0.0 and 1.0. The period is (m - 1) where m = 2,147,483,647 and the
* smallest and largest possible values are (1 / m) and 1 - (1 / m)
* respectively. For more details see:
*
* "Random Number Generators: Good Ones Are Hard To Find"
* Steve Park and Keith Miller
* Communications of the ACM, October 1988
*
* Name : Rngs.java (Random Number Generation - Multiple Streams)
* Authors : Steve Park & Dave Geyer
* Translated by : Jun Wang & Richard Dutton
* Language : Java
* Latest Revision : 6-10-04
*/
public class Rngs {
private final static int STREAMS = 256; /* # of streams, DON'T CHANGE THIS VALUE */
private final static long A256 = 22925; /* jump multiplier, DON'T CHANGE THIS VALUE */
private Rng[] rngs;
private int current = 0;
public Rngs(long seed) {
this.rngs = new Rng[STREAMS];
this.plantSeeds(seed);
}
public Rng getRng() {
return this.rngs[this.current];
}
public Rng getRng(int stream) {
return this.rngs[stream % STREAMS];
}
/**
* Use this function to set the state of all the random number generator
* streams by "planting" a sequence of states (seeds), one per stream,
* with all states dictated by the state of the default stream.
* The sequence of planted states is separated one from the next by
* 8,367,782 calls to Random().
*/
public void plantSeeds(long seed0) {
this.rngs[0] = new Rng(seed0);
for (int j = 1; j < STREAMS; j++) {
seed0 = Rng.newSeed(Rng.MODULUS, A256, seed0);
this.rngs[j] = new Rng(seed0);
}
}
/**
* Use this function to set the current random number generator
* stream -- that stream from which the next random number will come.
*/
public void selectStream(int index) {
this.current = index % STREAMS;
}
/**
* Use this (optional) function to test for a correct implementation.
*/
public static boolean testRandom() {
var rngs = new Rngs(1);
var first = rngs.getRng();
for (int i = 0; i < 10000; i++)
first.random();
var x = first.getSeed(); /* get the new state value */
var ok = (x == Rng.CHECK); /* and check for correctness */
x = rngs.getRng(1).getSeed(); /* get the state of stream 1 */
return ok && (x == A256); /* x should be the jump multiplier */
}
}

View File

@@ -5,7 +5,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import net.berack.upo.valpre.rand.Rng;
import net.berack.upo.valpre.rand.Rngs;
import net.berack.upo.valpre.sim.stats.ResultMultiple;
import net.berack.upo.valpre.sim.stats.Result;
@@ -33,11 +32,11 @@ public class SimulationMultiple {
* @return The statistics the network.
*/
public ResultMultiple run(long seed, int runs, EndCriteria... criterias) {
var rng = new Rng(seed);
var rngs = Rng.getMultipleStreams(seed, runs);
var stats = new Result[runs];
for (int i = 0; i < runs; i++) {
var sim = new Simulation(this.net, rng, criterias);
var sim = new Simulation(this.net, rngs[i], criterias);
stats[i] = sim.run();
}
return new ResultMultiple(stats);
@@ -60,7 +59,7 @@ public class SimulationMultiple {
*/
public ResultMultiple runParallel(long seed, int runs, EndCriteria... criterias)
throws InterruptedException, ExecutionException {
var rngs = new Rngs(seed);
var rngs = Rng.getMultipleStreams(seed, runs);
var results = new Result[runs];
var futures = new Future[runs];
@@ -69,7 +68,7 @@ public class SimulationMultiple {
for (int i = 0; i < runs; i++) {
final var id = i;
futures[i] = threads.submit(() -> {
var sim = new Simulation(this.net, rngs.getRng(id), criterias);
var sim = new Simulation(this.net, rngs[id], criterias);
results[id] = sim.run();
});
}

View File

@@ -1,5 +1,7 @@
package net.berack.upo.valpre.rand;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
@@ -7,8 +9,35 @@ import java.util.Arrays;
import org.junit.jupiter.api.Test;
public class TestRandom {
@Test
public void testRng() {
Rng rng = new Rng(1);
for (var i = 0; i < 10000; i++)
rng.random();
assertEquals(399268537L, rng.getSeed());
}
@Test
public void testRngs() {
var rngs = Rng.getMultipleStreams(1, 200);
assertEquals(256, rngs.length);
var rng0 = rngs[0];
var rng1 = rngs[1];
for (int i = 0; i < 8367781; i++) {
rng0.random();
assertNotEquals(rng1.getSeed(), rng0.getSeed());
}
assertEquals(Rng.MULT_256, rng1.getSeed());
rng0.random();
assertEquals(rng0.getSeed(), rng1.getSeed());
}
@Test
public void testRngVariance() {
var numbers = new int[5000];
var rng = new Rng(4656);