use crate::derive_support::Const;
const BANKS_PER_WIDTH: usize = 8;
const FEEDBACK_BANKS: [u64; 61 * BANKS_PER_WIDTH] = include!("metashift_banks.inc");
pub fn feedback_for_width_and_bank(width: u32, bank: usize) -> u64 {
assert!((4..=64).contains(&width), "LFSR width must be 4..64, got {width}");
let base = (width as usize - 4) * BANKS_PER_WIDTH;
FEEDBACK_BANKS[base + (bank % BANKS_PER_WIDTH)]
}
pub fn feedback_for_width(width: u32) -> u64 {
feedback_for_width_and_bank(width, 0)
}
pub fn width_for_period(period: u64) -> u32 {
assert!(period > 0, "period must be positive");
let bits = 64 - period.leading_zeros();
bits.max(4) }
pub fn feedback_for_size(size: u64) -> u64 {
feedback_for_width_and_bank(width_for_period(size), 0)
}
#[inline]
fn step(register: u64, feedback: u64) -> u64 {
let lsb = register & 1;
let shifted = register >> 1;
shifted ^ (lsb.wrapping_neg() & feedback)
}
#[crate::polydat_node(category = Permutation)]
fn shuffle(
input: u64,
#[poly_default(0u64)] feedback: Const<u64>,
#[poly_default(0u64)] size: Const<u64>,
#[poly_default(0u64)] min: Const<u64>,
) -> u64 {
let mut register = (input % *size) + 1;
loop {
register = step(register, *feedback);
if register <= *size {
break;
}
}
(register - 1) + *min
}
#[crate::polydat_node(category = Permutation)]
fn lfsr_step(input: u64, feedback: Const<u64>) -> u64 {
step(input, *feedback)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{PolydatNode, Value};
#[test]
fn lfsr_step_nonzero() {
let feedback = feedback_for_width(8);
let mut reg = 1u64;
for _ in 0..255 {
reg = step(reg, feedback);
assert_ne!(reg, 0, "LFSR must never produce 0");
}
}
#[test]
fn lfsr_full_cycle() {
let feedback = feedback_for_width(8);
let mut seen = vec![false; 256];
let mut reg = 1u64;
for _ in 0..255 {
reg = step(reg, feedback);
assert!(!seen[reg as usize], "duplicate value {reg}");
seen[reg as usize] = true;
}
for i in 1..=255u64 {
assert!(seen[i as usize], "value {i} not visited");
}
}
#[test]
fn lfsr_period_returns_to_start() {
let feedback = feedback_for_width(8);
let start = 42u64;
let mut reg = start;
for _ in 0..255 {
reg = step(reg, feedback);
}
assert_eq!(reg, start, "LFSR should return to start after 2^N-1 steps");
}
fn shuf(min: u64, size: u64) -> Shuffle {
Shuffle::new(feedback_for_size(size), size, min)
}
fn shuf0(size: u64) -> Shuffle {
shuf(0, size)
}
fn apply(node: &Shuffle, input: u64) -> u64 {
let mut out = [Value::None];
node.eval(&[Value::U64(input)], &mut out);
out[0].as_u64()
}
#[test]
fn shuffle_bijective_small() {
let node = shuf0(31);
let mut seen = [false; 31];
for i in 0..31u64 {
let out = apply(&node, i);
assert!(out < 31, "out of range: {out}");
assert!(!seen[out as usize], "duplicate at input {i}: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_bijective_non_power_of_two() {
let node = shuf0(50);
let mut seen = [false; 50];
for i in 0..50u64 {
let out = apply(&node, i);
assert!(out < 50, "out of range: {out}");
assert!(!seen[out as usize], "duplicate at input {i}: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_with_min_offset() {
let node = shuf(100, 20);
let mut seen = [false; 20];
for i in 0..20u64 {
let out = apply(&node, i);
assert!((100..120).contains(&out), "out of range: {out}");
seen[(out - 100) as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_deterministic() {
let node = shuf0(100);
let a = apply(&node, 42);
let b = apply(&node, 42);
assert_eq!(a, b);
}
#[test]
fn shuffle_not_identity() {
let node = shuf0(100);
let mut identity_count = 0;
for i in 0..100u64 {
if apply(&node, i) == i {
identity_count += 1;
}
}
assert!(identity_count < 50, "shuffle should reorder most values");
}
#[test]
fn shuffle_polydat_node() {
let node = shuf0(100);
let mut out = [Value::None];
node.eval(&[Value::U64(7)], &mut out);
assert!(out[0].as_u64() < 100);
}
#[test]
fn shuffle_compiled() {
let node = shuf0(100);
let op = node.compiled_u64().expect("should compile");
let mut out = [0u64];
op(&[7], &mut out);
assert!(out[0] < 100);
let mut eval_out = [Value::None];
node.eval(&[Value::U64(7)], &mut eval_out);
assert_eq!(out[0], eval_out[0].as_u64());
}
#[test]
fn lfsr_step_node() {
let node = LfsrStep::new(feedback_for_width(8));
let mut out = [Value::None];
node.eval(&[Value::U64(1)], &mut out);
let v = out[0].as_u64();
assert_ne!(v, 0);
assert_ne!(v, 1);
}
#[test]
fn shuffle_large_range() {
let node = shuf0(1000);
let mut seen = vec![false; 1000];
for i in 0..1000u64 {
let out = apply(&node, i);
assert!(out < 1000, "out of range: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn different_banks_different_orderings() {
let size = 100;
let fb0 = feedback_for_width_and_bank(width_for_period(size), 0);
let fb1 = feedback_for_width_and_bank(width_for_period(size), 1);
let n0 = Shuffle::new(fb0, size, 0);
let n1 = Shuffle::new(fb1, size, 0);
let mut seen0 = [false; 100];
let mut seen1 = [false; 100];
let mut differ = false;
for i in 0..100u64 {
let a = apply(&n0, i);
let b = apply(&n1, i);
assert!(a < 100);
assert!(b < 100);
seen0[a as usize] = true;
seen1[b as usize] = true;
if a != b {
differ = true;
}
}
assert!(seen0.iter().all(|&s| s), "bank 0 not bijective");
assert!(seen1.iter().all(|&s| s), "bank 1 not bijective");
assert!(differ, "different banks should produce different orderings");
}
#[test]
fn width_for_period_table() {
assert_eq!(width_for_period(1), 4); assert_eq!(width_for_period(15), 4); assert_eq!(width_for_period(16), 5); assert_eq!(width_for_period(31), 5);
assert_eq!(width_for_period(32), 6);
assert_eq!(width_for_period(255), 8);
assert_eq!(width_for_period(256), 9);
assert_eq!(width_for_period(1000), 10);
}
}