#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResidueType {
Type0 = 0,
Type1 = 1,
Type2 = 2,
}
impl Default for ResidueType {
fn default() -> Self {
Self::Type1
}
}
#[derive(Clone, Debug)]
pub struct ResidueConfig {
pub residue_type: ResidueType,
pub begin: u32,
pub end: u32,
pub partition_size: u32,
pub classifications: u8,
pub classbook: u8,
}
impl Default for ResidueConfig {
fn default() -> Self {
Self {
residue_type: ResidueType::Type1,
begin: 0,
end: 128,
partition_size: 32,
classifications: 4,
classbook: 0,
}
}
}
#[derive(Clone, Debug)]
pub struct ResidueEncoder {
pub config: ResidueConfig,
pub step: f64,
}
impl ResidueEncoder {
#[must_use]
pub fn new(config: ResidueConfig, step: f64) -> Self {
Self {
config,
step: step.max(1e-6),
}
}
#[must_use]
pub fn quantise(&self, residue: &[f64]) -> Vec<i16> {
residue
.iter()
.map(|&v| {
let q = (v / self.step).round();
q.clamp(i16::MIN as f64, i16::MAX as f64) as i16
})
.collect()
}
#[must_use]
pub fn dequantise(&self, codes: &[i16]) -> Vec<f64> {
codes.iter().map(|&c| f64::from(c) * self.step).collect()
}
#[must_use]
pub fn max_quant_error(&self, original: &[f64]) -> f64 {
let quantised = self.quantise(original);
let recovered = self.dequantise(&quantised);
original
.iter()
.zip(recovered.iter())
.map(|(&o, &r)| (o - r).abs())
.fold(0.0_f64, f64::max)
}
}
#[derive(Clone, Debug)]
pub struct ResidueDecoder {
pub config: ResidueConfig,
pub step: f64,
}
impl ResidueDecoder {
#[must_use]
pub fn new(config: ResidueConfig, step: f64) -> Self {
Self {
config,
step: step.max(1e-6),
}
}
#[must_use]
pub fn decode(&self, codes: &[i16]) -> Vec<f64> {
codes.iter().map(|&c| f64::from(c) * self.step).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_residue_type_default() {
assert_eq!(ResidueType::default(), ResidueType::Type1);
}
#[test]
fn test_residue_encoder_quantise_zero() {
let enc = ResidueEncoder::new(ResidueConfig::default(), 0.5);
let v = vec![0.0f64; 8];
let q = enc.quantise(&v);
assert!(q.iter().all(|&c| c == 0));
}
#[test]
fn test_residue_encoder_roundtrip() {
let step = 0.25;
let enc = ResidueEncoder::new(ResidueConfig::default(), step);
let original = vec![1.0, -1.0, 0.5, -0.5, 0.25, -0.25, 2.0, -2.0];
let q = enc.quantise(&original);
let recovered = enc.dequantise(&q);
for (&o, &r) in original.iter().zip(recovered.iter()) {
assert!(
(o - r).abs() <= step / 2.0 + 1e-9,
"Error too large: orig={o}, rec={r}"
);
}
}
#[test]
fn test_residue_max_quant_error_is_bounded() {
let step = 1.0;
let enc = ResidueEncoder::new(ResidueConfig::default(), step);
let data = vec![3.7, -2.1, 0.4, -0.9];
let err = enc.max_quant_error(&data);
assert!(
err <= step / 2.0 + 1e-9,
"Max error {err} exceeds half step"
);
}
#[test]
fn test_residue_decoder_decode() {
let step = 0.5;
let dec = ResidueDecoder::new(ResidueConfig::default(), step);
let codes: Vec<i16> = vec![2, -4, 0, 1];
let out = dec.decode(&codes);
assert!((out[0] - 1.0).abs() < 1e-9);
assert!((out[1] - -2.0).abs() < 1e-9);
assert!((out[2] - 0.0).abs() < 1e-9);
assert!((out[3] - 0.5).abs() < 1e-9);
}
#[test]
fn test_residue_encoder_clamping() {
let step = 1.0;
let enc = ResidueEncoder::new(ResidueConfig::default(), step);
let huge = vec![1e10f64, -1e10];
let q = enc.quantise(&huge);
assert_eq!(q[0], i16::MAX);
assert_eq!(q[1], i16::MIN);
}
}