Skip to main content

turbo_quant/
eval.rs

1//! Evaluation and benchmark receipt data structures.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::profile::CodecProfileV1;
7
8/// Summary of an exact-vs-compressed evaluation run.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
10pub struct CompressionEvalV1 {
11    pub schema: String,
12    pub recall_at_k: f32,
13    pub mean_absolute_error: f32,
14    pub queries: usize,
15    pub db_size: usize,
16    pub top_k: usize,
17}
18
19/// Machine-readable benchmark receipt.
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
21pub struct BenchmarkReceiptV1 {
22    pub schema: String,
23    pub profile: CodecProfileV1,
24    pub corpus: BenchmarkCorpus,
25    pub metrics: CompressionEvalV1,
26    pub comparisons: Vec<BenchmarkComparisonV1>,
27    pub commands: Vec<String>,
28    pub warnings: Vec<String>,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
32pub struct BenchmarkComparisonV1 {
33    pub name: String,
34    pub profile: CodecProfileV1,
35    pub metrics: CompressionEvalV1,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
39pub struct BenchmarkCorpus {
40    pub dim: usize,
41    pub db_size: usize,
42    pub queries: usize,
43    pub seed: u64,
44    pub generator: String,
45}
46
47pub fn recall_at_k(exact: &[Vec<usize>], estimated: &[Vec<usize>], k: usize) -> f32 {
48    if exact.is_empty() || exact.len() != estimated.len() || k == 0 {
49        return 0.0;
50    }
51    let mut hits = 0usize;
52    let mut total = 0usize;
53    for (exact_row, estimated_row) in exact.iter().zip(estimated.iter()) {
54        let exact_top = &exact_row[..exact_row.len().min(k)];
55        let estimated_top = &estimated_row[..estimated_row.len().min(k)];
56        total += exact_top.len();
57        hits += estimated_top
58            .iter()
59            .filter(|candidate| exact_top.contains(candidate))
60            .count();
61    }
62    if total == 0 {
63        0.0
64    } else {
65        hits as f32 / total as f32
66    }
67}