2026-01-21 10:56:16 -05:00

95 lines
3.7 KiB
Rust

use crate::RandomGenerator;
// DAS COMMENT:
// The prompt for creating this code was verbatim: Okay, now problem 3 is an interesting pro AI
// take. The prompt for problem 3 is "Write code to implement PCG32 in my favorite programming
// language (Rust!)" Please do similar statistics and procedures as LFSR and LCG.
//
// For your context, previous RNGs have had a little help for me to understand how traits work in
// Rust, but the implementations are almost entirely mine. This was generated using a Claude Code
// session with Sonnet 4.5.
// PCG32 - Permuted Congruential Generator
// A modern, high-quality PRNG with excellent statistical properties
// Uses a 64-bit LCG internally with output permutation
pub struct Pcg32 {
state: u64, // Internal LCG state (64-bit)
increment: u64, // Must be odd
}
impl Pcg32 {
// Standard PCG32 parameters
const MULTIPLIER: u64 = 6364136223846793005;
const DEFAULT_INCREMENT: u64 = 1442695040888963407;
pub fn new(seed: u64) -> Self {
// DAS - Any new instance of Pcg immediately runs through the new_with_increment function
Pcg32::new_with_increment(seed, Self::DEFAULT_INCREMENT)
}
pub fn new_with_increment(seed: u64, increment: u64) -> Self {
let mut pcg = Pcg32 {
state: 0,
increment: (increment << 1) | 1, // Ensure increment is odd
// DAS - I did NOT tell claude to do this. It basically copied the prose entirely on
// it's own, and Rust's syntax is almost exactly the same as written. This shifts the
// whole increment one bit left, and ensures the 2.pow(0) bit is always true (aka, 1,
// and always odd).
};
// Initialize state properly
//
// DAS - This is that first LCG step. Claude here I think is
// technically skipping step 3. It's just adding the value to the seed, and then stepping
// (steps 4 and 5 only). For the purpose of AI authorship, I'm going to let it rock and
// we'll see what happens.
pcg.state = pcg.state.wrapping_add(seed);
pcg.step();
pcg
}
// Internal LCG step
fn step(&mut self) {
// DAS - This is a basic LCG implementation.
self.state = self
.state
.wrapping_mul(Self::MULTIPLIER)
.wrapping_add(self.increment);
}
// PCG output permutation: XSH-RR (xorshift high, random rotation)
fn output(state: u64) -> u32 {
// XOR high and low parts
let xorshifted = (((state >> 18) ^ state) >> 27) as u32;
let rot = (state >> 59) as u32;
// Random rotation
//
// DAS - DGC, I think your implementation of this is wrong in the homework. You say "Return
// (xorshifted >> rot)", but wouldn't that pad zeros and not truly 'rotate' the number? At
// least, that's what Claude is intuiting here by using rotate_right() instead.
xorshifted.rotate_right(rot)
}
}
impl RandomGenerator for Pcg32 {
//DAS - This is implemenating the trait for this specific module. Man, I love Rust.
fn next(&mut self) -> u64 {
let old_state = self.state;
self.step();
//DAS - Techincally we've been fudging things as u32. We need to go back to u64 for all our
//nice traits to work with the plotting functions and statistics in the main script.
Self::output(old_state) as u64
//DAS - This does exactly as first described in the HW. Save the old state, advance the
//state, permute on the old state and output the permuted old state.
}
fn modulus(&self) -> u64 {
u32::MAX as u64
}
}