From 00a09be7566da1daa74dfb6522251ea32acfbf6d Mon Sep 17 00:00:00 2001 From: Berack96 Date: Fri, 24 Jan 2025 10:59:41 +0100 Subject: [PATCH] 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 --- .../java/net/berack/upo/valpre/rand/Rng.java | 115 ++++++++++++------ .../java/net/berack/upo/valpre/rand/Rngs.java | 95 --------------- .../upo/valpre/sim/SimulationMultiple.java | 9 +- .../berack/upo/valpre/rand/TestRandom.java | 29 +++++ 4 files changed, 108 insertions(+), 140 deletions(-) delete mode 100644 src/main/java/net/berack/upo/valpre/rand/Rngs.java diff --git a/src/main/java/net/berack/upo/valpre/rand/Rng.java b/src/main/java/net/berack/upo/valpre/rand/Rng.java index 37676d0..7890d88 100644 --- a/src/main/java/net/berack/upo/valpre/rand/Rng.java +++ b/src/main/java/net/berack/upo/valpre/rand/Rng.java @@ -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; diff --git a/src/main/java/net/berack/upo/valpre/rand/Rngs.java b/src/main/java/net/berack/upo/valpre/rand/Rngs.java deleted file mode 100644 index 0a591e7..0000000 --- a/src/main/java/net/berack/upo/valpre/rand/Rngs.java +++ /dev/null @@ -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 */ - } -} diff --git a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java index d20ef6f..434d60a 100644 --- a/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java +++ b/src/main/java/net/berack/upo/valpre/sim/SimulationMultiple.java @@ -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(); }); } diff --git a/src/test/java/net/berack/upo/valpre/rand/TestRandom.java b/src/test/java/net/berack/upo/valpre/rand/TestRandom.java index 3d5449c..bd87377 100644 --- a/src/test/java/net/berack/upo/valpre/rand/TestRandom.java +++ b/src/test/java/net/berack/upo/valpre/rand/TestRandom.java @@ -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);