const ROUNDS: usize = 3;
pub(crate) struct SplitMix64(pub u64);
impl SplitMix64 {
#[inline]
pub fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[inline]
fn next_below(&mut self, bound: u64) -> u64 {
debug_assert!(bound > 0);
loop {
let v = self.next_u64();
if v < u64::MAX - (u64::MAX % bound) {
return v % bound;
}
}
}
}
struct Round {
sign_words: Vec<u64>,
perm: Vec<u32>,
}
pub struct Rotation {
dim: usize,
rounds: Vec<Round>,
blocks: Vec<usize>,
}
impl Rotation {
pub fn new(dim: usize, seed: u64) -> Self {
assert!(dim >= 2, "Turbo4 rotation requires dim >= 2, got {dim}");
let mut rng = SplitMix64(seed ^ 0x5175_6472_616E_7434); let n_words = dim.div_ceil(64);
let rounds = (0..ROUNDS)
.map(|_| {
let sign_words: Vec<u64> = (0..n_words).map(|_| rng.next_u64()).collect();
let mut perm: Vec<u32> = (0..dim as u32).collect();
for i in (1..dim).rev() {
let j = rng.next_below(i as u64 + 1) as usize;
perm.swap(i, j);
}
Round { sign_words, perm }
})
.collect();
let mut blocks = Vec::new();
let mut bit = usize::BITS - 1 - dim.leading_zeros();
loop {
if dim & (1 << bit) != 0 {
blocks.push(1usize << bit);
}
if bit == 0 {
break;
}
bit -= 1;
}
Self {
dim,
rounds,
blocks,
}
}
#[inline]
pub fn dim(&self) -> usize {
self.dim
}
pub fn apply_in_place(&self, v: &mut [f32], scratch: &mut [f32]) {
assert_eq!(v.len(), self.dim);
assert_eq!(scratch.len(), self.dim);
for round in &self.rounds {
for (i, x) in v.iter_mut().enumerate() {
if round.sign_words[i / 64] >> (i % 64) & 1 != 0 {
*x = -*x;
}
}
for (i, &src) in round.perm.iter().enumerate() {
scratch[i] = v[src as usize];
}
v.copy_from_slice(scratch);
let mut off = 0;
for &b in &self.blocks {
fwht_normalized(&mut v[off..off + b]);
off += b;
}
}
}
pub fn apply(&self, v: &[f32]) -> Vec<f32> {
let mut out = v.to_vec();
let mut scratch = vec![0.0f32; self.dim];
self.apply_in_place(&mut out, &mut scratch);
out
}
pub fn apply_inverse(&self, v: &[f32]) -> Vec<f32> {
assert_eq!(v.len(), self.dim);
let mut out = v.to_vec();
let mut scratch = vec![0.0f32; self.dim];
for round in self.rounds.iter().rev() {
let mut off = 0;
for &b in &self.blocks {
fwht_normalized(&mut out[off..off + b]);
off += b;
}
for (i, &src) in round.perm.iter().enumerate() {
scratch[src as usize] = out[i];
}
out.copy_from_slice(&scratch);
for (i, x) in out.iter_mut().enumerate() {
if round.sign_words[i / 64] >> (i % 64) & 1 != 0 {
*x = -*x;
}
}
}
out
}
}
fn fwht_normalized(v: &mut [f32]) {
let n = v.len();
debug_assert!(n.is_power_of_two());
if n == 1 {
return;
}
let mut h = 1;
while h < n {
let mut i = 0;
while i < n {
for j in i..i + h {
let x = v[j];
let y = v[j + h];
v[j] = x + y;
v[j + h] = x - y;
}
i += h * 2;
}
h *= 2;
}
let scale = 1.0 / (n as f32).sqrt();
for x in v.iter_mut() {
*x *= scale;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn gauss_vec(dim: usize, seed: u64) -> Vec<f32> {
let mut rng = SplitMix64(seed);
let mut out = Vec::with_capacity(dim);
while out.len() < dim {
let u1 = (rng.next_u64() >> 11) as f64 / (1u64 << 53) as f64;
let u2 = (rng.next_u64() >> 11) as f64 / (1u64 << 53) as f64;
let r = (-2.0 * u1.max(1e-12).ln()).sqrt();
let (s, c) = (2.0 * std::f64::consts::PI * u2).sin_cos();
out.push((r * c) as f32);
if out.len() < dim {
out.push((r * s) as f32);
}
}
out
}
fn norm(v: &[f32]) -> f32 {
v.iter().map(|x| x * x).sum::<f32>().sqrt()
}
#[test]
fn preserves_norm_pow2_and_non_pow2() {
for dim in [64usize, 128, 96, 1536, 1000, 3] {
let rot = Rotation::new(dim, 42);
let v = gauss_vec(dim, 7);
let r = rot.apply(&v);
let (n0, n1) = (norm(&v), norm(&r));
assert!(
(n0 - n1).abs() < 1e-3 * n0.max(1.0),
"dim {dim}: norm {n0} -> {n1}"
);
}
}
#[test]
fn preserves_inner_products() {
let dim = 96; let rot = Rotation::new(dim, 9);
let a = gauss_vec(dim, 1);
let b = gauss_vec(dim, 2);
let dot = |x: &[f32], y: &[f32]| x.iter().zip(y).map(|(p, q)| p * q).sum::<f32>();
let (ra, rb) = (rot.apply(&a), rot.apply(&b));
assert!((dot(&a, &b) - dot(&ra, &rb)).abs() < 1e-2 * dim as f32);
}
#[test]
fn inverse_roundtrips() {
let dim = 200; let rot = Rotation::new(dim, 5);
let v = gauss_vec(dim, 3);
let back = rot.apply_inverse(&rot.apply(&v));
for (x, y) in v.iter().zip(&back) {
assert!((x - y).abs() < 1e-4, "{x} vs {y}");
}
}
#[test]
fn deterministic_across_builds() {
let dim = 128;
let (r1, r2) = (Rotation::new(dim, 42), Rotation::new(dim, 42));
let v = gauss_vec(dim, 11);
assert_eq!(r1.apply(&v), r2.apply(&v));
let r3 = Rotation::new(dim, 43);
assert_ne!(r1.apply(&v), r3.apply(&v));
}
#[test]
fn spreads_spike_across_coordinates() {
let dim = 1536;
let rot = Rotation::new(dim, 42);
let mut v = vec![0.0f32; dim];
v[17] = 1.0;
let r = rot.apply(&v);
let max = r.iter().fold(0.0f32, |m, x| m.max(x.abs()));
assert!(max < 0.25, "spike not spread: max coord {max}");
}
}