diff --git a/Cargo.toml b/Cargo.toml index 0c87694..9257b7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [dependencies] half = "2" -ngt-sys = { path = "ngt-sys", version = "2.0.11" } +ngt-sys = { path = "ngt-sys", version = "2.0.12" } num_enum = "0.5" scopeguard = "1" diff --git a/README.md b/README.md index 2bf6522..d127f19 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ten to several thousand dimensions). This crate provides the following indexes: * `NgtIndex`: Graph and tree-based index[^1] -* `QqIndex`: Quantized graph-based index[^2] +* `QgIndex`: Quantized graph-based index[^2] * `QbgIndex`: Quantized blob graph-based index The quantized indexes are available through the `quantized` Cargo feature. Note that @@ -37,16 +37,15 @@ features are available through the features `shared_mem` and `large_data` respec Defining the properties of a new index: ```rust,ignore -use ngt::{NgtProperties, NgtDistance, NgtObject}; +use ngt::{NgtProperties, NgtDistance}; // Defaut properties with vectors of dimension 3 -let prop = NgtProperties::dimension(3)?; +let prop = NgtProperties::::dimension(3)?; // Or customize values (here are the defaults) -let prop = NgtProperties::dimension(3)? +let prop = NgtProperties::::dimension(3)? .creation_edge_size(10)? .search_edge_size(40)? - .object_type(NgtObject::Float)? .distance_type(NgtDistance::L2)?; ``` @@ -57,7 +56,7 @@ use ngt::{NgtIndex, NgtProperties, EPSILON}; // Create a new index let prop = NgtProperties::dimension(3)?; -let index = NgtIndex::create("target/path/to/index/dir", prop)?; +let index: NgtIndex = NgtIndex::create("target/path/to/index/dir", prop)?; // Open an existing index let mut index = NgtIndex::open("target/path/to/index/dir")?; @@ -68,7 +67,7 @@ let vec2 = vec![4.0, 5.0, 6.0]; let id1 = index.insert(vec1)?; let id2 = index.insert(vec2)?; -// Actually build the index (not yet persisted on disk) +// Build the index in RAM (not yet persisted on disk) // This is required in order to be able to search vectors index.build(2)?; @@ -80,7 +79,7 @@ assert_eq!(index.get_vec(id1)?, vec![1.0, 2.0, 3.0]); // Remove a vector and check that it is not present anymore index.remove(id1)?; let res = index.get_vec(id1); -assert!(matches!(res, Result::Err(_))); +assert!(res.is_err()); // Verify that now our search result is different let res = index.search(&vec![1.1, 2.1, 3.1], 1, EPSILON)?; diff --git a/ngt-sys/Cargo.toml b/ngt-sys/Cargo.toml index 08150ef..9d3562b 100644 --- a/ngt-sys/Cargo.toml +++ b/ngt-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ngt-sys" -version = "2.0.11" +version = "2.0.12" authors = ["Romain Leroux "] edition = "2021" links = "ngt" diff --git a/ngt-sys/NGT b/ngt-sys/NGT index a91abde..5920ef0 160000 --- a/ngt-sys/NGT +++ b/ngt-sys/NGT @@ -1 +1 @@ -Subproject commit a91abde328aa70faad467b89545e99965eee0b2a +Subproject commit 5920ef01016998e96ad106284ed35e684981250d diff --git a/src/lib.rs b/src/lib.rs index db7c43a..8d43036 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,3 +22,6 @@ pub const EPSILON: f32 = 0.1; pub use crate::error::{Error, Result}; pub use crate::ngt::{optim, NgtDistance, NgtIndex, NgtObject, NgtProperties}; + +#[doc(inline)] +pub use half; diff --git a/src/ngt/index.rs b/src/ngt/index.rs index 6bd9cdf..388bbe8 100644 --- a/src/ngt/index.rs +++ b/src/ngt/index.rs @@ -9,25 +9,28 @@ use std::ptr; use ngt_sys as sys; use scopeguard::defer; -use super::{NgtObject, NgtProperties}; +use super::{NgtObject, NgtObjectType, NgtProperties}; use crate::error::{make_err, Error, Result}; use crate::{SearchResult, VecId}; #[derive(Debug)] -pub struct NgtIndex { +pub struct NgtIndex { pub(crate) path: CString, - pub(crate) prop: NgtProperties, + pub(crate) prop: NgtProperties, pub(crate) index: sys::NGTIndex, ospace: sys::NGTObjectSpace, ebuf: sys::NGTError, } -unsafe impl Send for NgtIndex {} -unsafe impl Sync for NgtIndex {} +unsafe impl Send for NgtIndex {} +unsafe impl Sync for NgtIndex {} -impl NgtIndex { +impl NgtIndex +where + T: NgtObjectType, +{ /// Creates an empty ANNG index with the given [`NgtProperties`](). - pub fn create>(path: P, prop: NgtProperties) -> Result { + pub fn create>(path: P, prop: NgtProperties) -> Result { if cfg!(feature = "shared_mem") && path.as_ref().exists() { Err(Error(format!("Path {:?} already exists", path.as_ref())))? } @@ -190,19 +193,33 @@ impl NgtIndex { /// discoverable yet. /// /// **The method [`build`](NgtIndex::build) must be called after inserting vectors**. - pub fn insert(&mut self, mut vec: Vec) -> Result { + pub fn insert(&mut self, mut vec: Vec) -> Result { unsafe { - let id = sys::ngt_insert_index_as_float( - self.index, - vec.as_mut_ptr(), - self.prop.dimension as u32, - self.ebuf, - ); + let id = match self.prop.object_type { + NgtObject::Float => sys::ngt_insert_index_as_float( + self.index, + vec.as_mut_ptr() as *mut _, + self.prop.dimension as u32, + self.ebuf, + ), + NgtObject::Uint8 => sys::ngt_insert_index_as_uint8( + self.index, + vec.as_mut_ptr() as *mut _, + self.prop.dimension as u32, + self.ebuf, + ), + NgtObject::Float16 => sys::ngt_insert_index_as_float16( + self.index, + vec.as_mut_ptr() as *mut _, + self.prop.dimension as u32, + self.ebuf, + ), + }; if id == 0 { Err(make_err(self.ebuf))? + } else { + Ok(id) } - - Ok(id) } } @@ -265,9 +282,9 @@ impl NgtIndex { } /// Get the specified vector. - pub fn get_vec(&self, id: VecId) -> Result> { + pub fn get_vec(&self, id: VecId) -> Result> { unsafe { - let results = match self.prop.object_type { + match self.prop.object_type { NgtObject::Float => { let results = sys::ngt_get_object_as_float(self.ospace, id, self.ebuf); if results.is_null() { @@ -281,7 +298,8 @@ impl NgtIndex { ); let results = mem::ManuallyDrop::new(results); - results.iter().copied().collect::>() + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) } NgtObject::Float16 => { let results = sys::ngt_get_object(self.ospace, id, self.ebuf); @@ -296,7 +314,8 @@ impl NgtIndex { ); let results = mem::ManuallyDrop::new(results); - results.iter().map(|f16| f16.to_f32()).collect::>() + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) } NgtObject::Uint8 => { let results = sys::ngt_get_object_as_integer(self.ospace, id, self.ebuf); @@ -311,11 +330,10 @@ impl NgtIndex { ); let results = mem::ManuallyDrop::new(results); - results.iter().map(|&byte| byte as f32).collect::>() + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) } - }; - - Ok(results) + } } } @@ -330,7 +348,7 @@ impl NgtIndex { } } -impl Drop for NgtIndex { +impl Drop for NgtIndex { fn drop(&mut self) { if !self.index.is_null() { unsafe { sys::ngt_close_index(self.index) }; @@ -364,7 +382,7 @@ mod tests { } // Create an index for vectors of dimension 3 - let prop = NgtProperties::dimension(3)?; + let prop = NgtProperties::::dimension(3)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Insert two vectors and get their id @@ -431,7 +449,7 @@ mod tests { } // Create an index for vectors of dimension 3 - let prop = NgtProperties::dimension(3)?; + let prop = NgtProperties::::dimension(3)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Batch insert 2 vectors, build and persist the index @@ -456,7 +474,7 @@ mod tests { } // Create an index for vectors of dimension 3 - let prop = NgtProperties::dimension(3)?.object_type(NgtObject::Uint8)?; + let prop = NgtProperties::::dimension(3)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Batch insert 2 vectors, build and persist the index @@ -481,7 +499,7 @@ mod tests { } // Create an index for vectors of dimension 3 - let prop = NgtProperties::dimension(3)?.object_type(NgtObject::Float16)?; + let prop = NgtProperties::::dimension(3)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Batch insert 2 vectors, build and persist the index @@ -506,7 +524,7 @@ mod tests { } // Create an index for vectors of dimension 3 - let prop = NgtProperties::dimension(3)?; + let prop = NgtProperties::::dimension(3)?; let mut index = NgtIndex::create(dir.path(), prop)?; let vecs = vec![ diff --git a/src/ngt/mod.rs b/src/ngt/mod.rs index 6a6e7b9..ff9d15e 100644 --- a/src/ngt/mod.rs +++ b/src/ngt/mod.rs @@ -2,16 +2,15 @@ //! //! ```rust //! # fn main() -> Result<(), ngt::Error> { -//! use ngt::{NgtProperties, NgtDistance, NgtObject}; +//! use ngt::{NgtProperties, NgtDistance}; //! //! // Defaut properties with vectors of dimension 3 -//! let prop = NgtProperties::dimension(3)?; +//! let prop = NgtProperties::::dimension(3)?; //! //! // Or customize values (here are the defaults) -//! let prop = NgtProperties::dimension(3)? +//! let prop = NgtProperties::::dimension(3)? //! .creation_edge_size(10)? //! .search_edge_size(40)? -//! .object_type(NgtObject::Float)? //! .distance_type(NgtDistance::L2)?; //! //! # Ok(()) @@ -26,7 +25,7 @@ //! //! // Create a new index //! let prop = NgtProperties::dimension(3)?; -//! let index = NgtIndex::create("target/path/to/index/dir", prop)?; +//! let index: NgtIndex = NgtIndex::create("target/path/to/index/dir", prop)?; //! //! // Open an existing index //! let mut index = NgtIndex::open("target/path/to/index/dir")?; @@ -37,7 +36,7 @@ //! let id1 = index.insert(vec1)?; //! let id2 = index.insert(vec2)?; //! -//! // Actually build the index (not yet persisted on disk) +//! // Build the index in RAM (not yet persisted on disk) //! // This is required in order to be able to search vectors //! index.build(2)?; //! @@ -49,7 +48,7 @@ //! // Remove a vector and check that it is not present anymore //! index.remove(id1)?; //! let res = index.get_vec(id1); -//! assert!(matches!(res, Result::Err(_))); +//! assert!(res.is_err()); //! //! // Verify that now our search result is different //! let res = index.search(&vec![1.1, 2.1, 3.1], 1, EPSILON)?; @@ -69,4 +68,4 @@ pub mod optim; mod properties; pub use self::index::NgtIndex; -pub use self::properties::{NgtDistance, NgtObject, NgtProperties}; +pub use self::properties::{NgtDistance, NgtObject, NgtObjectType, NgtProperties}; diff --git a/src/ngt/optim.rs b/src/ngt/optim.rs index 5233b3e..0fe7ad2 100644 --- a/src/ngt/optim.rs +++ b/src/ngt/optim.rs @@ -6,6 +6,7 @@ use std::ptr; use ngt_sys as sys; use scopeguard::defer; +use super::NgtObjectType; use crate::error::{make_err, Result}; use crate::ngt::index::NgtIndex; @@ -42,10 +43,14 @@ pub fn optimize_anng_edges_number>( /// /// Optimizes the search parameters about the explored edges and memory prefetch for the /// existing indexes. Does not modify the index data structure. -pub fn optimize_anng_search_parameters>(index_path: P) -> Result<()> { +pub fn optimize_anng_search_parameters(index_path: P) -> Result<()> +where + T: NgtObjectType, + P: AsRef, +{ let mut optimizer = GraphOptimizer::new(GraphOptimParams::default())?; optimizer.set_processing_modes(true, true, true)?; - optimizer.adjust_search_coefficients(index_path)?; + optimizer.adjust_search_coefficients::(index_path)?; Ok(()) } @@ -55,7 +60,10 @@ pub fn optimize_anng_search_parameters>(index_path: P) -> Result< /// node. Note that refinement takes a long processing time. An ANNG index can be /// refined only after it has been [`built`](NgtIndex::build). #[cfg(not(feature = "shared_mem"))] -pub fn refine_anng(index: &mut NgtIndex, params: AnngRefineParams) -> Result<()> { +pub fn refine_anng( + index: &mut NgtIndex, + params: AnngRefineParams, +) -> Result<()> { unsafe { let ebuf = sys::ngt_create_error_object(); defer! { sys::ngt_destroy_error_object(ebuf); } @@ -90,13 +98,17 @@ pub fn refine_anng(index: &mut NgtIndex, params: AnngRefineParams) -> Result<()> /// Important [`GraphOptimParams`](GraphOptimParams) parameters are `nb_outgoing` edges /// and `nb_incoming` edges. The latter can be set to an even higher number than the /// `creation_edge_size` of the original ANNG. -pub fn convert_anng_to_onng>( +pub fn convert_anng_to_onng( index_anng_in: P, index_onng_out: P, params: GraphOptimParams, -) -> Result<()> { +) -> Result<()> +where + T: NgtObjectType, + P: AsRef, +{ let mut optimizer = GraphOptimizer::new(params)?; - optimizer.convert_anng_to_onng(index_anng_in, index_onng_out)?; + optimizer.convert_anng_to_onng::(index_anng_in, index_onng_out)?; Ok(()) } @@ -253,8 +265,12 @@ impl GraphOptimizer { } /// Optimize for the search parameters of an ANNG. - fn adjust_search_coefficients>(&mut self, index_path: P) -> Result<()> { - let _ = NgtIndex::open(&index_path)?; + fn adjust_search_coefficients(&mut self, index_path: P) -> Result<()> + where + P: AsRef, + T: NgtObjectType, + { + let _ = NgtIndex::::open(&index_path)?; unsafe { let ebuf = sys::ngt_create_error_object(); @@ -271,12 +287,12 @@ impl GraphOptimizer { } /// Converts the `index_in` ANNG to an ONNG at `index_out`. - fn convert_anng_to_onng>( - &mut self, - index_anng_in: P, - index_onng_out: P, - ) -> Result<()> { - let _ = NgtIndex::open(&index_anng_in)?; + fn convert_anng_to_onng(&mut self, index_anng_in: P, index_onng_out: P) -> Result<()> + where + T: NgtObjectType, + P: AsRef, + { + let _ = NgtIndex::::open(&index_anng_in)?; unsafe { let ebuf = sys::ngt_create_error_object(); @@ -321,7 +337,7 @@ mod tests { let dir = tempdir()?; // Create an index for vectors of dimension 3 with cosine distance - let prop = NgtProperties::dimension(3)?.distance_type(NgtDistance::Cosine)?; + let prop = NgtProperties::::dimension(3)?.distance_type(NgtDistance::Cosine)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Populate the index, but don't build it yet @@ -335,12 +351,12 @@ mod tests { optimize_anng_edges_number(dir.path(), AnngEdgeOptimParams::default())?; // Now build and persist again the optimized index - let mut index = NgtIndex::open(dir.path())?; + let mut index = NgtIndex::::open(dir.path())?; index.build(4)?; index.persist()?; // Further optimize the index - optimize_anng_search_parameters(dir.path())?; + optimize_anng_search_parameters::(dir.path())?; dir.close()?; Ok(()) @@ -353,7 +369,7 @@ mod tests { let dir = tempdir()?; // Create an index for vectors of dimension 3 with cosine distance - let prop = NgtProperties::dimension(3)?.distance_type(NgtDistance::Cosine)?; + let prop = NgtProperties::::dimension(3)?.distance_type(NgtDistance::Cosine)?; let mut index = NgtIndex::create(dir.path(), prop)?; // Populate and build the index @@ -378,7 +394,7 @@ mod tests { let dir_in = tempdir()?; // Create an index for vectors of dimension 3 with cosine distance - let prop = NgtProperties::dimension(3)? + let prop = NgtProperties::::dimension(3)? .distance_type(NgtDistance::Cosine)? .creation_edge_size(100)?; // More than default value, improves the final ONNG @@ -395,7 +411,7 @@ mod tests { optimize_anng_edges_number(dir_in.path(), AnngEdgeOptimParams::default())?; // Now build and persist again the optimized index - let mut index = NgtIndex::open(dir_in.path())?; + let mut index = NgtIndex::::open(dir_in.path())?; index.build(4)?; index.persist()?; @@ -407,7 +423,7 @@ mod tests { let mut params = GraphOptimParams::default(); params.nb_outgoing = 10; params.nb_incoming = 100; // An even larger number of incoming edges can be specified - convert_anng_to_onng(dir_in.path(), dir_out.path(), params)?; + convert_anng_to_onng::(dir_in.path(), dir_out.path(), params)?; dir_out.close()?; dir_in.close()?; diff --git a/src/ngt/properties.rs b/src/ngt/properties.rs index 05b294a..8198a93 100644 --- a/src/ngt/properties.rs +++ b/src/ngt/properties.rs @@ -1,6 +1,7 @@ -use std::convert::TryFrom; use std::ptr; +use std::{convert::TryFrom, marker::PhantomData}; +use half::f16; use ngt_sys as sys; use num_enum::TryFromPrimitive; use scopeguard::defer; @@ -15,6 +16,35 @@ pub enum NgtObject { Float16 = 3, } +mod private { + pub trait Sealed {} +} + +pub trait NgtObjectType: private::Sealed { + fn as_obj() -> NgtObject; +} + +impl private::Sealed for f32 {} +impl NgtObjectType for f32 { + fn as_obj() -> NgtObject { + NgtObject::Float + } +} + +impl private::Sealed for u8 {} +impl NgtObjectType for u8 { + fn as_obj() -> NgtObject { + NgtObject::Uint8 + } +} + +impl private::Sealed for f16 {} +impl NgtObjectType for f16 { + fn as_obj() -> NgtObject { + NgtObject::Float16 + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] #[repr(i32)] pub enum NgtDistance { @@ -33,24 +63,28 @@ pub enum NgtDistance { } #[derive(Debug)] -pub struct NgtProperties { +pub struct NgtProperties { pub(crate) dimension: i32, pub(crate) creation_edge_size: i16, pub(crate) search_edge_size: i16, pub(crate) object_type: NgtObject, pub(crate) distance_type: NgtDistance, pub(crate) raw_prop: sys::NGTProperty, + _marker: PhantomData, } -unsafe impl Send for NgtProperties {} -unsafe impl Sync for NgtProperties {} +unsafe impl Send for NgtProperties {} +unsafe impl Sync for NgtProperties {} -impl NgtProperties { +impl NgtProperties +where + T: NgtObjectType, +{ pub fn dimension(dimension: usize) -> Result { let dimension = i32::try_from(dimension)?; let creation_edge_size = 10; let search_edge_size = 40; - let object_type = NgtObject::Float; + let object_type = T::as_obj(); let distance_type = NgtDistance::L2; unsafe { @@ -75,6 +109,7 @@ impl NgtProperties { object_type, distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -102,6 +137,7 @@ impl NgtProperties { object_type: self.object_type, distance_type: self.distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -154,6 +190,7 @@ impl NgtProperties { object_type, distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -205,12 +242,6 @@ impl NgtProperties { Ok(()) } - pub fn object_type(mut self, object_type: NgtObject) -> Result { - self.object_type = object_type; - unsafe { Self::set_object_type(self.raw_prop, object_type)? }; - Ok(self) - } - unsafe fn set_object_type(raw_prop: sys::NGTProperty, object_type: NgtObject) -> Result<()> { let ebuf = sys::ngt_create_error_object(); defer! { sys::ngt_destroy_error_object(ebuf); } @@ -316,7 +347,7 @@ impl NgtProperties { } } -impl Drop for NgtProperties { +impl Drop for NgtProperties { fn drop(&mut self) { if !self.raw_prop.is_null() { unsafe { sys::ngt_destroy_property(self.raw_prop) }; diff --git a/src/qbg/index.rs b/src/qbg/index.rs index 2edce50..44d9b29 100644 --- a/src/qbg/index.rs +++ b/src/qbg/index.rs @@ -1,28 +1,33 @@ use std::ffi::CString; +use std::marker::PhantomData; use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::{mem, ptr}; use ngt_sys as sys; -use num_enum::TryFromPrimitive; use scopeguard::defer; use crate::error::{make_err, Error, Result}; use crate::{SearchResult, VecId}; -use super::{QbgDistance, QbgObject}; +use super::{QbgBuildParams, QbgConstructParams, QbgObject, QbgObjectType}; #[derive(Debug)] -pub struct QbgIndex { +pub struct QbgIndex { pub(crate) index: sys::QBGIndex, path: CString, - _mode: T, + _mode: M, + obj_type: QbgObject, dimension: u32, ebuf: sys::NGTError, + _marker: PhantomData, } -impl QbgIndex { - pub fn create

