pub const LEGACY_IVF_PQ_TYPE: u8 = 2;
pub const FLAT_TYPE: u8 = 4;
pub const BINARY_IVF_TYPE: u8 = 6;
pub const TQ_FLAT_TYPE: u8 = 7;
pub const IVF_TQ_TYPE: u8 = 8;
#[cfg(feature = "native")]
use crate::structures::CoarseCentroids;
#[cfg(feature = "native")]
pub fn build_ivf_tq(
dim: usize,
routing: crate::dsl::IvfRoutingMode,
centroids: &CoarseCentroids,
doc_id_ordinals: &[(u32, u16)],
vectors: &[f32],
) -> crate::Result<Vec<u8>> {
if !crate::structures::is_ivf_tq_cosine_generation(centroids.version) {
return Err(crate::Error::Corruption(
"legacy raw IVF-TQ centroids cannot encode a correct cosine index; \
rebuild the index with a current Hermes version"
.into(),
));
}
let codec = crate::structures::vector::quantization::tq_shared_codec(dim);
let mut index = crate::structures::IvfTqIndex::new(dim, routing, centroids.version, codec);
index
.add_vectors_parallel(centroids, doc_id_ordinals, vectors)
.map_err(|error| crate::Error::Internal(format!("IVF-TQ encode failed: {error}")))?;
let mut bytes = Vec::new();
crate::segment::ann_disk::write_built_ivf_tq(&index, centroids.num_clusters, &mut bytes)
.map_err(crate::Error::Io)?;
Ok(bytes)
}
#[cfg(feature = "native")]
pub fn build_tq_flat(
dim: usize,
doc_id_ordinals: &[(u32, u16)],
vectors: &[f32],
) -> crate::Result<Vec<u8>> {
let codec = crate::structures::vector::quantization::tq_shared_codec(dim);
let mut builder = crate::structures::TqFlatBuilder::new(codec);
builder
.add_batch(doc_id_ordinals, vectors)
.map_err(|error| crate::Error::Internal(format!("TQ encode failed: {error}")))?;
builder.finish();
let mut bytes = Vec::new();
crate::segment::ann_disk::write_built_tq_flat(&builder, &mut bytes)
.map_err(crate::Error::Io)?;
Ok(bytes)
}
#[cfg(all(test, feature = "native"))]
mod tests {
use super::*;
use crate::directories::OwnedBytes;
use crate::dsl::IvfRoutingMode;
use crate::segment::ann_disk::{AnnDiskIndex, AnnKind};
#[test]
fn ivf_tq_header_preserves_cosine_generation_marker() {
let marked = crate::structures::mark_ivf_tq_cosine_generation(7);
let centroids = CoarseCentroids {
num_clusters: 1,
dim: 2,
centroids: vec![1.0, 0.0],
version: marked,
soar_config: None,
routing_index: None,
};
let bytes = build_ivf_tq(
2,
IvfRoutingMode::Flat,
¢roids,
&[(0, 0)],
&[100.0, 0.0],
)
.unwrap();
let disk = AnnDiskIndex::open(OwnedBytes::new(bytes), AnnKind::IvfTq, 1).unwrap();
assert_eq!(disk.header().quantizer_version, marked);
assert!(crate::structures::is_ivf_tq_cosine_generation(
disk.header().quantizer_version
));
}
#[test]
fn ivf_tq_build_rejects_legacy_raw_generation() {
let centroids = CoarseCentroids {
num_clusters: 1,
dim: 2,
centroids: vec![1.0, 0.0],
version: 7,
soar_config: None,
routing_index: None,
};
let error = build_ivf_tq(2, IvfRoutingMode::Flat, ¢roids, &[(0, 0)], &[1.0, 0.0])
.expect_err("legacy IVF-TQ generation must not encode new segments")
.to_string();
assert!(error.contains("legacy raw IVF-TQ"), "{error}");
assert!(error.contains("rebuild the index"), "{error}");
}
}