1use crate::sparse_io::{SparseIo, SLAB_BUDGET_BYTES, TRIPLET_BYTES};
8use rayon::prelude::*;
9
10pub(crate) fn distinct_within(selection: &[usize], n: usize, what: &str) -> anyhow::Result<()> {
13 let mut seen = vec![false; n];
14 for &i in selection {
15 let slot = seen
16 .get_mut(i)
17 .ok_or_else(|| anyhow::anyhow!("{what} {i} is past the {n} {what}s"))?;
18 anyhow::ensure!(
19 !std::mem::replace(slot, true),
20 "the {what} selection repeats {what} {i}"
21 );
22 }
23 Ok(())
24}
25
26pub struct RowMap {
31 pub to: Vec<Option<u64>>,
32 pub nrow: usize,
33}
34
35impl RowMap {
36 pub fn keeping(keep: &[usize], nrow_full: usize) -> anyhow::Result<Self> {
41 distinct_within(keep, nrow_full, "row")?;
42 let mut to = vec![None; nrow_full];
43 for (new, &old) in keep.iter().enumerate() {
44 to[old] = Some(new as u64);
45 }
46 Ok(Self {
47 to,
48 nrow: keep.len(),
49 })
50 }
51
52 pub fn by_names(
63 old_names: &[Box<str>],
64 order: &[Box<str>],
65 nrow_full: usize,
66 ) -> anyhow::Result<Self> {
67 let place = crate::sparse_io::build_name2index_map(order);
68 if place.len() != order.len() {
69 let (first, name) = order
71 .iter()
72 .enumerate()
73 .find(|&(i, name)| place[name] != i)
74 .expect("a shorter map means a repeated name");
75 anyhow::bail!(
76 "the row order lists `{name}` twice (positions {first} and {})",
77 place[name]
78 );
79 }
80 let mut claimed: Vec<Option<usize>> = vec![None; order.len()];
82 let named = old_names.len().min(nrow_full);
83 let mut to = old_names[..named]
84 .iter()
85 .enumerate()
86 .map(|(old, name)| {
87 let Some(&new) = place.get(name) else {
88 return Ok(None);
89 };
90 if let Some(first) = claimed[new].replace(old) {
91 anyhow::bail!(
92 "rows {first} and {old} are both named `{name}`; \
93 they cannot share one position"
94 );
95 }
96 Ok(Some(new as u64))
97 })
98 .collect::<anyhow::Result<Vec<_>>>()?;
99 to.resize(nrow_full, None);
100 Ok(Self {
101 to,
102 nrow: order.len(),
103 })
104 }
105
106 fn check(&self, nrow_full: usize) -> anyhow::Result<()> {
109 anyhow::ensure!(
110 self.to.len() == nrow_full,
111 "the row map covers {} rows, the data has {nrow_full}",
112 self.to.len()
113 );
114 let mut fed = vec![false; self.nrow];
115 for &new in self.to.iter().flatten() {
116 let slot = fed.get_mut(new as usize).ok_or_else(|| {
117 anyhow::anyhow!("row {new} is past the {} output rows", self.nrow)
118 })?;
119 anyhow::ensure!(
120 !std::mem::replace(slot, true),
121 "two rows of the data land on output row {new}"
122 );
123 }
124 Ok(())
125 }
126}
127
128pub(crate) fn nnz_of_kept_rows(indptr: &[u64], to: &[Option<u64>]) -> Option<u64> {
133 if indptr.len() != to.len() + 1 {
134 return None;
135 }
136 to.iter()
137 .zip(indptr.windows(2))
138 .filter(|(kept, _)| kept.is_some())
139 .try_fold(0u64, |acc, (_, w)| Some(acc + w[1].checked_sub(w[0])?))
140}
141
142pub(crate) fn csc_slab(
154 triplets: Vec<(u64, u64, f32)>,
155 ncol: usize,
156 rows: Option<&[Option<u64>]>,
157) -> (Vec<u64>, Vec<u64>, Vec<f32>) {
158 let new_row = |r: u64| match rows {
159 None => Some(r),
160 Some(to) => to[r as usize],
161 };
162
163 let mut colptr = vec![0u64; ncol + 1];
164 for &(r, c_local, _) in &triplets {
165 if new_row(r).is_some() {
166 colptr[c_local as usize + 1] += 1;
167 }
168 }
169 for c in 0..ncol {
170 colptr[c + 1] += colptr[c];
171 }
172 let slab_len = colptr[ncol] as usize;
173 let mut row_indices = vec![0u64; slab_len];
174 let mut values = vec![0f32; slab_len];
175 let mut cursor = colptr[..ncol].to_vec();
176 for (r, c_local, x) in triplets {
177 if let Some(new_r) = new_row(r) {
178 let at = &mut cursor[c_local as usize];
179 row_indices[*at as usize] = new_r;
180 values[*at as usize] = x;
181 *at += 1;
182 }
183 }
184
185 let mut column: Vec<(u64, f32)> = Vec::new();
186 for w in colptr.windows(2) {
187 let span = w[0] as usize..w[1] as usize;
188 if row_indices[span.clone()].is_sorted() {
189 continue;
190 }
191 column.clear();
192 column.extend(
193 row_indices[span.clone()]
194 .iter()
195 .copied()
196 .zip(values[span.clone()].iter().copied()),
197 );
198 column.sort_by_key(|&(r, _)| r);
199 for (k, &(r, x)) in span.zip(&column) {
200 row_indices[k] = r;
201 values[k] = x;
202 }
203 }
204
205 colptr.truncate(ncol);
206 (colptr, row_indices, values)
207}
208
209pub(crate) struct SelectionPlan {
212 nrow: usize,
213 nnz: u64,
214 slabs: Vec<(usize, usize)>,
216}
217
218pub(crate) fn plan_csc_selection<S>(
229 src: &S,
230 old_cols: &[usize],
231 rows: Option<&RowMap>,
232) -> anyhow::Result<SelectionPlan>
233where
234 S: SparseIo + Sync + ?Sized,
235{
236 plan_within(src, old_cols, rows, SLAB_BUDGET_BYTES)
237}
238
239fn plan_within<S>(
241 src: &S,
242 old_cols: &[usize],
243 rows: Option<&RowMap>,
244 slab_budget: usize,
245) -> anyhow::Result<SelectionPlan>
246where
247 S: SparseIo + Sync + ?Sized,
248{
249 use legume_numeric::matrix::utils::byte_budget_intervals;
250
251 let nrow_src = src
252 .num_rows()
253 .ok_or_else(|| anyhow::anyhow!("backend has no `nrow`"))?;
254 if let Some(rows) = rows {
255 rows.check(nrow_src)?;
256 }
257 let ncol_src = src
258 .num_columns()
259 .ok_or_else(|| anyhow::anyhow!("backend has no `ncol`"))?;
260
261 let stored_nnz: Vec<u64> = old_cols
264 .iter()
265 .map(|&c| {
266 anyhow::ensure!(c < ncol_src, "column {c} is past the {ncol_src} columns");
267 src.column_nnz(c)
268 .ok_or_else(|| anyhow::anyhow!("no indptr entry for column {c}"))
269 })
270 .collect::<anyhow::Result<_>>()?;
271
272 let share = slab_budget / rayon::current_num_threads().max(1);
281 let slabs = byte_budget_intervals(&stored_nnz, share, TRIPLET_BYTES);
282 let nnz: u64 = match rows {
283 None => stored_nnz.iter().sum(),
284 Some(remap) => {
285 let every_column_once =
286 old_cols.len() == ncol_src && distinct_within(old_cols, ncol_src, "column").is_ok();
287 match every_column_once
288 .then(|| nnz_of_kept_rows(src.row_indptr(), &remap.to))
289 .flatten()
290 {
291 Some(total) => total,
292 None => slabs
293 .par_iter()
294 .map(|&(lb, ub)| -> anyhow::Result<u64> {
295 let (_, _, triplets) = src
296 .read_triplets_by_columns(old_cols[lb..ub].iter().copied().collect())?;
297 Ok(triplets
298 .iter()
299 .filter(|(r, _, _)| remap.to[*r as usize].is_some())
300 .count() as u64)
301 })
302 .sum::<anyhow::Result<u64>>()?,
303 }
304 }
305 };
306
307 Ok(SelectionPlan {
308 nrow: rows.map_or(nrow_src, |r| r.nrow),
309 nnz,
310 slabs,
311 })
312}
313
314pub(crate) fn append_csc_slabs<S, O>(
332 src: &S,
333 cols: &[usize],
334 rows: Option<&[Option<u64>]>,
335 slabs: &[(usize, usize)],
336 out: &mut O,
337 col_offset: u64,
338 nnz_offset: u64,
339) -> anyhow::Result<u64>
340where
341 S: SparseIo + Sync + ?Sized,
342 O: SparseIo + ?Sized,
343{
344 let threads = rayon::current_num_threads().max(1);
345 let stored_bytes = |&(lb, ub): &(usize, usize)| -> usize {
346 let nnz: u64 = cols[lb..ub].iter().filter_map(|&c| src.column_nnz(c)).sum();
347 (nnz as usize).saturating_mul(TRIPLET_BYTES)
348 };
349 let mut written = 0u64;
350 let mut rest = slabs;
351 while !rest.is_empty() {
352 let mut resident = 0usize;
354 let len = rest
355 .iter()
356 .take(threads)
357 .take_while(|&slab| {
358 let fits = resident == 0 || resident + stored_bytes(slab) <= SLAB_BUDGET_BYTES;
359 resident += stored_bytes(slab);
360 fits
361 })
362 .count();
363 let (window, later) = rest.split_at(len);
364 rest = later;
365 let built = window
371 .par_iter()
372 .map(|&(lb, ub)| -> anyhow::Result<_> {
373 let (_, _, triplets) =
374 src.read_triplets_by_columns(cols[lb..ub].iter().copied().collect())?;
375 Ok(csc_slab(triplets, ub - lb, rows))
376 })
377 .collect::<anyhow::Result<Vec<_>>>()?;
378
379 let window_nnz: usize = built.iter().map(|(_, _, values)| values.len()).sum();
382 let mut colptr: Vec<u64> = Vec::with_capacity(window[window.len() - 1].1 - window[0].0);
383 let mut row_indices: Vec<u64> = Vec::with_capacity(window_nnz);
384 let mut values: Vec<f32> = Vec::with_capacity(window_nnz);
385 for (slab_colptr, slab_rows, slab_values) in built {
386 let before = values.len() as u64;
387 colptr.extend(slab_colptr.iter().map(|&at| at + before));
388 row_indices.extend(slab_rows);
389 values.extend(slab_values);
390 }
391 out.append_csc_slab(
392 col_offset + window[0].0 as u64,
393 nnz_offset + written,
394 &colptr,
395 &row_indices,
396 &values,
397 )?;
398 written += window_nnz as u64;
399 }
400 Ok(written)
401}
402
403pub(crate) fn write_csc_selection<S, O>(
408 src: &S,
409 old_cols: &[usize],
410 rows: Option<&RowMap>,
411 plan: &SelectionPlan,
412 out: &mut O,
413) -> anyhow::Result<(usize, usize, usize)>
414where
415 S: SparseIo + Sync + ?Sized,
416 O: SparseIo + ?Sized,
417{
418 let shape = (plan.nrow, old_cols.len(), plan.nnz as usize);
419 out.begin_streaming_csc(shape)?;
420
421 let to = rows.map(|r| r.to.as_slice());
422 append_csc_slabs(src, old_cols, to, &plan.slabs, out, 0, 0)?;
423
424 out.finalize_streaming_csc()?;
425 out.build_csr_from_csc_streaming()?;
426 Ok(shape)
427}
428
429pub(crate) fn stream_csc_selection<S, O>(
435 src: &S,
436 old_cols: &[usize],
437 rows: Option<&RowMap>,
438 out: &mut O,
439) -> anyhow::Result<(usize, usize, usize)>
440where
441 S: SparseIo + Sync + ?Sized,
442 O: SparseIo + ?Sized,
443{
444 let plan = plan_csc_selection(src, old_cols, rows)?;
445 write_csc_selection(src, old_cols, rows, &plan, out)
446}
447
448pub fn stream_column_selection(
455 data: &dyn SparseIo<IndexIter = Vec<usize>>,
456 selected_columns: &[usize],
457 row_filter: Option<&[usize]>,
458 out_row_names: &[Box<str>],
459 out_col_names: &[Box<str>],
460 file_out: &str,
461 backend_out: &crate::sparse_io::SparseIoBackend,
462) -> anyhow::Result<(usize, usize, usize)> {
463 let nrow_full = data
464 .num_rows()
465 .ok_or_else(|| anyhow::anyhow!("backend has no `nrow`"))?;
466 let rows = row_filter
467 .map(|keep| RowMap::keeping(keep, nrow_full))
468 .transpose()?;
469 stream_mapped_selection(
470 data,
471 selected_columns,
472 rows.as_ref(),
473 out_row_names,
474 out_col_names,
475 file_out,
476 backend_out,
477 )
478}
479
480pub fn stream_mapped_selection(
492 data: &dyn SparseIo<IndexIter = Vec<usize>>,
493 selected_columns: &[usize],
494 rows: Option<&RowMap>,
495 out_row_names: &[Box<str>],
496 out_col_names: &[Box<str>],
497 file_out: &str,
498 backend_out: &crate::sparse_io::SparseIoBackend,
499) -> anyhow::Result<(usize, usize, usize)> {
500 let plan = plan_csc_selection(data, selected_columns, rows)?;
501 let mut out =
502 crate::sparse_io::create_sparse_streaming_empty(Some(file_out), Some(backend_out))?;
503
504 let t_stream = std::time::Instant::now();
505 let shape = write_csc_selection(data, selected_columns, rows, &plan, &mut *out)?;
506 log::info!(
507 "streamed {} entries in {} columns ({:.1}s)",
508 shape.2,
509 shape.1,
510 t_stream.elapsed().as_secs_f32()
511 );
512 out.register_row_names_vec(out_row_names);
513 out.register_column_names_vec(out_col_names);
514 out.set_metadata(&data.metadata())?;
515 Ok(shape)
516}
517
518#[cfg(test)]
519#[path = "column_subset_tests.rs"]
520mod tests;