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 row_indptr(&self) -> &[u64] {
566 &self.by_row_indptr
567 }
568
569 fn metadata(&self) -> Metadata {
570 Self::_get_group_attr::<Metadata>(self.read_store.clone(), "/", meta::ATTR)
571 .unwrap_or_default()
572 }
573
574 fn set_metadata(&mut self, values: &Metadata) -> anyhow::Result<()> {
575 let mut root = zarrs::group::Group::open(self.write_store()?.clone(), "/")?;
576 if values.is_empty() {
577 root.attributes_mut().remove(meta::ATTR);
578 } else {
579 root.attributes_mut()
580 .insert(meta::ATTR.to_string(), serde_json::to_value(values)?);
581 }
582 root.store_metadata()?;
583 Ok(())
584 }
585
586 fn reopen_backend(&mut self) -> anyhow::Result<()> {
587 let store = Arc::new(FilesystemStore::new(&self.file_name)?);
593 self.read_store = store.clone();
594 self.write_store = Some(store);
595 self.by_column_data_cache = Arc::new(OnceLock::new());
596 self.by_column_indices_cache = Arc::new(OnceLock::new());
597 self.by_row_data_cache = Arc::new(OnceLock::new());
598 self.by_row_indices_cache = Arc::new(OnceLock::new());
599 self.streamed_nnz = 0;
600 self.read_column_indptr()?;
601 self.read_row_indptr()?;
602 Ok(())
603 }
604
605 fn note_streamed_nnz(&mut self, n: u64) {
606 self.streamed_nnz += n;
607 }
608
609 fn streamed_nnz(&self) -> u64 {
610 self.streamed_nnz
611 }
612
613 fn reset_streamed_nnz(&mut self) {
614 self.streamed_nnz = 0;
615 }
616
617 fn read_column_indptr(&mut self) -> anyhow::Result<()> {
618 use zarrs::array::Array as ZArray;
619 let key = "/by_column/indptr";
620 if let Ok(indptr) = ZArray::open(self.read_store.clone(), key) {
621 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
622 self.by_column_indptr.clear();
623 self.by_column_indptr.extend(indptr_vec);
624 }
625 Ok(())
626 }
627
628 fn clean_preloaded_columns(&mut self) {
629 self.by_column_data = None;
630 self.by_column_indices = None;
631 self.column_preload = None;
632 }
633
634 fn preload_columns(&mut self) -> anyhow::Result<()> {
636 if self.by_column_data.is_some() && self.by_column_indices.is_some() {
637 return Ok(());
638 }
639 let reservation = match self.num_non_zeros() {
640 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "column") {
641 Some(r) => Some(r),
642 None => return Ok(()),
643 },
644 None => None,
645 };
646 use zarrs::array::Array as ZArray;
647
648 let key = "/by_column/data";
649 let data = ZArray::open(self.read_store.clone(), key)?;
650 let key = "/by_column/indices";
651 let indices = ZArray::open(self.read_store.clone(), key)?;
652
653 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
654 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
655
656 index_audit::check_preload(
657 index_audit::Major::Column,
658 self.num_rows(),
659 self.num_columns(),
660 &self.by_column_indptr,
661 &indices,
662 data.len(),
663 )?;
664
665 self.by_column_indices = Some(indices);
666 self.by_column_data = Some(data);
667 self.column_preload = reservation;
668 Ok(())
669 }
670
671 fn clean_preloaded_rows(&mut self) {
672 self.by_row_data = None;
673 self.by_row_indices = None;
674 self.row_preload = None;
675 }
676
677 fn preload_rows(&mut self) -> anyhow::Result<()> {
679 if self.by_row_data.is_some() && self.by_row_indices.is_some() {
680 return Ok(());
681 }
682 let reservation = match self.num_non_zeros() {
683 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "row") {
684 Some(r) => Some(r),
685 None => return Ok(()),
686 },
687 None => None,
688 };
689 use zarrs::array::Array as ZArray;
690
691 let data = ZArray::open(self.read_store.clone(), KEY_BY_ROW_DATA)?;
692 let indices = ZArray::open(self.read_store.clone(), KEY_BY_ROW_INDICES)?;
693
694 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
695 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
696
697 index_audit::check_preload(
698 index_audit::Major::Row,
699 self.num_rows(),
700 self.num_columns(),
701 &self.by_row_indptr,
702 &indices,
703 data.len(),
704 )?;
705
706 self.by_row_indices = Some(indices);
707 self.by_row_data = Some(data);
708 self.row_preload = reservation;
709 Ok(())
710 }
711
712 fn record_mtx_shape(&mut self, mtx_shape: Option<(usize, usize, usize)>) -> anyhow::Result<()> {
714 if let Some((nrow, ncol, nnz)) = mtx_shape {
715 let ws = self.write_store()?;
716 let read_store = self.read_store.clone();
717
718 let check_set_attr = |attr_name: &str, value: usize| -> anyhow::Result<()> {
719 let old_value = Self::_get_group_attr::<usize>(read_store.clone(), "/", attr_name);
720 let new_value = serde_json::to_value(value)?;
721
722 match old_value {
723 Some(old_value) => {
724 if old_value != new_value {
725 return Err(anyhow!("{} mismatch", attr_name));
726 }
727 }
728 _ => {
729 Self::_set_group_attr(ws.clone(), "/", attr_name, &new_value)?;
730 }
731 }
732 Ok(())
733 };
734
735 check_set_attr("nrow", nrow)?;
736 check_set_attr("ncol", ncol)?;
737 check_set_attr("nnz", nnz)?;
738 }
739 Ok(())
740 }
741
742 fn initialize_backend(&mut self) -> anyhow::Result<()> {
744 use zarrs::group::GroupBuilder;
745
746 self.remove_backend_file()?;
747 let zarr_file = &self.file_name;
748 let store = Arc::new(FilesystemStore::new(zarr_file)?);
749 let root = GroupBuilder::new().build(store.clone(), "/")?;
750 root.store_metadata()?;
751
752 self.read_store = store.clone();
753 self.write_store = Some(store);
754 self.file_name = zarr_file.to_string();
755 self.max_column_name_idx = MAX_COLUMN_NAME_IDX;
756 self.max_row_name_idx = MAX_ROW_NAME_IDX;
757 self.by_column_indptr = vec![];
758 self.by_row_indptr = vec![];
759
760 Ok(())
761 }
762
763 fn remove_backend_file(&self) -> anyhow::Result<()> {
765 let backend = std::path::Path::new(&self.file_name);
766 if backend.exists() {
767 if backend.is_file() {
768 std::fs::remove_file(backend)?;
769 } else {
770 std::fs::remove_dir_all(backend)?;
771 }
772 }
773 Ok(())
774 }
775
776 fn get_backend_file_name(&self) -> &str {
778 &self.file_name
779 }
780
781 fn backend_type(&self) -> SparseIoBackend {
782 SparseIoBackend::Zarr
783 }
784
785 fn to_mtx_file(&self, mtx_file: &str) -> anyhow::Result<()> {
788 if let (Some(ncol), Some(nrow), Some(nnz)) =
789 (self.num_columns(), self.num_rows(), self.num_non_zeros())
790 {
791 let (nrow, ncol, nnz) = (nrow, ncol, nnz);
792
793 let mut buf = open_buf_writer(mtx_file)?;
794 shared::write_mtx_header(&mut buf, nrow, ncol, nnz)?;
795
796 let (indptr, data, indices) = self.open_csc_triplets()?;
797 let indptr = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
798 crate::sparse_io::index_audit::check_indptr("to_mtx_file", &indptr, ncol, nnz)?;
799
800 let total_nnz = indptr[ncol];
807 let mut jj = 0usize; let mut pos = 0u64;
809 while pos < total_nnz {
810 let end = (pos + MTX_STREAM_BLOCK).min(total_nnz);
811 let subset = Self::create_subset(pos..end);
812 let data_block = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
813 let indices_block = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
814
815 for (k, (&val, &ii)) in data_block.iter().zip(&indices_block).enumerate() {
816 let global = pos + k as u64;
817 while jj + 1 < indptr.len() && indptr[jj + 1] <= global {
819 jj += 1;
820 }
821 writeln!(buf, "{}\t{}\t{}", ii as usize + 1, jj + 1, val)?;
823 }
824 pos = end;
825 }
826 buf.flush()?;
827 Ok(())
828 } else {
829 Err(anyhow!("Unable to figure out the size of the backend data"))
830 }
831 }
832
833 fn register_row_names_file(&mut self, row_name_file: &str) {
836 let _ = self.register_names_file(
837 "/row_names",
838 row_name_file,
839 0..self.max_row_name_idx,
840 ROW_SEP,
841 );
842 }
843
844 fn register_row_names_vec(&mut self, rows: &[Box<str>]) {
847 let _ = self.register_names_vec("/row_names", rows);
848 }
849
850 fn register_column_names_file(&mut self, column_name_file: &str) {
853 let _ = self.register_names_file(
854 "/column_names",
855 column_name_file,
856 0..self.max_column_name_idx,
857 COLUMN_SEP,
858 );
859 }
860
861 fn register_column_names_vec(&mut self, columns: &[Box<str>]) {
864 let _ = self.register_names_vec("/column_names", columns);
865 }
866
867 fn num_rows(&self) -> Option<usize> {
869 Self::_num_rows(self.read_store.clone())
870 }
871
872 fn num_columns(&self) -> Option<usize> {
874 Self::_num_columns(self.read_store.clone())
875 }
876
877 fn num_non_zeros(&self) -> Option<usize> {
879 Self::_num_nnz(self.read_store.clone())
880 }
881
882 fn register_names_file(
888 &mut self,
889 key: &str,
890 name_file: &str,
891 name_columns: Range<usize>,
892 name_sep: &str,
893 ) -> anyhow::Result<()> {
894 let names = parse_name_file(name_file, name_columns, name_sep)?;
895 self.new_filled_vector(key, data_type::string(), &names)?;
896 Ok(())
897 }
898
899 fn register_names_vec(&mut self, key: &str, names: &[Box<str>]) -> anyhow::Result<()> {
903 let names_vec: Vec<String> = names.iter().map(|x| x.to_string()).collect();
904 self.new_filled_vector(key, data_type::string(), &names_vec)?;
905 Ok(())
906 }
907
908 fn row_names(&self) -> anyhow::Result<Vec<Box<str>>> {
909 self.retrieve_registered_names("/row_names")
910 }
911
912 fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
913 self.retrieve_registered_names("/column_names")
914 }
915
916 fn retrieve_registered_names(&self, key: &str) -> anyhow::Result<Vec<Box<str>>> {
919 Ok(self
920 ._retrieve_vector::<String>(key)?
921 .into_iter()
922 .map(|s| s.into_boxed_str())
923 .collect())
924 }
925
926 fn read_triplets_by_single_column(
930 &self,
931 j_data: usize,
932 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
933 use zarrs::array::Array as ZArray;
934
935 let indptr = &self.by_column_indptr;
936
937 let nrow = self
938 .num_rows()
939 .ok_or(anyhow!("can't figure out the number of rows"))?;
940 index_audit::check_requested_slots("column", [j_data], indptr.len())?;
941
942 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
943 let ncol_out = 1;
944 let jj = 0;
945
946 let mut ret = Vec::new();
947 index_audit::emit_slot(
948 "preloaded column",
949 j_data,
950 indptr,
951 indices,
952 data,
953 nrow,
954 &mut ret,
955 |ii, x| (ii, jj, x),
956 )?;
957
958 Ok((nrow, ncol_out, ret))
959 } else {
960 let key = "/by_column/data";
961 let data = ZArray::open(self.read_store.clone(), key)?;
962 let key = "/by_column/indices";
963 let indices = ZArray::open(self.read_store.clone(), key)?;
964
965 let ncol_out = 1;
966 let jj = 0;
967
968 let start = indptr[j_data];
970 let end = indptr[j_data + 1];
971 anyhow::ensure!(
972 start <= end,
973 "column read: slot {j_data} points at [{start}, {end})"
974 );
975
976 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity((end - start) as usize);
977
978 if start < end {
979 let subset = Self::create_subset(start..end);
980 let data_slice = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
981 let indices_slice = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
982
983 for k in 0..(end - start) {
984 let x_ij = data_slice[k as usize];
985 let ii = indices_slice[k as usize];
986 if ii >= nrow as u64 {
987 return Err(index_audit::inner_out_of_range(
988 "column read",
989 (start + k) as usize,
990 ii,
991 nrow,
992 ));
993 }
994 ret.push((ii, jj, x_ij));
995 }
996 }
997
998 Ok((nrow, ncol_out, ret))
999 }
1000 }
1001
1002 fn read_triplets_by_columns(
1006 &self,
1007 columns: Self::IndexIter,
1008 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1009 let indptr = &self.by_column_indptr;
1010 let columns_vec = columns.into_iter().collect::<Vec<usize>>();
1011
1012 let nrow = self
1013 .num_rows()
1014 .ok_or(anyhow!("can't figure out the number of rows"))?;
1015
1016 let ncol = self
1017 .num_columns()
1018 .ok_or(anyhow!("can't figure out the number of columns"))?;
1019
1020 let ncol_out = columns_vec.len();
1021 index_audit::check_requested_slots(
1022 "column",
1023 columns_vec.iter().copied().filter(|&j| j < ncol),
1024 indptr.len(),
1025 )?;
1026
1027 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
1028 if let Some(&bad) = columns_vec.iter().find(|&&j| j >= ncol) {
1029 anyhow::bail!("read column {bad}: outside {ncol} columns");
1030 }
1031 let min_start = columns_vec
1032 .iter()
1033 .map(|&j_data| indptr[j_data])
1034 .min()
1035 .unwrap_or(0);
1036
1037 let max_end = columns_vec
1038 .iter()
1039 .map(|&j_data| indptr[j_data + 1])
1040 .max()
1041 .unwrap_or(0);
1042
1043 let mut ret: Vec<(u64, u64, f32)> =
1044 Vec::with_capacity(max_end.saturating_sub(min_start) as usize);
1045
1046 for (jj, &j_data) in columns_vec.iter().enumerate() {
1047 let jj = jj as u64;
1048 index_audit::emit_slot(
1049 "preloaded column",
1050 j_data,
1051 indptr,
1052 indices,
1053 data,
1054 nrow,
1055 &mut ret,
1056 |ii, x| (ii, jj, x),
1057 )?;
1058 }
1059
1060 Ok((nrow, ncol_out, ret))
1061 } else {
1062 let mut tagged: Vec<(u64, u64, u64)> = columns_vec
1066 .iter()
1067 .enumerate()
1068 .filter_map(|(jj, &j_data)| {
1069 if j_data >= ncol {
1070 return None;
1071 }
1072 let start = indptr[j_data];
1073 let end = indptr[j_data + 1];
1074 (start < end).then_some((jj as u64, start, end))
1075 })
1076 .collect();
1077 tagged.sort_by_key(|&(_, start, _)| start);
1078
1079 let data_cache = self.cache_for(&self.by_column_data_cache, KEY_BY_COLUMN_DATA)?;
1080 let indices_cache =
1081 self.cache_for(&self.by_column_indices_cache, KEY_BY_COLUMN_INDICES)?;
1082
1083 let opts = zarrs::array::CodecOptions::default();
1084 let ret = shared::coalesce_and_emit(
1085 &tagged,
1086 nrow,
1087 |jj, ii, val| (ii, jj, val),
1088 |s, e| {
1089 let subset = Self::create_subset(s..e);
1090 let data_buf =
1091 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1092 Vec<f32>,
1093 >(data_cache, &subset, &opts)?;
1094 let indices_buf =
1095 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1096 Vec<u64>,
1097 >(indices_cache, &subset, &opts)?;
1098 Ok((data_buf, indices_buf))
1099 },
1100 )?;
1101 Ok((nrow, ncol_out, ret))
1102 }
1103 }
1104
1105 fn csc_column_arrays(&self) -> Option<(&[u64], &[u64], &[f32])> {
1106 match (
1107 self.by_column_data.as_ref(),
1108 self.by_column_indices.as_ref(),
1109 ) {
1110 (Some(data), Some(indices)) if !self.by_column_indptr.is_empty() => Some((
1111 self.by_column_indptr.as_slice(),
1112 indices.as_slice(),
1113 data.as_slice(),
1114 )),
1115 _ => None,
1116 }
1117 }
1118
1119 fn read_triplets_by_rows(
1123 &self,
1124 rows: Self::IndexIter,
1125 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1126 let indptr = &self.by_row_indptr;
1127
1128 let rows_vec = rows.into_iter().collect::<Vec<_>>();
1129
1130 let (nrow, ncol) = match (self.num_rows(), self.num_columns()) {
1131 (Some(nrow), Some(ncol)) => (nrow, ncol),
1132 _ => return Err(anyhow!("Unable to figure out the size of the backend data")),
1133 };
1134 let nrow_out = rows_vec.len();
1135 index_audit::check_requested_slots(
1136 "row",
1137 rows_vec.iter().copied().filter(|&i| i < nrow),
1138 indptr.len(),
1139 )?;
1140
1141 if let (Some(data), Some(indices)) = (&self.by_row_data, &self.by_row_indices) {
1142 let mut nnz_total: usize = 0;
1143 let valid: Vec<(u64, usize)> = rows_vec
1144 .iter()
1145 .enumerate()
1146 .filter_map(|(ii, &i_data)| {
1147 if i_data >= nrow {
1148 return None;
1149 }
1150 nnz_total += indptr[i_data + 1].saturating_sub(indptr[i_data]) as usize;
1151 Some((ii as u64, i_data))
1152 })
1153 .collect();
1154
1155 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(nnz_total);
1156 for (ii, i_data) in valid {
1157 index_audit::emit_slot(
1158 "preloaded row",
1159 i_data,
1160 indptr,
1161 indices,
1162 data,
1163 ncol,
1164 &mut ret,
1165 |jj, x| (ii, jj, x),
1166 )?;
1167 }
1168 return Ok((nrow_out, ncol, ret));
1169 }
1170
1171 let mut tagged: Vec<(u64, u64, u64)> = rows_vec
1173 .iter()
1174 .enumerate()
1175 .filter_map(|(ii, &i_data)| {
1176 if i_data >= nrow {
1177 return None;
1178 }
1179 let start = indptr[i_data];
1180 let end = indptr[i_data + 1];
1181 (start < end).then_some((ii as u64, start, end))
1182 })
1183 .collect();
1184 tagged.sort_by_key(|&(_, start, _)| start);
1185
1186 let data_cache = self.cache_for(&self.by_row_data_cache, KEY_BY_ROW_DATA)?;
1187 let indices_cache = self.cache_for(&self.by_row_indices_cache, KEY_BY_ROW_INDICES)?;
1188
1189 let opts = zarrs::array::CodecOptions::default();
1190 let ret = shared::coalesce_and_emit(
1191 &tagged,
1192 ncol,
1193 |ii, jj, val| (ii, jj, val),
1194 |s, e| {
1195 let subset = Self::create_subset(s..e);
1196 let data_buf = <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1197 Vec<f32>,
1198 >(data_cache, &subset, &opts)?;
1199 let indices_buf =
1200 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<Vec<u64>>(
1201 indices_cache,
1202 &subset,
1203 &opts,
1204 )?;
1205 Ok((data_buf, indices_buf))
1206 },
1207 )?;
1208 Ok((nrow_out, ncol, ret))
1209 }
1210 fn record_csr_dataset_backend(
1219 &mut self,
1220 csr_cols: &[u64],
1221 csr_vals: &[f32],
1222 csr_rowptr: &[u64],
1223 ) -> anyhow::Result<()> {
1224 let key = "/by_row";
1226 self._add_group(key)?;
1227
1228 let key = "/by_row/data";
1229 self.new_filled_vector(key, data_type::float32(), csr_vals)?;
1230 let key = "/by_row/indices";
1231 self.new_filled_vector(key, data_type::uint64(), csr_cols)?;
1232 let key = "/by_row/indptr";
1233 self.new_filled_vector(key, data_type::uint64(), csr_rowptr)?;
1234
1235 Ok(())
1236 }
1237
1238 fn record_csc_dataset_backend(
1248 &mut self,
1249 csc_rows: &[u64],
1250 csc_vals: &[f32],
1251 csc_colptr: &[u64],
1252 ) -> anyhow::Result<()> {
1253 let key = "/by_column";
1255 self._add_group(key)?;
1256
1257 let key = "/by_column/data";
1258 self.new_filled_vector(key, data_type::float32(), csc_vals)?;
1259 let key = "/by_column/indices";
1260 self.new_filled_vector(key, data_type::uint64(), csc_rows)?;
1261 let key = "/by_column/indptr";
1262 self.new_filled_vector(key, data_type::uint64(), csc_colptr)?;
1263
1264 Ok(())
1265 }
1266
1267 fn cs_create(&mut self, key: CsKey, len: usize) -> anyhow::Result<()> {
1268 let (group, path, dt, elem_bytes) = match key {
1269 CsKey::CscData => (
1270 "/by_column",
1271 "/by_column/data",
1272 data_type::float32(),
1273 std::mem::size_of::<f32>(),
1274 ),
1275 CsKey::CscIndices => (
1276 "/by_column",
1277 "/by_column/indices",
1278 data_type::uint64(),
1279 std::mem::size_of::<u64>(),
1280 ),
1281 CsKey::CscIndptr => (
1282 "/by_column",
1283 "/by_column/indptr",
1284 data_type::uint64(),
1285 std::mem::size_of::<u64>(),
1286 ),
1287 CsKey::CsrData => (
1288 "/by_row",
1289 "/by_row/data",
1290 data_type::float32(),
1291 std::mem::size_of::<f32>(),
1292 ),
1293 CsKey::CsrIndices => (
1294 "/by_row",
1295 "/by_row/indices",
1296 data_type::uint64(),
1297 std::mem::size_of::<u64>(),
1298 ),
1299 CsKey::CsrIndptr => (
1300 "/by_row",
1301 "/by_row/indptr",
1302 data_type::uint64(),
1303 std::mem::size_of::<u64>(),
1304 ),
1305 };
1306 self._add_group(group)?;
1307 self.create_shaped_vector(path, dt, elem_bytes, len)
1308 }
1309
1310 fn cs_write_u64(&mut self, key: CsKey, offset: u64, data: &[u64]) -> anyhow::Result<()> {
1311 let path = match key {
1312 CsKey::CscIndices => "/by_column/indices",
1313 CsKey::CscIndptr => "/by_column/indptr",
1314 CsKey::CsrIndices => "/by_row/indices",
1315 CsKey::CsrIndptr => "/by_row/indptr",
1316 CsKey::CscData | CsKey::CsrData => {
1317 return Err(anyhow!("cs_write_u64 called on f32 slot {:?}", key));
1318 }
1319 };
1320 self.write_slab_u64(path, offset, data)
1321 }
1322
1323 fn cs_write_f32(&mut self, key: CsKey, offset: u64, data: &[f32]) -> anyhow::Result<()> {
1324 let path = match key {
1325 CsKey::CscData => "/by_column/data",
1326 CsKey::CsrData => "/by_row/data",
1327 _ => {
1328 return Err(anyhow!("cs_write_f32 called on u64 slot {:?}", key));
1329 }
1330 };
1331 self.write_slab_f32(path, offset, data)
1332 }
1333}