use super::super::distance::DistanceEngine;
use super::super::layer::{Layer, NodeId};
use super::{NativeHnsw, NO_ENTRY_POINT};
use crate::perf_optimizations::ContiguousVectors;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
const MAX_ANCHOR_DESCENT: usize = 32;
enum Placement {
Linked,
Descend(NodeId),
}
impl<D: DistanceEngine> NativeHnsw<D> {
pub(in crate::index::hnsw::native) fn anchor_node(
&self,
node: NodeId,
vectors: &ContiguousVectors,
layers: &[Layer],
) -> bool {
let base = &layers[0];
debug_assert!(base.records_anchors(), "layer 0 must record anchors");
let entry_point = self.entry_point.load(Ordering::Acquire);
if node == entry_point || base.anchor_of(node).is_some() {
return true;
}
let parent = base
.with_neighbors(node, |list| {
list.iter()
.copied()
.find(|&n| n == entry_point || base.anchor_of(n).is_some())
})
.flatten();
parent.is_some_and(|parent| self.link_protected(parent, node, vectors, base))
}
pub(in crate::index::hnsw::native) fn link_protected(
&self,
parent: NodeId,
child: NodeId,
vectors: &ContiguousVectors,
base: &Layer,
) -> bool {
let mut owner = parent;
for depth in 0..=MAX_ANCHOR_DESCENT {
let placement = base.with_neighbors_mut(owner, |list| {
if !list.contains(&child) {
if list.len() >= self.max_connections_0 {
match self.farthest_unprotected(owner, list, vectors, base) {
Some(evict) => {
list.swap_remove(evict);
}
None if depth < MAX_ANCHOR_DESCENT => {
if let Some(next) =
Self::spread_protected_child(owner, list, child, base)
{
return Placement::Descend(next);
}
}
None => {}
}
}
list.push(child);
}
base.set_anchor(child, owner);
Placement::Linked
});
match placement {
Some(Placement::Linked) => return true,
Some(Placement::Descend(next)) => owner = next,
None => return false,
}
}
false
}
fn spread_protected_child(
owner: NodeId,
list: &[NodeId],
child: NodeId,
base: &Layer,
) -> Option<NodeId> {
let mix = |n: NodeId| {
(n as u64 ^ (child as u64).rotate_left(32)).wrapping_mul(0x9E37_79B9_7F4A_7C15)
};
list.iter()
.copied()
.filter(|&n| base.anchor_of(n) == Some(owner))
.min_by_key(|&n| mix(n))
}
fn farthest_unprotected(
&self,
owner: NodeId,
list: &[NodeId],
vectors: &ContiguousVectors,
base: &Layer,
) -> Option<usize> {
let owner_vec = vectors.get(owner)?;
list.iter()
.enumerate()
.filter(|&(_, &n)| base.anchor_of(n) != Some(owner))
.filter_map(|(i, &n)| {
vectors
.get(n)
.map(|v| (i, self.distance.distance(owner_vec, v)))
})
.max_by(|a, b| a.1.total_cmp(&b.1))
.map(|(i, _)| i)
}
pub(in crate::index::hnsw::native) fn anchor_pending(&self, mut pending: Vec<NodeId>) {
if pending.is_empty() {
return;
}
self.with_vectors_and_layers_read(|vectors, layers| {
loop {
let before = pending.len();
pending.retain(|&node| !self.anchor_node(node, vectors, layers));
if pending.is_empty() || pending.len() == before {
break;
}
}
for node in pending {
self.link_from_entry_point(node, vectors, layers);
}
});
}
pub(in crate::index::hnsw::native) fn anchor_one(&self, node: NodeId) {
self.with_vectors_and_layers_read(|vectors, layers| {
if !self.anchor_node(node, vectors, layers) {
self.link_from_entry_point(node, vectors, layers);
}
});
}
fn link_from_entry_point(&self, node: NodeId, vectors: &ContiguousVectors, layers: &[Layer]) {
let entry_point = self.entry_point.load(Ordering::Acquire);
if entry_point != NO_ENTRY_POINT && node != entry_point {
self.link_protected(entry_point, node, vectors, &layers[0]);
}
}
pub(in crate::index::hnsw::native) fn reparent_entry_point(
&self,
previous: NodeId,
new_entry: NodeId,
) {
if previous == NO_ENTRY_POINT || previous == new_entry {
return;
}
self.with_vectors_and_layers_read(|vectors, layers| {
layers[0].clear_anchor(new_entry);
self.link_protected(new_entry, previous, vectors, &layers[0]);
});
}
pub(in crate::index::hnsw::native) fn rebuild_anchors(&self) {
let layers = self.layers.read();
let Some(base) = layers.first() else {
return;
};
let reached = self.walk_base_from_entry_point(base, |parent, node| {
base.set_anchor(node, parent);
});
let lost: Vec<NodeId> = reached
.iter()
.enumerate()
.filter(|&(node, &seen)| {
!seen && base.with_neighbors(node, |list| !list.is_empty()) == Some(true)
})
.map(|(node, _)| node)
.collect();
drop(layers);
self.anchor_pending(lost);
}
fn walk_base_from_entry_point(
&self,
base: &Layer,
mut reached: impl FnMut(NodeId, NodeId),
) -> Vec<bool> {
let entry_point = self.entry_point.load(Ordering::Acquire);
let capacity = base.neighbors.len();
let mut seen = vec![false; capacity];
if entry_point >= capacity {
return seen;
}
seen[entry_point] = true;
let mut queue = VecDeque::from([entry_point]);
while let Some(node) = queue.pop_front() {
let _ = base.with_neighbors(node, |list| {
for &next in list {
if next < capacity && !seen[next] {
seen[next] = true;
reached(node, next);
queue.push_back(next);
}
}
});
}
seen
}
#[cfg(test)]
#[must_use]
fn unreachable_among(&self, nodes: impl IntoIterator<Item = NodeId>) -> Vec<NodeId> {
let layers = self.layers.read();
let Some(base) = layers.first() else {
return nodes.into_iter().collect();
};
let reached = self.walk_base_from_entry_point(base, |_, _| {});
drop(layers);
nodes
.into_iter()
.filter(|&node| !reached.get(node).copied().unwrap_or(false))
.collect()
}
}
#[cfg(test)]
#[path = "anchors_tests.rs"]
mod anchors_tests;