1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::profile::CodecProfileV1;
7
8#[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#[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}