(path: P, create_params: QbgConstructParams) -> Result +impl QbgIndex +where + T: QbgObjectType, +{ + pub fn create

(path: P, create_params: QbgConstructParams) -> Result where P: AsRef, { @@ -57,11 +62,14 @@ impl QbgIndex { path, _mode: ModeWrite, dimension, + obj_type: T::as_obj(), ebuf: sys::ngt_create_error_object(), + _marker: PhantomData, }) } } + // TODO: should be mut vec: Vec pub fn insert(&mut self, mut vec: Vec) -> Result { unsafe { let id = @@ -96,14 +104,17 @@ impl QbgIndex { } } - pub fn into_readable(self) -> Result> { + pub fn into_readable(self) -> Result> { let path = self.path.clone(); drop(self); QbgIndex::open(path.into_string()?) } } -impl QbgIndex { +impl QbgIndex +where + T: QbgObjectType, +{ pub fn open>(path: P) -> Result { if !is_x86_feature_detected!("avx2") { return Err(Error( @@ -134,8 +145,10 @@ impl QbgIndex { index, path, _mode: ModeRead, + obj_type: T::as_obj(), dimension, ebuf: sys::ngt_create_error_object(), + _marker: PhantomData, }) } } @@ -171,7 +184,7 @@ impl QbgIndex { } } - pub fn into_writable(self) -> Result> { + pub fn into_writable(self) -> Result> { unsafe { let ebuf = sys::ngt_create_error_object(); defer! { sys::ngt_destroy_error_object(ebuf); } @@ -193,38 +206,63 @@ impl QbgIndex { index, path, _mode: ModeWrite, + obj_type: T::as_obj(), dimension, ebuf: sys::ngt_create_error_object(), + _marker: PhantomData, }) } } } -impl QbgIndex +impl QbgIndex where - T: IndexMode, + T: QbgObjectType, + M: IndexMode, { - pub fn get_vec(&self, id: VecId) -> Result> { + /// Get the specified vector. + pub fn get_vec(&self, id: VecId) -> Result> { unsafe { - let results = sys::qbg_get_object(self.index, id, self.ebuf); - if results.is_null() { - Err(make_err(self.ebuf))? + match self.obj_type { + QbgObject::Float => { + let results = sys::qbg_get_object(self.index, id, self.ebuf); + if results.is_null() { + Err(make_err(self.ebuf))? + } + + let results = Vec::from_raw_parts( + results as *mut f32, + self.dimension as usize, + self.dimension as usize, + ); + let results = mem::ManuallyDrop::new(results); + + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) + } + QbgObject::Uint8 => { + // TODO: Would need some kind of qbg_get_object_as_integer + let results = sys::qbg_get_object(self.index, id, self.ebuf); + if results.is_null() { + Err(make_err(self.ebuf))? + } + + let results = Vec::from_raw_parts( + results as *mut f32, + self.dimension as usize, + self.dimension as usize, + ); + let results = mem::ManuallyDrop::new(results); + + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) + } } - - let results = Vec::from_raw_parts( - results as *mut f32, - self.dimension as usize, - self.dimension as usize, - ); - let results = mem::ManuallyDrop::new(results); - let results = results.iter().copied().collect::>(); - - Ok(results) } } } -impl Drop for QbgIndex { +impl Drop for QbgIndex { fn drop(&mut self) { if !self.index.is_null() { unsafe { sys::qbg_close_index(self.index) }; @@ -255,227 +293,6 @@ pub struct ModeWrite; impl private::Sealed for ModeWrite {} impl IndexMode for ModeWrite {} -#[derive(Debug, Clone, PartialEq)] -pub struct QbgConstructParams { - extended_dimension: u64, - dimension: u64, - number_of_subvectors: u64, - number_of_blobs: u64, - internal_data_type: QbgObject, - data_type: QbgObject, - distance_type: QbgDistance, -} - -impl Default for QbgConstructParams { - fn default() -> Self { - Self { - extended_dimension: 0, - dimension: 0, - number_of_subvectors: 1, - number_of_blobs: 0, - internal_data_type: QbgObject::Float, - data_type: QbgObject::Float, - distance_type: QbgDistance::L2, - } - } -} - -impl QbgConstructParams { - pub fn extended_dimension(mut self, extended_dimension: u64) -> Self { - self.extended_dimension = extended_dimension; - self - } - - pub fn dimension(mut self, dimension: u64) -> Self { - self.dimension = dimension; - self - } - - pub fn number_of_subvectors(mut self, number_of_subvectors: u64) -> Self { - self.number_of_subvectors = number_of_subvectors; - self - } - - pub fn number_of_blobs(mut self, number_of_blobs: u64) -> Self { - self.number_of_blobs = number_of_blobs; - self - } - - pub fn internal_data_type(mut self, internal_data_type: QbgObject) -> Self { - self.internal_data_type = internal_data_type; - self - } - - pub fn data_type(mut self, data_type: QbgObject) -> Self { - self.data_type = data_type; - self - } - - pub fn distance_type(mut self, distance_type: QbgDistance) -> Self { - self.distance_type = distance_type; - self - } - - unsafe fn into_raw(self) -> sys::QBGConstructionParameters { - sys::QBGConstructionParameters { - extended_dimension: self.extended_dimension, - dimension: self.dimension, - number_of_subvectors: self.number_of_subvectors, - number_of_blobs: self.number_of_blobs, - internal_data_type: self.internal_data_type as i32, - data_type: self.data_type as i32, - distance_type: self.distance_type as i32, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] -#[repr(i32)] -pub enum QbgClusteringInitMode { - Head = 0, - Random = 1, - KmeansPlusPlus = 2, - RandomFixedSeed = 3, - KmeansPlusPlusFixedSeed = 4, - Best = 5, -} - -#[derive(Debug, Clone)] -pub struct QbgBuildParams { - // hierarchical kmeans - hierarchical_clustering_init_mode: QbgClusteringInitMode, - number_of_first_objects: u64, - number_of_first_clusters: u64, - number_of_second_objects: u64, - number_of_second_clusters: u64, - number_of_third_clusters: u64, - // optimization - number_of_objects: u64, - number_of_subvectors: u64, - optimization_clustering_init_mode: QbgClusteringInitMode, - rotation_iteration: u64, - subvector_iteration: u64, - number_of_matrices: u64, - rotation: bool, - repositioning: bool, -} - -impl Default for QbgBuildParams { - fn default() -> Self { - Self { - hierarchical_clustering_init_mode: QbgClusteringInitMode::KmeansPlusPlus, - number_of_first_objects: 0, - number_of_first_clusters: 0, - number_of_second_objects: 0, - number_of_second_clusters: 0, - number_of_third_clusters: 0, - number_of_objects: 1000, - number_of_subvectors: 1, - optimization_clustering_init_mode: QbgClusteringInitMode::KmeansPlusPlus, - rotation_iteration: 2000, - subvector_iteration: 400, - number_of_matrices: 3, - rotation: true, - repositioning: false, - } - } -} - -impl QbgBuildParams { - pub fn hierarchical_clustering_init_mode( - mut self, - clustering_init_mode: QbgClusteringInitMode, - ) -> Self { - self.hierarchical_clustering_init_mode = clustering_init_mode; - self - } - - pub fn number_of_first_objects(mut self, number_of_first_objects: u64) -> Self { - self.number_of_first_objects = number_of_first_objects; - self - } - - pub fn number_of_first_clusters(mut self, number_of_first_clusters: u64) -> Self { - self.number_of_first_clusters = number_of_first_clusters; - self - } - - pub fn number_of_second_objects(mut self, number_of_second_objects: u64) -> Self { - self.number_of_second_objects = number_of_second_objects; - self - } - - pub fn number_of_second_clusters(mut self, number_of_second_clusters: u64) -> Self { - self.number_of_second_clusters = number_of_second_clusters; - self - } - - pub fn number_of_third_clusters(mut self, number_of_third_clusters: u64) -> Self { - self.number_of_third_clusters = number_of_third_clusters; - self - } - - pub fn number_of_objects(mut self, number_of_objects: u64) -> Self { - self.number_of_objects = number_of_objects; - self - } - pub fn number_of_subvectors(mut self, number_of_subvectors: u64) -> Self { - self.number_of_subvectors = number_of_subvectors; - self - } - pub fn optimization_clustering_init_mode( - mut self, - clustering_init_mode: QbgClusteringInitMode, - ) -> Self { - self.optimization_clustering_init_mode = clustering_init_mode; - self - } - - pub fn rotation_iteration(mut self, rotation_iteration: u64) -> Self { - self.rotation_iteration = rotation_iteration; - self - } - - pub fn subvector_iteration(mut self, subvector_iteration: u64) -> Self { - self.subvector_iteration = subvector_iteration; - self - } - - pub fn number_of_matrices(mut self, number_of_matrices: u64) -> Self { - self.number_of_matrices = number_of_matrices; - self - } - - pub fn rotation(mut self, rotation: bool) -> Self { - self.rotation = rotation; - self - } - - pub fn repositioning(mut self, repositioning: bool) -> Self { - self.repositioning = repositioning; - self - } - - unsafe fn into_raw(self) -> sys::QBGBuildParameters { - sys::QBGBuildParameters { - hierarchical_clustering_init_mode: self.hierarchical_clustering_init_mode as i32, - number_of_first_objects: self.number_of_first_objects, - number_of_first_clusters: self.number_of_first_clusters, - number_of_second_objects: self.number_of_second_objects, - number_of_second_clusters: self.number_of_second_clusters, - number_of_third_clusters: self.number_of_third_clusters, - number_of_objects: self.number_of_objects, - number_of_subvectors: self.number_of_subvectors, - optimization_clustering_init_mode: self.optimization_clustering_init_mode as i32, - rotation_iteration: self.rotation_iteration, - subvector_iteration: self.subvector_iteration, - number_of_matrices: self.number_of_matrices, - rotation: self.rotation, - repositioning: self.repositioning, - } - } -} - #[derive(Debug, Clone, PartialEq)] pub struct QbgQuery<'a> { query: &'a [f32], @@ -569,8 +386,7 @@ mod tests { // Create a QGB index let ndims = 3; - let mut index = - QbgIndex::create(dir.path(), QbgConstructParams::default().dimension(ndims))?; + let mut index = QbgIndex::create(dir.path(), QbgConstructParams::dimension(ndims))?; // Insert vectors and get their ids let nvecs = 16; diff --git a/src/qbg/mod.rs b/src/qbg/mod.rs index 5f1059c..0cb3da2 100644 --- a/src/qbg/mod.rs +++ b/src/qbg/mod.rs @@ -1,20 +1,9 @@ -mod index; - -use num_enum::TryFromPrimitive; +// TODO: Add module doc (specify available types) -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] -#[repr(i32)] -pub enum QbgObject { - Uint8 = 0, - Float = 1, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] -#[repr(i32)] -pub enum QbgDistance { - L2 = 1, -} +mod index; +mod properties; -pub use self::index::{ - IndexMode, ModeRead, ModeWrite, QbgBuildParams, QbgConstructParams, QbgIndex, QbgQuery, +pub use self::index::{IndexMode, ModeRead, ModeWrite, QbgIndex, QbgQuery}; +pub use self::properties::{ + QbgBuildParams, QbgConstructParams, QbgDistance, QbgObject, QbgObjectType, }; diff --git a/src/qbg/properties.rs b/src/qbg/properties.rs new file mode 100644 index 0000000..08cbe79 --- /dev/null +++ b/src/qbg/properties.rs @@ -0,0 +1,260 @@ +use std::marker::PhantomData; + +use ngt_sys as sys; +use num_enum::TryFromPrimitive; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[repr(i32)] +pub enum QbgObject { + Uint8 = 0, + Float = 1, +} + +mod private { + pub trait Sealed {} +} + +pub trait QbgObjectType: private::Sealed { + fn as_obj() -> QbgObject; +} + +impl private::Sealed for f32 {} +impl QbgObjectType for f32 { + fn as_obj() -> QbgObject { + QbgObject::Float + } +} + +impl private::Sealed for u8 {} +impl QbgObjectType for u8 { + fn as_obj() -> QbgObject { + QbgObject::Uint8 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[repr(i32)] +pub enum QbgDistance { + L2 = 1, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct QbgConstructParams { + extended_dimension: u64, + dimension: u64, + number_of_subvectors: u64, + number_of_blobs: u64, + internal_data_type: QbgObject, + data_type: QbgObject, + distance_type: QbgDistance, + _marker: PhantomData, +} + +impl QbgConstructParams +where + T: QbgObjectType, +{ + pub fn dimension(dimension: u64) -> Self { + let extended_dimension = 0; + let number_of_subvectors = 1; + let number_of_blobs = 0; + let internal_data_type = QbgObject::Float; // TODO: Should be T::as_obj() ? + let data_type = T::as_obj(); + let distance_type = QbgDistance::L2; + + Self { + extended_dimension, + dimension, + number_of_subvectors, + number_of_blobs, + internal_data_type, + data_type, + distance_type, + _marker: PhantomData, + } + } + + pub fn extended_dimension(mut self, extended_dimension: u64) -> Self { + self.extended_dimension = extended_dimension; + self + } + + pub fn number_of_subvectors(mut self, number_of_subvectors: u64) -> Self { + self.number_of_subvectors = number_of_subvectors; + self + } + + pub fn number_of_blobs(mut self, number_of_blobs: u64) -> Self { + self.number_of_blobs = number_of_blobs; + self + } + + pub fn internal_data_type(mut self, internal_data_type: QbgObject) -> Self { + self.internal_data_type = internal_data_type; + self + } + + pub fn distance_type(mut self, distance_type: QbgDistance) -> Self { + self.distance_type = distance_type; + self + } + + pub(crate) unsafe fn into_raw(self) -> sys::QBGConstructionParameters { + sys::QBGConstructionParameters { + extended_dimension: self.extended_dimension, + dimension: self.dimension, + number_of_subvectors: self.number_of_subvectors, + number_of_blobs: self.number_of_blobs, + internal_data_type: self.internal_data_type as i32, + data_type: self.data_type as i32, + distance_type: self.distance_type as i32, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[repr(i32)] +pub enum QbgClusteringInitMode { + Head = 0, + Random = 1, + KmeansPlusPlus = 2, + RandomFixedSeed = 3, + KmeansPlusPlusFixedSeed = 4, + Best = 5, +} + +#[derive(Debug, Clone)] +pub struct QbgBuildParams { + // hierarchical kmeans + hierarchical_clustering_init_mode: QbgClusteringInitMode, + number_of_first_objects: u64, + number_of_first_clusters: u64, + number_of_second_objects: u64, + number_of_second_clusters: u64, + number_of_third_clusters: u64, + // optimization + number_of_objects: u64, + number_of_subvectors: u64, + optimization_clustering_init_mode: QbgClusteringInitMode, + rotation_iteration: u64, + subvector_iteration: u64, + number_of_matrices: u64, + rotation: bool, + repositioning: bool, +} + +impl Default for QbgBuildParams { + fn default() -> Self { + Self { + hierarchical_clustering_init_mode: QbgClusteringInitMode::KmeansPlusPlus, + number_of_first_objects: 0, + number_of_first_clusters: 0, + number_of_second_objects: 0, + number_of_second_clusters: 0, + number_of_third_clusters: 0, + number_of_objects: 1000, + number_of_subvectors: 1, + optimization_clustering_init_mode: QbgClusteringInitMode::KmeansPlusPlus, + rotation_iteration: 2000, + subvector_iteration: 400, + number_of_matrices: 3, + rotation: true, + repositioning: false, + } + } +} + +impl QbgBuildParams { + pub fn hierarchical_clustering_init_mode( + mut self, + clustering_init_mode: QbgClusteringInitMode, + ) -> Self { + self.hierarchical_clustering_init_mode = clustering_init_mode; + self + } + + pub fn number_of_first_objects(mut self, number_of_first_objects: u64) -> Self { + self.number_of_first_objects = number_of_first_objects; + self + } + + pub fn number_of_first_clusters(mut self, number_of_first_clusters: u64) -> Self { + self.number_of_first_clusters = number_of_first_clusters; + self + } + + pub fn number_of_second_objects(mut self, number_of_second_objects: u64) -> Self { + self.number_of_second_objects = number_of_second_objects; + self + } + + pub fn number_of_second_clusters(mut self, number_of_second_clusters: u64) -> Self { + self.number_of_second_clusters = number_of_second_clusters; + self + } + + pub fn number_of_third_clusters(mut self, number_of_third_clusters: u64) -> Self { + self.number_of_third_clusters = number_of_third_clusters; + self + } + + pub fn number_of_objects(mut self, number_of_objects: u64) -> Self { + self.number_of_objects = number_of_objects; + self + } + pub fn number_of_subvectors(mut self, number_of_subvectors: u64) -> Self { + self.number_of_subvectors = number_of_subvectors; + self + } + pub fn optimization_clustering_init_mode( + mut self, + clustering_init_mode: QbgClusteringInitMode, + ) -> Self { + self.optimization_clustering_init_mode = clustering_init_mode; + self + } + + pub fn rotation_iteration(mut self, rotation_iteration: u64) -> Self { + self.rotation_iteration = rotation_iteration; + self + } + + pub fn subvector_iteration(mut self, subvector_iteration: u64) -> Self { + self.subvector_iteration = subvector_iteration; + self + } + + pub fn number_of_matrices(mut self, number_of_matrices: u64) -> Self { + self.number_of_matrices = number_of_matrices; + self + } + + pub fn rotation(mut self, rotation: bool) -> Self { + self.rotation = rotation; + self + } + + pub fn repositioning(mut self, repositioning: bool) -> Self { + self.repositioning = repositioning; + self + } + + pub(crate) unsafe fn into_raw(self) -> sys::QBGBuildParameters { + sys::QBGBuildParameters { + hierarchical_clustering_init_mode: self.hierarchical_clustering_init_mode as i32, + number_of_first_objects: self.number_of_first_objects, + number_of_first_clusters: self.number_of_first_clusters, + number_of_second_objects: self.number_of_second_objects, + number_of_second_clusters: self.number_of_second_clusters, + number_of_third_clusters: self.number_of_third_clusters, + number_of_objects: self.number_of_objects, + number_of_subvectors: self.number_of_subvectors, + optimization_clustering_init_mode: self.optimization_clustering_init_mode as i32, + rotation_iteration: self.rotation_iteration, + subvector_iteration: self.subvector_iteration, + number_of_matrices: self.number_of_matrices, + rotation: self.rotation, + repositioning: self.repositioning, + } + } +} diff --git a/src/qg/index.rs b/src/qg/index.rs index 64fb291..9f142aa 100644 --- a/src/qg/index.rs +++ b/src/qg/index.rs @@ -7,21 +7,25 @@ use std::ptr; use ngt_sys as sys; use scopeguard::defer; -use super::{QgObject, QgProperties}; +use super::{QgObject, QgObjectType, QgProperties, QgQuantizationParams}; use crate::error::{make_err, Error, Result}; use crate::ngt::NgtIndex; use crate::{SearchResult, VecId}; #[derive(Debug)] -pub struct QgIndex { - pub(crate) prop: QgProperties, +pub struct QgIndex { + pub(crate) prop: QgProperties, pub(crate) index: sys::NGTQGIndex, ebuf: sys::NGTError, } -impl QgIndex { +impl QgIndex +where + T: QgObjectType, +{ /// Quantize an NGT index - pub fn quantize(index: NgtIndex, params: QgQuantizationParams) -> Result { + pub fn quantize(index: NgtIndex, params: QgQuantizationParams) -> Result { + // if !is_x86_feature_detected!("avx2") { return Err(Error( "Cannot quantize an index without AVX2 support".into(), @@ -75,7 +79,7 @@ impl QgIndex { } } - pub fn search(&self, query: QgQuery) -> Result> { + pub fn search(&self, query: QgQuery) -> Result> { unsafe { let results = sys::ngt_create_empty_results(self.ebuf); if results.is_null() { @@ -107,9 +111,9 @@ impl QgIndex { } /// Get the specified vector. - pub fn get_vec(&self, id: VecId) -> Result> { + pub fn get_vec(&self, id: VecId) -> Result> { unsafe { - let results = match self.prop.object_type { + match self.prop.object_type { QgObject::Float => { let ospace = sys::ngt_get_object_space(self.index, self.ebuf); if ospace.is_null() { @@ -128,7 +132,8 @@ impl QgIndex { ); let results = mem::ManuallyDrop::new(results); - results.iter().copied().collect::>() + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) } QgObject::Uint8 => { let ospace = sys::ngt_get_object_space(self.index, self.ebuf); @@ -148,16 +153,15 @@ impl QgIndex { ); let results = mem::ManuallyDrop::new(results); - results.iter().map(|byte| *byte as f32).collect::>() + let results = results.iter().copied().collect::>(); + Ok(mem::transmute::<_, Vec>(results)) } - }; - - Ok(results) + } } } } -impl Drop for QgIndex { +impl Drop for QgIndex { fn drop(&mut self) { if !self.index.is_null() { unsafe { sys::ngtqg_close_index(self.index) }; @@ -171,40 +175,16 @@ impl Drop for QgIndex { } #[derive(Debug, Clone, PartialEq)] -pub struct QgQuantizationParams { - pub dimension_of_subvector: f32, - pub max_number_of_edges: u64, -} - -impl Default for QgQuantizationParams { - fn default() -> Self { - Self { - dimension_of_subvector: 0.0, - max_number_of_edges: 128, - } - } -} - -impl QgQuantizationParams { - unsafe fn into_raw(self) -> sys::NGTQGQuantizationParameters { - sys::NGTQGQuantizationParameters { - dimension_of_subvector: self.dimension_of_subvector, - max_number_of_edges: self.max_number_of_edges, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct QgQuery<'a> { - query: &'a [f32], +pub struct QgQuery<'a, T> { + query: &'a [T], pub size: u64, pub epsilon: f32, pub result_expansion: f32, pub radius: f32, } -impl<'a> QgQuery<'a> { - pub fn new(query: &'a [f32]) -> Self { +impl<'a, T> QgQuery<'a, T> { + pub fn new(query: &'a [T]) -> Self { Self { query, size: 20, @@ -254,7 +234,7 @@ mod tests { use tempfile::tempdir; use super::*; - use crate::{NgtDistance, NgtObject, NgtProperties}; + use crate::{NgtDistance, NgtProperties}; #[test] fn test_qg() -> StdResult<(), Box> { @@ -263,19 +243,17 @@ mod tests { // Create an NGT index for vectors let ndims = 3; - let props = NgtProperties::dimension(ndims)? - .object_type(NgtObject::Uint8)? - .distance_type(NgtDistance::L2)?; + let props = NgtProperties::::dimension(ndims)?.distance_type(NgtDistance::L2)?; let mut index = NgtIndex::create(dir.path(), props)?; // Insert vectors and get their ids - let nvecs = 16; + let nvecs = 64; let ids = (1..ndims * nvecs) .step_by(ndims) - .map(|i| i as f32) + .map(|i| i as u8) .map(|i| { repeat(i) - .zip((0..ndims).map(|j| j as f32)) + .zip((0..ndims).map(|j| j as u8)) .map(|(i, j)| i + j) .collect() }) @@ -294,7 +272,7 @@ mod tests { let index = QgIndex::quantize(index, params)?; // Perform a vector search (with 2 results) - let v: Vec = (1..=ndims).into_iter().map(|x| x as f32).collect(); + let v: Vec = (1..=ndims).into_iter().map(|x| x as u8).collect(); let query = QgQuery::new(&v).size(2); let res = index.search(query)?; assert_eq!(ids[0], res[0].id); diff --git a/src/qg/mod.rs b/src/qg/mod.rs index 21d170a..eb774b0 100644 --- a/src/qg/mod.rs +++ b/src/qg/mod.rs @@ -1,5 +1,9 @@ +// TODO: Add module doc (specify available types) + mod index; mod properties; -pub use self::index::{QgIndex, QgQuantizationParams, QgQuery}; -pub use self::properties::{QgDistance, QgObject, QgProperties}; +pub use self::index::{QgIndex, QgQuery}; +pub use self::properties::{ + QgDistance, QgObject, QgObjectType, QgProperties, QgQuantizationParams, +}; diff --git a/src/qg/properties.rs b/src/qg/properties.rs index 8112080..4cb23ea 100644 --- a/src/qg/properties.rs +++ b/src/qg/properties.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::ptr; use ngt_sys as sys; @@ -13,6 +14,28 @@ pub enum QgObject { Float = 2, } +mod private { + pub trait Sealed {} +} + +pub trait QgObjectType: private::Sealed { + fn as_obj() -> QgObject; +} + +impl private::Sealed for f32 {} +impl QgObjectType for f32 { + fn as_obj() -> QgObject { + QgObject::Float + } +} + +impl private::Sealed for u8 {} +impl QgObjectType for u8 { + fn as_obj() -> QgObject { + QgObject::Uint8 + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] #[repr(i32)] pub enum QgDistance { @@ -21,24 +44,28 @@ pub enum QgDistance { } #[derive(Debug)] -pub struct QgProperties { +pub struct QgProperties { pub(crate) dimension: i32, pub(crate) creation_edge_size: i16, pub(crate) search_edge_size: i16, pub(crate) object_type: QgObject, pub(crate) distance_type: QgDistance, pub(crate) raw_prop: sys::NGTProperty, + _marker: PhantomData, } -unsafe impl Send for QgProperties {} -unsafe impl Sync for QgProperties {} +unsafe impl Send for QgProperties {} +unsafe impl Sync for QgProperties {} -impl QgProperties { +impl QgProperties +where + T: QgObjectType, +{ pub fn dimension(dimension: usize) -> Result { let dimension = i32::try_from(dimension)?; let creation_edge_size = 10; let search_edge_size = 40; - let object_type = QgObject::Float; + let object_type = T::as_obj(); let distance_type = QgDistance::L2; unsafe { @@ -63,6 +90,7 @@ impl QgProperties { object_type, distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -90,6 +118,7 @@ impl QgProperties { object_type: self.object_type, distance_type: self.distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -142,6 +171,7 @@ impl QgProperties { object_type, distance_type, raw_prop, + _marker: PhantomData, }) } } @@ -249,7 +279,7 @@ impl QgProperties { } } -impl Drop for QgProperties { +impl Drop for QgProperties { fn drop(&mut self) { if !self.raw_prop.is_null() { unsafe { sys::ngt_destroy_property(self.raw_prop) }; @@ -257,3 +287,27 @@ impl Drop for QgProperties { } } } + +#[derive(Debug, Clone, PartialEq)] +pub struct QgQuantizationParams { + pub dimension_of_subvector: f32, + pub max_number_of_edges: u64, +} + +impl Default for QgQuantizationParams { + fn default() -> Self { + Self { + dimension_of_subvector: 0.0, + max_number_of_edges: 128, + } + } +} + +impl QgQuantizationParams { + pub(crate) fn into_raw(self) -> sys::NGTQGQuantizationParameters { + sys::NGTQGQuantizationParameters { + dimension_of_subvector: self.dimension_of_subvector, + max_number_of_edges: self.max_number_of_edges, + } + } +}