From 91d7699a697eb303d689097c2733ae7d9f6af8cc Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Sat, 3 Apr 2021 19:06:32 -0700 Subject: [PATCH 1/7] Add an advance function to PCG generators Based on the C implementations to advance the generator. --- rand_pcg/src/pcg128.rs | 58 ++++++++++++++++++++++++++++++++++++++++++ rand_pcg/src/pcg64.rs | 29 +++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index 58a8e36b..db006d22 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -40,6 +40,35 @@ pub struct Lcg128Xsl64 { pub type Pcg64 = Lcg128Xsl64; impl Lcg128Xsl64 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u128) { + let mut acc_mult: u128 = 1; + let mut acc_plus: u128 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus = self.increment; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed and stream. /// /// Note that PCG specifies default values for both parameters: @@ -140,6 +169,35 @@ pub struct Mcg128Xsl64 { pub type Pcg64Mcg = Mcg128Xsl64; impl Mcg128Xsl64 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u128) { + let mut acc_mult: u128 = 1; + let mut acc_plus: u128 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus: u128 = 0; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed. /// /// Note that PCG specifies a default value for the parameter: diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ed7442f9..ec30ad2a 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -40,6 +40,35 @@ pub struct Lcg64Xsh32 { pub type Pcg32 = Lcg64Xsh32; impl Lcg64Xsh32 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u64) { + let mut acc_mult: u64 = 1; + let mut acc_plus: u64 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus = self.increment; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed and stream. /// /// Note that PCG specifies default values for both parameters: From 90177320116c3c5eac46c72bcbbddafcd052c12e Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Sat, 10 Apr 2021 17:35:00 -0700 Subject: [PATCH 2/7] Add tests for advancing PCG generators A simple test to check the values after 20 steps. --- rand_pcg/tests/lcg128xsl64.rs | 12 ++++++++++++ rand_pcg/tests/lcg64xsh32.rs | 12 ++++++++++++ rand_pcg/tests/mcg128xsl64.rs | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index ac238b51..8038d426 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Lcg128Xsl64, Pcg64}; +#[test] +fn test_lcg128xsl64_advancing() { + let seed = Default::default(); + let mut rng1 = Lcg128Xsl64::from_seed(seed); + let mut rng2 = Lcg128Xsl64::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_lcg128xsl64_construction() { // Test that various construction techniques produce a working RNG. diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 24a06d32..17d87858 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Lcg64Xsh32, Pcg32}; +#[test] +fn test_lcg64xsh32_advancing() { + let seed = Default::default(); + let mut rng1 = Lcg64Xsh32::from_seed(seed); + let mut rng2 = Lcg64Xsh32::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_lcg64xsh32_construction() { // Test that various construction techniques produce a working RNG. diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index 32f363f3..cf145503 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; +#[test] +fn test_mcg128xsl64_advancing() { + let seed = Default::default(); + let mut rng1 = Mcg128Xsl64::from_seed(seed); + let mut rng2 = Mcg128Xsl64::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_mcg128xsl64_construction() { // Test that various construction techniques produce a working RNG. From 624d24e8ae0043bfbd59c369e1741ab3bc848a64 Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Fri, 16 Apr 2021 13:52:56 -0700 Subject: [PATCH 3/7] Delta needs to be doubled when advancing Lcg64Xsh32 --- rand_pcg/src/pcg64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ec30ad2a..abaa1079 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -55,8 +55,8 @@ impl Lcg64Xsh32 { let mut acc_plus: u64 = 0; let mut cur_mult = MULTIPLIER; let mut cur_plus = self.increment; - let mut mdelta = delta; - + let mut mdelta = delta.wrapping_mul(2); + while mdelta > 0 { if (mdelta & 1) != 0 { acc_mult = acc_mult.wrapping_mul(cur_mult); From f7960e9fb24c8599bd4e73de61254f25d7a6e1a9 Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Mon, 19 Apr 2021 14:00:18 -0700 Subject: [PATCH 4/7] Revert "Delta needs to be doubled when advancing Lcg64Xsh32" This reverts commit 624d24e8ae0043bfbd59c369e1741ab3bc848a64. --- rand_pcg/src/pcg64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index abaa1079..ec30ad2a 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -55,8 +55,8 @@ impl Lcg64Xsh32 { let mut acc_plus: u64 = 0; let mut cur_mult = MULTIPLIER; let mut cur_plus = self.increment; - let mut mdelta = delta.wrapping_mul(2); - + let mut mdelta = delta; + while mdelta > 0 { if (mdelta & 1) != 0 { acc_mult = acc_mult.wrapping_mul(cur_mult); From 53b54b247e4d6579fcab8c644b5b74ed92abd41d Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Mon, 19 Apr 2021 14:12:23 -0700 Subject: [PATCH 5/7] Fix Lcg64Xsh32 advancing test to use 32 bit generation functions --- rand_pcg/tests/lcg64xsh32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 17d87858..b5716778 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -7,10 +7,10 @@ fn test_lcg64xsh32_advancing() { let mut rng1 = Lcg64Xsh32::from_seed(seed); let mut rng2 = Lcg64Xsh32::from_seed(seed); for _ in 0..20 { - rng1.next_u64(); + rng1.next_u32(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1.next_u32(), rng2.next_u32()); } #[test] From c775705b882acfbb8a951f0bcfacdf9332fdf4cc Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Tue, 20 Apr 2021 20:38:38 -0700 Subject: [PATCH 6/7] Touch up changes around advancing feature * Add additional comments on how to use the advance() function * Use clone() for clarity instead of reusing a seed * Compare the generator as a whole as opposed the next value in unit tests --- rand_pcg/src/pcg128.rs | 6 ++++++ rand_pcg/src/pcg64.rs | 3 +++ rand_pcg/tests/lcg128xsl64.rs | 4 ++-- rand_pcg/tests/lcg64xsh32.rs | 4 ++-- rand_pcg/tests/mcg128xsl64.rs | 4 ++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index db006d22..2c5b2495 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -49,6 +49,9 @@ impl Lcg128Xsl64 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_64()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u128) { let mut acc_mult: u128 = 1; @@ -178,6 +181,9 @@ impl Mcg128Xsl64 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_64()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u128) { let mut acc_mult: u128 = 1; diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ec30ad2a..dc157fc3 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -49,6 +49,9 @@ impl Lcg64Xsh32 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_32()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u64) { let mut acc_mult: u64 = 1; diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 8038d426..793e371d 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -5,12 +5,12 @@ use rand_pcg::{Lcg128Xsl64, Pcg64}; fn test_lcg128xsl64_advancing() { let seed = Default::default(); let mut rng1 = Lcg128Xsl64::from_seed(seed); - let mut rng2 = Lcg128Xsl64::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u64(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index b5716778..b1b613f2 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -5,12 +5,12 @@ use rand_pcg::{Lcg64Xsh32, Pcg32}; fn test_lcg64xsh32_advancing() { let seed = Default::default(); let mut rng1 = Lcg64Xsh32::from_seed(seed); - let mut rng2 = Lcg64Xsh32::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u32(); } rng2.advance(20); - assert_eq!(rng1.next_u32(), rng2.next_u32()); + assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index cf145503..859f5892 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -5,12 +5,12 @@ use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; fn test_mcg128xsl64_advancing() { let seed = Default::default(); let mut rng1 = Mcg128Xsl64::from_seed(seed); - let mut rng2 = Mcg128Xsl64::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u64(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1, rng2); } #[test] From 57cd95999d5a235da2bd4a662ec5d69431660e5b Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Thu, 22 Apr 2021 16:32:08 -0700 Subject: [PATCH 7/7] Expand advancing tests to use multiple seeds --- rand_pcg/tests/lcg128xsl64.rs | 15 ++++++++------- rand_pcg/tests/lcg64xsh32.rs | 15 ++++++++------- rand_pcg/tests/mcg128xsl64.rs | 15 ++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 793e371d..57f559a1 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -3,14 +3,15 @@ use rand_pcg::{Lcg128Xsl64, Pcg64}; #[test] fn test_lcg128xsl64_advancing() { - let seed = Default::default(); - let mut rng1 = Lcg128Xsl64::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u64(); + for seed in 0..20 { + let mut rng1 = Lcg128Xsl64::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index b1b613f2..1efe5c58 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -3,14 +3,15 @@ use rand_pcg::{Lcg64Xsh32, Pcg32}; #[test] fn test_lcg64xsh32_advancing() { - let seed = Default::default(); - let mut rng1 = Lcg64Xsh32::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u32(); + for seed in 0..20 { + let mut rng1 = Lcg64Xsh32::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u32(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index 859f5892..c508a8ff 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -3,14 +3,15 @@ use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; #[test] fn test_mcg128xsl64_advancing() { - let seed = Default::default(); - let mut rng1 = Mcg128Xsl64::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u64(); + for seed in 0..20 { + let mut rng1 = Mcg128Xsl64::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test]