pub use polydat::numeric::permute::{
feedback_for_size, feedback_for_width, feedback_for_width_and_bank, width_for_period,
};
use polydat::numeric::permute::{lfsr_step as step, shuffle_bounded};
#[polydat::polydat_node(category = Permutation, validate = shuffle_validate)]
fn shuffle(
input: u64,
#[poly_default(0u64)] feedback: Const<u64>,
#[poly_default(0u64)] size: Const<u64>,
#[poly_default(0u64)] min: Const<u64>,
) -> u64 {
shuffle_bounded(input, *feedback, *size, *min)
}
fn shuffle_validate(_name: &str, consts: &[polydat::dsl::factory::ConstArg]) -> Result<(), String> {
let size = consts.get(1).map(|c| c.as_u64()).unwrap_or(0);
let min = consts.get(2).map(|c| c.as_u64()).unwrap_or(0);
if size > 0 && min.checked_add(size - 1).is_none() {
return Err(format!(
"the range [min, min + size) must fit in u64; min {min} + size {size} runs past {}",
u64::MAX
));
}
Ok(())
}
#[polydat::polydat_node(category = Permutation)]
fn lfsr_step(input: u64, feedback: Const<u64>) -> u64 {
step(input, *feedback)
}
#[cfg(test)]
mod tests {
use super::*;
use polydat::ast::{PolydatNode, Value};
#[test]
fn an_empty_range_answers_its_floor() {
for min in [0u64, 7, u64::MAX] {
let node = Shuffle::new(0, 0, min);
let mut out = [Value::None];
for input in [0u64, 1, 42, u64::MAX] {
node.eval(&[Value::U64(input)], &mut out);
assert_eq!(
out[0].as_u64(),
min,
"shuffle over an empty range at min={min}, input={input}"
);
}
}
}
#[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);
}
}