Skip to content

adrianleb/rust-pcg64-py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rust-pcg64

Bit-exact Python reimplementation of Rust's rand_pcg::Pcg64 (LCG128-XSL-RR).

Produces identical sequences to Rust's rand_pcg v0.3 + rand v0.8 + rand_distr v0.4 for the same seed. Zero dependencies beyond the Python standard library.

Why?

When you have a Rust simulation that uses rand::Rng and you need to reproduce its exact random sequences in Python — for testing, debugging, analysis, or building a fast Python prototype that matches Rust output bit-for-bit.

NumPy's PCG64 uses the DXSM output permutation. Rust's Pcg64 uses XSL-RR. They are not interchangeable — same state, different output. This library bridges that gap.

Install

pip install rust-pcg64

Usage

Low-level API (mirrors Rust's Pcg64)

from rust_pcg64 import RustPcg64

# Same as Rust's: let mut rng = Pcg64::seed_from_u64(42);
rng = RustPcg64.seed_from_u64(42)

val = rng.next_u64()              # rng.gen::<u64>()
f = rng.gen_f64()                 # rng.gen::<f64>()
x = rng.gen_range_f64(0.0, 10.0) # rng.gen_range(0.0..10.0)
n = rng.gen_range_int(1, 6)      # rng.gen_range(1..=6) (u64 by default)

# Distribution samplers (matching rand_distr)
z = rng.standard_normal()        # StandardNormal
p = rng.poisson(5.0)             # Poisson(5.0)
y = rng.lognormal(0.0, 1.0)     # LogNormal(0.0, 1.0)

High-level API (NumPy-like)

from rust_pcg64 import RustRNG

rng = RustRNG(seed=42)

z = rng.standard_normal()
u = rng.uniform(0.0, 1.0)
n = rng.integers(0, 100)         # [0, 100)
p = rng.poisson(5.0)
y = rng.lognormal(0.0, 1.0)
f = rng.random()                 # [0, 1)

What's covered

Python Rust equivalent Notes
next_u64() rng.gen::<u64>() Core output function
gen_f64() rng.gen::<f64>() Standard distribution, [0, 1)
gen_open01() rng.sample(Open01) (0, 1)
gen_range_f64(lo, hi) rng.gen_range(lo..hi) Uses sample_single
gen_range_int(lo, hi) rng.gen_range(lo..=hi) Supports u8/u16/u32/u64
standard_normal() StandardNormal Ziggurat (ZIGNOR)
poisson(lam) Poisson::new(lam) Knuth / rejection
lognormal(mu, sigma) LogNormal::new(mu, sigma) Via standard_normal
seed_from_u64(seed) Pcg64::seed_from_u64(seed) PCG32-based expansion
from_seed(bytes) Pcg64::from_seed(bytes) 32-byte seed

Integer range details

Rust uses different algorithms for different integer widths. The int_type parameter selects which variant to emulate:

rng.gen_range_int(0, 255, int_type='u8')   # u8 range (modulus zone)
rng.gen_range_int(0, 999, int_type='u16')  # u16 range (modulus zone)
rng.gen_range_int(0, 999, int_type='u32')  # u32 range (bit-shift zone)
rng.gen_range_int(0, 999, int_type='u64')  # u64 range (bit-shift zone, default)

How it works

The core is a 128-bit Linear Congruential Generator with the XSL-RR (xorshift-low, rotate-right) output permutation:

state = state * MULT + INCREMENT   (mod 2^128)
output = rotate_right_64((state >> 64) ^ state, state >> 122)

Seed expansion from u64 to 32 bytes uses a PCG32 generator (matching rand_core::SeedableRng::seed_from_u64).

Distribution samplers match Rust's rand_distr implementations:

  • Normal: Ziggurat method (ZIGNOR variant) with 256-entry tables
  • Poisson: Knuth's method for small lambda, rejection with Cauchy for large lambda
  • LogNormal: exp(normal * sigma + mu)

Verifying against Rust

// Rust verification program
use rand::SeedableRng;
use rand::Rng;
use rand_pcg::Pcg64;

fn main() {
    let mut rng = Pcg64::seed_from_u64(42);
    println!("{}", rng.gen::<u64>());    // Compare with Python
    println!("{}", rng.gen::<f64>());
    println!("{}", rng.gen_range(0.0..10.0));
}
# Python verification
from rust_pcg64 import RustPcg64

rng = RustPcg64.seed_from_u64(42)
print(rng.next_u64())        # Same value
print(rng.gen_f64())         # Same value
print(rng.gen_range_f64(0.0, 10.0))  # Same value

Compatibility

Rust crate Version Status
rand_pcg 0.3.x Fully matched
rand 0.8.x Fully matched
rand_distr 0.4.x Fully matched

Performance

This is a pure Python implementation optimized for correctness, not speed. For high-throughput generation, use it to verify sequences then switch to Rust. Typical throughput: ~500K u64/sec on modern hardware.

License

MIT

About

Rust Pcg64 reimplemented in Python

Resources

License

Stars

8 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages