1use clap::ValueEnum;
2
3pub struct ValuesIndicesPointers<'a> {
4 pub values: &'a [f32],
5 pub indices: &'a [u64],
6 pub indptr: &'a [u64],
7}
8
9pub struct CooTripletsShape {
10 pub triplets: Vec<(u64, u64, f32)>,
11 pub shape: TripletsShape,
12}
13
14pub struct TripletsShape {
15 pub nrows: usize,
16 pub ncols: usize,
17 pub nnz: usize,
18}
19
20#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
21#[clap(rename_all = "lowercase")]
22pub enum IndexPointerType {
23 Column,
24 Row,
25}
26
27pub trait SparseTripletsTraits {
28 fn to_coo(&self, pointer_type: IndexPointerType) -> anyhow::Result<CooTripletsShape>;
30}
31
32impl<'a> SparseTripletsTraits for ValuesIndicesPointers<'a> {
37 fn to_coo(&self, pointer_type: IndexPointerType) -> anyhow::Result<CooTripletsShape> {
38 use rayon::prelude::*;
39
40 let indices = self.indices;
41 let indptr = self.indptr;
42 let values = self.values;
43
44 let nelem = values.len();
45 if nelem != indices.len() {
46 return Err(anyhow::anyhow!(
47 "`values` and `indices` have different sizes"
48 ));
49 }
50 if indptr.is_empty() {
51 return Err(anyhow::anyhow!("`indptr` is empty"));
52 }
53 let nvectors = indptr.len() - 1;
54
55 let spans: Vec<(usize, usize)> = indptr
57 .windows(2)
58 .map(|w| {
59 let end = (w[1] as usize).min(nelem);
60 ((w[0] as usize).min(end), end)
61 })
62 .collect();
63
64 let total: usize = spans.iter().map(|&(start, end)| end - start).sum();
68 let mut triplets: Vec<(u64, u64, f32)> = vec![(0, 0, 0.0); total];
69 let mut slots: Vec<&mut [(u64, u64, f32)]> = Vec::with_capacity(nvectors);
70 let mut rest = triplets.as_mut_slice();
71 for &(start, end) in &spans {
72 let (slot, tail) = rest.split_at_mut(end - start);
73 slots.push(slot);
74 rest = tail;
75 }
76 slots
77 .into_par_iter()
78 .zip(spans.par_iter())
79 .enumerate()
80 .for_each(|(idx, (slot, &(start, end)))| {
81 let j = idx as u64;
82 let entries = indices[start..end].iter().zip(&values[start..end]);
83 for (out, (&i, &x_ij)) in slot.iter_mut().zip(entries) {
84 *out = match pointer_type {
85 IndexPointerType::Column => (i, j, x_ij),
86 IndexPointerType::Row => (j, i, x_ij),
87 };
88 }
89 });
90
91 let nnz = triplets.len();
92
93 let max_idx_index = indices.par_iter().copied().max().unwrap_or(0) as usize + 1;
96 let (nrows, ncols) = match pointer_type {
97 IndexPointerType::Column => (max_idx_index, nvectors),
98 IndexPointerType::Row => (nvectors, max_idx_index),
99 };
100
101 Ok(CooTripletsShape {
102 triplets,
103 shape: TripletsShape { nrows, ncols, nnz },
104 })
105 }
106}
107
108pub(crate) fn sum_duplicate_triplets(triplets: &mut Vec<(u64, u64, f32)>) {
114 use rayon::prelude::*;
115 triplets.par_sort_by_key(|&(r, c, _)| (c, r));
116 triplets.dedup_by(|later, kept| {
117 let same = (later.0, later.1) == (kept.0, kept.1);
118 if same {
119 kept.2 += later.2;
120 }
121 same
122 });
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
133 fn values_on_one_coordinate_are_summed_in_the_order_they_arrived() {
134 let n = 5_000u64;
135 let mut triplets: Vec<(u64, u64, f32)> = Vec::new();
136 for value in [1e8_f32, 1.0, -1e8] {
137 triplets.extend((0..n).rev().map(|k| (k % 50, k / 50, value)));
139 }
140 sum_duplicate_triplets(&mut triplets);
141 assert_eq!(triplets.len(), n as usize);
142 assert!(triplets
143 .windows(2)
144 .all(|w| (w[0].1, w[0].0) < (w[1].1, w[1].0)));
145 assert!(
146 triplets.iter().all(|&(_, _, x)| x == 0.0),
147 "summed out of arrival order"
148 );
149 }
150
151 #[test]
154 fn duplicates_are_summed_and_singletons_kept() {
155 let (a, b, c) = (0.1_f32, 0.2, 0.3);
156 let mut triplets = vec![(1, 0, a), (0, 1, 2.0), (1, 0, b), (0, 0, 5.0), (1, 0, c)];
157 sum_duplicate_triplets(&mut triplets);
158 assert_eq!(triplets, [(0, 0, 5.0), (1, 0, (a + b) + c), (0, 1, 2.0)]);
159 }
160
161 fn walk(
163 values: &[f32],
164 indices: &[u64],
165 indptr: &[u64],
166 pointer_type: IndexPointerType,
167 ) -> Vec<(u64, u64, f32)> {
168 let mut out = Vec::new();
169 for (j, w) in indptr.windows(2).enumerate() {
170 let end = (w[1] as usize).min(values.len());
171 for k in (w[0] as usize).min(end)..end {
172 out.push(match pointer_type {
173 IndexPointerType::Column => (indices[k], j as u64, values[k]),
174 IndexPointerType::Row => (j as u64, indices[k], values[k]),
175 });
176 }
177 }
178 out
179 }
180
181 #[test]
182 fn to_coo_keeps_the_order_of_the_compressed_vectors() {
183 let mut indptr = vec![0u64];
185 let mut indices = Vec::new();
186 let mut values = Vec::new();
187 for j in 0..5_000u64 {
188 for k in 0..(j * 7 + 3) % 7 {
189 indices.push((j + 11 * k) % 97);
190 values.push((j * 31 + k) as f32);
191 }
192 indptr.push(indices.len() as u64);
193 }
194 for pointer_type in [IndexPointerType::Column, IndexPointerType::Row] {
195 let coo = ValuesIndicesPointers {
196 values: &values,
197 indices: &indices,
198 indptr: &indptr,
199 }
200 .to_coo(pointer_type)
201 .unwrap();
202 assert_eq!(coo.triplets, walk(&values, &indices, &indptr, pointer_type));
203 assert_eq!(coo.shape.nnz, values.len());
204 let (major, minor) = match pointer_type {
205 IndexPointerType::Column => (coo.shape.ncols, coo.shape.nrows),
206 IndexPointerType::Row => (coo.shape.nrows, coo.shape.ncols),
207 };
208 assert_eq!((major, minor), (5_000, 97));
209 }
210 }
211
212 #[test]
213 fn to_coo_clamps_pointers_past_the_arrays() {
214 let values = [1.0f32, 2.0, 3.0];
215 let indices = [0u64, 1, 2];
216 let indptr = [0u64, 2, 5];
218 let coo = ValuesIndicesPointers {
219 values: &values,
220 indices: &indices,
221 indptr: &indptr,
222 }
223 .to_coo(IndexPointerType::Column)
224 .unwrap();
225 assert_eq!(coo.triplets, vec![(0, 0, 1.0), (1, 0, 2.0), (2, 1, 3.0)]);
226 }
227}