Insert a vector into the index. 1. Assign a random layer using the exponential distribution 2. Greedily descend from the entry point to the new node's layer + 1 3. At each layer from the node's layer down to 0, search for nearest neighbors, select via the diversity heuristic, and add bidirectional edges 4. Prune over-connected nodes to maintain the M/M0 invariant
(&mut self, vector: Vec<f32>)
| 17 | /// neighbors, select via the diversity heuristic, and add bidirectional edges |
| 18 | /// 4. Prune over-connected nodes to maintain the M/M0 invariant |
| 19 | pub fn insert(&mut self, vector: Vec<f32>) -> Result<(), VectorError> { |
| 20 | // Materialize flat neighbor storage on first mutation. |
| 21 | self.ensure_mutable_neighbors(); |
| 22 | |
| 23 | if vector.len() != self.dim { |
| 24 | return Err(VectorError::DimensionMismatch { |
| 25 | expected: self.dim, |
| 26 | got: vector.len(), |
| 27 | }); |
| 28 | } |
| 29 | |
| 30 | let new_id = self.nodes.len() as u32; |
| 31 | let new_layer = self.random_layer(); |
| 32 | |
| 33 | let storage = match self.params.dtype { |
| 34 | VectorStorageDtype::F32 => NodeStorage::F32(vector.clone()), |
| 35 | dtype => NodeStorage::Bytes { |
| 36 | dtype, |
| 37 | bytes: cast_from_f32(&vector, dtype), |
| 38 | }, |
| 39 | }; |
| 40 | let node = Node { |
| 41 | storage, |
| 42 | neighbors: (0..=new_layer).map(|_| Vec::new()).collect(), |
| 43 | deleted: false, |
| 44 | }; |
| 45 | self.nodes.push(node); |
| 46 | |
| 47 | let Some(ep) = self.entry_point else { |
| 48 | self.entry_point = Some(new_id); |
| 49 | self.max_layer = new_layer; |
| 50 | return Ok(()); |
| 51 | }; |
| 52 | |
| 53 | // Encode query once to the index dtype for all dist_to_node calls below. |
| 54 | let query = vector; |
| 55 | let query_bytes = cast_from_f32(&query, self.params.dtype); |
| 56 | |
| 57 | let mut current_ep = ep; |
| 58 | |
| 59 | // Phase 1: Greedy descent from top layer to new_layer + 1. |
| 60 | if self.max_layer > new_layer { |
| 61 | for layer in (new_layer + 1..=self.max_layer).rev() { |
| 62 | let results = search_layer(self, &query_bytes, current_ep, 1, layer, None, 0); |
| 63 | if let Some(nearest) = results.first() { |
| 64 | current_ep = nearest.id; |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | // Phase 2: Insert at each layer from min(new_layer, max_layer) down to 0. |
| 70 | let insert_top = new_layer.min(self.max_layer); |
| 71 | for layer in (0..=insert_top).rev() { |
| 72 | let ef = self.params.ef_construction; |
| 73 | let candidates = search_layer(self, &query_bytes, current_ep, ef, layer, None, 0); |
| 74 | |
| 75 | let m = self.max_neighbors(layer); |
| 76 | let selected = select_neighbors_heuristic(self, &candidates, m); |