1use anyhow::Result;
19
20use crate::core::schema::{DistanceMetric, VectorIndexType};
21use crate::muvera::DEFAULT_FDE_SEED;
22
23#[derive(Debug, Default, Clone)]
26pub struct VectorIndexOpts<'a> {
27 pub type_name: Option<&'a str>,
30 pub partitions: Option<u32>,
31 pub m: Option<u32>,
32 pub ef_construction: Option<u32>,
33 pub sub_vectors: Option<u32>,
34 pub num_bits: Option<u8>,
35 pub k_sim: Option<u32>,
37 pub reps: Option<u32>,
38 pub d_proj: Option<u32>,
39 pub seed: Option<u64>,
40 pub inner: Option<&'a str>,
42}
43
44fn ann_type(o: &VectorIndexOpts, t: Option<&str>) -> VectorIndexType {
47 match t {
48 Some("flat") => VectorIndexType::Flat,
49 Some("ivf_flat") => VectorIndexType::IvfFlat {
50 num_partitions: o.partitions.unwrap_or(256),
51 },
52 Some("ivf_sq") => VectorIndexType::IvfSq {
53 num_partitions: o.partitions.unwrap_or(256),
54 },
55 Some("ivf_rq") => VectorIndexType::IvfRq {
56 num_partitions: o.partitions.unwrap_or(256),
57 num_bits: o.num_bits,
58 },
59 Some("hnsw_flat") => VectorIndexType::HnswFlat {
60 m: o.m.unwrap_or(16),
61 ef_construction: o.ef_construction.unwrap_or(200),
62 num_partitions: o.partitions,
63 },
64 Some("hnsw" | "hnsw_sq") => VectorIndexType::HnswSq {
65 m: o.m.unwrap_or(16),
66 ef_construction: o.ef_construction.unwrap_or(200),
67 num_partitions: o.partitions,
68 },
69 Some("hnsw_pq") => VectorIndexType::HnswPq {
70 m: o.m.unwrap_or(16),
71 ef_construction: o.ef_construction.unwrap_or(200),
72 num_sub_vectors: o.sub_vectors.unwrap_or(16),
73 num_partitions: o.partitions,
74 },
75 _ => VectorIndexType::IvfPq {
77 num_partitions: o.partitions.unwrap_or(256),
78 num_sub_vectors: o.sub_vectors.unwrap_or(16),
79 bits_per_subvector: o.num_bits.unwrap_or(8),
80 },
81 }
82}
83
84pub fn build_vector_index_type(o: &VectorIndexOpts) -> VectorIndexType {
94 match o.type_name {
95 Some("muvera") => VectorIndexType::Muvera {
96 k_sim: o.k_sim.unwrap_or(4),
97 reps: o.reps.unwrap_or(20),
98 d_proj: o.d_proj.unwrap_or(16),
99 seed: o.seed.unwrap_or(DEFAULT_FDE_SEED),
100 inner: Box::new(ann_type(o, o.inner)),
101 },
102 other => ann_type(o, other),
103 }
104}
105
106pub fn parse_vector_metric(s: Option<&str>) -> Result<DistanceMetric> {
109 match s.map(|m| m.to_ascii_lowercase()).as_deref() {
110 Some("l2" | "euclidean") => Ok(DistanceMetric::L2),
111 Some("dot") => Ok(DistanceMetric::Dot),
112 Some("l1" | "manhattan") => Ok(DistanceMetric::L1),
113 Some("hamming") => Ok(DistanceMetric::Hamming),
114 Some("jaccard") => Ok(DistanceMetric::Jaccard),
115 Some("cosine") | None => Ok(DistanceMetric::Cosine),
116 Some(other) => Err(anyhow::anyhow!(
117 "Unknown vector index metric '{other}' \
118 (expected cosine, l2, dot, l1, hamming, or jaccard)"
119 )),
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 fn opts(type_name: Option<&str>) -> VectorIndexOpts<'_> {
128 VectorIndexOpts {
129 type_name,
130 ..Default::default()
131 }
132 }
133
134 #[test]
135 fn default_is_ivf_pq_for_both_paths() {
136 assert!(matches!(
138 build_vector_index_type(&opts(None)),
139 VectorIndexType::IvfPq { .. }
140 ));
141 assert!(matches!(
142 build_vector_index_type(&opts(Some("nonsense"))),
143 VectorIndexType::IvfPq { .. }
144 ));
145 }
146
147 #[test]
148 fn named_types_map() {
149 assert!(matches!(
150 build_vector_index_type(&opts(Some("flat"))),
151 VectorIndexType::Flat
152 ));
153 assert!(matches!(
154 build_vector_index_type(&opts(Some("hnsw"))),
155 VectorIndexType::HnswSq { .. }
156 ));
157 }
158
159 #[test]
160 fn muvera_defaults_and_inner() {
161 let o = VectorIndexOpts {
162 type_name: Some("muvera"),
163 inner: Some("flat"),
164 ..Default::default()
165 };
166 match build_vector_index_type(&o) {
167 VectorIndexType::Muvera {
168 k_sim,
169 reps,
170 d_proj,
171 seed,
172 inner,
173 } => {
174 assert_eq!((k_sim, reps, d_proj), (4, 20, 16));
175 assert_eq!(seed, DEFAULT_FDE_SEED);
176 assert!(matches!(*inner, VectorIndexType::Flat));
177 }
178 other => panic!("expected Muvera, got {other:?}"),
179 }
180 assert!(matches!(
182 build_vector_index_type(&opts(Some("muvera"))),
183 VectorIndexType::Muvera { inner, .. } if matches!(*inner, VectorIndexType::IvfPq { .. })
184 ));
185 }
186
187 #[test]
188 fn metric_parsing() {
189 assert_eq!(parse_vector_metric(None).unwrap(), DistanceMetric::Cosine);
190 assert_eq!(parse_vector_metric(Some("L2")).unwrap(), DistanceMetric::L2);
191 assert_eq!(
192 parse_vector_metric(Some("dot")).unwrap(),
193 DistanceMetric::Dot
194 );
195 assert_eq!(parse_vector_metric(Some("l1")).unwrap(), DistanceMetric::L1);
196 assert_eq!(
197 parse_vector_metric(Some("hamming")).unwrap(),
198 DistanceMetric::Hamming
199 );
200 assert_eq!(
201 parse_vector_metric(Some("jaccard")).unwrap(),
202 DistanceMetric::Jaccard
203 );
204 assert!(parse_vector_metric(Some("no_such_metric")).is_err());
205 }
206}