Files
ruvnet--RuView/vendor/ruvector/crates/ruvector-dither/src/pi.rs
T

111 lines
4.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! π-digit dither: cyclic table of the first 256 digits of π scaled to [-0.5, 0.5].
//!
//! Period = 256. Each entry is an independent offset making the sequence
//! suitable for small buffers where you want exact reproducibility from a
//! named tensor / layer rather than a stateful RNG.
use crate::DitherSource;
/// First 256 bytes of π (hex digits 3.243F6A8885A308D3…).
///
/// Each byte spans [0, 255]; we map to [-0.5, 0.5] by `(b as f32 / 255.0) - 0.5`.
#[rustfmt::skip]
const PI_BYTES: [u8; 256] = [
0x32, 0x43, 0xF6, 0xA8, 0x88, 0x5A, 0x30, 0x8D, 0x31, 0x31, 0x98, 0xA2,
0xE0, 0x37, 0x07, 0x34, 0x4A, 0x40, 0x93, 0x82, 0x22, 0x99, 0xF3, 0x1D,
0x00, 0x82, 0xEF, 0xA9, 0x8E, 0xC4, 0xE6, 0xC8, 0x94, 0x52, 0x21, 0xE6,
0x38, 0xD0, 0x13, 0x77, 0xBE, 0x54, 0x66, 0xCF, 0x34, 0xE9, 0x0C, 0x6C,
0xC0, 0xAC, 0x29, 0xB7, 0xC9, 0x7C, 0x50, 0xDD, 0x3F, 0x84, 0xD5, 0xB5,
0xB5, 0x47, 0x09, 0x17, 0x92, 0x16, 0xD5, 0xD9, 0x89, 0x79, 0xFB, 0x1B,
0xD1, 0x31, 0x0B, 0xA6, 0x98, 0xDF, 0xB5, 0xAC, 0x2F, 0xFD, 0x72, 0xDB,
0xD0, 0x1A, 0xDF, 0xB7, 0xB8, 0xE1, 0xAF, 0xED, 0x6A, 0x26, 0x7E, 0x96,
0xBA, 0x7C, 0x90, 0x45, 0xF1, 0x2C, 0x7F, 0x99, 0x24, 0xA1, 0x99, 0x47,
0xB3, 0x91, 0x6C, 0xF7, 0x08, 0x01, 0xF2, 0xE2, 0x85, 0x8E, 0xFC, 0x16,
0x63, 0x69, 0x20, 0xD8, 0x71, 0x57, 0x4E, 0x69, 0xA4, 0x58, 0xFE, 0xA3,
0xF4, 0x93, 0x3D, 0x7E, 0x0D, 0x95, 0x74, 0x8F, 0x72, 0x8E, 0xB6, 0x58,
0x71, 0x8B, 0xCD, 0x58, 0x82, 0x15, 0x4A, 0xEE, 0x7B, 0x54, 0xA4, 0x1D,
0xC2, 0x5A, 0x59, 0xB5, 0x9C, 0x30, 0xD5, 0x39, 0x2A, 0xF2, 0x60, 0x13,
0xC5, 0xD1, 0xB0, 0x23, 0x28, 0x60, 0x85, 0xF0, 0xCA, 0x41, 0x79, 0x18,
0xB8, 0xDB, 0x38, 0xEF, 0x8E, 0x79, 0xDC, 0xB0, 0x60, 0x3A, 0x18, 0x0E,
0x6C, 0x9E, 0xD0, 0xE8, 0x9D, 0x44, 0x8F, 0x39, 0xF9, 0x93, 0xDB, 0x07,
0x3A, 0xA3, 0x45, 0x22, 0x7E, 0xD8, 0xAC, 0x87, 0x2F, 0x85, 0x5D, 0x28,
0x55, 0xB0, 0x89, 0x73, 0x36, 0xF3, 0xEB, 0xCD, 0xF6, 0x00, 0x4A, 0xDB,
0x36, 0x47, 0xDB, 0xF7, 0x82, 0x48, 0xDB, 0xF3, 0xD3, 0x7C, 0x45, 0x10,
0xC6, 0x7A, 0x70, 0xAA, 0x56, 0x78, 0x5A, 0xC6, 0x37, 0x10, 0xA2, 0x44,
0x32, 0x34, 0xFE, 0x08,
];
/// Cyclic π-digit dither. Period = 256; index wraps with bitwise AND.
#[derive(Clone, Debug)]
pub struct PiDither {
idx: u8,
}
impl PiDither {
/// Create a new instance starting at `offset` (0255).
#[inline]
pub fn new(offset: u8) -> Self {
Self { idx: offset }
}
/// Construct from a tensor/layer identifier for structural reproducibility.
#[inline]
pub fn from_tensor_id(tensor_id: u32) -> Self {
// Mix bits so different tensor IDs get distinct offsets
let mixed = tensor_id
.wrapping_mul(0x9E37_79B9)
.wrapping_add(tensor_id >> 16);
Self {
idx: (mixed & 0xFF) as u8,
}
}
}
impl DitherSource for PiDither {
/// Advance and return next value in `[-0.5, 0.5]`.
#[inline]
fn next_unit(&mut self) -> f32 {
let b = PI_BYTES[self.idx as usize];
self.idx = self.idx.wrapping_add(1);
(b as f32 / 255.0) - 0.5
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DitherSource;
#[test]
fn output_is_in_range() {
let mut d = PiDither::new(0);
for _ in 0..256 * 4 {
let v = d.next_unit();
assert!(v >= -0.5 && v <= 0.5, "out of range: {v}");
}
}
#[test]
fn period_is_256() {
let mut d = PiDither::new(0);
let first: Vec<f32> = (0..256).map(|_| d.next_unit()).collect();
let second: Vec<f32> = (0..256).map(|_| d.next_unit()).collect();
assert_eq!(first, second);
}
#[test]
fn mean_is_near_zero() {
let mut d = PiDither::new(0);
let sum: f32 = (0..256).map(|_| d.next_unit()).sum();
let mean = sum / 256.0;
assert!(mean.abs() < 0.05, "π-digit mean too large: {mean}");
}
#[test]
fn from_tensor_id_gives_distinct_offsets() {
let d0 = PiDither::from_tensor_id(0);
let d1 = PiDither::from_tensor_id(1);
assert_ne!(d0.idx, d1.idx);
}
}