Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion crates/benchmarks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
15 changes: 14 additions & 1 deletion crates/benchmarks/benches/modules/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion crates/pecos-random/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions crates/pecos-random/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
45 changes: 45 additions & 0 deletions crates/pecos-random/src/rapid_rng.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
8 changes: 4 additions & 4 deletions crates/pecos-random/src/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/pecos-random/src/scalar_rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
59 changes: 59 additions & 0 deletions crates/pecos-random/tests/seed_stream_stability.rs
Original file line number Diff line number Diff line change
@@ -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<u64> = (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,
];
9 changes: 5 additions & 4 deletions crates/pecos-random/tests/statistical_quality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,13 @@ fn generate_pcgrandom_bytes(seed: u64, count: usize) -> Vec<u8> {

/// Generate random bytes using `RapidRng`
fn generate_rapidrng_bytes(seed: u64, count: usize) -> Vec<u8> {
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
}
Expand Down
Loading