use crate::index::DistanceMetric;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HnswConfig {
pub m: usize,
pub ef_construction: usize,
pub ef_search: usize,
pub distance_metric: DistanceMetric,
pub is_compact: bool,
pub is_recompute: bool,
#[serde(default)]
pub seed: Option<u64>,
}
impl Default for HnswConfig {
fn default() -> Self {
Self {
m: 32,
ef_construction: 200,
ef_search: 64,
distance_metric: DistanceMetric::Mips,
is_compact: true,
is_recompute: true,
seed: None,
}
}
}
pub struct HnswGraph {
pub ntotal: usize,
pub dimensions: usize,
pub entry_point: i32,
pub max_level: i32,
pub levels: Vec<i32>,
pub assign_probas: Vec<f64>,
pub cum_nneighbor_per_level: Vec<i32>,
pub config: HnswConfig,
pub metric_type: i32,
pub metric_arg: f32,
pub storage: GraphStorage,
pub vector_storage: VectorStorage,
}
pub enum GraphStorage {
Standard {
offsets: Vec<u64>,
neighbors: Vec<i32>,
},
Compact {
level_ptr: Vec<u64>,
node_offsets: Vec<u64>,
neighbors: Vec<i32>,
},
}
pub enum VectorStorage {
Null,
Raw { fourcc: u32, data: Vec<u8> },
}
pub const FOURCC_HNSW_FLAT: u32 = u32::from_le_bytes(*b"IHNf");
pub const FOURCC_NULL: u32 = u32::from_le_bytes(*b"null");
impl HnswGraph {
#[inline]
pub fn neighbors_at_level(&self, level: usize) -> usize {
if level == 0 {
if self.cum_nneighbor_per_level.is_empty() {
return 0;
}
self.cum_nneighbor_per_level[0] as usize
} else if level < self.cum_nneighbor_per_level.len() {
(self.cum_nneighbor_per_level[level] - self.cum_nneighbor_per_level[level - 1]) as usize
} else {
0
}
}
#[inline]
pub fn get_neighbors(&self, node: usize, level: usize) -> &[i32] {
match &self.storage {
GraphStorage::Standard { offsets, neighbors } => {
let offset = offsets[node] as usize;
let cum_begin = if level == 0 {
0
} else {
self.cum_nneighbor_per_level[level - 1] as usize
};
let cum_end = if level < self.cum_nneighbor_per_level.len() {
self.cum_nneighbor_per_level[level] as usize
} else {
return &[];
};
let begin = offset + cum_begin;
let end = offset + cum_end;
if end <= neighbors.len() {
&neighbors[begin..end]
} else {
&[]
}
}
GraphStorage::Compact {
level_ptr,
node_offsets,
neighbors,
} => {
let base = node_offsets[node] as usize;
let ptr_idx = base + level;
if ptr_idx + 1 >= level_ptr.len() {
return &[];
}
let begin = level_ptr[ptr_idx] as usize;
let end = level_ptr[ptr_idx + 1] as usize;
if end <= neighbors.len() {
&neighbors[begin..end]
} else {
&[]
}
}
}
}
pub fn is_compact(&self) -> bool {
matches!(self.storage, GraphStorage::Compact { .. })
}
pub fn is_pruned(&self) -> bool {
matches!(self.vector_storage, VectorStorage::Null)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hnsw_config_default() {
let config = HnswConfig::default();
assert_eq!(config.m, 32);
assert_eq!(config.ef_construction, 200);
assert_eq!(config.ef_search, 64);
assert_eq!(config.distance_metric, DistanceMetric::Mips);
}
#[test]
fn test_fourcc_constants() {
assert_eq!(FOURCC_HNSW_FLAT, u32::from_le_bytes(*b"IHNf"));
assert_eq!(FOURCC_NULL, u32::from_le_bytes(*b"null"));
}
}