Skip to main content

ruvector_dither/
lib.rs

1//! # ruvector-dither
2//!
3//! Deterministic, low-discrepancy **pre-quantization dithering** for low-bit
4//! inference on tiny devices (WASM, Seed, STM32).
5//!
6//! ## Why dither?
7//!
8//! Quantizers at 3 / 5 / 7 bits can align with power-of-two boundaries and
9//! produce idle tones / limit cycles — sticky activations and periodic errors
10//! that degrade accuracy.  A sub-LSB pre-quantization offset:
11//!
12//! - Decorrelates the signal from grid boundaries.
13//! - Pushes quantization error toward high frequencies (blue-noise-like),
14//!   which average out downstream.
15//! - Uses **no RNG** — outputs are deterministic, reproducible across
16//!   platforms (WASM / x86 / ARM), and cache-friendly.
17//!
18//! ## Sequences
19//!
20//! | Type | State update | Properties |
21//! |------|-------------|------------|
22//! | [`GoldenRatioDither`] | frac(state + φ) | Best 1-D equidistribution |
23//! | [`PiDither`] | table of π bytes | Reproducible, period = 256 |
24//!
25//! ## Quick start
26//!
27//! ```
28//! use ruvector_dither::{GoldenRatioDither, PiDither, quantize_dithered};
29//!
30//! // Quantize with golden-ratio dither, 8-bit, ε = 0.5 LSB
31//! let mut gr = GoldenRatioDither::new(0.0);
32//! let q = quantize_dithered(0.314, 8, 0.5, &mut gr);
33//! assert!(q >= -1.0 && q <= 1.0);
34//!
35//! // Quantize with π-digit dither
36//! let mut pi = PiDither::new(0);
37//! let q2 = quantize_dithered(0.271, 5, 0.5, &mut pi);
38//! assert!(q2 >= -1.0 && q2 <= 1.0);
39//! ```
40
41#![cfg_attr(feature = "no_std", no_std)]
42
43pub mod golden;
44pub mod pi;
45pub mod quantize;
46pub mod channel;
47
48pub use golden::GoldenRatioDither;
49pub use pi::PiDither;
50pub use quantize::{quantize_dithered, quantize_slice_dithered};
51pub use channel::ChannelDither;
52
53/// Trait implemented by any deterministic dither source.
54pub trait DitherSource {
55    /// Advance the sequence and return the next zero-mean offset in `[-0.5, +0.5]`.
56    fn next_unit(&mut self) -> f32;
57
58    /// Scale output to ε × LSB amplitude.
59    #[inline]
60    fn next(&mut self, eps_lsb: f32) -> f32 {
61        self.next_unit() * eps_lsb
62    }
63}