Search: find top-k nearest neighbors.
(&self, query: &[f32], top_k: usize)
| 128 | |
| 129 | /// Search: find top-k nearest neighbors. |
| 130 | pub fn search(&self, query: &[f32], top_k: usize) -> Vec<SearchResult> { |
| 131 | assert_eq!(query.len(), self.dim); |
| 132 | if self.centroids.is_empty() || self.count == 0 { |
| 133 | return Vec::new(); |
| 134 | } |
| 135 | |
| 136 | let pq = match &self.pq { |
| 137 | Some(p) => p, |
| 138 | None => return Vec::new(), |
| 139 | }; |
| 140 | |
| 141 | let nprobe = self.params.nprobe.min(self.centroids.len()); |
| 142 | let mut centroid_dists: Vec<(usize, f32)> = self |
| 143 | .centroids |
| 144 | .iter() |
| 145 | .enumerate() |
| 146 | .map(|(i, c)| (i, distance(query, c, self.params.metric))) |
| 147 | .collect(); |
| 148 | centroid_dists.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)); |
| 149 | |
| 150 | let mut candidates: Vec<SearchResult> = Vec::new(); |
| 151 | |
| 152 | for &(cell_idx, _) in centroid_dists.iter().take(nprobe) { |
| 153 | let residual_query: Vec<f32> = query |
| 154 | .iter() |
| 155 | .zip(&self.centroids[cell_idx]) |
| 156 | .map(|(q, c)| q - c) |
| 157 | .collect(); |
| 158 | let table = match pq.build_distance_table(&residual_query) { |
| 159 | Ok(t) => t, |
| 160 | Err(e) => { |
| 161 | tracing::warn!(error = %e, "IVF PQ build_distance_table budget exhausted; skipping cell"); |
| 162 | continue; |
| 163 | } |
| 164 | }; |
| 165 | |
| 166 | for (id, code) in &self.cells[cell_idx] { |
| 167 | let dist = pq.asymmetric_distance(&table, code); |
| 168 | candidates.push(SearchResult { |
| 169 | id: *id, |
| 170 | distance: dist, |
| 171 | }); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | if candidates.len() > top_k { |
| 176 | candidates.select_nth_unstable_by(top_k, |a, b| { |
| 177 | a.distance |
| 178 | .partial_cmp(&b.distance) |
| 179 | .unwrap_or(std::cmp::Ordering::Equal) |
| 180 | }); |
| 181 | candidates.truncate(top_k); |
| 182 | } |
| 183 | candidates.sort_by(|a, b| { |
| 184 | a.distance |
| 185 | .partial_cmp(&b.distance) |
| 186 | .unwrap_or(std::cmp::Ordering::Equal) |
| 187 | }); |