Skip to main content

FlatIndex

Struct FlatIndex 

Source
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

Source

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());
Source

pub fn dimensions(&self) -> u32

Returns the vector dimension.

Source

pub fn metric(&self) -> DistanceMetric

Returns the distance metric.

Source

pub fn len(&self) -> usize

Returns the number of vectors (excluding deleted).

Source

pub fn is_empty(&self) -> bool

Returns true if the index is empty (no non-deleted vectors).

Source

pub fn capacity(&self) -> usize

Returns the total number of slots (including deleted).

Source

pub fn config(&self) -> &FlatIndexConfig

Returns a reference to the configuration.

Source

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);
Source

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]);
Source

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());
Source

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));
Source

pub fn deleted_count(&self) -> usize

Returns the number of deleted vectors.

Source

pub fn deletion_ratio(&self) -> f32

Returns the deletion ratio (deleted / total).

Source

pub fn is_quantized(&self) -> bool

Returns true if quantization is enabled.

Source

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)
Source

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);
Source

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 accessible
Source

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 slots
Source

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.

Source

pub fn disable_quantization(&mut self)

Disable quantization, freeing the quantized storage.

Search will use original F32 vectors only.

Source

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::QuantizationNotEnabled if quantization is not enabled
  • FlatIndexError::DimensionMismatch if query dimension is wrong
  • FlatIndexError::InvalidK if 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);
Source

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::DimensionMismatch if query dimension is wrong
  • FlatIndexError::InvalidK if 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 similar
Source§

impl FlatIndex

Source

pub fn to_snapshot(&self) -> Result<Vec<u8>, PersistenceError>

Serialize the index to a snapshot.

The snapshot format is:

  1. Header length (u32, 4 bytes)
  2. Header (postcard serialized FlatIndexHeader)
  3. Deleted bitmap length (u32, 4 bytes)
  4. Deleted bitmap (variable bytes)
  5. Vectors length (u64, 8 bytes)
  6. Vectors (n × dim × 4 bytes, little-endian f32)
  7. Quantized length (u64, 8 bytes, 0 if not enabled)
  8. 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.

Source

pub fn from_snapshot(data: &[u8]) -> Result<Self, PersistenceError>

Restore index from a snapshot.

§Arguments
  • data - Bytes from to_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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows 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
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows 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
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .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
where Self: BorrowMut<B>, B: ?Sized,

Calls .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
where Self: AsRef<R>, R: ?Sized,

Calls .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
where Self: AsMut<R>, R: ?Sized,

Calls .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
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V