pub struct FlatIndex { /* private fields */ }Expand description
Flat (brute-force) index for exact nearest neighbor search.
Stores vectors in row-major layout for simple slicing. Provides O(1) insertion and O(n·d) search with 100% recall guarantee.
§ID Convention
FlatIndex uses 0-based direct indexing. The first inserted vector
receives ID 0, the second receives 1, and so on. IDs are assigned
by a monotonically increasing next_id counter and are never reused
after deletion. This means vector_id == slot_index in the contiguous
storage, allowing O(1) retrieval via &vectors[id * dim..(id+1) * dim].
§Memory Layout
- Vectors:
Vec<f32>in row-major order (n × d elements) - Deletion bitmap:
BitVec(1 bit per vector) - For 10k vectors @ 768 dimensions:
- F32: ~30 MB (10,000 × 768 × 4 bytes)
- Bitmap: ~1.25 KB
§Thread Safety
FlatIndex is not thread-safe by default. Wrap in Arc<RwLock<_>> for
concurrent access.
Implementations§
Source§impl FlatIndex
impl FlatIndex
Sourcepub fn new(config: FlatIndexConfig) -> Self
pub fn new(config: FlatIndexConfig) -> Self
Create a new FlatIndex with the given configuration.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let config = FlatIndexConfig::new(128);
let index = FlatIndex::new(config);
assert_eq!(index.dimensions(), 128);
assert!(index.is_empty());Sourcepub fn dimensions(&self) -> u32
pub fn dimensions(&self) -> u32
Returns the vector dimension.
Sourcepub fn metric(&self) -> DistanceMetric
pub fn metric(&self) -> DistanceMetric
Returns the distance metric.
Sourcepub fn config(&self) -> &FlatIndexConfig
pub fn config(&self) -> &FlatIndexConfig
Returns a reference to the configuration.
Sourcepub fn insert(&mut self, vector: &[f32]) -> Result<u64, FlatIndexError>
pub fn insert(&mut self, vector: &[f32]) -> Result<u64, FlatIndexError>
Insert a vector into the index.
Returns the assigned vector ID.
§Errors
Returns FlatIndexError::DimensionMismatch if the vector dimension
doesn’t match the index configuration.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
let id = index.insert(&[1.0, 2.0, 3.0]).unwrap();
assert_eq!(id, 0);
let id2 = index.insert(&[4.0, 5.0, 6.0]).unwrap();
assert_eq!(id2, 1);Sourcepub fn insert_batch(
&mut self,
vectors: &[&[f32]],
) -> Result<Vec<u64>, FlatIndexError>
pub fn insert_batch( &mut self, vectors: &[&[f32]], ) -> Result<Vec<u64>, FlatIndexError>
Insert multiple vectors in batch.
Returns the IDs assigned to each vector.
§Errors
Returns error if any vector has wrong dimension. On error, vectors inserted before the error remain in the index.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
let vectors: Vec<&[f32]> = vec![
&[1.0, 2.0, 3.0],
&[4.0, 5.0, 6.0],
];
let ids = index.insert_batch(&vectors).unwrap();
assert_eq!(ids, vec![0, 1]);Sourcepub fn get(&self, id: u64) -> Option<&[f32]>
pub fn get(&self, id: u64) -> Option<&[f32]>
Get a vector by ID.
Returns None if the ID doesn’t exist or was deleted.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
let id = index.insert(&[1.0, 2.0, 3.0]).unwrap();
let v = index.get(id).unwrap();
assert_eq!(v, &[1.0, 2.0, 3.0]);
// Non-existent ID returns None
assert!(index.get(999).is_none());Sourcepub fn contains(&self, id: u64) -> bool
pub fn contains(&self, id: u64) -> bool
Check if a vector ID exists and is not deleted.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
let id = index.insert(&[1.0, 2.0, 3.0]).unwrap();
assert!(index.contains(id));
assert!(!index.contains(999));Sourcepub fn deleted_count(&self) -> usize
pub fn deleted_count(&self) -> usize
Returns the number of deleted vectors.
Sourcepub fn deletion_ratio(&self) -> f32
pub fn deletion_ratio(&self) -> f32
Returns the deletion ratio (deleted / total).
Sourcepub fn is_quantized(&self) -> bool
pub fn is_quantized(&self) -> bool
Returns true if quantization is enabled.
Sourcepub fn memory_usage(&self) -> usize
pub fn memory_usage(&self) -> usize
Returns memory usage in bytes (approximate).
Includes:
- Vector storage (n × dim × 4 bytes for f32)
- Deleted bitmap (n / 8 bytes)
- Quantized storage if enabled (n × ceil(dim/8) bytes)
Sourcepub fn deletion_stats(&self) -> (usize, usize, f32)
pub fn deletion_stats(&self) -> (usize, usize, f32)
Get deletion statistics.
Returns a tuple of (total_vectors, deleted_count, deletion_ratio).
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
index.insert(&[1.0, 2.0, 3.0]).unwrap();
index.insert(&[4.0, 5.0, 6.0]).unwrap();
index.delete(0);
let (total, deleted, ratio) = index.deletion_stats();
assert_eq!(total, 2);
assert_eq!(deleted, 1);
assert!((ratio - 0.5).abs() < 0.01);Sourcepub fn delete(&mut self, id: u64) -> bool
pub fn delete(&mut self, id: u64) -> bool
Mark a vector as deleted.
The vector is not immediately removed; its slot is marked in the
deletion bitmap and skipped during search. Call compact() to
reclaim space when deletion rate exceeds the threshold.
§Returns
Returns true if the vector was deleted, false if it didn’t exist
or was already deleted.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(3));
let id = index.insert(&[1.0, 2.0, 3.0]).unwrap();
assert!(index.delete(id)); // Success
assert!(!index.delete(id)); // Already deleted
assert!(!index.delete(999)); // Doesn't exist
assert!(!index.contains(id)); // No longer accessibleSourcepub fn compact(&mut self)
pub fn compact(&mut self)
Compact the index by removing deleted vectors.
This rebuilds the internal storage, removing deleted slots.
§Warning
This operation changes vector IDs! After compaction, vectors are renumbered to be contiguous. Use with caution if external systems reference vector IDs.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(
FlatIndexConfig::new(3).with_cleanup_threshold(1.0) // Disable auto-compact
);
index.insert(&[1.0, 2.0, 3.0]).unwrap();
index.insert(&[4.0, 5.0, 6.0]).unwrap();
index.insert(&[7.0, 8.0, 9.0]).unwrap();
// Delete middle vector
index.delete(1);
assert_eq!(index.len(), 2);
assert_eq!(index.capacity(), 3); // Still has 3 slots
// Compact
index.compact();
assert_eq!(index.capacity(), 2); // Now only 2 slotsSourcepub fn enable_quantization(&mut self) -> Result<(), FlatIndexError>
pub fn enable_quantization(&mut self) -> Result<(), FlatIndexError>
Enable binary quantization for memory reduction.
Converts stored vectors to binary format (32x compression).
After enabling, use search_quantized() for fast Hamming distance search.
§Memory Reduction
- Original: 768D × 4 bytes = 3072 bytes per vector
- Quantized: 768D / 8 = 96 bytes per vector (32x reduction)
§Warning
This is a lossy operation. Recall will decrease from 100%
but memory usage drops significantly. The original F32 vectors
are preserved for search() to maintain 100% recall option.
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(8));
index.insert(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0]).unwrap();
index.enable_quantization().unwrap();
assert!(index.is_quantized());
// Can still use exact search
let exact = index.search(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0], 1).unwrap();
// Or use faster quantized search
let approx = index.search_quantized(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0], 1).unwrap();§Errors
Currently infallible, but returns Result for API consistency
and future error conditions.
Sourcepub fn disable_quantization(&mut self)
pub fn disable_quantization(&mut self)
Disable quantization, freeing the quantized storage.
Search will use original F32 vectors only.
Sourcepub fn search_quantized(
&self,
query: &[f32],
k: usize,
) -> Result<Vec<FlatSearchResult>, FlatIndexError>
pub fn search_quantized( &self, query: &[f32], k: usize, ) -> Result<Vec<FlatSearchResult>, FlatIndexError>
Search using quantized vectors.
Uses Hamming distance on binary-quantized vectors for fast approximate search. Results are sorted by ascending Hamming distance (lower = more similar).
§Errors
FlatIndexError::QuantizationNotEnabledif quantization is not enabledFlatIndexError::DimensionMismatchif query dimension is wrongFlatIndexError::InvalidKif k is 0
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig};
let mut index = FlatIndex::new(FlatIndexConfig::new(8));
index.insert(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0]).unwrap();
index.insert(&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]).unwrap();
index.enable_quantization().unwrap();
let results = index.search_quantized(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0], 2).unwrap();
assert_eq!(results[0].id, 0); // Exact match has Hamming distance 0
assert!((results[0].score - 0.0).abs() < f32::EPSILON);Sourcepub fn search(
&self,
query: &[f32],
k: usize,
) -> Result<Vec<FlatSearchResult>, FlatIndexError>
pub fn search( &self, query: &[f32], k: usize, ) -> Result<Vec<FlatSearchResult>, FlatIndexError>
Search for the k nearest neighbors.
Returns results sorted by relevance (best first):
- For distance metrics (L2, Hamming): lowest distance first
- For similarity metrics (Cosine, Dot): highest similarity first
§Arguments
query- Query vector (must match index dimensions)k- Number of results to return
§Errors
FlatIndexError::DimensionMismatchif query dimension is wrongFlatIndexError::InvalidKif k is 0
§Example
use edgevec::index::{FlatIndex, FlatIndexConfig, DistanceMetric};
let config = FlatIndexConfig::new(3).with_metric(DistanceMetric::Cosine);
let mut index = FlatIndex::new(config);
index.insert(&[1.0, 0.0, 0.0]).unwrap();
index.insert(&[0.0, 1.0, 0.0]).unwrap();
let results = index.search(&[0.9, 0.1, 0.0], 2).unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results[0].id, 0); // First vector is most similarSource§impl FlatIndex
impl FlatIndex
Sourcepub fn to_snapshot(&self) -> Result<Vec<u8>, PersistenceError>
pub fn to_snapshot(&self) -> Result<Vec<u8>, PersistenceError>
Serialize the index to a snapshot.
The snapshot format is:
- Header length (u32, 4 bytes)
- Header (postcard serialized
FlatIndexHeader) - Deleted bitmap length (u32, 4 bytes)
- Deleted bitmap (variable bytes)
- Vectors length (u64, 8 bytes)
- Vectors (n × dim × 4 bytes, little-endian f32)
- Quantized length (u64, 8 bytes, 0 if not enabled)
- Quantized vectors (optional, n × ceil(dim/8) bytes)
§Returns
Bytes that can be written to IndexedDB or file system.
§Errors
Returns PersistenceError::SerializationError if serialization fails.
Sourcepub fn from_snapshot(data: &[u8]) -> Result<Self, PersistenceError>
pub fn from_snapshot(data: &[u8]) -> Result<Self, PersistenceError>
Restore index from a snapshot.
§Arguments
data- Bytes fromto_snapshot()or loaded from storage
§Errors
Returns error if:
- Magic number doesn’t match
- Version is unsupported
- Checksum doesn’t match
- Data is corrupted or truncated
§Panics
This function does not panic. The internal expect is guarded by
chunks_exact(4) which guarantees the slice length.
Auto Trait Implementations§
impl Freeze for FlatIndex
impl RefUnwindSafe for FlatIndex
impl Send for FlatIndex
impl Sync for FlatIndex
impl Unpin for FlatIndex
impl UnsafeUnpin for FlatIndex
impl UnwindSafe for FlatIndex
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.