Skip to main content

holos_tda/distances/
sparse.rs

1use crate::{Error, Result};
2
3#[allow(unused_imports)]
4use super::matrix::DistanceMatrix;
5
6/// Sparse dissimilarities: only listed pairs have finite distance. An
7/// unlisted pair is an absent edge (+inf). No metric assumptions, same
8/// entry rules as [`DistanceMatrix`].
9///
10/// The neighbor lists live in one compressed block: an offset for each
11/// vertex, then the neighbor vertices as `u32` and their distances in two
12/// arrays of the same length. A list is sorted by neighbor vertex. The
13/// cofacet merge then walks four bytes an entry and reads a distance only
14/// where two lists meet.
15#[derive(Debug, Clone)]
16pub struct SparseDistanceMatrix {
17    pub(super) n: usize,
18    /// Where each vertex's neighbor list starts, plus the total at the end.
19    /// Length `n + 1`.
20    pub(super) offsets: Vec<usize>,
21    /// Neighbor vertices, per vertex ascending. `from_triplets` rejects an
22    /// `n` above `u32::MAX`, so a vertex fits in a `u32`.
23    pub(super) indices: Vec<u32>,
24    /// The distance to the neighbor at the same position in `indices`.
25    pub(super) values: Vec<f64>,
26    /// The largest stored distance, or 0 when no pair is stored.
27    pub(super) max_distance: f64,
28}
29
30impl SparseDistanceMatrix {
31    pub(super) fn from_lower_rows(n: usize, rows: &[Vec<(usize, f64)>]) -> Result<Self> {
32        debug_assert_eq!(rows.len(), n);
33        let mut degree = vec![0usize; n];
34        let mut max_distance = 0.0f64;
35        for (i, row) in rows.iter().enumerate() {
36            debug_assert!(row.is_sorted_by_key(|&(j, _)| j));
37            for &(j, distance) in row {
38                debug_assert!(j < i);
39                degree[i] += 1;
40                degree[j] += 1;
41                max_distance = max_distance.max(distance);
42            }
43        }
44        let mut offsets = vec![0usize; n + 1];
45        let mut total = 0usize;
46        for (vertex, &count) in degree.iter().enumerate() {
47            offsets[vertex] = total;
48            total = total
49                .checked_add(count)
50                .ok_or_else(|| Error::InvalidInput("sparse edge storage overflows usize".into()))?;
51        }
52        offsets[n] = total;
53        let mut indices = vec![0u32; total];
54        let mut values = vec![0.0; total];
55        let mut cursor = offsets[..n].to_vec();
56        for (i, row) in rows.iter().enumerate() {
57            for &(j, distance) in row {
58                indices[cursor[i]] = j as u32;
59                values[cursor[i]] = distance;
60                cursor[i] += 1;
61                indices[cursor[j]] = i as u32;
62                values[cursor[j]] = distance;
63                cursor[j] += 1;
64            }
65        }
66        // Lower neighbors arrive first in ascending order. Higher neighbors
67        // arrive later as their rows are visited, also in ascending order.
68        debug_assert!((0..n).all(|v| {
69            let start = offsets[v];
70            let end = offsets[v + 1];
71            indices[start..end].is_sorted()
72        }));
73        Ok(Self {
74            n,
75            offsets,
76            indices,
77            values,
78            max_distance,
79        })
80    }
81
82    /// Build from `(i, j, d)` triplets over `n` points. A repeated unordered
83    /// pair must carry an identical distance. Entries must be finite and
84    /// non-negative. Omit a pair to make it absent. `n` must be at or below
85    /// `u32::MAX`.
86    pub fn from_triplets(n: usize, triplets: &[(usize, usize, f64)]) -> Result<Self> {
87        let degree = validate_triplets(n, triplets)?;
88        let mut offsets = offsets_from_degrees(&degree);
89        let (mut indices, mut values) = fill_neighbor_storage(triplets, &offsets);
90        let write = compact_neighbor_storage(n, &degree, &mut offsets, &mut indices, &mut values)?;
91        offsets[n] = write;
92        indices.truncate(write);
93        values.truncate(write);
94
95        let max_distance = triplets.iter().fold(0.0f64, |m, &(_, _, d)| m.max(d));
96        Ok(Self {
97            n,
98            offsets,
99            indices,
100            values,
101            max_distance,
102        })
103    }
104
105    /// Number of points.
106    pub fn len(&self) -> usize {
107        self.n
108    }
109
110    /// True when there are no points.
111    pub fn is_empty(&self) -> bool {
112        self.n == 0
113    }
114
115    /// Number of stored edges.
116    pub fn num_edges(&self) -> usize {
117        self.indices.len() / 2
118    }
119
120    /// Where vertex `v`'s neighbor list sits in `indices` and `values`.
121    #[inline]
122    pub(super) fn span(&self, v: usize) -> (usize, usize) {
123        (self.offsets[v], self.offsets[v + 1])
124    }
125
126    /// How many neighbors vertex `v` has.
127    #[cfg(test)]
128    #[inline]
129    pub(super) fn degree(&self, v: usize) -> usize {
130        self.offsets[v + 1] - self.offsets[v]
131    }
132
133    /// Distance between `i` and `j`; +inf when the pair is not listed.
134    #[inline]
135    pub fn get(&self, i: usize, j: usize) -> f64 {
136        debug_assert!(i < self.n && j < self.n);
137        if i == j {
138            return 0.0;
139        }
140        let (start, end) = self.span(i);
141        match self.indices[start..end].binary_search(&(j as u32)) {
142            Ok(pos) => self.values[start + pos],
143            Err(_) => f64::INFINITY,
144        }
145    }
146
147    /// Visit every stored edge once, as `(u, v, value)` with `u < v`, in
148    /// ascending `u` then `v` order.
149    pub fn edges(&self) -> impl Iterator<Item = (usize, usize, f64)> + '_ {
150        (0..self.n).flat_map(move |u| {
151            let (start, end) = self.span(u);
152            self.indices[start..end]
153                .iter()
154                .zip(&self.values[start..end])
155                .filter(move |&(&v, _)| u < v as usize)
156                .map(move |(&v, &d)| (u, v as usize, d))
157        })
158    }
159}
160
161fn validate_triplets(n: usize, triplets: &[(usize, usize, f64)]) -> Result<Vec<usize>> {
162    if n > u32::MAX as usize {
163        return Err(Error::InvalidInput(format!(
164            "sparse matrix holds at most {} points, got {n}",
165            u32::MAX
166        )));
167    }
168    let mut degree = vec![0usize; n];
169    for (index, &(i, j, distance)) in triplets.iter().enumerate() {
170        validate_triplet(index, i, j, distance, n)?;
171        degree[i] += 1;
172        degree[j] += 1;
173    }
174    Ok(degree)
175}
176
177fn validate_triplet(index: usize, i: usize, j: usize, distance: f64, n: usize) -> Result<()> {
178    if i >= n || j >= n {
179        return Err(Error::InvalidInput(format!(
180            "triplet {index}: vertex out of range ({i}, {j}) for n = {n}"
181        )));
182    }
183    if i == j {
184        return Err(Error::InvalidInput(format!(
185            "triplet {index}: self-distance for vertex {i}"
186        )));
187    }
188    if !distance.is_finite() || distance < 0.0 {
189        return Err(Error::InvalidDistance(format!(
190            "triplet {index}: distance must be finite and non-negative, got {distance}"
191        )));
192    }
193    Ok(())
194}
195
196fn offsets_from_degrees(degree: &[usize]) -> Vec<usize> {
197    let mut offsets = vec![0usize; degree.len() + 1];
198    let mut total = 0usize;
199    for (vertex, &value) in degree.iter().enumerate() {
200        offsets[vertex] = total;
201        total += value;
202    }
203    offsets[degree.len()] = total;
204    offsets
205}
206
207fn fill_neighbor_storage(
208    triplets: &[(usize, usize, f64)],
209    offsets: &[usize],
210) -> (Vec<u32>, Vec<f64>) {
211    let total = offsets.last().copied().unwrap_or(0);
212    let mut indices = vec![0u32; total];
213    let mut values = vec![0.0f64; total];
214    let mut cursor = offsets[..offsets.len() - 1].to_vec();
215    for &(i, j, distance) in triplets {
216        let distance = if distance == 0.0 { 0.0 } else { distance };
217        indices[cursor[i]] = j as u32;
218        values[cursor[i]] = distance;
219        cursor[i] += 1;
220        indices[cursor[j]] = i as u32;
221        values[cursor[j]] = distance;
222        cursor[j] += 1;
223    }
224    (indices, values)
225}
226
227fn compact_neighbor_storage(
228    n: usize,
229    degree: &[usize],
230    offsets: &mut [usize],
231    indices: &mut [u32],
232    values: &mut [f64],
233) -> Result<usize> {
234    let widest = degree.iter().copied().max().unwrap_or(0);
235    let mut list = Vec::<(u32, f64)>::with_capacity(widest);
236    let mut write = 0usize;
237    for vertex in 0..n {
238        let (start, end) = (offsets[vertex], offsets[vertex + 1]);
239        offsets[vertex] = write;
240        if indices[start..end].is_sorted_by(|a, b| a < b) {
241            copy_sorted_neighbors(start, end, write, indices, values);
242            write += end - start;
243        } else {
244            write = sort_and_copy_neighbors(vertex, start, end, write, indices, values, &mut list)?;
245        }
246    }
247    Ok(write)
248}
249
250fn copy_sorted_neighbors(
251    start: usize,
252    end: usize,
253    write: usize,
254    indices: &mut [u32],
255    values: &mut [f64],
256) {
257    if start != write {
258        indices.copy_within(start..end, write);
259        values.copy_within(start..end, write);
260    }
261}
262
263fn sort_and_copy_neighbors(
264    vertex: usize,
265    start: usize,
266    end: usize,
267    mut write: usize,
268    indices: &mut [u32],
269    values: &mut [f64],
270    list: &mut Vec<(u32, f64)>,
271) -> Result<usize> {
272    list.clear();
273    list.extend(
274        indices[start..end]
275            .iter()
276            .zip(&values[start..end])
277            .map(|(&neighbor, &distance)| (neighbor, distance)),
278    );
279    list.sort_unstable_by(|left, right| left.0.cmp(&right.0).then(left.1.total_cmp(&right.1)));
280    reject_conflicting_neighbors(vertex, list)?;
281    list.dedup_by(|left, right| left.0 == right.0 && left.1 == right.1);
282    for &(neighbor, distance) in list.iter() {
283        indices[write] = neighbor;
284        values[write] = distance;
285        write += 1;
286    }
287    Ok(write)
288}
289
290fn reject_conflicting_neighbors(vertex: usize, neighbors: &[(u32, f64)]) -> Result<()> {
291    for pair in neighbors.windows(2) {
292        if pair[0].0 == pair[1].0 && pair[0].1 != pair[1].1 {
293            return Err(Error::InvalidInput(format!(
294                "conflicting distances for pair ({vertex}, {}): {} vs {}",
295                pair[0].0, pair[0].1, pair[1].1
296            )));
297        }
298    }
299    Ok(())
300}