178 lines
4.9 KiB
Rust
178 lines
4.9 KiB
Rust
//! Bit-Parallel Simulation Primitives
|
||
//!
|
||
//! Each u64 word simulates 64 binary states simultaneously,
|
||
//! providing a 64x multiplier over scalar simulation.
|
||
|
||
/// Generic bit-parallel automaton trait
|
||
pub trait BitParallelAutomaton {
|
||
/// Evolve all cells for one generation
|
||
fn step(&mut self);
|
||
|
||
/// Number of cells (bits) being simulated
|
||
fn num_cells(&self) -> usize;
|
||
|
||
/// Simulations per step (= num_cells)
|
||
fn simulations_per_step(&self) -> u64 {
|
||
self.num_cells() as u64
|
||
}
|
||
}
|
||
|
||
/// Rule-based 1D cellular automaton (Wolfram-style)
|
||
/// Each u64 contains 64 cells, evolved using a lookup table
|
||
#[repr(align(64))]
|
||
pub struct CellularAutomaton1D {
|
||
/// State: each bit is one cell
|
||
state: Vec<u64>,
|
||
/// Lookup table for 3-cell neighborhood → next cell
|
||
rule_lut: [u8; 256],
|
||
}
|
||
|
||
impl CellularAutomaton1D {
|
||
/// Create CA with given number of u64 words and rule number
|
||
pub fn new(num_words: usize, rule: u8) -> Self {
|
||
// Build LUT: for each 8-bit pattern, compute result
|
||
let mut rule_lut = [0u8; 256];
|
||
for pattern in 0..=255u8 {
|
||
let mut result = 0u8;
|
||
for bit in 0..8 {
|
||
let neighborhood = (pattern >> bit) & 0b111;
|
||
let next = (rule >> neighborhood) & 1;
|
||
result |= next << bit;
|
||
}
|
||
rule_lut[pattern as usize] = result;
|
||
}
|
||
|
||
Self {
|
||
state: vec![0xAAAA_AAAA_AAAA_AAAAu64; num_words],
|
||
rule_lut,
|
||
}
|
||
}
|
||
|
||
/// Set initial state
|
||
pub fn set_state(&mut self, initial: &[u64]) {
|
||
self.state.copy_from_slice(initial);
|
||
}
|
||
|
||
/// Get current state
|
||
pub fn state(&self) -> &[u64] {
|
||
&self.state
|
||
}
|
||
}
|
||
|
||
impl BitParallelAutomaton for CellularAutomaton1D {
|
||
fn step(&mut self) {
|
||
let len = self.state.len();
|
||
if len == 0 { return; }
|
||
|
||
// We need to update in-place, so use temp for boundary handling
|
||
let first = self.state[0];
|
||
let last = self.state[len - 1];
|
||
|
||
for i in 0..len {
|
||
let left = if i == 0 { last } else { self.state[i - 1] };
|
||
let center = self.state[i];
|
||
let right = if i == len - 1 { first } else { self.state[i + 1] };
|
||
|
||
let mut next = 0u64;
|
||
for byte_idx in 0..8 {
|
||
let shift = byte_idx * 8;
|
||
// Extract 8-bit windows
|
||
let l = ((left >> shift) & 0xFF) as u8;
|
||
let c = ((center >> shift) & 0xFF) as u8;
|
||
let r = ((right >> shift) & 0xFF) as u8;
|
||
|
||
// Combine into neighborhood pattern and lookup
|
||
let pattern = l.rotate_right(1) ^ c ^ r.rotate_left(1);
|
||
let result = self.rule_lut[pattern as usize];
|
||
next |= (result as u64) << shift;
|
||
}
|
||
self.state[i] = next;
|
||
}
|
||
}
|
||
|
||
fn num_cells(&self) -> usize {
|
||
self.state.len() * 64
|
||
}
|
||
}
|
||
|
||
/// Binary Markov chain with bit-parallel transitions
|
||
/// Each bit represents one independent chain
|
||
#[repr(align(64))]
|
||
pub struct BinaryMarkovChain {
|
||
/// Current states: 64 chains per u64
|
||
states: Vec<u64>,
|
||
/// Transition probability (0-65535 = 0.0-1.0)
|
||
transition_threshold: u16,
|
||
/// PRNG state
|
||
rng_state: u64,
|
||
}
|
||
|
||
impl BinaryMarkovChain {
|
||
/// Create n×64 independent binary chains
|
||
pub fn new(num_words: usize, transition_prob: f64) -> Self {
|
||
let threshold = (transition_prob * 65535.0) as u16;
|
||
Self {
|
||
states: vec![0; num_words],
|
||
transition_threshold: threshold,
|
||
rng_state: 0x12345678_9ABCDEF0,
|
||
}
|
||
}
|
||
|
||
/// Fast xorshift64 PRNG
|
||
#[inline(always)]
|
||
fn next_random(&mut self) -> u64 {
|
||
let mut x = self.rng_state;
|
||
x ^= x << 13;
|
||
x ^= x >> 7;
|
||
x ^= x << 17;
|
||
self.rng_state = x;
|
||
x
|
||
}
|
||
}
|
||
|
||
impl BitParallelAutomaton for BinaryMarkovChain {
|
||
fn step(&mut self) {
|
||
let threshold = self.transition_threshold;
|
||
let len = self.states.len();
|
||
|
||
for i in 0..len {
|
||
let random = self.next_random();
|
||
// Flip bit where random < threshold (probabilistic)
|
||
// Using bit manipulation for parallel evaluation
|
||
let flip_mask = random.wrapping_mul(threshold as u64);
|
||
self.states[i] ^= flip_mask;
|
||
}
|
||
}
|
||
|
||
fn num_cells(&self) -> usize {
|
||
self.states.len() * 64
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_ca_creation() {
|
||
let ca = CellularAutomaton1D::new(16, 110);
|
||
assert_eq!(ca.num_cells(), 16 * 64);
|
||
}
|
||
|
||
#[test]
|
||
fn test_ca_step() {
|
||
let mut ca = CellularAutomaton1D::new(4, 110);
|
||
let initial = ca.state().to_vec();
|
||
ca.step();
|
||
// State should change
|
||
assert_ne!(ca.state(), &initial[..]);
|
||
}
|
||
|
||
#[test]
|
||
fn test_markov_chain() {
|
||
let mut mc = BinaryMarkovChain::new(8, 0.1);
|
||
mc.step();
|
||
assert_eq!(mc.num_cells(), 8 * 64);
|
||
}
|
||
}
|