Skip to main content

uqa_storage/diskann_index/
pq.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use std::ops::Range;
8
9use uqa_core::memory::{BudgetedVec, MemoryError};
10
11use super::metric::{checkpoint, squared_distance};
12use super::NavigationVector;
13use crate::{read_control::StorageReadControl, StorageBackendError, StorageBackendResult};
14
15mod lloyd;
16mod training;
17
18pub use training::{PQTrainer, PQTrainingOptions, PQTrainingSummary};
19
20/// Euclidean chunk centroids. Resident ownership retains its allocation allowance, independently of query cancellation.
21#[derive(Debug)]
22pub struct PQCodebook {
23    dimensions: usize,
24    pq_bytes: usize,
25    centroid_count: u16,
26    centroids: BudgetedVec<f64>,
27    training: PQTrainingSummary,
28}
29
30#[derive(Debug)]
31pub struct PQLookupTable {
32    pq_bytes: usize,
33    centroid_count: u16,
34    distances: BudgetedVec<f64>,
35}
36
37/// Approximate navigation value; it is neither a canonical cosine score nor calibrated evidence.
38#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
39pub struct PQDistance(f64);
40
41impl PQDistance {
42    pub fn get(self) -> f64 {
43        self.0
44    }
45}
46
47impl PQCodebook {
48    pub const CODEC_REVISION: u32 = 1;
49    pub const TRAINING_REVISION: u32 = 1;
50
51    pub(in crate::diskann_index) fn restore(
52        dimensions: u32,
53        pq_bytes: usize,
54        centroid_count: u16,
55        training: PQTrainingSummary,
56        centroids: BudgetedVec<f64>,
57        control: &StorageReadControl,
58    ) -> StorageBackendResult<Self> {
59        control.check()?;
60        if centroids.len() != training.validate(dimensions, pq_bytes, centroid_count)? {
61            return Err(invalid("centroid scalar count differs from dimensions"));
62        }
63        for (offset, &value) in centroids.iter().enumerate() {
64            checkpoint(offset, control)?;
65            // Unit-coordinate means stay within this conservative rounding envelope for u32 dimensions and sample counts.
66            if !value.is_finite() || value.abs() > 2.0 {
67                return Err(invalid(
68                    "centroid exceeds finite navigation coordinate bounds",
69                ));
70            }
71        }
72        control.check()?;
73        Ok(Self {
74            dimensions: dimensions as usize,
75            pq_bytes,
76            centroid_count,
77            centroids,
78            training,
79        })
80    }
81
82    pub fn dimensions(&self) -> usize {
83        self.dimensions
84    }
85
86    pub fn pq_bytes(&self) -> usize {
87        self.pq_bytes
88    }
89
90    pub fn centroid_count(&self) -> u16 {
91        self.centroid_count
92    }
93
94    pub fn training(&self) -> PQTrainingSummary {
95        self.training
96    }
97
98    pub fn chunk_range(&self, chunk: usize) -> Option<Range<usize>> {
99        (chunk < self.pq_bytes).then(|| chunk_range(self.dimensions, self.pq_bytes, chunk))
100    }
101
102    /// Centroid-major coordinates for one contiguous chunk, using scalar `f64` values.
103    pub fn chunk_centroids(&self, chunk: usize) -> Option<&[f64]> {
104        let range = self.chunk_range(chunk)?;
105        let count = usize::from(self.centroid_count);
106        Some(&self.centroids[range.start * count..range.end * count])
107    }
108
109    pub fn encode(
110        &self,
111        vector: &NavigationVector,
112        control: &StorageReadControl,
113    ) -> StorageBackendResult<BudgetedVec<u8>> {
114        self.encode_coordinates(vector.coordinates(), control)
115    }
116
117    fn encode_coordinates(
118        &self,
119        coordinates: &[f64],
120        control: &StorageReadControl,
121    ) -> StorageBackendResult<BudgetedVec<u8>> {
122        self.check_dimensions(coordinates, control)?;
123        let mut codes = BudgetedVec::new(control.memory());
124        codes.reserve(self.pq_bytes)?;
125        for chunk in 0..self.pq_bytes {
126            let range = chunk_range(self.dimensions, self.pq_bytes, chunk);
127            let count = usize::from(self.centroid_count);
128            let centroids = &self.centroids[range.start * count..range.end * count];
129            codes.push(nearest(&coordinates[range], centroids, control)?)?;
130        }
131        control.check()?;
132        Ok(codes)
133    }
134
135    pub fn lookup(
136        &self,
137        query: &NavigationVector,
138        control: &StorageReadControl,
139    ) -> StorageBackendResult<PQLookupTable> {
140        self.lookup_coordinates(query.coordinates(), control)
141    }
142
143    fn lookup_coordinates(
144        &self,
145        coordinates: &[f64],
146        control: &StorageReadControl,
147    ) -> StorageBackendResult<PQLookupTable> {
148        self.check_dimensions(coordinates, control)?;
149        let mut distances = BudgetedVec::new(control.memory());
150        let count = usize::from(self.centroid_count);
151        distances.reserve(product(self.pq_bytes, count)?)?;
152        for chunk in 0..self.pq_bytes {
153            let range = chunk_range(self.dimensions, self.pq_bytes, chunk);
154            let centroids = &self.centroids[range.start * count..range.end * count];
155            for centroid in centroids.chunks_exact(range.len()) {
156                distances.push(squared_distance(
157                    &coordinates[range.clone()],
158                    centroid,
159                    control,
160                )?)?;
161            }
162        }
163        control.check()?;
164        Ok(PQLookupTable {
165            pq_bytes: self.pq_bytes,
166            centroid_count: self.centroid_count,
167            distances,
168        })
169    }
170
171    fn check_dimensions(
172        &self,
173        coordinates: &[f64],
174        control: &StorageReadControl,
175    ) -> StorageBackendResult<()> {
176        control.check()?;
177        if coordinates.len() != self.dimensions {
178            return Err(invalid("vector dimensions do not match the codebook"));
179        }
180        Ok(())
181    }
182}
183
184impl PQLookupTable {
185    /// Labels must belong to this query's codebook; the generation owner supplies that identity boundary.
186    pub fn estimate(
187        &self,
188        codes: &[u8],
189        control: &StorageReadControl,
190    ) -> StorageBackendResult<PQDistance> {
191        control.check()?;
192        if codes.len() != self.pq_bytes {
193            return Err(invalid("code length does not match the codebook"));
194        }
195        let mut distance = 0.0;
196        for (chunk, &label) in codes.iter().enumerate() {
197            checkpoint(chunk, control)?;
198            if u16::from(label) >= self.centroid_count {
199                return Err(invalid("code label exceeds the actual centroid count"));
200            }
201            distance +=
202                self.distances[chunk * usize::from(self.centroid_count) + usize::from(label)];
203        }
204        Ok(PQDistance(distance))
205    }
206}
207
208pub(in crate::diskann_index) fn chunk_range(
209    dimensions: usize,
210    chunks: usize,
211    chunk: usize,
212) -> Range<usize> {
213    let width = dimensions / chunks;
214    let extra = dimensions % chunks;
215    let start = chunk * width + chunk.min(extra);
216    start..start + width + usize::from(chunk < extra)
217}
218
219fn nearest(
220    coordinates: &[f64],
221    centroids: &[f64],
222    control: &StorageReadControl,
223) -> StorageBackendResult<u8> {
224    let mut label = 0;
225    let mut minimum = f64::INFINITY;
226    for (index, centroid) in centroids.chunks_exact(coordinates.len()).enumerate() {
227        let distance = squared_distance(coordinates, centroid, control)?;
228        if distance < minimum {
229            minimum = distance;
230            label = index as u8;
231        }
232    }
233    Ok(label)
234}
235
236fn product(left: usize, right: usize) -> StorageBackendResult<usize> {
237    left.checked_mul(right)
238        .ok_or_else(|| MemoryError::SizeOverflow.into())
239}
240
241fn filled<T: Copy>(
242    len: usize,
243    value: T,
244    control: &StorageReadControl,
245) -> StorageBackendResult<BudgetedVec<T>> {
246    let mut values = BudgetedVec::new(control.memory());
247    values.reserve(len)?;
248    for offset in 0..len {
249        checkpoint(offset, control)?;
250        values.push(value)?;
251    }
252    Ok(values)
253}
254
255fn invalid(message: &str) -> StorageBackendError {
256    StorageBackendError::Other(format!("invalid DiskANN PQ: {message}"))
257}
258
259#[cfg(test)]
260mod tests;