uqa-storage 0.5.3

Document store, inverted index, IVF/HNSW vectors, B-tree, R*Tree, catalog
Documentation
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

//! Greedy hierarchy traversal and bounded layer search.

mod query;
pub(super) use query::Query;

use std::cmp::Ordering;

use super::prepare::{check, Control};
use super::queue::{Candidates, Queue};
use super::types::{HNSWIndex, NodeId};
use super::visited::Visited;
use crate::{read_control::StorageReadControl, StorageBackendResult};

#[derive(Debug, Clone, Copy)]
pub(super) struct Candidate {
    pub(super) distance: f32,
    pub(super) node_id: NodeId,
}

impl PartialEq for Candidate {
    fn eq(&self, other: &Self) -> bool {
        self.distance.to_bits() == other.distance.to_bits() && self.node_id == other.node_id
    }
}

impl Eq for Candidate {}

impl PartialOrd for Candidate {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Candidate {
    fn cmp(&self, other: &Self) -> Ordering {
        self.distance
            .total_cmp(&other.distance)
            .then_with(|| self.node_id.cmp(&other.node_id))
    }
}

impl HNSWIndex {
    pub(super) fn greedy_search_layer(
        &self,
        query: &mut Query<'_>,
        entry: NodeId,
        layer: usize,
        control: Control<'_>,
    ) -> StorageBackendResult<NodeId> {
        check(control)?;
        let Some(mut best) = query.score(self, entry)? else {
            return Ok(entry);
        };
        loop {
            check(control)?;
            let mut improved = false;
            let Some(node) = self.node(best.node_id)? else {
                return Ok(best.node_id);
            };
            let Some(neighbors) = node.neighbors.get(layer) else {
                return Ok(best.node_id);
            };
            for &neighbor_id in neighbors {
                check(control)?;
                let Some(candidate) = query.score(self, neighbor_id)? else {
                    continue;
                };
                if candidate < best {
                    best = candidate;
                    improved = true;
                }
            }
            if !improved {
                return Ok(best.node_id);
            }
        }
    }

    pub(super) fn search_layer(
        &self,
        query: &mut Query<'_>,
        entries: &[NodeId],
        ef: usize,
        layer: usize,
        control: Control<'_>,
        workspace: Control<'_>,
    ) -> StorageBackendResult<Candidates> {
        check(control)?;
        let fallback = StorageReadControl::new(&self.memory, &uqa_core::CancellationToken::new());
        let workspace = workspace.unwrap_or(&fallback);
        let ef = ef.max(1);
        let mut visited = Visited::new(
            self.nodes.next_key(None, &self.memory)?.map(|id| id as u64),
            self.next_node_id.checked_sub(1),
            ef.saturating_mul(2).min(self.nodes.len()),
            Some(workspace),
        )?;
        let mut candidates = Queue::<true>::new(workspace);
        let mut nearest = Queue::<false>::new(workspace);
        for entry in entries {
            check(control)?;
            let Some(candidate) = query.score(self, *entry)? else {
                continue;
            };
            if !visited.insert(*entry)? {
                continue;
            }
            candidates.push(candidate)?;
            nearest.push(candidate)?;
        }
        while let Some(current) = candidates.pop()? {
            check(control)?;
            if nearest.len() >= ef && nearest.peek()?.is_some_and(|worst| current > worst) {
                break;
            }
            let Some(node) = self.node(current.node_id)? else {
                continue;
            };
            let Some(neighbors) = node.neighbors.get(layer) else {
                continue;
            };
            for &neighbor_id in neighbors {
                check(control)?;
                if !visited.insert(neighbor_id)? {
                    continue;
                }
                let Some(candidate) = query.score(self, neighbor_id)? else {
                    continue;
                };
                if nearest.len() < ef || nearest.peek()?.is_some_and(|worst| candidate < worst) {
                    candidates.push(candidate)?;
                    nearest.push(candidate)?;
                    if nearest.len() > ef {
                        nearest.pop()?;
                    }
                }
            }
        }
        check(control)?;
        Ok(nearest.into_sorted())
    }

    pub(super) fn query_candidates(
        &self,
        query: &[f32],
        ef: usize,
        control: Control<'_>,
    ) -> StorageBackendResult<Candidates> {
        check(control)?;
        let Some(mut entry) = self.entry_point else {
            let fallback =
                StorageReadControl::new(&self.memory, &uqa_core::CancellationToken::new());
            return Ok(Queue::<false>::new(control.unwrap_or(&fallback)).into_sorted());
        };
        let mut query = Query::new(query, &self.memory);
        for layer in (1..=self.max_level).rev() {
            entry = self.greedy_search_layer(&mut query, entry, layer, control)?;
        }
        self.search_layer(&mut query, &[entry], ef, 0, control, control)
    }
}