urna_runtime/
space_search.rs1use crate::dtype::DType;
11use crate::error::RuntimeError;
12use crate::mmap_file::MmapUrnaFile;
13use crate::rerank::RerankSource;
14use crate::search::sort_scores_desc;
15use crate::{RerankSourceKind, SearchExplain, SearchResult};
16
17impl MmapUrnaFile {
18 pub fn search_space(
25 &self,
26 name: &str,
27 query: &[f32],
28 k: i32,
29 expected_model_hash: Option<&str>,
30 ) -> Result<SearchResult, RuntimeError> {
31 let t0 = std::time::Instant::now();
32 let (space, band) = self
33 .space(name)
34 .ok_or_else(|| RuntimeError::SpaceNotFound(name.to_string()))?;
35 let entry = &space.entry;
36
37 if let Some(expected) = expected_model_hash {
38 if expected != entry.model_hash {
39 return Err(RuntimeError::SpaceModelMismatch {
40 space: name.to_string(),
41 expected: expected.to_string(),
42 actual: entry.model_hash.clone(),
43 });
44 }
45 }
46 if k <= 0 {
47 return Err(RuntimeError::InvalidK(k));
48 }
49 if query.is_empty() {
50 return Err(RuntimeError::EmptyQuery);
51 }
52 if query.len() != entry.dim as usize {
53 return Err(RuntimeError::DimensionMismatch {
54 expected: entry.dim as usize,
55 got: query.len(),
56 });
57 }
58 for &v in query {
59 if v.is_nan() || v.is_infinite() {
60 return Err(RuntimeError::InvalidQueryValue);
61 }
62 }
63 let mut qnorm = query.to_vec();
64 let norm: f32 = qnorm.iter().map(|x| x * x).sum::<f32>().sqrt();
65 if norm == 0.0 {
66 return Err(RuntimeError::ZeroNormQuery);
67 }
68 for x in &mut qnorm {
69 *x /= norm;
70 }
71
72 let dtype = DType::from_str(entry.dtype_str())?;
73 let n = entry.n_vectors as usize;
74 let mut src = RerankSource::new(dtype, band, n, entry.dim as usize)?;
75 let mut scores: Vec<(usize, f32)> = Vec::with_capacity(n);
76 for i in 0..n {
77 scores.push((i, src.score(&qnorm, i)));
78 }
79 sort_scores_desc(&mut scores);
80 let k_usize = k as usize;
81 let truncated = k_usize < n;
82 let top = &scores[..k_usize.min(n)];
83 let hits = self.materialize_hits(top, "space", false);
84 Ok(SearchResult {
85 hits: hits.clone(),
86 query_time_ms: t0.elapsed().as_secs_f64() * 1000.0,
87 index_type: "space",
88 recall: 1.0,
89 truncated,
90 k_requested: k,
91 k_returned: hits.len(),
92 explain: SearchExplain::exact(n, RerankSourceKind::from_dtype(dtype)),
93 })
94 }
95}