use crate::hnsw::repair::{validate_connectivity, RepairConfig, RepairStats};
use crate::RetrieveError;
use std::collections::{BinaryHeap, HashSet};
#[derive(Clone, Debug)]
pub struct InPlaceConfig {
pub max_degree: usize,
pub beam_width: usize,
pub alpha: f32,
pub max_in_neighbors: usize,
pub enable_back_edges: bool,
}
impl Default for InPlaceConfig {
fn default() -> Self {
Self {
max_degree: 32,
beam_width: 64,
alpha: 1.2,
max_in_neighbors: 64,
enable_back_edges: true,
}
}
}
struct InPlaceNode {
vector: Vec<f32>,
out_neighbors: Vec<u32>,
in_neighbors: Vec<u32>,
deleted: bool,
}
impl InPlaceNode {
fn new(vector: Vec<f32>) -> Self {
Self {
vector,
out_neighbors: Vec::new(),
in_neighbors: Vec::new(),
deleted: false,
}
}
fn is_deleted(&self) -> bool {
self.deleted
}
fn mark_deleted(&mut self) {
self.deleted = true;
}
}
pub struct InPlaceIndex {
config: InPlaceConfig,
dim: usize,
nodes: Vec<Option<InPlaceNode>>,
free_slots: Vec<u32>,
entry_point: u32,
active_count: u32,
}
impl InPlaceIndex {
pub fn new(dim: usize, config: InPlaceConfig) -> Self {
Self {
config,
dim,
nodes: Vec::new(),
free_slots: Vec::new(),
entry_point: u32::MAX,
active_count: 0,
}
}
pub fn insert(&mut self, vector: Vec<f32>) -> Result<u32, RetrieveError> {
if vector.len() != self.dim {
return Err(RetrieveError::DimensionMismatch {
query_dim: vector.len(),
doc_dim: self.dim,
});
}
let id = if let Some(slot) = self.free_slots.pop() {
self.nodes[slot as usize] = Some(InPlaceNode::new(vector.clone()));
slot
} else {
let id = self.nodes.len() as u32;
self.nodes.push(Some(InPlaceNode::new(vector.clone())));
id
};
self.active_count += 1;
if self.entry_point == u32::MAX {
self.entry_point = id;
return Ok(id);
}
let candidates = self.search_for_candidates(&vector);
let neighbors = self.select_neighbors(&vector, &candidates);
if let Some(ref mut node) = self.nodes[id as usize] {
node.out_neighbors = neighbors.clone();
}
let mut back_edge_candidates: Vec<(u32, bool)> = Vec::new();
for &neighbor_id in &neighbors {
let should_add_back_edge = if self.config.enable_back_edges {
if let Some(Some(neighbor)) = self.nodes.get(neighbor_id as usize) {
if neighbor.out_neighbors.len() < self.config.max_degree {
let dist_to_new = euclidean_distance(&neighbor.vector, &vector);
let worst_neighbor_dist = neighbor
.out_neighbors
.iter()
.filter_map(|&n| {
self.nodes
.get(n as usize)
.and_then(|opt| opt.as_ref())
.map(|node| euclidean_distance(&neighbor.vector, &node.vector))
})
.fold(0.0f32, f32::max);
dist_to_new < worst_neighbor_dist
|| neighbor.out_neighbors.len() < self.config.max_degree / 2
} else {
false
}
} else {
false
}
} else {
false
};
back_edge_candidates.push((neighbor_id, should_add_back_edge));
}
for (neighbor_id, should_add_back_edge) in back_edge_candidates {
if let Some(ref mut neighbor) = self.nodes[neighbor_id as usize] {
if neighbor.in_neighbors.len() < self.config.max_in_neighbors {
neighbor.in_neighbors.push(id);
}
if should_add_back_edge {
neighbor.out_neighbors.push(id);
}
}
if should_add_back_edge {
if let Some(ref mut new_node) = self.nodes[id as usize] {
if new_node.in_neighbors.len() < self.config.max_in_neighbors {
new_node.in_neighbors.push(neighbor_id);
}
}
}
}
Ok(id)
}
pub fn delete(&mut self, id: u32) -> Result<(), RetrieveError> {
if id as usize >= self.nodes.len() {
return Err(RetrieveError::OutOfBounds(id as usize));
}
let node = self.nodes[id as usize]
.as_mut()
.ok_or(RetrieveError::OutOfBounds(id as usize))?;
if node.is_deleted() {
return Err(RetrieveError::OutOfBounds(id as usize));
}
let in_neighbors: Vec<u32> = node.in_neighbors.clone();
let out_neighbors: Vec<u32> = node.out_neighbors.clone();
node.mark_deleted();
self.active_count -= 1;
if self.entry_point == id {
let new_entry = out_neighbors
.iter()
.chain(in_neighbors.iter())
.find(|&&n| {
self.nodes
.get(n as usize)
.and_then(|opt| opt.as_ref())
.map(|node| !node.is_deleted())
.unwrap_or(false)
})
.copied()
.unwrap_or(u32::MAX);
self.entry_point = new_entry;
}
let mut repair_info: Vec<(u32, Vec<u32>)> = Vec::new();
for in_neighbor_id in &in_neighbors {
if let Some(ref mut in_neighbor) = self.nodes[*in_neighbor_id as usize] {
if in_neighbor.is_deleted() {
continue;
}
in_neighbor.out_neighbors.retain(|&n| n != id);
repair_info.push((*in_neighbor_id, in_neighbor.out_neighbors.clone()));
}
}
let mut replacements: Vec<(u32, Option<u32>)> = Vec::new();
for (in_neighbor_id, current_neighbors) in &repair_info {
let replacement = self.find_replacement_neighbor(*in_neighbor_id, current_neighbors);
replacements.push((*in_neighbor_id, replacement));
}
for (in_neighbor_id, replacement) in replacements {
if let Some(new_neighbor) = replacement {
if let Some(ref mut in_neighbor) = self.nodes[in_neighbor_id as usize] {
if !in_neighbor.out_neighbors.contains(&new_neighbor) {
in_neighbor.out_neighbors.push(new_neighbor);
}
}
if let Some(ref mut new_nb) = self.nodes[new_neighbor as usize] {
if new_nb.in_neighbors.len() < self.config.max_in_neighbors {
new_nb.in_neighbors.push(in_neighbor_id);
}
}
}
}
for out_neighbor_id in out_neighbors {
if let Some(ref mut out_neighbor) = self.nodes[out_neighbor_id as usize] {
out_neighbor.in_neighbors.retain(|&n| n != id);
}
}
self.nodes[id as usize] = None;
self.free_slots.push(id);
Ok(())
}
pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<(u32, f32)>, RetrieveError> {
if query.len() != self.dim {
return Err(RetrieveError::DimensionMismatch {
query_dim: query.len(),
doc_dim: self.dim,
});
}
let entry = self.entry_point;
if entry == u32::MAX {
return Ok(Vec::new());
}
let mut visited: HashSet<u32> = HashSet::new();
let mut candidates: BinaryHeap<Candidate> = BinaryHeap::new();
let mut results: BinaryHeap<Candidate> = BinaryHeap::new();
let entry_dist = self.distance_to_vector(entry, query);
candidates.push(Candidate {
id: entry,
distance: -entry_dist,
}); results.push(Candidate {
id: entry,
distance: entry_dist,
});
visited.insert(entry);
while let Some(Candidate {
id: current,
distance: neg_dist,
}) = candidates.pop()
{
let current_dist = -neg_dist;
let worst = results.peek().map(|c| c.distance).unwrap_or(f32::INFINITY);
if current_dist > worst && results.len() >= k {
break;
}
if let Some(Some(node)) = self.nodes.get(current as usize) {
if node.is_deleted() {
continue;
}
for &neighbor in &node.out_neighbors {
if visited.insert(neighbor) {
if let Some(Some(nb_node)) = self.nodes.get(neighbor as usize) {
if nb_node.is_deleted() {
continue;
}
let dist = self.distance_to_vector(neighbor, query);
if results.len() < k || dist < worst {
results.push(Candidate {
id: neighbor,
distance: dist,
});
while results.len() > k {
results.pop();
}
}
if candidates.len() < self.config.beam_width {
candidates.push(Candidate {
id: neighbor,
distance: -dist,
});
}
}
}
}
}
}
let mut result_vec: Vec<(u32, f32)> =
results.into_iter().map(|c| (c.id, c.distance)).collect();
result_vec.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
result_vec.truncate(k);
Ok(result_vec)
}
fn search_for_candidates(&self, query: &[f32]) -> Vec<(u32, f32)> {
let entry = self.entry_point;
if entry == u32::MAX {
return Vec::new();
}
let mut visited: HashSet<u32> = HashSet::new();
let mut candidates: BinaryHeap<Candidate> = BinaryHeap::new();
let mut results: Vec<(u32, f32)> = Vec::new();
let entry_dist = self.distance_to_vector(entry, query);
candidates.push(Candidate {
id: entry,
distance: -entry_dist,
});
visited.insert(entry);
while let Some(Candidate { id: current, .. }) = candidates.pop() {
if let Some(Some(node)) = self.nodes.get(current as usize) {
if node.is_deleted() {
continue;
}
let dist = self.distance_to_vector(current, query);
results.push((current, dist));
for &neighbor in &node.out_neighbors {
if visited.insert(neighbor) {
if let Some(Some(nb)) = self.nodes.get(neighbor as usize) {
if !nb.is_deleted() {
let d = self.distance_to_vector(neighbor, query);
candidates.push(Candidate {
id: neighbor,
distance: -d,
});
}
}
}
}
}
if visited.len() >= self.config.beam_width * 2 {
break;
}
}
results.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
results.truncate(self.config.beam_width);
results
}
fn select_neighbors(&self, _query: &[f32], candidates: &[(u32, f32)]) -> Vec<u32> {
let mut selected = Vec::new();
for &(candidate, dist) in candidates {
if selected.len() >= self.config.max_degree {
break;
}
let is_diverse = selected.iter().all(|&s| {
let s_dist = self.distance(candidate, s);
s_dist > dist * self.config.alpha || s_dist > dist
});
if is_diverse {
selected.push(candidate);
}
}
selected
}
fn find_replacement_neighbor(&self, node_id: u32, current_neighbors: &[u32]) -> Option<u32> {
let node = self.nodes.get(node_id as usize)?.as_ref()?;
let current_set: HashSet<u32> = current_neighbors.iter().cloned().collect();
let mut candidates: Vec<(u32, f32)> = Vec::new();
for &neighbor in current_neighbors {
if let Some(Some(nb_node)) = self.nodes.get(neighbor as usize) {
if nb_node.is_deleted() {
continue;
}
for &two_hop in &nb_node.out_neighbors {
if two_hop != node_id
&& !current_set.contains(&two_hop)
&& !candidates.iter().any(|(id, _)| *id == two_hop)
{
if let Some(Some(th_node)) = self.nodes.get(two_hop as usize) {
if !th_node.is_deleted() {
let dist = euclidean_distance(&node.vector, &th_node.vector);
candidates.push((two_hop, dist));
}
}
}
}
}
}
candidates.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
candidates.first().map(|(id, _)| *id)
}
fn distance(&self, a: u32, b: u32) -> f32 {
match (&self.nodes.get(a as usize), &self.nodes.get(b as usize)) {
(Some(Some(na)), Some(Some(nb))) => euclidean_distance(&na.vector, &nb.vector),
_ => f32::INFINITY,
}
}
fn distance_to_vector(&self, id: u32, query: &[f32]) -> f32 {
match self.nodes.get(id as usize) {
Some(Some(node)) if !node.is_deleted() => euclidean_distance(&node.vector, query),
_ => f32::INFINITY,
}
}
pub fn len(&self) -> usize {
self.active_count as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn stats(&self) -> InPlaceStats {
let mut total_out_degree = 0usize;
let mut total_in_degree = 0usize;
let mut active = 0usize;
for node in self.nodes.iter().flatten() {
if !node.is_deleted() {
active += 1;
total_out_degree += node.out_neighbors.len();
total_in_degree += node.in_neighbors.len();
}
}
InPlaceStats {
active_nodes: active,
free_slots: self.free_slots.len(),
avg_out_degree: if active > 0 {
total_out_degree as f32 / active as f32
} else {
0.0
},
avg_in_degree: if active > 0 {
total_in_degree as f32 / active as f32
} else {
0.0
},
}
}
pub fn repair(&mut self) -> RepairStats {
self.repair_with_config(RepairConfig {
max_candidates: 64,
max_neighbors: self.config.max_degree,
bidirectional: self.config.enable_back_edges,
alpha: self.config.alpha,
})
}
pub fn repair_with_config(&mut self, config: RepairConfig) -> RepairStats {
let mut total_stats = RepairStats::default();
let deleted_set: std::collections::HashSet<u32> = self
.nodes
.iter()
.enumerate()
.filter(|(_, slot)| match slot {
None => true,
Some(n) => n.is_deleted(),
})
.map(|(i, _)| i as u32)
.collect();
if deleted_set.is_empty() {
return total_stats;
}
let mut nodes_needing_repair: Vec<u32> = Vec::new();
for (i, slot) in self.nodes.iter().enumerate() {
if let Some(node) = slot {
if !node.is_deleted() && node.out_neighbors.iter().any(|n| deleted_set.contains(n))
{
nodes_needing_repair.push(i as u32);
}
}
}
for &node_id in &nodes_needing_repair {
let node = match &self.nodes[node_id as usize] {
Some(n) if !n.is_deleted() => n,
_ => continue,
};
let current_valid: Vec<u32> = node
.out_neighbors
.iter()
.filter(|&&n| !deleted_set.contains(&n))
.copied()
.collect();
let removed_count = node.out_neighbors.len() - current_valid.len();
if removed_count == 0 {
continue;
}
total_stats.edges_removed += removed_count;
total_stats.nodes_processed += 1;
let nodes_ref = &self.nodes;
let get_neighbors = |id: u32| -> Vec<u32> {
match nodes_ref.get(id as usize) {
Some(Some(n)) if !n.is_deleted() => n.out_neighbors.clone(),
_ => Vec::new(),
}
};
let compute_distance = |a: u32, b: u32| -> f32 {
match (nodes_ref.get(a as usize), nodes_ref.get(b as usize)) {
(Some(Some(na)), Some(Some(nb))) if !na.is_deleted() && !nb.is_deleted() => {
crate::distance::l2_distance(&na.vector, &nb.vector)
}
_ => f32::INFINITY,
}
};
let mut visited: std::collections::HashSet<u32> =
current_valid.iter().copied().collect();
visited.insert(node_id);
visited.extend(deleted_set.iter().copied());
let mut candidates: Vec<(u32, f32)> = Vec::new();
for &n in ¤t_valid {
for two_hop in get_neighbors(n) {
if visited.insert(two_hop) {
let dist = compute_distance(node_id, two_hop);
if dist.is_finite() {
candidates.push((two_hop, dist));
}
}
}
}
candidates.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
let mut new_neighbors = current_valid;
for (candidate, _dist) in &candidates {
if new_neighbors.len() >= config.max_neighbors {
break;
}
new_neighbors.push(*candidate);
total_stats.edges_added += 1;
}
if let Some(ref mut node) = self.nodes[node_id as usize] {
node.out_neighbors = new_neighbors.clone();
}
if config.bidirectional {
for (candidate, _) in &candidates {
if let Some(ref mut cand_node) = self.nodes[*candidate as usize] {
if !cand_node.is_deleted()
&& !cand_node.out_neighbors.contains(&node_id)
&& cand_node.out_neighbors.len() < config.max_neighbors
{
cand_node.out_neighbors.push(node_id);
total_stats.bidirectional_edges += 1;
}
if cand_node.in_neighbors.len() < self.config.max_in_neighbors
&& !cand_node.in_neighbors.contains(&node_id)
{
cand_node.in_neighbors.push(node_id);
}
}
if let Some(ref mut node) = self.nodes[node_id as usize] {
if node.in_neighbors.len() < self.config.max_in_neighbors
&& !node.in_neighbors.contains(candidate)
{
node.in_neighbors.push(*candidate);
}
}
}
}
}
total_stats
}
pub fn validate_connectivity(&self) -> (usize, usize) {
if self.entry_point == u32::MAX {
return (0, 0);
}
let total = self.nodes.len();
validate_connectivity(
self.entry_point,
total,
|id| match self.nodes.get(id as usize) {
Some(Some(n)) if !n.is_deleted() => n.out_neighbors.clone(),
_ => Vec::new(),
},
|id| match self.nodes.get(id as usize) {
Some(Some(n)) => n.is_deleted(),
Some(None) | None => true,
},
)
}
}
#[derive(Clone, Debug)]
pub struct InPlaceStats {
pub active_nodes: usize,
pub free_slots: usize,
pub avg_out_degree: f32,
pub avg_in_degree: f32,
}
#[derive(Clone, Copy)]
struct Candidate {
id: u32,
distance: f32,
}
impl PartialEq for Candidate {
fn eq(&self, other: &Self) -> bool {
self.distance == other.distance
}
}
impl Eq for Candidate {}
impl PartialOrd for Candidate {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Candidate {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.distance.total_cmp(&other.distance)
}
}
use crate::distance::l2_distance as euclidean_distance;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_inplace_insert_search() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for i in 0..20 {
let v = vec![i as f32, (i * 2) as f32, 0.0, 0.0];
index.insert(v).unwrap();
}
assert_eq!(index.len(), 20);
let results = index.search(&[5.0, 10.0, 0.0, 0.0], 5).unwrap();
assert!(!results.is_empty());
assert!(results.len() <= 5);
}
#[test]
fn test_inplace_delete() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for i in 0..10 {
index.insert(vec![i as f32; 4]).unwrap();
}
assert_eq!(index.len(), 10);
index.delete(3).unwrap();
index.delete(5).unwrap();
assert_eq!(index.len(), 8);
let results = index.search(&[4.0; 4], 3).unwrap();
assert!(!results.is_empty());
for (id, _) in &results {
assert!(*id != 3 && *id != 5);
}
}
#[test]
fn test_slot_reuse() {
let mut index = InPlaceIndex::new(2, InPlaceConfig::default());
let id1 = index.insert(vec![1.0, 2.0]).unwrap();
let _id2 = index.insert(vec![3.0, 4.0]).unwrap();
index.delete(id1).unwrap();
let id3 = index.insert(vec![5.0, 6.0]).unwrap();
assert_eq!(id3, id1, "Should reuse deleted slot");
}
#[test]
fn test_stats() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for i in 0..10 {
index.insert(vec![i as f32; 4]).unwrap();
}
let stats = index.stats();
assert_eq!(stats.active_nodes, 10);
assert_eq!(stats.free_slots, 0);
}
#[test]
fn test_rapid_insert_delete() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for cycle in 0..5 {
let mut ids = Vec::new();
for i in 0..10 {
let id = index.insert(vec![(cycle * 10 + i) as f32; 4]).unwrap();
ids.push(id);
}
for i in (0..10).step_by(2) {
index.delete(ids[i]).unwrap();
}
}
let results = index.search(&[25.0; 4], 5).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_repair_after_deletions() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for i in 0..30 {
index
.insert(vec![i as f32, (i % 5) as f32, 0.0, 0.0])
.unwrap();
}
assert_eq!(index.len(), 30);
let (reachable_before, _) = index.validate_connectivity();
assert_eq!(reachable_before, 30);
for id in [5, 10, 15, 20] {
index.delete(id).unwrap();
}
assert_eq!(index.len(), 26);
let stats = index.repair();
assert!(stats.edges_removed == 0 || stats.edges_removed > 0);
let (reachable_after, orphans) = index.validate_connectivity();
assert_eq!(orphans, 0, "no orphans after repair");
assert_eq!(reachable_after, 26);
let results = index.search(&[7.0, 2.0, 0.0, 0.0], 5).unwrap();
assert!(!results.is_empty());
for (id, _) in &results {
assert!(![5, 10, 15, 20].contains(id));
}
}
#[test]
fn test_validate_connectivity_empty() {
let index = InPlaceIndex::new(4, InPlaceConfig::default());
let (reachable, orphans) = index.validate_connectivity();
assert_eq!(reachable, 0);
assert_eq!(orphans, 0);
}
#[test]
fn test_repair_no_deletions_is_noop() {
let mut index = InPlaceIndex::new(4, InPlaceConfig::default());
for i in 0..10 {
index.insert(vec![i as f32; 4]).unwrap();
}
let stats = index.repair();
assert_eq!(stats.nodes_processed, 0);
assert_eq!(stats.edges_removed, 0);
assert_eq!(stats.edges_added, 0);
}
}
use crate::streaming::IndexOps;
pub struct MappedInPlaceIndex {
inner: InPlaceIndex,
id_map: std::collections::HashMap<u32, u32>,
reverse_map: std::collections::HashMap<u32, u32>,
}
impl MappedInPlaceIndex {
pub fn new(dim: usize, config: InPlaceConfig) -> Self {
Self {
inner: InPlaceIndex::new(dim, config),
id_map: std::collections::HashMap::new(),
reverse_map: std::collections::HashMap::new(),
}
}
pub fn inner(&self) -> &InPlaceIndex {
&self.inner
}
pub fn stats(&self) -> InPlaceStats {
self.inner.stats()
}
}
impl IndexOps for MappedInPlaceIndex {
fn insert(&mut self, id: u32, vector: Vec<f32>) -> crate::error::Result<()> {
if let Some(&internal_id) = self.id_map.get(&id) {
self.inner.delete(internal_id)?;
self.reverse_map.remove(&internal_id);
}
let internal_id = self.inner.insert(vector)?;
self.id_map.insert(id, internal_id);
self.reverse_map.insert(internal_id, id);
Ok(())
}
fn delete(&mut self, id: u32) -> crate::error::Result<()> {
if let Some(&internal_id) = self.id_map.get(&id) {
self.inner.delete(internal_id)?;
self.id_map.remove(&id);
self.reverse_map.remove(&internal_id);
Ok(())
} else {
Ok(())
}
}
fn search(&self, query: &[f32], k: usize) -> crate::error::Result<Vec<(u32, f32)>> {
let results = self.inner.search(query, k)?;
Ok(results
.into_iter()
.filter_map(|(internal_id, dist)| {
self.reverse_map
.get(&internal_id)
.map(|&external_id| (external_id, dist))
})
.collect())
}
}
impl IndexOps for InPlaceIndex {
fn insert(&mut self, _id: u32, vector: Vec<f32>) -> crate::error::Result<()> {
self.insert(vector)?;
Ok(())
}
fn delete(&mut self, id: u32) -> crate::error::Result<()> {
InPlaceIndex::delete(self, id)
}
fn search(&self, query: &[f32], k: usize) -> crate::error::Result<Vec<(u32, f32)>> {
InPlaceIndex::search(self, query, k)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod streaming_tests {
use super::*;
use crate::streaming::{IndexOps, StreamingCoordinator};
#[test]
fn test_inplace_with_streaming_coordinator() {
let index = InPlaceIndex::new(4, InPlaceConfig::default());
let mut streaming = StreamingCoordinator::new(index);
streaming.insert(0, vec![1.0, 0.0, 0.0, 0.0]).unwrap();
streaming.insert(1, vec![0.0, 1.0, 0.0, 0.0]).unwrap();
streaming.insert(2, vec![0.0, 0.0, 1.0, 0.0]).unwrap();
let results = streaming.search(&[1.0, 0.0, 0.0, 0.0], 3).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_mapped_inplace_preserves_ids() {
let mut index = MappedInPlaceIndex::new(4, InPlaceConfig::default());
index.insert(100, vec![1.0, 0.0, 0.0, 0.0]).unwrap();
index.insert(200, vec![0.0, 1.0, 0.0, 0.0]).unwrap();
let results = index.search(&[1.0, 0.0, 0.0, 0.0], 2).unwrap();
assert!(!results.is_empty());
let ids: Vec<u32> = results.iter().map(|(id, _)| *id).collect();
assert!(ids.contains(&100) || ids.contains(&200));
index.delete(100).unwrap();
let results_after = index.search(&[1.0, 0.0, 0.0, 0.0], 2).unwrap();
let ids_after: Vec<u32> = results_after.iter().map(|(id, _)| *id).collect();
assert!(!ids_after.contains(&100));
}
}