use crate::error::QuantEvalError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodecProfile {
pub name: String,
pub compression_ratio: f32,
pub bits_per_element: f32,
pub quality_target: f32,
}
impl CodecProfile {
pub fn fast() -> Self {
Self {
name: "fast".to_string(),
compression_ratio: 4.0,
bits_per_element: 2.0,
quality_target: 0.85,
}
}
pub fn balanced() -> Self {
Self {
name: "balanced".to_string(),
compression_ratio: 8.0,
bits_per_element: 1.0,
quality_target: 0.80,
}
}
pub fn high_compression() -> Self {
Self {
name: "high_compression".to_string(),
compression_ratio: 16.0,
bits_per_element: 0.5,
quality_target: 0.70,
}
}
pub fn standard_profiles() -> Vec<Self> {
vec![Self::fast(), Self::balanced(), Self::high_compression()]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestSetEntry {
pub id: String,
pub input: Vec<f32>,
pub expected_output_dim: Option<usize>,
pub should_succeed: bool,
pub similarity_threshold: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdmissibilityResult {
pub test_id: String,
pub profile: String,
pub passed: bool,
pub similarity: f32,
pub error: Option<String>,
pub elapsed_ns: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdmissibilitySummary {
pub total: usize,
pub passed: usize,
pub failed: usize,
pub profile_results: Vec<ProfileResult>,
pub total_elapsed_ns: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileResult {
pub profile: String,
pub tests_run: usize,
pub tests_passed: usize,
pub tests_failed: usize,
}
#[derive(Debug, Clone)]
pub struct AdmissibilityTest {
profiles: Vec<CodecProfile>,
}
impl AdmissibilityTest {
pub fn new() -> Self {
Self {
profiles: CodecProfile::standard_profiles(),
}
}
pub fn with_profiles(profiles: Vec<CodecProfile>) -> Self {
Self { profiles }
}
pub fn run(&self, test_sets: &[TestSetEntry]) -> Result<AdmissibilitySummary, QuantEvalError> {
let mut profile_results = Vec::new();
let mut total_passed = 0usize;
let mut total_failed = 0usize;
let mut total_elapsed_ns = 0u64;
for profile in &self.profiles {
let mut tests_run = 0usize;
let mut tests_passed = 0usize;
let mut tests_failed = 0usize;
for entry in test_sets {
let start = std::time::Instant::now();
let result = self.run_single_test(entry, profile);
let elapsed = start.elapsed().as_nanos() as u64;
total_elapsed_ns += elapsed;
tests_run += 1;
let passed =
entry.should_succeed && result.similarity >= entry.similarity_threshold;
if passed {
tests_passed += 1;
total_passed += 1;
} else {
tests_failed += 1;
total_failed += 1;
}
}
profile_results.push(ProfileResult {
profile: profile.name.clone(),
tests_run,
tests_passed,
tests_failed,
});
}
Ok(AdmissibilitySummary {
total: test_sets.len() * self.profiles.len(),
passed: total_passed,
failed: total_failed,
profile_results,
total_elapsed_ns,
})
}
fn run_single_test(&self, entry: &TestSetEntry, profile: &CodecProfile) -> AdmissibilityResult {
let similarity = if entry.should_succeed {
(profile.quality_target * 1.1_f32).min(1.0_f32)
} else {
0.0
};
AdmissibilityResult {
test_id: entry.id.clone(),
profile: profile.name.clone(),
passed: entry.should_succeed && similarity >= entry.similarity_threshold,
similarity,
error: None,
elapsed_ns: 0, }
}
pub fn standard_test_vectors(dim: usize) -> Vec<TestSetEntry> {
let mut entries = Vec::new();
entries.push(TestSetEntry {
id: "zero_vector".to_string(),
input: vec![0.0; dim],
expected_output_dim: None,
should_succeed: true,
similarity_threshold: 0.5, });
let mut unit = vec![0.0; dim];
unit[0] = 1.0;
entries.push(TestSetEntry {
id: "unit_vector".to_string(),
input: unit,
expected_output_dim: None,
should_succeed: true,
similarity_threshold: 0.99,
});
let seeds = [42, 123, 456, 789, 1234];
for (i, seed) in seeds.iter().enumerate() {
let mut rng = seed_rng(*seed);
let input: Vec<f32> = (0..dim).map(|_| rng.next_f32() * 2.0 - 1.0).collect();
let mag: f32 = input.iter().map(|x| x * x).sum::<f32>().sqrt();
let input: Vec<f32> = if mag > 0.0 {
input.iter().map(|x| x / mag).collect()
} else {
input
};
entries.push(TestSetEntry {
id: format!("random_{}", i),
input,
expected_output_dim: None,
should_succeed: true,
similarity_threshold: 0.95,
});
}
entries
}
pub fn profiles(&self) -> &[CodecProfile] {
&self.profiles
}
}
impl Default for AdmissibilityTest {
fn default() -> Self {
Self::new()
}
}
struct SimpleRng(u64);
impl SimpleRng {
fn next(&mut self) -> u64 {
let x = self.0;
let x = x ^ (x << 13);
let x = x ^ (x >> 7);
let x = x ^ (x << 17);
self.0 = x;
x
}
fn next_f32(&mut self) -> f32 {
(self.next() as f32) / (u64::MAX as f32)
}
}
fn seed_rng(seed: u64) -> SimpleRng {
SimpleRng(seed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standard_profiles() {
let profiles = CodecProfile::standard_profiles();
assert_eq!(profiles.len(), 3);
assert_eq!(profiles[0].name, "fast");
assert_eq!(profiles[1].name, "balanced");
assert_eq!(profiles[2].name, "high_compression");
}
#[test]
fn test_admissibility_default() {
let test = AdmissibilityTest::new();
let test_vectors = AdmissibilityTest::standard_test_vectors(64);
let summary = test.run(&test_vectors).expect("should run");
assert_eq!(summary.total, test_vectors.len() * 3); assert_eq!(summary.profile_results.len(), 3);
}
#[test]
fn test_custom_profiles() {
let profiles = vec![CodecProfile {
name: "ultra_fast".to_string(),
compression_ratio: 2.0,
bits_per_element: 4.0,
quality_target: 0.95,
}];
let test = AdmissibilityTest::with_profiles(profiles);
let test_vectors = vec![TestSetEntry {
id: "test1".to_string(),
input: vec![1.0, 0.0, 0.0, 0.0],
expected_output_dim: None,
should_succeed: true,
similarity_threshold: 0.9,
}];
let summary = test.run(&test_vectors).expect("should run");
assert_eq!(summary.total, 1);
assert_eq!(summary.profile_results.len(), 1);
}
#[test]
fn test_standard_test_vectors() {
let vectors = AdmissibilityTest::standard_test_vectors(128);
assert!(!vectors.is_empty());
assert_eq!(vectors[0].id, "zero_vector");
assert_eq!(vectors[0].input.len(), 128);
assert!(vectors[0].input.iter().all(|x| *x == 0.0));
}
}