use super::distance::DistanceEngine;
use super::graph::NO_ENTRY_POINT;
use super::layer::NodeId;
use super::quantized_precision::{QuantizedPrecisionHnsw, TraversalCodec};
use rustc_hash::FxHashSet;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::sync::atomic::Ordering;
#[derive(Clone, Copy)]
struct DistNode<T> {
dist: T,
node: NodeId,
}
impl<T: Ord> PartialEq for DistNode<T> {
fn eq(&self, other: &Self) -> bool {
self.dist == other.dist
}
}
impl<T: Ord> Eq for DistNode<T> {}
impl<T: Ord> PartialOrd for DistNode<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: Ord> Ord for DistNode<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.dist.cmp(&other.dist)
}
}
impl<D: DistanceEngine, C: TraversalCodec> QuantizedPrecisionHnsw<D, C> {
pub(super) fn search_layer_quantized(
&self,
prepared: &C::Prepared,
k: usize,
ef_search: usize,
quantizer: &C::Quantizer,
store: &C::Store,
) -> Vec<(NodeId, C::Dist)> {
let ep = self.inner.entry_point.load(Ordering::Acquire);
if ep == NO_ENTRY_POINT {
return Vec::new();
}
let max_layer = self.inner.max_layer.load(Ordering::Relaxed);
let mut current_ep = ep;
for layer_idx in (1..=max_layer).rev() {
current_ep =
self.greedy_search_quantized(prepared, current_ep, layer_idx, quantizer, store);
}
self.expand_layer0_quantized(prepared, current_ep, ef_search.max(k), k, quantizer, store)
}
fn greedy_search_quantized(
&self,
prepared: &C::Prepared,
entry: NodeId,
layer: usize,
quantizer: &C::Quantizer,
store: &C::Store,
) -> NodeId {
let mut current = entry;
let mut current_dist =
C::distance(quantizer, store, prepared, current).unwrap_or(C::MAX_DIST);
loop {
let mut improved = false;
let layers = self.inner.layers.read();
let _ = layers[layer].with_neighbors(current, |neighbors| {
for &neighbor in neighbors {
if let Some(dist) = C::distance(quantizer, store, prepared, neighbor) {
if dist < current_dist {
current = neighbor;
current_dist = dist;
improved = true;
}
}
}
});
drop(layers);
if !improved {
break;
}
}
current
}
fn expand_layer0_quantized(
&self,
prepared: &C::Prepared,
ep: NodeId,
ef: usize,
k: usize,
quantizer: &C::Quantizer,
store: &C::Store,
) -> Vec<(NodeId, C::Dist)> {
let mut visited: FxHashSet<NodeId> = FxHashSet::default();
let mut candidates: BinaryHeap<Reverse<DistNode<C::Dist>>> = BinaryHeap::new();
let mut results: BinaryHeap<DistNode<C::Dist>> = BinaryHeap::new();
Self::init_quantized_search(
prepared,
ep,
quantizer,
store,
&mut visited,
&mut candidates,
&mut results,
);
while let Some(Reverse(closest)) = candidates.pop() {
let furthest_dist = results.peek().map_or(C::MAX_DIST, |r| r.dist);
if closest.dist > furthest_dist && results.len() >= ef {
break;
}
let layers = self.inner.layers.read();
let _ = layers[0].with_neighbors(closest.node, |neighbors| {
Self::process_quantized_neighbors(
prepared,
neighbors,
quantizer,
store,
ef,
&mut visited,
&mut candidates,
&mut results,
);
});
}
let mut result_vec: Vec<(NodeId, C::Dist)> =
results.into_iter().map(|dn| (dn.node, dn.dist)).collect();
result_vec.sort_unstable_by(|a, b| a.1.cmp(&b.1));
result_vec.truncate(k);
result_vec
}
fn init_quantized_search(
prepared: &C::Prepared,
ep: NodeId,
quantizer: &C::Quantizer,
store: &C::Store,
visited: &mut FxHashSet<NodeId>,
candidates: &mut BinaryHeap<Reverse<DistNode<C::Dist>>>,
results: &mut BinaryHeap<DistNode<C::Dist>>,
) {
if let Some(dist) = C::distance(quantizer, store, prepared, ep) {
let dn = DistNode { dist, node: ep };
candidates.push(Reverse(dn));
results.push(dn);
visited.insert(ep);
}
}
#[allow(clippy::too_many_arguments)]
fn process_quantized_neighbors(
prepared: &C::Prepared,
neighbors: &[NodeId],
quantizer: &C::Quantizer,
store: &C::Store,
ef: usize,
visited: &mut FxHashSet<NodeId>,
candidates: &mut BinaryHeap<Reverse<DistNode<C::Dist>>>,
results: &mut BinaryHeap<DistNode<C::Dist>>,
) {
for &neighbor in neighbors {
if !visited.insert(neighbor) {
continue;
}
let Some(dist) = C::distance(quantizer, store, prepared, neighbor) else {
continue;
};
let furthest = results.peek().map_or(C::MAX_DIST, |r| r.dist);
if dist < furthest || results.len() < ef {
let dn = DistNode {
dist,
node: neighbor,
};
candidates.push(Reverse(dn));
results.push(dn);
if results.len() > ef {
results.pop();
}
}
}
}
}