1#![allow(dead_code)]
2
3use super::*;
4use crate::sparse_io::index_audit;
5
6#[inline]
10fn remap_row(
11 l2g: &[usize],
12 g2c: &[Option<usize>],
13 row: u64,
14 didx: usize,
15 col: usize,
16) -> anyhow::Result<Option<usize>> {
17 let Some(&g) = l2g.get(row as usize) else {
18 anyhow::bail!(
19 "backend {didx}, column {col}: row index {row} (0x{row:016x}) is outside \
20 the backend's {} rows",
21 l2g.len()
22 );
23 };
24 g2c.get(g).copied().ok_or_else(|| {
25 anyhow::anyhow!(
26 "backend {didx}, column {col}: row index {row} maps to global row {g}, \
27 outside the global row table"
28 )
29 })
30}
31
32impl SparseIoVec {
33 fn backends_share_rows(&self) -> bool {
36 self.row_count_by_global.iter().any(|&n| n > 1)
37 }
38
39 #[inline]
43 fn scale_of(&self, glob: usize) -> f32 {
44 self.column_scales.as_ref().map_or(1.0, |s| s[glob])
45 }
46
47 pub fn for_each_triplet<I, F>(
69 &self,
70 cells: I,
71 chunk_cols: usize,
72 mut f: F,
73 ) -> anyhow::Result<(usize, usize)>
74 where
75 I: Iterator<Item = usize>,
76 F: FnMut(u64, u64, f32),
77 {
78 let nrow = self.num_rows();
79 let cells: Vec<usize> = cells.collect();
80 let ncol = cells.len();
81 let chunk = chunk_cols.max(1);
82 let g2c = self.global_to_compact_row.as_slice();
83 let scale: Vec<f32> = cells.iter().map(|&g| self.scale_of(g)).collect();
84
85 let union_shares_rows =
87 self.column_alignment == ColumnAlignment::Union && self.backends_share_rows();
88 let mut pending: Vec<(u64, u64, f32)> = Vec::new();
89
90 for slab_start in (0..ncol).step_by(chunk) {
91 let slab_end = (slab_start + chunk).min(ncol);
92
93 let mut backend_groups: HashMap<usize, Vec<(usize, usize)>> = HashMap::default();
100 let mut multi_source = vec![false; slab_end - slab_start];
102 for (k, &glob) in cells[slab_start..slab_end].iter().enumerate() {
103 let out_col = slab_start + k;
104 let sources = self.col_to_data[glob].as_slice();
105 multi_source[k] = union_shares_rows && sources.len() > 1;
106 for source in sources {
107 backend_groups
108 .entry(source.backend as usize)
109 .or_default()
110 .push((source.local_col as usize, out_col));
111 }
112 }
113
114 for (&didx, group) in &backend_groups {
115 let local_cols: Vec<usize> = group.iter().map(|&(loc, _)| loc).collect();
116 let (_, _, group_triplets) =
117 self.data_vec[didx].read_triplets_by_columns(local_cols)?;
118
119 let l2g = self.data_local_to_global_row[didx].as_slice();
120 let intra_merges = self.data_has_intra_row_merges[didx];
127 for (i, j, v) in group_triplets {
128 if let Some(c) = g2c[l2g[i as usize]] {
129 let out_col = group[j as usize].1;
130 if intra_merges || multi_source[out_col - slab_start] {
131 pending.push((c as u64, out_col as u64, v));
132 } else {
133 f(c as u64, out_col as u64, v / scale[out_col]);
134 }
135 }
136 }
137 }
138
139 crate::sparse_util::sum_duplicate_triplets(&mut pending);
141 for (r, c, v) in pending.drain(..) {
142 f(r, c, v / scale[c as usize]);
143 }
144 }
145
146 Ok((nrow, ncol))
147 }
148
149 #[allow(clippy::type_complexity)]
155 pub fn columns_triplets<I>(
156 &self,
157 cells: I,
158 ) -> anyhow::Result<((usize, usize), Vec<(u64, u64, f32)>)>
159 where
160 I: Iterator<Item = usize>,
161 {
162 let cells: Vec<usize> = cells.collect();
163 let one_slab = cells.len().max(1);
164 let mut triplets = Vec::new();
165 let dims = self.for_each_triplet(cells.into_iter(), one_slab, |r, c, v| {
166 triplets.push((r, c, v));
167 })?;
168 Ok((dims, triplets))
169 }
170
171 #[cfg(feature = "ndarray")]
172 pub fn read_columns_ndarray<I>(&self, cells: I) -> anyhow::Result<ndarray::Array2<f32>>
173 where
174 I: Iterator<Item = usize>,
175 {
176 let ((nrow, ncol), triplets) = self.columns_triplets(cells)?;
177 ndarray::Array2::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
178 }
179
180 pub fn read_columns_dmatrix<I>(&self, cells: I) -> anyhow::Result<nalgebra::DMatrix<f32>>
181 where
182 I: Iterator<Item = usize>,
183 {
184 let ((nrow, ncol), triplets) = self.columns_triplets(cells)?;
185 DMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
186 }
187
188 pub fn read_columns_csc<I>(&self, cells: I) -> anyhow::Result<CscMatrix<f32>>
199 where
200 I: Iterator<Item = usize>,
201 {
202 let cells: Vec<usize> = cells.collect();
203 let nrow = self.num_rows();
204 let ncol = cells.len();
205
206 let mut backend_groups: Vec<Vec<(usize, usize)>> =
213 (0..self.data_vec.len()).map(|_| Vec::new()).collect();
214 for (out_col, &glob) in cells.iter().enumerate() {
215 for source in self.col_to_data[glob].iter() {
216 backend_groups[source.backend as usize].push((source.local_col as usize, out_col));
217 }
218 }
219
220 let mut buckets: Vec<Vec<(u32, f32)>> = (0..ncol).map(|_| Vec::new()).collect();
222 let g2c = self.global_to_compact_row.as_slice();
223
224 for (didx, group) in backend_groups.iter().enumerate() {
225 if group.is_empty() {
226 continue;
227 }
228 let l2g = self.data_local_to_global_row[didx].as_slice();
229
230 if let Some((indptr, indices, values)) = self.data_vec[didx].csc_column_arrays() {
231 for &(loc, out_col) in group {
235 if loc + 1 >= indptr.len() {
236 continue;
237 }
238 index_audit::check_slot(
239 "preloaded column",
240 loc,
241 indptr[loc],
242 indptr[loc + 1],
243 indices.len().min(values.len()),
244 )?;
245 let s = indptr[loc] as usize;
246 let e = indptr[loc + 1] as usize;
247 let bucket = &mut buckets[out_col];
248 bucket.reserve(e - s);
249 for k in s..e {
250 if let Some(c) = remap_row(l2g, g2c, indices[k], didx, loc)? {
251 bucket.push((c as u32, values[k]));
252 }
253 }
254 }
255 } else {
256 for &(loc, out_col) in group {
265 if let Some(nnz) = self.data_vec[didx].column_nnz(loc) {
266 buckets[out_col].reserve(nnz as usize);
267 }
268 }
269 let cols: Vec<usize> = group.iter().map(|&(loc, _)| loc).collect();
270 let (_, _, trip) = self.data_vec[didx].read_triplets_by_columns(cols)?;
271 for (i, jj, v) in trip {
272 let &(loc, out_col) = group.get(jj as usize).ok_or_else(|| {
273 anyhow::anyhow!(
274 "backend {didx}: read returned output column {jj} of {}",
275 group.len()
276 )
277 })?;
278 if let Some(c) = remap_row(l2g, g2c, i, didx, loc)? {
279 buckets[out_col].push((c as u32, v));
280 }
281 }
282 }
283 }
284
285 let total_nnz: usize = buckets.iter().map(|b| b.len()).sum();
287 let mut col_offsets: Vec<usize> = Vec::with_capacity(ncol + 1);
288 let mut row_indices: Vec<usize> = Vec::with_capacity(total_nnz);
289 let mut values: Vec<f32> = Vec::with_capacity(total_nnz);
290 col_offsets.push(0);
291
292 for (out_col, bucket) in buckets.iter_mut().enumerate() {
293 let strictly_sorted_unique = bucket.windows(2).all(|w| w[0].0 < w[1].0);
305 if !strictly_sorted_unique {
306 bucket.sort_by_key(|&(r, _)| r);
307 let mut write = 0usize;
309 let mut read = 0usize;
310 while read < bucket.len() {
311 let (r, mut v) = bucket[read];
312 read += 1;
313 while read < bucket.len() && bucket[read].0 == r {
314 v += bucket[read].1;
315 read += 1;
316 }
317 bucket[write] = (r, v);
318 write += 1;
319 }
320 bucket.truncate(write);
321 }
322 let scale = self.scale_of(cells[out_col]);
324 for &(r, v) in bucket.iter() {
325 row_indices.push(r as usize);
326 values.push(v / scale);
327 }
328 col_offsets.push(row_indices.len());
329 }
330
331 CscMatrix::try_from_csc_data(nrow, ncol, col_offsets, row_indices, values)
332 .map_err(|e| anyhow::anyhow!("CSC construction failed: {:?}", e))
333 }
334
335 pub fn read_columns_csr<I>(&self, cells: I) -> anyhow::Result<CsrMatrix<f32>>
336 where
337 I: Iterator<Item = usize>,
338 {
339 let ((nrow, ncol), triplets) = self.columns_triplets(cells)?;
340 nalgebra_sparse::CsrMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
341 }
342
343 #[cfg(feature = "tensor")]
344 pub fn read_columns_tensor<I>(&self, cells: I) -> anyhow::Result<Tensor>
345 where
346 I: Iterator<Item = usize>,
347 {
348 let ((nrow, ncol), triplets) = self.columns_triplets(cells)?;
349 Tensor::from_nonzero_triplets(nrow, ncol, &triplets)
350 }
351
352 #[allow(clippy::type_complexity)]
360 pub fn rows_triplets<I>(
361 &self,
362 rows: I,
363 ) -> anyhow::Result<((usize, usize), Vec<(u64, u64, f32)>)>
364 where
365 I: Iterator<Item = usize>,
366 {
367 let rows_compact: Vec<usize> = rows.collect();
368 let nrow_out = rows_compact.len();
369 let ncol_out = self.num_columns();
370
371 let n_compact = self.cached_num_rows;
372 let compact_to_global = self.compact_to_global_row.as_slice();
373
374 let mut has_duplicates =
377 self.column_alignment == ColumnAlignment::Union && self.backends_share_rows();
378
379 let mut triplets: Vec<(u64, u64, f32)> = Vec::new();
380 let mut local_to_out: Vec<usize> = Vec::with_capacity(rows_compact.len());
381 for didx in 0..self.data_vec.len() {
382 let g2l = self.data_global_to_local_row[didx].as_slice();
383
384 local_to_out.clear();
387 let mut local_rows: Vec<usize> = Vec::with_capacity(rows_compact.len());
388 for (out_row, &c) in rows_compact.iter().enumerate() {
389 if c >= n_compact {
390 continue;
391 }
392 let g = compact_to_global[c];
393 let first = g2l.partition_point(|&(global, _)| global < g);
394 for &(_, l) in g2l[first..].iter().take_while(|&&(global, _)| global == g) {
395 local_rows.push(l);
396 local_to_out.push(out_row);
397 }
398 }
399 if local_rows.is_empty() {
400 continue;
401 }
402 has_duplicates |= self.data_has_intra_row_merges[didx];
403
404 let (_, _, group_triplets) = self.data_vec[didx].read_triplets_by_rows(local_rows)?;
405 let cols_map = self
406 .data_to_cols
407 .get(&didx)
408 .ok_or_else(|| anyhow::anyhow!("missing data_to_cols entry for didx {}", didx))?;
409 triplets.reserve(group_triplets.len());
410 for (i, j, v) in group_triplets {
411 let mapped = cols_map[j as usize];
413 if mapped == usize::MAX {
414 continue;
415 }
416 let out_row = local_to_out[i as usize] as u64;
417 triplets.push((out_row, mapped as u64, v));
418 }
419 }
420
421 if has_duplicates {
422 crate::sparse_util::sum_duplicate_triplets(&mut triplets);
423 }
424 if let Some(scales) = self.column_scales.as_ref() {
426 for t in triplets.iter_mut() {
427 t.2 /= scales[t.1 as usize];
428 }
429 }
430
431 Ok(((nrow_out, ncol_out), triplets))
432 }
433
434 #[cfg(feature = "ndarray")]
435 pub fn read_rows_ndarray<I>(&self, rows: I) -> anyhow::Result<ndarray::Array2<f32>>
436 where
437 I: Iterator<Item = usize>,
438 {
439 let ((nrow, ncol), triplets) = self.rows_triplets(rows)?;
440 ndarray::Array2::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
441 }
442
443 pub fn read_rows_dmatrix<I>(&self, rows: I) -> anyhow::Result<nalgebra::DMatrix<f32>>
444 where
445 I: Iterator<Item = usize>,
446 {
447 let ((nrow, ncol), triplets) = self.rows_triplets(rows)?;
448 DMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
449 }
450
451 pub fn read_rows_csc<I>(&self, rows: I) -> anyhow::Result<CscMatrix<f32>>
452 where
453 I: Iterator<Item = usize>,
454 {
455 let ((nrow, ncol), triplets) = self.rows_triplets(rows)?;
456 nalgebra_sparse::CscMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
457 }
458
459 pub fn read_rows_csr<I>(&self, rows: I) -> anyhow::Result<CsrMatrix<f32>>
460 where
461 I: Iterator<Item = usize>,
462 {
463 let ((nrow, ncol), triplets) = self.rows_triplets(rows)?;
464 nalgebra_sparse::CsrMatrix::<f32>::from_nonzero_triplets(nrow, ncol, &triplets)
465 }
466
467 #[cfg(feature = "tensor")]
468 pub fn read_rows_tensor<I>(&self, rows: I) -> anyhow::Result<Tensor>
469 where
470 I: Iterator<Item = usize>,
471 {
472 let ((nrow, ncol), triplets) = self.rows_triplets(rows)?;
473 Tensor::from_nonzero_triplets(nrow, ncol, &triplets)
474 }
475}