1#![allow(dead_code)]
2
3use crate::sparse_io::*;
4use legume_numeric::matrix::common_io::*;
5use log::info;
6use std::ops::Range;
7use std::sync::{Arc, OnceLock};
8use zarrs::array::chunk_cache::ChunkCacheDecodedLruChunkLimit;
9use zarrs::array::{data_type, ArraySubset, DataType};
10use zarrs::filesystem::FilesystemStore;
11use zarrs::storage::ReadableListableStorageTraits as ZReadStorageTraits;
12
13const DEFAULT_CACHE_CHUNK_CAP: u64 = 4;
20
21fn cache_chunk_cap() -> u64 {
22 static CAP: OnceLock<u64> = OnceLock::new();
23 *CAP.get_or_init(|| {
24 std::env::var("LEGUME_ZARR_CACHE_CAP")
25 .ok()
26 .and_then(|s| s.parse().ok())
27 .unwrap_or(DEFAULT_CACHE_CHUNK_CAP)
28 })
29}
30
31const KEY_BY_COLUMN_DATA: &str = "/by_column/data";
32const KEY_BY_COLUMN_INDICES: &str = "/by_column/indices";
33const KEY_BY_ROW_DATA: &str = "/by_row/data";
34const KEY_BY_ROW_INDICES: &str = "/by_row/indices";
35
36use anyhow::anyhow;
37
38use crate::sparse_backend::shared;
39use crate::utilities::io_helpers::{chunk_elems, parse_name_file};
40
41const COMPRESSION_LEVEL: i32 = 5;
42
43const MTX_STREAM_BLOCK: u64 = 1 << 20;
48
49#[derive(Clone)]
66pub struct SparseMtxData {
67 read_store: Arc<dyn ZReadStorageTraits>,
68 write_store: Option<Arc<FilesystemStore>>,
69 file_name: String,
70 max_row_name_idx: usize,
71 max_column_name_idx: usize,
72 by_column_indptr: Vec<u64>,
73 streamed_nnz: u64,
75 by_row_indptr: Vec<u64>,
76 by_column_indices: Option<Vec<u64>>,
77 by_column_data: Option<Vec<f32>>,
78 by_row_indices: Option<Vec<u64>>,
79 by_row_data: Option<Vec<f32>>,
80 column_preload: Option<crate::sparse_io::PreloadReservation>,
82 row_preload: Option<crate::sparse_io::PreloadReservation>,
84 by_column_data_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
89 by_column_indices_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
90 by_row_data_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
91 by_row_indices_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
92}
93
94impl SparseMtxData {
95 fn write_store(&self) -> anyhow::Result<&Arc<FilesystemStore>> {
97 self.write_store
98 .as_ref()
99 .ok_or_else(|| anyhow!("store is read-only (zip archive)"))
100 }
101}
102
103impl SparseMtxData {
104 pub fn new(zarr_file: Option<&str>) -> anyhow::Result<Self> {
110 Self::create_backend(zarr_file)
111 }
112
113 fn create_backend(zarr_file: Option<&str>) -> anyhow::Result<Self> {
115 match zarr_file {
116 Some(backend_file) => Self::register_backend_file(backend_file),
117 None => {
118 let backend_file = create_temp_dir_file(".zarr")?;
119 let backend_file = backend_file
120 .to_str()
121 .ok_or_else(|| anyhow::anyhow!("Failed to convert path to string"))?;
122 Self::register_backend_file(backend_file)
123 }
124 }
125 }
126
127 pub fn open(backend_file: &str) -> anyhow::Result<Self> {
130 let (read_store, write_store) = crate::zarr_io::open_zarr_store_rw(backend_file)?;
131
132 if (
133 Self::_num_rows(read_store.clone()),
134 Self::_num_columns(read_store.clone()),
135 Self::_num_nnz(read_store.clone()),
136 ) == (None, None, None)
137 {
138 anyhow::bail!("Couldn't figure out the size of this sparse matrix data");
139 }
140
141 let mut ret = Self {
142 read_store,
143 write_store,
144 file_name: backend_file.to_string(),
145 max_row_name_idx: MAX_ROW_NAME_IDX,
146 max_column_name_idx: MAX_COLUMN_NAME_IDX,
147 by_column_indptr: vec![],
148 streamed_nnz: 0,
149 by_row_indptr: vec![],
150 by_column_indices: None,
151 by_column_data: None,
152 by_row_indices: None,
153 by_row_data: None,
154 column_preload: None,
155 row_preload: None,
156 by_column_data_cache: Arc::new(OnceLock::new()),
157 by_column_indices_cache: Arc::new(OnceLock::new()),
158 by_row_data_cache: Arc::new(OnceLock::new()),
159 by_row_indices_cache: Arc::new(OnceLock::new()),
160 };
161
162 ret.read_column_indptr()?;
163 ret.read_row_indptr()?;
164
165 Ok(ret)
166 }
167
168 pub fn from_mtx_file(
175 mtx_file: &str,
176 backend_file: Option<&str>,
177 index_by_row: Option<bool>,
178 ) -> anyhow::Result<Self> {
179 let zarr_file = backend_file
180 .map(|s| s.to_string())
181 .unwrap_or_else(|| format!("{}.zarr", mtx_file));
182
183 info!("backend file: {}", zarr_file);
184 let mut ret = Self::register_backend_file(&zarr_file)?;
185
186 ret.import_mtx_file(mtx_file, index_by_row == Some(true))?;
187
188 info!("created sparse backend from {}", mtx_file);
189 Ok(ret)
190 }
191
192 #[cfg(feature = "ndarray")]
193 pub fn from_ndarray(
198 array: &Array2<f32>,
199 zarr_file: Option<&str>,
200 index_by_row: Option<bool>,
201 ) -> anyhow::Result<Self> {
202 let mut ret = Self::create_backend(zarr_file)?;
203
204 ret.import_ndarray_by_col(array)?;
205 ret.read_column_indptr()?;
206
207 if index_by_row == Some(true) {
208 ret.import_ndarray_by_row(array)?;
209 ret.read_row_indptr()?;
210 }
211 Ok(ret)
212 }
213
214 pub fn from_dmatrix(
219 matrix: &DMatrix<f32>,
220 zarr_file: Option<&str>,
221 index_by_row: Option<bool>,
222 ) -> anyhow::Result<Self> {
223 let mut ret = Self::create_backend(zarr_file)?;
224
225 ret.import_dmatrix_by_col(matrix)?;
226 ret.read_column_indptr()?;
227
228 if index_by_row == Some(true) {
229 ret.import_dmatrix_by_row(matrix)?;
230 ret.read_row_indptr()?;
231 }
232 Ok(ret)
233 }
234
235 pub fn print_hierarchy(&self) -> anyhow::Result<()> {
237 use zarrs::config::MetadataRetrieveVersion;
238 let node =
239 zarrs::node::Node::open_opt(&self.read_store, "/", &MetadataRetrieveVersion::Default)?;
240 let tree = node.hierarchy_tree();
241 info!("hierarchy_tree:\n{}", tree);
242 Ok(())
243 }
244
245 fn register_backend_file(zarr_file: &str) -> anyhow::Result<Self> {
247 use zarrs::group::GroupBuilder;
248 let store = Arc::new(FilesystemStore::new(zarr_file)?);
249 let root = GroupBuilder::new().build(store.clone(), "/")?;
250 root.store_metadata()?;
251
252 Ok(Self {
253 read_store: store.clone(),
254 write_store: Some(store),
255 file_name: zarr_file.to_string(),
256 max_row_name_idx: MAX_ROW_NAME_IDX,
257 max_column_name_idx: MAX_COLUMN_NAME_IDX,
258 by_column_indptr: vec![],
259 streamed_nnz: 0,
260 by_row_indptr: vec![],
261 by_column_indices: None,
262 by_column_data: None,
263 by_row_indices: None,
264 by_row_data: None,
265 column_preload: None,
266 row_preload: None,
267 by_column_data_cache: Arc::new(OnceLock::new()),
268 by_column_indices_cache: Arc::new(OnceLock::new()),
269 by_row_data_cache: Arc::new(OnceLock::new()),
270 by_row_indices_cache: Arc::new(OnceLock::new()),
271 })
272 }
273
274 fn new_filled_vector<V>(&mut self, key: &str, dt: DataType, vec: &[V]) -> anyhow::Result<()>
287 where
288 V: zarrs::array::Element,
289 {
290 use zarrs::array::codec::ZstdCodec;
291 use zarrs::array::ArrayBuilder;
292 use zarrs::array::FillValue;
293
294 let ws = self.write_store()?;
295
296 let nelem = vec.len();
297 let chunk_size = chunk_elems(nelem, std::mem::size_of::<V>());
298
299 let fill = if dt == data_type::float32() {
300 FillValue::from(zarrs::array::ZARR_NAN_F32)
301 } else if dt == data_type::uint64() {
302 FillValue::from(0u64)
303 } else if dt == data_type::string() {
304 FillValue::from("")
305 } else {
306 FillValue::from(0)
307 };
308
309 let array = ArrayBuilder::new(
310 vec![vec.len() as u64], vec![chunk_size as u64], dt, fill, )
315 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
316 .build(ws.clone(), key)?;
317
318 array.store_metadata()?;
319
320 let subset = Self::create_subset(0..vec.len() as u64);
321 array.store_array_subset(&subset, vec)?;
322
323 Ok(())
324 }
325
326 fn _open_vector(
327 &self,
328 key: &str,
329 ) -> anyhow::Result<zarrs::array::Array<dyn ZReadStorageTraits>> {
330 use zarrs::array::Array as ZArray;
331 let ret = ZArray::open(self.read_store.clone(), key)?;
332 Ok(ret)
333 }
334
335 fn create_shaped_vector(
341 &mut self,
342 key: &str,
343 dt: DataType,
344 elem_bytes: usize,
345 nelem: usize,
346 ) -> anyhow::Result<()> {
347 use zarrs::array::codec::ZstdCodec;
348 use zarrs::array::ArrayBuilder;
349 use zarrs::array::FillValue;
350
351 let ws = self.write_store()?;
352
353 let chunk_size = chunk_elems(nelem, elem_bytes);
354
355 let fill = if dt == data_type::float32() {
356 FillValue::from(zarrs::array::ZARR_NAN_F32)
357 } else if dt == data_type::uint64() {
358 FillValue::from(0u64)
359 } else {
360 FillValue::from(0)
361 };
362
363 let array = ArrayBuilder::new(
364 vec![nelem.max(1) as u64],
365 vec![chunk_size.max(1) as u64],
366 dt,
367 fill,
368 )
369 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
370 .build(ws.clone(), key)?;
371
372 array.store_metadata()?;
373 Ok(())
374 }
375
376 fn _open_writable_vector(
380 &self,
381 key: &str,
382 ) -> anyhow::Result<zarrs::array::Array<FilesystemStore>> {
383 use zarrs::array::Array as ZArray;
384 let ws = self.write_store()?.clone();
385 let ret = ZArray::open(ws, key)?;
386 Ok(ret)
387 }
388
389 fn write_slab_u64(&mut self, key: &str, offset: u64, data: &[u64]) -> anyhow::Result<()> {
392 if data.is_empty() {
393 return Ok(());
394 }
395 let array = self._open_writable_vector(key)?;
396 let subset = Self::create_subset(offset..offset + data.len() as u64);
397 array.store_array_subset(&subset, data)?;
398 Ok(())
399 }
400
401 fn write_slab_f32(&mut self, key: &str, offset: u64, data: &[f32]) -> anyhow::Result<()> {
403 if data.is_empty() {
404 return Ok(());
405 }
406 let array = self._open_writable_vector(key)?;
407 let subset = Self::create_subset(offset..offset + data.len() as u64);
408 array.store_array_subset(&subset, data)?;
409 Ok(())
410 }
411
412 #[allow(clippy::type_complexity)]
413 fn open_csc_triplets(
414 &self,
415 ) -> anyhow::Result<(
416 zarrs::array::Array<dyn ZReadStorageTraits>,
417 zarrs::array::Array<dyn ZReadStorageTraits>,
418 zarrs::array::Array<dyn ZReadStorageTraits>,
419 )> {
420 Ok((
421 self._open_vector("/by_column/indptr")?,
422 self._open_vector("/by_column/data")?,
423 self._open_vector("/by_column/indices")?,
424 ))
425 }
426
427 #[inline]
429 fn create_subset(range: Range<u64>) -> ArraySubset {
430 ArraySubset::new_with_ranges(&[range])
431 }
432
433 fn open_chunk_cache(
439 read_store: &Arc<dyn ZReadStorageTraits>,
440 key: &str,
441 ) -> anyhow::Result<ChunkCacheDecodedLruChunkLimit> {
442 use zarrs::array::Array as ZArray;
443 use zarrs::storage::ReadableStorageTraits;
444
445 let storage_readable: Arc<dyn ReadableStorageTraits> = read_store.clone().readable();
446 let arr = ZArray::open(read_store.clone(), key)?;
447 let arr_arc = Arc::new(arr.with_storage(storage_readable));
448 Ok(ChunkCacheDecodedLruChunkLimit::new(
449 arr_arc,
450 cache_chunk_cap(),
451 ))
452 }
453
454 fn cache_for<'a>(
458 &'a self,
459 cell: &'a OnceLock<ChunkCacheDecodedLruChunkLimit>,
460 key: &str,
461 ) -> anyhow::Result<&'a ChunkCacheDecodedLruChunkLimit> {
462 if let Some(cache) = cell.get() {
463 return Ok(cache);
464 }
465 let _ = cell.set(Self::open_chunk_cache(&self.read_store, key)?);
466 Ok(cell.get().expect("OnceLock populated above"))
467 }
468
469 fn _retrieve_vector<V>(&self, key: &str) -> anyhow::Result<Vec<V>>
470 where
471 V: zarrs::array::ElementOwned,
472 {
473 let data = self._open_vector(key)?;
474 let ntot = data.shape()[0];
475 let subset = Self::create_subset(0..ntot);
476 Ok(data.retrieve_array_subset::<Vec<V>>(&subset)?)
477 }
478
479 fn _set_group_attr<V>(
485 store: Arc<FilesystemStore>,
486 group_name: &str,
487 attr_name: &str,
488 value: &V,
489 ) -> anyhow::Result<()>
490 where
491 V: serde::Serialize,
492 {
493 use zarrs::group::Group;
494 let mut group = Group::open(store, group_name)?;
495
496 let new_value = serde_json::to_value(value)?;
497 group
498 .attributes_mut()
499 .insert((*attr_name).to_string(), new_value);
500 group.store_metadata()?;
501 Ok(())
502 }
503
504 fn _get_group_attr<V>(
506 store: Arc<dyn ZReadStorageTraits>,
507 group_name: &str,
508 attr_name: &str,
509 ) -> Option<V>
510 where
511 V: serde::de::DeserializeOwned,
512 {
513 zarrs::group::Group::open(store, group_name)
514 .ok()
515 .and_then(|grp| grp.attributes().get(attr_name).cloned())
516 .and_then(|attr| serde_json::from_value(attr).ok())
517 }
518
519 fn _num_nnz(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
520 Self::_get_group_attr::<usize>(store, "/", "nnz")
521 }
522
523 fn _num_rows(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
524 Self::_get_group_attr::<usize>(store, "/", "nrow")
525 }
526
527 fn _num_columns(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
528 Self::_get_group_attr::<usize>(store, "/", "ncol")
529 }
530
531 fn _add_group(&mut self, group_name: &str) -> anyhow::Result<()> {
533 use zarrs::group::Group;
534 let ws = self.write_store()?;
535
536 if Group::open(ws.clone(), group_name).is_err() {
537 let new_group = zarrs::group::GroupBuilder::new().build(ws.clone(), group_name)?;
538 new_group.store_metadata()?;
539 }
540
541 Ok(())
542 }
543}
544
545impl SparseIo for SparseMtxData {
546 type IndexIter = Vec<usize>;
547
548 fn read_row_indptr(&mut self) -> anyhow::Result<()> {
550 use zarrs::array::Array as Zarray;
551 let key = "/by_row/indptr";
552 if let Ok(indptr) = Zarray::open(self.read_store.clone(), key) {
553 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
554 self.by_row_indptr.clear();
555 self.by_row_indptr.extend(indptr_vec);
556 }
557 Ok(())
558 }
559
560 fn column_indptr(&self) -> &[u64] {
562 &self.by_column_indptr
563 }
564
565 fn metadata(&self) -> Metadata {
566 Self::_get_group_attr::<Metadata>(self.read_store.clone(), "/", meta::ATTR)
567 .unwrap_or_default()
568 }
569
570 fn set_metadata(&mut self, values: &Metadata) -> anyhow::Result<()> {
571 let mut root = zarrs::group::Group::open(self.write_store()?.clone(), "/")?;
572 if values.is_empty() {
573 root.attributes_mut().remove(meta::ATTR);
574 } else {
575 root.attributes_mut()
576 .insert(meta::ATTR.to_string(), serde_json::to_value(values)?);
577 }
578 root.store_metadata()?;
579 Ok(())
580 }
581
582 fn reopen_backend(&mut self) -> anyhow::Result<()> {
583 let store = Arc::new(FilesystemStore::new(&self.file_name)?);
589 self.read_store = store.clone();
590 self.write_store = Some(store);
591 self.by_column_data_cache = Arc::new(OnceLock::new());
592 self.by_column_indices_cache = Arc::new(OnceLock::new());
593 self.by_row_data_cache = Arc::new(OnceLock::new());
594 self.by_row_indices_cache = Arc::new(OnceLock::new());
595 self.streamed_nnz = 0;
596 self.read_column_indptr()?;
597 self.read_row_indptr()?;
598 Ok(())
599 }
600
601 fn note_streamed_nnz(&mut self, n: u64) {
602 self.streamed_nnz += n;
603 }
604
605 fn streamed_nnz(&self) -> u64 {
606 self.streamed_nnz
607 }
608
609 fn reset_streamed_nnz(&mut self) {
610 self.streamed_nnz = 0;
611 }
612
613 fn read_column_indptr(&mut self) -> anyhow::Result<()> {
614 use zarrs::array::Array as ZArray;
615 let key = "/by_column/indptr";
616 if let Ok(indptr) = ZArray::open(self.read_store.clone(), key) {
617 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
618 self.by_column_indptr.clear();
619 self.by_column_indptr.extend(indptr_vec);
620 }
621 Ok(())
622 }
623
624 fn clean_preloaded_columns(&mut self) {
625 self.by_column_data = None;
626 self.by_column_indices = None;
627 self.column_preload = None;
628 }
629
630 fn preload_columns(&mut self) -> anyhow::Result<()> {
632 if self.by_column_data.is_some() && self.by_column_indices.is_some() {
633 return Ok(());
634 }
635 let reservation = match self.num_non_zeros() {
636 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "column") {
637 Some(r) => Some(r),
638 None => return Ok(()),
639 },
640 None => None,
641 };
642 use zarrs::array::Array as ZArray;
643
644 let key = "/by_column/data";
645 let data = ZArray::open(self.read_store.clone(), key)?;
646 let key = "/by_column/indices";
647 let indices = ZArray::open(self.read_store.clone(), key)?;
648
649 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
650 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
651
652 self.by_column_indices = Some(indices);
653 self.by_column_data = Some(data);
654 self.column_preload = reservation;
655 Ok(())
656 }
657
658 fn clean_preloaded_rows(&mut self) {
659 self.by_row_data = None;
660 self.by_row_indices = None;
661 self.row_preload = None;
662 }
663
664 fn preload_rows(&mut self) -> anyhow::Result<()> {
666 if self.by_row_data.is_some() && self.by_row_indices.is_some() {
667 return Ok(());
668 }
669 let reservation = match self.num_non_zeros() {
670 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "row") {
671 Some(r) => Some(r),
672 None => return Ok(()),
673 },
674 None => None,
675 };
676 use zarrs::array::Array as ZArray;
677
678 let data = ZArray::open(self.read_store.clone(), KEY_BY_ROW_DATA)?;
679 let indices = ZArray::open(self.read_store.clone(), KEY_BY_ROW_INDICES)?;
680
681 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
682 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
683
684 self.by_row_indices = Some(indices);
685 self.by_row_data = Some(data);
686 self.row_preload = reservation;
687 Ok(())
688 }
689
690 fn record_mtx_shape(&mut self, mtx_shape: Option<(usize, usize, usize)>) -> anyhow::Result<()> {
692 if let Some((nrow, ncol, nnz)) = mtx_shape {
693 let ws = self.write_store()?;
694 let read_store = self.read_store.clone();
695
696 let check_set_attr = |attr_name: &str, value: usize| -> anyhow::Result<()> {
697 let old_value = Self::_get_group_attr::<usize>(read_store.clone(), "/", attr_name);
698 let new_value = serde_json::to_value(value)?;
699
700 match old_value {
701 Some(old_value) => {
702 if old_value != new_value {
703 return Err(anyhow!("{} mismatch", attr_name));
704 }
705 }
706 _ => {
707 Self::_set_group_attr(ws.clone(), "/", attr_name, &new_value)?;
708 }
709 }
710 Ok(())
711 };
712
713 check_set_attr("nrow", nrow)?;
714 check_set_attr("ncol", ncol)?;
715 check_set_attr("nnz", nnz)?;
716 }
717 Ok(())
718 }
719
720 fn initialize_backend(&mut self) -> anyhow::Result<()> {
722 use zarrs::group::GroupBuilder;
723
724 self.remove_backend_file()?;
725 let zarr_file = &self.file_name;
726 let store = Arc::new(FilesystemStore::new(zarr_file)?);
727 let root = GroupBuilder::new().build(store.clone(), "/")?;
728 root.store_metadata()?;
729
730 self.read_store = store.clone();
731 self.write_store = Some(store);
732 self.file_name = zarr_file.to_string();
733 self.max_column_name_idx = MAX_COLUMN_NAME_IDX;
734 self.max_row_name_idx = MAX_ROW_NAME_IDX;
735 self.by_column_indptr = vec![];
736 self.by_row_indptr = vec![];
737
738 Ok(())
739 }
740
741 fn remove_backend_file(&self) -> anyhow::Result<()> {
743 let backend = std::path::Path::new(&self.file_name);
744 if backend.exists() {
745 if backend.is_file() {
746 std::fs::remove_file(backend)?;
747 } else {
748 std::fs::remove_dir_all(backend)?;
749 }
750 }
751 Ok(())
752 }
753
754 fn get_backend_file_name(&self) -> &str {
756 &self.file_name
757 }
758
759 fn backend_type(&self) -> SparseIoBackend {
760 SparseIoBackend::Zarr
761 }
762
763 fn to_mtx_file(&self, mtx_file: &str) -> anyhow::Result<()> {
766 if let (Some(ncol), Some(nrow), Some(nnz)) =
767 (self.num_columns(), self.num_rows(), self.num_non_zeros())
768 {
769 let (nrow, ncol, nnz) = (nrow, ncol, nnz);
770
771 let mut buf = open_buf_writer(mtx_file)?;
772 shared::write_mtx_header(&mut buf, nrow, ncol, nnz)?;
773
774 let (indptr, data, indices) = self.open_csc_triplets()?;
775 let indptr = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
776 debug_assert!(indptr.len() == ncol + 1);
777
778 let total_nnz = indptr[ncol];
785 let mut jj = 0usize; let mut pos = 0u64;
787 while pos < total_nnz {
788 let end = (pos + MTX_STREAM_BLOCK).min(total_nnz);
789 let subset = Self::create_subset(pos..end);
790 let data_block = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
791 let indices_block = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
792
793 for (k, (&val, &ii)) in data_block.iter().zip(&indices_block).enumerate() {
794 let global = pos + k as u64;
795 while jj + 1 < indptr.len() && indptr[jj + 1] <= global {
797 jj += 1;
798 }
799 writeln!(buf, "{}\t{}\t{}", ii as usize + 1, jj + 1, val)?;
801 }
802 pos = end;
803 }
804 buf.flush()?;
805 Ok(())
806 } else {
807 Err(anyhow!("Unable to figure out the size of the backend data"))
808 }
809 }
810
811 fn register_row_names_file(&mut self, row_name_file: &str) {
814 let _ = self.register_names_file(
815 "/row_names",
816 row_name_file,
817 0..self.max_row_name_idx,
818 ROW_SEP,
819 );
820 }
821
822 fn register_row_names_vec(&mut self, rows: &[Box<str>]) {
825 let _ = self.register_names_vec("/row_names", rows);
826 }
827
828 fn register_column_names_file(&mut self, column_name_file: &str) {
831 let _ = self.register_names_file(
832 "/column_names",
833 column_name_file,
834 0..self.max_column_name_idx,
835 COLUMN_SEP,
836 );
837 }
838
839 fn register_column_names_vec(&mut self, columns: &[Box<str>]) {
842 let _ = self.register_names_vec("/column_names", columns);
843 }
844
845 fn num_rows(&self) -> Option<usize> {
847 Self::_num_rows(self.read_store.clone())
848 }
849
850 fn num_columns(&self) -> Option<usize> {
852 Self::_num_columns(self.read_store.clone())
853 }
854
855 fn num_non_zeros(&self) -> Option<usize> {
857 Self::_num_nnz(self.read_store.clone())
858 }
859
860 fn register_names_file(
866 &mut self,
867 key: &str,
868 name_file: &str,
869 name_columns: Range<usize>,
870 name_sep: &str,
871 ) -> anyhow::Result<()> {
872 let names = parse_name_file(name_file, name_columns, name_sep)?;
873 self.new_filled_vector(key, data_type::string(), &names)?;
874 Ok(())
875 }
876
877 fn register_names_vec(&mut self, key: &str, names: &[Box<str>]) -> anyhow::Result<()> {
881 let names_vec: Vec<String> = names.iter().map(|x| x.to_string()).collect();
882 self.new_filled_vector(key, data_type::string(), &names_vec)?;
883 Ok(())
884 }
885
886 fn row_names(&self) -> anyhow::Result<Vec<Box<str>>> {
887 self.retrieve_registered_names("/row_names")
888 }
889
890 fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
891 self.retrieve_registered_names("/column_names")
892 }
893
894 fn retrieve_registered_names(&self, key: &str) -> anyhow::Result<Vec<Box<str>>> {
897 Ok(self
898 ._retrieve_vector::<String>(key)?
899 .into_iter()
900 .map(|s| s.into_boxed_str())
901 .collect())
902 }
903
904 fn read_triplets_by_single_column(
908 &self,
909 j_data: usize,
910 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
911 use zarrs::array::Array as ZArray;
912
913 debug_assert!(!self.by_column_indptr.is_empty()); debug_assert!(j_data < self.num_columns().unwrap_or(0)); let indptr = &self.by_column_indptr;
917
918 debug_assert!((j_data + 1) < indptr.len());
919 debug_assert!(indptr.len() > self.num_columns().unwrap_or(0));
920
921 let nrow = self
922 .num_rows()
923 .ok_or(anyhow!("can't figure out the number of rows"))?;
924
925 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
926 let ncol_out = 1;
927 let jj = 0;
928
929 let start = indptr[j_data] as usize;
931 let end = indptr[j_data + 1] as usize;
932 let ret: Vec<(u64, u64, f32)> = indices[start..end]
933 .iter()
934 .zip(data[start..end].iter())
935 .map(|(&ii, &x_ij)| (ii, jj, x_ij))
936 .collect();
937
938 Ok((nrow, ncol_out, ret))
939 } else {
940 let key = "/by_column/data";
941 let data = ZArray::open(self.read_store.clone(), key)?;
942 let key = "/by_column/indices";
943 let indices = ZArray::open(self.read_store.clone(), key)?;
944
945 let ncol_out = 1;
946 let jj = 0;
947
948 let start = indptr[j_data];
950 let end = indptr[j_data + 1];
951
952 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity((end - start) as usize);
953
954 if start < end {
955 let subset = Self::create_subset(start..end);
956 let data_slice = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
957 let indices_slice = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
958
959 for k in 0..(end - start) {
960 let x_ij = data_slice[k as usize];
961 let ii = indices_slice[k as usize];
962 debug_assert!((ii as usize) < nrow);
963 ret.push((ii, jj, x_ij));
964 }
965 }
966
967 Ok((nrow, ncol_out, ret))
968 }
969 }
970
971 fn read_triplets_by_columns(
975 &self,
976 columns: Self::IndexIter,
977 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
978 debug_assert!(!self.by_column_indptr.is_empty());
979 let indptr = &self.by_column_indptr;
980 let columns_vec = columns.into_iter().collect::<Vec<usize>>();
981
982 debug_assert!(indptr.len() > self.num_columns().unwrap_or(0));
983
984 let nrow = self
985 .num_rows()
986 .ok_or(anyhow!("can't figure out the number of rows"))?;
987
988 let ncol = self
989 .num_columns()
990 .ok_or(anyhow!("can't figure out the number of columns"))?;
991
992 let ncol_out = columns_vec.len();
993
994 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
995 let min_start = columns_vec
996 .iter()
997 .map(|&j_data| indptr[j_data])
998 .min()
999 .unwrap_or(0);
1000
1001 let max_end = columns_vec
1002 .iter()
1003 .map(|&j_data| indptr[j_data + 1])
1004 .max()
1005 .unwrap_or(0);
1006
1007 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity((max_end - min_start) as usize);
1008
1009 for (jj, &j_data) in columns_vec.iter().enumerate() {
1010 let jj = jj as u64;
1011 let start = indptr[j_data] as usize;
1012 let end = indptr[j_data + 1] as usize;
1013 for (&ii, &x_ij) in indices[start..end].iter().zip(data[start..end].iter()) {
1014 ret.push((ii, jj, x_ij));
1015 }
1016 }
1017
1018 Ok((nrow, ncol_out, ret))
1019 } else {
1020 let mut tagged: Vec<(u64, u64, u64)> = columns_vec
1024 .iter()
1025 .enumerate()
1026 .filter_map(|(jj, &j_data)| {
1027 if j_data >= ncol {
1028 return None;
1029 }
1030 let start = indptr[j_data];
1031 let end = indptr[j_data + 1];
1032 (start < end).then_some((jj as u64, start, end))
1033 })
1034 .collect();
1035 tagged.sort_by_key(|&(_, start, _)| start);
1036
1037 let data_cache = self.cache_for(&self.by_column_data_cache, KEY_BY_COLUMN_DATA)?;
1038 let indices_cache =
1039 self.cache_for(&self.by_column_indices_cache, KEY_BY_COLUMN_INDICES)?;
1040
1041 let opts = zarrs::array::CodecOptions::default();
1042 let ret = shared::coalesce_and_emit(
1043 &tagged,
1044 nrow,
1045 |jj, ii, val| (ii, jj, val),
1046 |s, e| {
1047 let subset = Self::create_subset(s..e);
1048 let data_buf =
1049 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1050 Vec<f32>,
1051 >(data_cache, &subset, &opts)?;
1052 let indices_buf =
1053 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1054 Vec<u64>,
1055 >(indices_cache, &subset, &opts)?;
1056 Ok((data_buf, indices_buf))
1057 },
1058 )?;
1059 Ok((nrow, ncol_out, ret))
1060 }
1061 }
1062
1063 fn csc_column_arrays(&self) -> Option<(&[u64], &[u64], &[f32])> {
1064 match (
1065 self.by_column_data.as_ref(),
1066 self.by_column_indices.as_ref(),
1067 ) {
1068 (Some(data), Some(indices)) if !self.by_column_indptr.is_empty() => Some((
1069 self.by_column_indptr.as_slice(),
1070 indices.as_slice(),
1071 data.as_slice(),
1072 )),
1073 _ => None,
1074 }
1075 }
1076
1077 fn read_triplets_by_rows(
1081 &self,
1082 rows: Self::IndexIter,
1083 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1084 debug_assert!(!self.by_row_indptr.is_empty());
1085 let indptr = &self.by_row_indptr;
1086 debug_assert!(indptr.len() > self.num_rows().unwrap_or(0));
1087
1088 let rows_vec = rows.into_iter().collect::<Vec<_>>();
1089
1090 let (nrow, ncol) = match (self.num_rows(), self.num_columns()) {
1091 (Some(nrow), Some(ncol)) => (nrow, ncol),
1092 _ => return Err(anyhow!("Unable to figure out the size of the backend data")),
1093 };
1094 let nrow_out = rows_vec.len();
1095
1096 if let (Some(data), Some(indices)) = (&self.by_row_data, &self.by_row_indices) {
1097 let mut nnz_total: usize = 0;
1098 let valid: Vec<(u64, usize)> = rows_vec
1099 .iter()
1100 .enumerate()
1101 .filter_map(|(ii, &i_data)| {
1102 if i_data >= nrow {
1103 return None;
1104 }
1105 nnz_total += (indptr[i_data + 1] - indptr[i_data]) as usize;
1106 Some((ii as u64, i_data))
1107 })
1108 .collect();
1109
1110 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(nnz_total);
1111 for (ii, i_data) in valid {
1112 let start = indptr[i_data] as usize;
1113 let end = indptr[i_data + 1] as usize;
1114 for (&jj, &x_ij) in indices[start..end].iter().zip(data[start..end].iter()) {
1115 ret.push((ii, jj, x_ij));
1116 }
1117 }
1118 return Ok((nrow_out, ncol, ret));
1119 }
1120
1121 let mut tagged: Vec<(u64, u64, u64)> = rows_vec
1123 .iter()
1124 .enumerate()
1125 .filter_map(|(ii, &i_data)| {
1126 if i_data >= nrow {
1127 return None;
1128 }
1129 debug_assert!((i_data + 1) < indptr.len());
1130 let start = indptr[i_data];
1131 let end = indptr[i_data + 1];
1132 (start < end).then_some((ii as u64, start, end))
1133 })
1134 .collect();
1135 tagged.sort_by_key(|&(_, start, _)| start);
1136
1137 let data_cache = self.cache_for(&self.by_row_data_cache, KEY_BY_ROW_DATA)?;
1138 let indices_cache = self.cache_for(&self.by_row_indices_cache, KEY_BY_ROW_INDICES)?;
1139
1140 let opts = zarrs::array::CodecOptions::default();
1141 let ret = shared::coalesce_and_emit(
1142 &tagged,
1143 ncol,
1144 |ii, jj, val| (ii, jj, val),
1145 |s, e| {
1146 let subset = Self::create_subset(s..e);
1147 let data_buf = <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1148 Vec<f32>,
1149 >(data_cache, &subset, &opts)?;
1150 let indices_buf =
1151 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<Vec<u64>>(
1152 indices_cache,
1153 &subset,
1154 &opts,
1155 )?;
1156 Ok((data_buf, indices_buf))
1157 },
1158 )?;
1159 Ok((nrow_out, ncol, ret))
1160 }
1161 fn record_csr_dataset_backend(
1170 &mut self,
1171 csr_cols: &[u64],
1172 csr_vals: &[f32],
1173 csr_rowptr: &[u64],
1174 ) -> anyhow::Result<()> {
1175 let key = "/by_row";
1177 self._add_group(key)?;
1178
1179 let key = "/by_row/data";
1180 self.new_filled_vector(key, data_type::float32(), csr_vals)?;
1181 let key = "/by_row/indices";
1182 self.new_filled_vector(key, data_type::uint64(), csr_cols)?;
1183 let key = "/by_row/indptr";
1184 self.new_filled_vector(key, data_type::uint64(), csr_rowptr)?;
1185
1186 Ok(())
1187 }
1188
1189 fn record_csc_dataset_backend(
1199 &mut self,
1200 csc_rows: &[u64],
1201 csc_vals: &[f32],
1202 csc_colptr: &[u64],
1203 ) -> anyhow::Result<()> {
1204 let key = "/by_column";
1206 self._add_group(key)?;
1207
1208 let key = "/by_column/data";
1209 self.new_filled_vector(key, data_type::float32(), csc_vals)?;
1210 let key = "/by_column/indices";
1211 self.new_filled_vector(key, data_type::uint64(), csc_rows)?;
1212 let key = "/by_column/indptr";
1213 self.new_filled_vector(key, data_type::uint64(), csc_colptr)?;
1214
1215 Ok(())
1216 }
1217
1218 fn cs_create(&mut self, key: CsKey, len: usize) -> anyhow::Result<()> {
1219 let (group, path, dt, elem_bytes) = match key {
1220 CsKey::CscData => (
1221 "/by_column",
1222 "/by_column/data",
1223 data_type::float32(),
1224 std::mem::size_of::<f32>(),
1225 ),
1226 CsKey::CscIndices => (
1227 "/by_column",
1228 "/by_column/indices",
1229 data_type::uint64(),
1230 std::mem::size_of::<u64>(),
1231 ),
1232 CsKey::CscIndptr => (
1233 "/by_column",
1234 "/by_column/indptr",
1235 data_type::uint64(),
1236 std::mem::size_of::<u64>(),
1237 ),
1238 CsKey::CsrData => (
1239 "/by_row",
1240 "/by_row/data",
1241 data_type::float32(),
1242 std::mem::size_of::<f32>(),
1243 ),
1244 CsKey::CsrIndices => (
1245 "/by_row",
1246 "/by_row/indices",
1247 data_type::uint64(),
1248 std::mem::size_of::<u64>(),
1249 ),
1250 CsKey::CsrIndptr => (
1251 "/by_row",
1252 "/by_row/indptr",
1253 data_type::uint64(),
1254 std::mem::size_of::<u64>(),
1255 ),
1256 };
1257 self._add_group(group)?;
1258 self.create_shaped_vector(path, dt, elem_bytes, len)
1259 }
1260
1261 fn cs_write_u64(&mut self, key: CsKey, offset: u64, data: &[u64]) -> anyhow::Result<()> {
1262 let path = match key {
1263 CsKey::CscIndices => "/by_column/indices",
1264 CsKey::CscIndptr => "/by_column/indptr",
1265 CsKey::CsrIndices => "/by_row/indices",
1266 CsKey::CsrIndptr => "/by_row/indptr",
1267 CsKey::CscData | CsKey::CsrData => {
1268 return Err(anyhow!("cs_write_u64 called on f32 slot {:?}", key));
1269 }
1270 };
1271 self.write_slab_u64(path, offset, data)
1272 }
1273
1274 fn cs_write_f32(&mut self, key: CsKey, offset: u64, data: &[f32]) -> anyhow::Result<()> {
1275 let path = match key {
1276 CsKey::CscData => "/by_column/data",
1277 CsKey::CsrData => "/by_row/data",
1278 _ => {
1279 return Err(anyhow!("cs_write_f32 called on u64 slot {:?}", key));
1280 }
1281 };
1282 self.write_slab_f32(path, offset, data)
1283 }
1284}