diff --git a/Cargo.lock b/Cargo.lock index 42d591b91..a91828419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,7 +471,7 @@ dependencies = [ "quizx", "rand 0.10.2", "rand_xoshiro 0.8.1", - "rapidhash", + "rapidrand", "wide", ] @@ -4708,7 +4708,7 @@ dependencies = [ "rand_core 0.10.1", "rand_xoshiro 0.8.1", "random_tester", - "rapidhash", + "rapidrand", "wide", ] @@ -5727,13 +5727,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" [[package]] -name = "rapidhash" -version = "4.4.1" +name = "rapidrand" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "c8342ccc4b00f4d1592b5dee20d74a3b2fdb72ba0cf2802b5f0af2886974d032" dependencies = [ - "rand_core 0.9.5", - "rustversion", + "rand_core 0.10.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5265ffd0b..dbf4ddc1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,9 +102,7 @@ fastrand = "2" rand = "0.10" rand_core = "0.10" rand_xoshiro = "0.8" -# 4.5 deprecates RapidRng (moved to the separate `rapidrand` crate); stay on -# 4.4 until pecos-random migrates -- tracked as a follow-up. -rapidhash = { version = "4, <4.5", features = ["rng"] } +rapidrand = { version = "0.1", features = ["rand"] } # --- Data structures --- bitflags = "2" diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml index 5de039a7f..0df32c69d 100644 --- a/crates/benchmarks/Cargo.toml +++ b/crates/benchmarks/Cargo.toml @@ -46,7 +46,7 @@ pecos-quantum.workspace = true pecos-random.workspace = true rand.workspace = true rand_xoshiro.workspace = true -rapidhash.workspace = true +rapidrand.workspace = true wide.workspace = true [[bench]] diff --git a/crates/benchmarks/benches/modules/rng.rs b/crates/benchmarks/benches/modules/rng.rs index 1a9f03f78..8957d177b 100644 --- a/crates/benchmarks/benches/modules/rng.rs +++ b/crates/benchmarks/benches/modules/rng.rs @@ -33,9 +33,22 @@ use criterion::{BenchmarkId, Criterion, Throughput, measurement::Measurement}; use pecos::prelude::{PCG64Fast, PecosQualityRng, PecosRng}; use pecos_random::{PecosScalarRng, Rng, RngExt, SeedableRng}; +use rand::TryRng; use rand::rngs::SmallRng; use rand_xoshiro::{Xoroshiro128PlusPlus, Xoshiro256PlusPlus, Xoshiro512PlusPlus}; -use rapidhash::rng::RapidRng; + +/// rapidrand with the legacy raw-seed semantics (stream-identical to the old +/// rapidhash 4.4 `RapidRng::new`). +struct RapidRng(rapidrand::RapidRng); +impl RapidRng { + fn new(seed: u64) -> Self { + Self(rapidrand::RapidRng::from_seed(seed.to_le_bytes())) + } + fn next(&mut self) -> u64 { + let Ok(v) = self.0.try_next_u64(); + v + } +} use std::hint::black_box; use wide::u64x4; diff --git a/crates/pecos-random/Cargo.toml b/crates/pecos-random/Cargo.toml index 4fbde499e..aca838804 100644 --- a/crates/pecos-random/Cargo.toml +++ b/crates/pecos-random/Cargo.toml @@ -15,7 +15,7 @@ description = "Random number generation for PECOS" wide.workspace = true rand_core.workspace = true rand.workspace = true -rapidhash.workspace = true +rapidrand.workspace = true [dev-dependencies] random_tester.workspace = true diff --git a/crates/pecos-random/src/lib.rs b/crates/pecos-random/src/lib.rs index ced10b6e6..1f527b2c7 100644 --- a/crates/pecos-random/src/lib.rs +++ b/crates/pecos-random/src/lib.rs @@ -1,5 +1,6 @@ pub mod prelude; pub mod quality_rng; +mod rapid_rng; pub mod rng; pub mod rng_ext; pub mod rng_manageable; diff --git a/crates/pecos-random/src/rapid_rng.rs b/crates/pecos-random/src/rapid_rng.rs new file mode 100644 index 000000000..d72ff1696 --- /dev/null +++ b/crates/pecos-random/src/rapid_rng.rs @@ -0,0 +1,45 @@ +// Copyright 2026 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License.You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. + +//! Thin wrapper over [`rapidrand::RapidRng`] preserving the seeded output +//! streams of the rapidhash 4.4 `rng::RapidRng` this crate previously used. +//! +//! rapidhash 4.5 deprecated its `rng` module in favor of the `rapidrand` +//! crate. The mixing function and secrets are identical, but the seeding +//! paths differ: the old `RapidRng::new(seed)` stored the seed RAW, which in +//! rapidrand corresponds to `SeedableRng::from_seed(seed.to_le_bytes())`. +//! rapidrand's `seed_from_u64` pre-mixes the seed once and produces a +//! DIFFERENT stream -- do not switch to it without a deliberate, +//! release-noted reproducibility break. Stream identity is locked by +//! `tests/seed_stream_stability.rs`. + +use rand_core::{SeedableRng, TryRng}; + +/// Single-stream rapidhash-mixing RNG with the legacy seeding semantics. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct RapidRng(rapidrand::RapidRng); + +impl RapidRng { + /// Create a generator whose stream matches the old `RapidRng::new(seed)`. + #[inline] + #[must_use] + pub fn new(seed: u64) -> Self { + Self(rapidrand::RapidRng::from_seed(seed.to_le_bytes())) + } + + /// Next value in the stream. + #[inline] + pub fn next(&mut self) -> u64 { + let Ok(v) = self.0.try_next_u64(); + v + } +} diff --git a/crates/pecos-random/src/rng.rs b/crates/pecos-random/src/rng.rs index fbbddb2c2..af0654d87 100644 --- a/crates/pecos-random/src/rng.rs +++ b/crates/pecos-random/src/rng.rs @@ -39,12 +39,12 @@ //! //! # Implementation //! -//! Backed by 4 parallel [`RapidRng`] instances, providing similar bulk generation +//! Backed by 4 parallel `RapidRng` instances, providing similar bulk generation //! capabilities to [`PecosRng`](crate::quality_rng::PecosQualityRng). +use crate::rapid_rng::RapidRng; use core::convert::Infallible; use rand_core::{SeedableRng, TryRng}; -use rapidhash::rng::RapidRng; use wide::u64x4; /// Buffer size in chunks (each chunk = 4 u64s from the 4 parallel RNGs). @@ -54,12 +54,12 @@ const BUFFER_SIZE: usize = BUFFER_CHUNKS * 4; // 16 elements /// A high-performance RNG using `RapidHash` mixing with 4 parallel streams. /// -/// This implementation maintains 4 independent [`RapidRng`] instances to provide +/// This implementation maintains 4 independent `RapidRng` instances to provide /// bulk random number generation with an interface matching [`PecosRng`](crate::quality_rng::PecosQualityRng). /// /// # State Size /// -/// Maintains 4 independent [`RapidRng`] generators, each with 64 bits of state, +/// Maintains 4 independent `RapidRng` generators, each with 64 bits of state, /// for a total of 256 bits of state. /// /// # Optimized Methods diff --git a/crates/pecos-random/src/scalar_rng.rs b/crates/pecos-random/src/scalar_rng.rs index 9be9ead22..bef2262fb 100644 --- a/crates/pecos-random/src/scalar_rng.rs +++ b/crates/pecos-random/src/scalar_rng.rs @@ -40,9 +40,9 @@ //! } //! ``` +use crate::rapid_rng::RapidRng; use core::convert::Infallible; use rand_core::{SeedableRng, TryRng}; -use rapidhash::rng::RapidRng; use wide::u64x4; /// Scalar-optimized RNG with dedicated scalar RNG and parallel RNGs for bulk operations. diff --git a/crates/pecos-random/tests/seed_stream_stability.rs b/crates/pecos-random/tests/seed_stream_stability.rs new file mode 100644 index 000000000..d3fd82dc3 --- /dev/null +++ b/crates/pecos-random/tests/seed_stream_stability.rs @@ -0,0 +1,59 @@ +// Copyright 2026 The PECOS Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License.You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. + +//! Golden-value tests locking the seeded output streams of the PECOS RNGs. +//! +//! Seeded reproducibility is load-bearing: users record seeds to reproduce +//! simulation runs. These values were captured from the rapidhash 4.4 +//! `RapidRng`-backed implementation; any change to them is a breaking change +//! to seeded reproducibility and must be deliberate (and release-noted), not +//! an accident of a dependency swap. + +use pecos_random::{ParallelRapidRng, PecosScalarRng}; + +#[test] +fn parallel_rapid_rng_seed_42_stream_is_stable() { + let mut rng = ParallelRapidRng::seed_from_u64(42); + let mut out = [0u64; 8]; + rng.fill_u64(&mut out); + assert_eq!(out, GOLDEN_PARALLEL_SEED_42); +} + +#[test] +fn scalar_rng_seed_42_stream_is_stable() { + let mut rng = PecosScalarRng::seed_from_u64(42); + let out: Vec = (0..8).map(|_| rng.next_u64()).collect(); + assert_eq!(out, GOLDEN_SCALAR_SEED_42); +} + +// Captured from the rapidhash 4.4 implementation (pre-rapidrand migration). +// Regenerate ONLY for a deliberate, release-noted reproducibility break. +const GOLDEN_PARALLEL_SEED_42: [u64; 8] = [ + 15_898_102_487_349_570_925, + 4_075_293_041_860_347_929, + 2_961_045_109_388_800_320, + 7_198_908_955_497_073_420, + 12_155_105_407_659_006_943, + 9_311_516_336_095_720_243, + 17_539_611_694_522_935_563, + 3_088_116_801_777_502_571, +]; +const GOLDEN_SCALAR_SEED_42: [u64; 8] = [ + 15_898_102_487_349_570_925, + 12_155_105_407_659_006_943, + 9_267_879_203_684_296_501, + 11_858_079_087_261_110_352, + 4_827_150_399_489_690_183, + 17_360_650_844_478_325_545, + 12_169_232_296_622_201_862, + 13_970_818_894_244_782_829, +]; diff --git a/crates/pecos-random/tests/statistical_quality.rs b/crates/pecos-random/tests/statistical_quality.rs index 3b7c95661..645f49c2c 100644 --- a/crates/pecos-random/tests/statistical_quality.rs +++ b/crates/pecos-random/tests/statistical_quality.rs @@ -136,12 +136,13 @@ fn generate_pcgrandom_bytes(seed: u64, count: usize) -> Vec { /// Generate random bytes using `RapidRng` fn generate_rapidrng_bytes(seed: u64, count: usize) -> Vec { - use rapidhash::rng::RapidRng; - let mut rng = RapidRng::new(seed); + use rand_core::{SeedableRng, TryRng}; + // Raw from_seed preserves the legacy rapidhash-4.4 RapidRng::new stream. + let mut rng = rapidrand::RapidRng::from_seed(seed.to_le_bytes()); let mut bytes = vec![0u8; count]; - // RapidRng uses rand_core 0.9, so use the next() method directly for chunk in bytes.chunks_exact_mut(8) { - chunk.copy_from_slice(&rng.next().to_le_bytes()); + let Ok(v) = rng.try_next_u64(); + chunk.copy_from_slice(&v.to_le_bytes()); } bytes }