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
43#[derive(Clone)]
60pub struct SparseMtxData {
61 read_store: Arc<dyn ZReadStorageTraits>,
62 write_store: Option<Arc<FilesystemStore>>,
63 file_name: String,
64 max_row_name_idx: usize,
65 max_column_name_idx: usize,
66 by_column_indptr: Vec<u64>,
67 streamed_nnz: u64,
69 by_row_indptr: Vec<u64>,
70 by_column_indices: Option<Vec<u64>>,
71 by_column_data: Option<Vec<f32>>,
72 by_row_indices: Option<Vec<u64>>,
73 by_row_data: Option<Vec<f32>>,
74 column_preload: Option<crate::sparse_io::PreloadReservation>,
76 row_preload: Option<crate::sparse_io::PreloadReservation>,
78 by_column_data_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
83 by_column_indices_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
84 by_row_data_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
85 by_row_indices_cache: Arc<OnceLock<ChunkCacheDecodedLruChunkLimit>>,
86 shape: MtxShape,
92}
93
94#[derive(Clone, Copy, Default)]
96struct MtxShape {
97 nrow: Option<usize>,
98 ncol: Option<usize>,
99 nnz: Option<usize>,
100}
101
102impl MtxShape {
103 fn read(store: &Arc<dyn ZReadStorageTraits>) -> Self {
104 Self {
105 nrow: SparseMtxData::_num_rows(store.clone()),
106 ncol: SparseMtxData::_num_columns(store.clone()),
107 nnz: SparseMtxData::_num_nnz(store.clone()),
108 }
109 }
110}
111
112impl SparseMtxData {
113 fn write_store(&self) -> anyhow::Result<&Arc<FilesystemStore>> {
115 self.write_store
116 .as_ref()
117 .ok_or_else(|| anyhow!("store is read-only (zip archive)"))
118 }
119}
120
121impl SparseMtxData {
122 pub fn new(zarr_file: Option<&str>) -> anyhow::Result<Self> {
128 Self::create_backend(zarr_file)
129 }
130
131 fn create_backend(zarr_file: Option<&str>) -> anyhow::Result<Self> {
133 match zarr_file {
134 Some(backend_file) => Self::register_backend_file(backend_file),
135 None => {
136 let backend_file = create_temp_dir_file(".zarr")?;
137 let backend_file = backend_file
138 .to_str()
139 .ok_or_else(|| anyhow::anyhow!("Failed to convert path to string"))?;
140 Self::register_backend_file(backend_file)
141 }
142 }
143 }
144
145 pub fn open(backend_file: &str) -> anyhow::Result<Self> {
148 let (read_store, write_store) = crate::zarr_io::open_zarr_store_rw(backend_file)?;
149
150 let shape = MtxShape::read(&read_store);
151 if (shape.nrow, shape.ncol, shape.nnz) == (None, None, None) {
152 anyhow::bail!("Couldn't figure out the size of this sparse matrix data");
153 }
154
155 let mut ret = Self {
156 read_store,
157 write_store,
158 file_name: backend_file.to_string(),
159 max_row_name_idx: MAX_ROW_NAME_IDX,
160 max_column_name_idx: MAX_COLUMN_NAME_IDX,
161 by_column_indptr: vec![],
162 streamed_nnz: 0,
163 by_row_indptr: vec![],
164 by_column_indices: None,
165 by_column_data: None,
166 by_row_indices: None,
167 by_row_data: None,
168 column_preload: None,
169 row_preload: None,
170 by_column_data_cache: Arc::new(OnceLock::new()),
171 by_column_indices_cache: Arc::new(OnceLock::new()),
172 by_row_data_cache: Arc::new(OnceLock::new()),
173 by_row_indices_cache: Arc::new(OnceLock::new()),
174 shape,
175 };
176
177 ret.read_column_indptr()?;
178 ret.read_row_indptr()?;
179
180 Ok(ret)
181 }
182
183 pub fn from_mtx_file(
190 mtx_file: &str,
191 backend_file: Option<&str>,
192 index_by_row: Option<bool>,
193 ) -> anyhow::Result<Self> {
194 let zarr_file = backend_file
195 .map(|s| s.to_string())
196 .unwrap_or_else(|| format!("{}.zarr", mtx_file));
197
198 info!("backend file: {}", zarr_file);
199 let mut ret = Self::register_backend_file(&zarr_file)?;
200
201 ret.import_mtx_file(mtx_file, index_by_row == Some(true))?;
202
203 info!("created sparse backend from {}", mtx_file);
204 Ok(ret)
205 }
206
207 #[cfg(feature = "ndarray")]
208 pub fn from_ndarray(
213 array: &Array2<f32>,
214 zarr_file: Option<&str>,
215 index_by_row: Option<bool>,
216 ) -> anyhow::Result<Self> {
217 let mut ret = Self::create_backend(zarr_file)?;
218
219 ret.import_ndarray_by_col(array)?;
220 ret.read_column_indptr()?;
221
222 if index_by_row == Some(true) {
223 ret.import_ndarray_by_row(array)?;
224 ret.read_row_indptr()?;
225 }
226 Ok(ret)
227 }
228
229 pub fn from_dmatrix(
234 matrix: &DMatrix<f32>,
235 zarr_file: Option<&str>,
236 index_by_row: Option<bool>,
237 ) -> anyhow::Result<Self> {
238 let mut ret = Self::create_backend(zarr_file)?;
239
240 ret.import_dmatrix_by_col(matrix)?;
241 ret.read_column_indptr()?;
242
243 if index_by_row == Some(true) {
244 ret.import_dmatrix_by_row(matrix)?;
245 ret.read_row_indptr()?;
246 }
247 Ok(ret)
248 }
249
250 pub fn print_hierarchy(&self) -> anyhow::Result<()> {
252 use zarrs::config::MetadataRetrieveVersion;
253 let node =
254 zarrs::node::Node::open_opt(&self.read_store, "/", &MetadataRetrieveVersion::Default)?;
255 let tree = node.hierarchy_tree();
256 info!("hierarchy_tree:\n{}", tree);
257 Ok(())
258 }
259
260 fn register_backend_file(zarr_file: &str) -> anyhow::Result<Self> {
262 use zarrs::group::GroupBuilder;
263 crate::zarr_io::make_stage(zarr_file)?;
264 let store = Arc::new(FilesystemStore::new(zarr_file)?);
265 let root = GroupBuilder::new().build(store.clone(), "/")?;
266 root.store_metadata()?;
267
268 Ok(Self {
269 read_store: store.clone(),
270 write_store: Some(store),
271 file_name: zarr_file.to_string(),
272 max_row_name_idx: MAX_ROW_NAME_IDX,
273 max_column_name_idx: MAX_COLUMN_NAME_IDX,
274 by_column_indptr: vec![],
275 streamed_nnz: 0,
276 by_row_indptr: vec![],
277 by_column_indices: None,
278 by_column_data: None,
279 by_row_indices: None,
280 by_row_data: None,
281 column_preload: None,
282 row_preload: None,
283 by_column_data_cache: Arc::new(OnceLock::new()),
284 by_column_indices_cache: Arc::new(OnceLock::new()),
285 by_row_data_cache: Arc::new(OnceLock::new()),
286 by_row_indices_cache: Arc::new(OnceLock::new()),
287 shape: MtxShape::default(),
288 })
289 }
290
291 fn forget_array_chunks(&mut self, key: &str) {
296 let cell = match key {
297 KEY_BY_COLUMN_DATA => &mut self.by_column_data_cache,
298 KEY_BY_COLUMN_INDICES => &mut self.by_column_indices_cache,
299 KEY_BY_ROW_DATA => &mut self.by_row_data_cache,
300 KEY_BY_ROW_INDICES => &mut self.by_row_indices_cache,
301 _ => return,
302 };
303 *cell = Arc::new(OnceLock::new());
304 }
305
306 fn forget_store_contents(&mut self) {
312 for key in [
313 KEY_BY_COLUMN_DATA,
314 KEY_BY_COLUMN_INDICES,
315 KEY_BY_ROW_DATA,
316 KEY_BY_ROW_INDICES,
317 ] {
318 self.forget_array_chunks(key);
319 }
320 self.clean_preloaded_columns();
321 self.clean_preloaded_rows();
322 self.shape = MtxShape::default();
323 self.streamed_nnz = 0;
324 }
325
326 fn new_filled_vector<V>(&mut self, key: &str, dt: DataType, vec: &[V]) -> anyhow::Result<()>
339 where
340 V: zarrs::array::Element,
341 {
342 use zarrs::array::codec::ZstdCodec;
343 use zarrs::array::ArrayBuilder;
344 use zarrs::array::FillValue;
345
346 self.forget_array_chunks(key);
347 let ws = self.write_store()?;
348
349 let nelem = vec.len();
350 let chunk_size = chunk_elems(nelem, std::mem::size_of::<V>());
351
352 let fill = if dt == data_type::float32() {
353 FillValue::from(zarrs::array::ZARR_NAN_F32)
354 } else if dt == data_type::uint64() {
355 FillValue::from(0u64)
356 } else if dt == data_type::string() {
357 FillValue::from("")
358 } else {
359 FillValue::from(0)
360 };
361
362 let array = ArrayBuilder::new(
363 vec![vec.len() as u64], vec![chunk_size as u64], dt, fill, )
368 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
369 .build(ws.clone(), key)?;
370
371 array.store_metadata()?;
372
373 let subset = Self::create_subset(0..vec.len() as u64);
374 array.store_array_subset(&subset, vec)?;
375
376 Ok(())
377 }
378
379 fn _open_vector(
380 &self,
381 key: &str,
382 ) -> anyhow::Result<zarrs::array::Array<dyn ZReadStorageTraits>> {
383 use zarrs::array::Array as ZArray;
384 let ret = ZArray::open(self.read_store.clone(), key)?;
385 Ok(ret)
386 }
387
388 fn create_shaped_vector(
396 &mut self,
397 key: &str,
398 dt: DataType,
399 elem_bytes: usize,
400 nelem: usize,
401 ) -> anyhow::Result<()> {
402 use zarrs::array::codec::ZstdCodec;
403 use zarrs::array::ArrayBuilder;
404 use zarrs::array::FillValue;
405
406 self.forget_array_chunks(key);
407 let ws = self.write_store()?;
408
409 let chunk_size = chunk_elems(nelem, elem_bytes);
410
411 let fill = if dt == data_type::float32() {
412 FillValue::from(zarrs::array::ZARR_NAN_F32)
413 } else if dt == data_type::uint64() {
414 FillValue::from(0u64)
415 } else {
416 FillValue::from(0)
417 };
418
419 let array = ArrayBuilder::new(vec![nelem as u64], vec![chunk_size as u64], dt, fill)
420 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
421 .build(ws.clone(), key)?;
422
423 array.store_metadata()?;
424 Ok(())
425 }
426
427 fn _open_writable_vector(
431 &self,
432 key: &str,
433 ) -> anyhow::Result<zarrs::array::Array<FilesystemStore>> {
434 use zarrs::array::Array as ZArray;
435 let ws = self.write_store()?.clone();
436 let ret = ZArray::open(ws, key)?;
437 Ok(ret)
438 }
439
440 fn write_slab_u64(&mut self, key: &str, offset: u64, data: &[u64]) -> anyhow::Result<()> {
443 if data.is_empty() {
444 return Ok(());
445 }
446 self.forget_array_chunks(key);
447 let array = self._open_writable_vector(key)?;
448 let subset = Self::create_subset(offset..offset + data.len() as u64);
449 array.store_array_subset(&subset, data)?;
450 Ok(())
451 }
452
453 fn write_slab_f32(&mut self, key: &str, offset: u64, data: &[f32]) -> anyhow::Result<()> {
455 if data.is_empty() {
456 return Ok(());
457 }
458 self.forget_array_chunks(key);
459 let array = self._open_writable_vector(key)?;
460 let subset = Self::create_subset(offset..offset + data.len() as u64);
461 array.store_array_subset(&subset, data)?;
462 Ok(())
463 }
464
465 #[allow(clippy::type_complexity)]
466 fn open_csc_triplets(
467 &self,
468 ) -> anyhow::Result<(
469 zarrs::array::Array<dyn ZReadStorageTraits>,
470 zarrs::array::Array<dyn ZReadStorageTraits>,
471 zarrs::array::Array<dyn ZReadStorageTraits>,
472 )> {
473 Ok((
474 self._open_vector("/by_column/indptr")?,
475 self._open_vector("/by_column/data")?,
476 self._open_vector("/by_column/indices")?,
477 ))
478 }
479
480 #[inline]
482 fn create_subset(range: Range<u64>) -> ArraySubset {
483 ArraySubset::new_with_ranges(&[range])
484 }
485
486 fn open_chunk_cache(
492 read_store: &Arc<dyn ZReadStorageTraits>,
493 key: &str,
494 ) -> anyhow::Result<ChunkCacheDecodedLruChunkLimit> {
495 use zarrs::array::Array as ZArray;
496 use zarrs::storage::ReadableStorageTraits;
497
498 let storage_readable: Arc<dyn ReadableStorageTraits> = read_store.clone().readable();
499 let arr = ZArray::open(read_store.clone(), key)?;
500 let arr_arc = Arc::new(arr.with_storage(storage_readable));
501 Ok(ChunkCacheDecodedLruChunkLimit::new(
502 arr_arc,
503 cache_chunk_cap(),
504 ))
505 }
506
507 fn cache_for<'a>(
511 &'a self,
512 cell: &'a OnceLock<ChunkCacheDecodedLruChunkLimit>,
513 key: &str,
514 ) -> anyhow::Result<&'a ChunkCacheDecodedLruChunkLimit> {
515 if let Some(cache) = cell.get() {
516 return Ok(cache);
517 }
518 let _ = cell.set(Self::open_chunk_cache(&self.read_store, key)?);
519 Ok(cell.get().expect("OnceLock populated above"))
520 }
521
522 fn read_csc_ranges(
526 &self,
527 tagged: &[(u64, u64, u64)],
528 nrow: usize,
529 ) -> anyhow::Result<Vec<(u64, u64, f32)>> {
530 use zarrs::array::chunk_cache::ChunkCache;
531
532 let data_cache = self.cache_for(&self.by_column_data_cache, KEY_BY_COLUMN_DATA)?;
533 let indices_cache = self.cache_for(&self.by_column_indices_cache, KEY_BY_COLUMN_INDICES)?;
534
535 let opts = zarrs::array::CodecOptions::default();
536 shared::coalesce_and_emit(
537 tagged,
538 nrow,
539 |jj, ii, val| (ii, jj, val),
540 |s, e| {
541 let subset = Self::create_subset(s..e);
542 let data_buf = <_ as ChunkCache>::retrieve_array_subset::<Vec<f32>>(
543 data_cache, &subset, &opts,
544 )?;
545 let indices_buf = <_ as ChunkCache>::retrieve_array_subset::<Vec<u64>>(
546 indices_cache,
547 &subset,
548 &opts,
549 )?;
550 Ok((data_buf, indices_buf))
551 },
552 )
553 }
554
555 fn _retrieve_vector<V>(&self, key: &str) -> anyhow::Result<Vec<V>>
556 where
557 V: zarrs::array::ElementOwned,
558 {
559 let data = self._open_vector(key)?;
560 let ntot = data.shape()[0];
561 let subset = Self::create_subset(0..ntot);
562 Ok(data.retrieve_array_subset::<Vec<V>>(&subset)?)
563 }
564
565 fn _set_group_attr<V>(
571 store: Arc<FilesystemStore>,
572 group_name: &str,
573 attr_name: &str,
574 value: &V,
575 ) -> anyhow::Result<()>
576 where
577 V: serde::Serialize,
578 {
579 use zarrs::group::Group;
580 let mut group = Group::open(store, group_name)?;
581
582 let new_value = serde_json::to_value(value)?;
583 group
584 .attributes_mut()
585 .insert((*attr_name).to_string(), new_value);
586 group.store_metadata()?;
587 Ok(())
588 }
589
590 fn _get_group_attr<V>(
592 store: Arc<dyn ZReadStorageTraits>,
593 group_name: &str,
594 attr_name: &str,
595 ) -> Option<V>
596 where
597 V: serde::de::DeserializeOwned,
598 {
599 zarrs::group::Group::open(store, group_name)
600 .ok()
601 .and_then(|grp| grp.attributes().get(attr_name).cloned())
602 .and_then(|attr| serde_json::from_value(attr).ok())
603 }
604
605 fn _num_nnz(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
606 Self::_get_group_attr::<usize>(store, "/", "nnz")
607 }
608
609 fn _num_rows(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
610 Self::_get_group_attr::<usize>(store, "/", "nrow")
611 }
612
613 fn _num_columns(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
614 Self::_get_group_attr::<usize>(store, "/", "ncol")
615 }
616
617 fn _add_group(&mut self, group_name: &str) -> anyhow::Result<()> {
619 use zarrs::group::Group;
620 let ws = self.write_store()?;
621
622 if Group::open(ws.clone(), group_name).is_err() {
623 let new_group = zarrs::group::GroupBuilder::new().build(ws.clone(), group_name)?;
624 new_group.store_metadata()?;
625 }
626
627 Ok(())
628 }
629}
630
631impl SparseIo for SparseMtxData {
632 type IndexIter = Vec<usize>;
633
634 fn read_row_indptr(&mut self) -> anyhow::Result<()> {
636 use zarrs::array::Array as Zarray;
637 let key = "/by_row/indptr";
638 if let Ok(indptr) = Zarray::open(self.read_store.clone(), key) {
639 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
640 self.by_row_indptr.clear();
641 self.by_row_indptr.extend(indptr_vec);
642 }
643 Ok(())
644 }
645
646 fn column_indptr(&self) -> &[u64] {
648 &self.by_column_indptr
649 }
650
651 fn row_indptr(&self) -> &[u64] {
652 &self.by_row_indptr
653 }
654
655 fn metadata(&self) -> Metadata {
656 Self::_get_group_attr::<Metadata>(self.read_store.clone(), "/", meta::ATTR)
657 .unwrap_or_default()
658 }
659
660 fn set_metadata(&mut self, values: &Metadata) -> anyhow::Result<()> {
661 let mut root = zarrs::group::Group::open(self.write_store()?.clone(), "/")?;
662 if values.is_empty() {
663 root.attributes_mut().remove(meta::ATTR);
664 } else {
665 root.attributes_mut()
666 .insert(meta::ATTR.to_string(), serde_json::to_value(values)?);
667 }
668 root.store_metadata()?;
669 Ok(())
670 }
671
672 fn reopen_backend(&mut self) -> anyhow::Result<()> {
673 let store = Arc::new(FilesystemStore::new(&self.file_name)?);
675 self.read_store = store.clone();
676 self.write_store = Some(store);
677 self.forget_store_contents();
678 self.shape = MtxShape::read(&self.read_store);
679 self.read_column_indptr()?;
680 self.read_row_indptr()?;
681 Ok(())
682 }
683
684 fn note_streamed_nnz(&mut self, n: u64) {
685 self.streamed_nnz += n;
686 }
687
688 fn streamed_nnz(&self) -> u64 {
689 self.streamed_nnz
690 }
691
692 fn reset_streamed_nnz(&mut self) {
693 self.streamed_nnz = 0;
694 }
695
696 fn read_column_indptr(&mut self) -> anyhow::Result<()> {
697 use zarrs::array::Array as ZArray;
698 let key = "/by_column/indptr";
699 if let Ok(indptr) = ZArray::open(self.read_store.clone(), key) {
700 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
701 self.by_column_indptr.clear();
702 self.by_column_indptr.extend(indptr_vec);
703 }
704 Ok(())
705 }
706
707 fn clean_preloaded_columns(&mut self) {
708 self.by_column_data = None;
709 self.by_column_indices = None;
710 self.column_preload = None;
711 }
712
713 fn preload_columns(&mut self) -> anyhow::Result<()> {
715 if self.by_column_data.is_some() && self.by_column_indices.is_some() {
716 return Ok(());
717 }
718 let reservation = match self.num_non_zeros() {
719 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "column") {
720 Some(r) => Some(r),
721 None => return Ok(()),
722 },
723 None => None,
724 };
725 use zarrs::array::Array as ZArray;
726
727 let key = "/by_column/data";
728 let data = ZArray::open(self.read_store.clone(), key)?;
729 let key = "/by_column/indices";
730 let indices = ZArray::open(self.read_store.clone(), key)?;
731
732 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
733 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
734
735 index_audit::check_preload(
736 index_audit::Major::Column,
737 self.num_rows(),
738 self.num_columns(),
739 &self.by_column_indptr,
740 &indices,
741 data.len(),
742 )?;
743
744 self.by_column_indices = Some(indices);
745 self.by_column_data = Some(data);
746 self.column_preload = reservation;
747 Ok(())
748 }
749
750 fn clean_preloaded_rows(&mut self) {
751 self.by_row_data = None;
752 self.by_row_indices = None;
753 self.row_preload = None;
754 }
755
756 fn preload_rows(&mut self) -> anyhow::Result<()> {
758 if self.by_row_data.is_some() && self.by_row_indices.is_some() {
759 return Ok(());
760 }
761 let reservation = match self.num_non_zeros() {
762 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "row") {
763 Some(r) => Some(r),
764 None => return Ok(()),
765 },
766 None => None,
767 };
768 use zarrs::array::Array as ZArray;
769
770 let data = ZArray::open(self.read_store.clone(), KEY_BY_ROW_DATA)?;
771 let indices = ZArray::open(self.read_store.clone(), KEY_BY_ROW_INDICES)?;
772
773 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
774 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
775
776 index_audit::check_preload(
777 index_audit::Major::Row,
778 self.num_rows(),
779 self.num_columns(),
780 &self.by_row_indptr,
781 &indices,
782 data.len(),
783 )?;
784
785 self.by_row_indices = Some(indices);
786 self.by_row_data = Some(data);
787 self.row_preload = reservation;
788 Ok(())
789 }
790
791 fn record_mtx_shape(&mut self, mtx_shape: Option<(usize, usize, usize)>) -> anyhow::Result<()> {
793 if let Some((nrow, ncol, nnz)) = mtx_shape {
794 let ws = self.write_store()?;
795 let read_store = self.read_store.clone();
796
797 let check_set_attr = |attr_name: &str, value: usize| -> anyhow::Result<()> {
798 let old_value = Self::_get_group_attr::<usize>(read_store.clone(), "/", attr_name);
799 let new_value = serde_json::to_value(value)?;
800
801 match old_value {
802 Some(old_value) => {
803 if old_value != new_value {
804 return Err(anyhow!("{} mismatch", attr_name));
805 }
806 }
807 _ => {
808 Self::_set_group_attr(ws.clone(), "/", attr_name, &new_value)?;
809 }
810 }
811 Ok(())
812 };
813
814 check_set_attr("nrow", nrow)?;
815 check_set_attr("ncol", ncol)?;
816 check_set_attr("nnz", nnz)?;
817 self.shape = MtxShape {
818 nrow: Some(nrow),
819 ncol: Some(ncol),
820 nnz: Some(nnz),
821 };
822 }
823 Ok(())
824 }
825
826 fn initialize_backend(&mut self) -> anyhow::Result<()> {
828 use zarrs::group::GroupBuilder;
829
830 self.remove_backend_file()?;
831 let zarr_file = &self.file_name;
832 let store = Arc::new(FilesystemStore::new(zarr_file)?);
833 let root = GroupBuilder::new().build(store.clone(), "/")?;
834 root.store_metadata()?;
835
836 self.read_store = store.clone();
837 self.write_store = Some(store);
838 self.file_name = zarr_file.to_string();
839 self.max_column_name_idx = MAX_COLUMN_NAME_IDX;
840 self.max_row_name_idx = MAX_ROW_NAME_IDX;
841 self.by_column_indptr = vec![];
842 self.by_row_indptr = vec![];
843 self.forget_store_contents();
845
846 Ok(())
847 }
848
849 fn remove_backend_file(&self) -> anyhow::Result<()> {
851 let backend = std::path::Path::new(&self.file_name);
852 if backend.exists() {
853 if backend.is_file() {
854 std::fs::remove_file(backend)?;
855 } else {
856 std::fs::remove_dir_all(backend)?;
857 }
858 }
859 Ok(())
860 }
861
862 fn get_backend_file_name(&self) -> &str {
864 &self.file_name
865 }
866
867 fn backend_type(&self) -> SparseIoBackend {
868 SparseIoBackend::Zarr
869 }
870
871 fn to_mtx_file(&self, mtx_file: &str) -> anyhow::Result<()> {
874 if let (Some(ncol), Some(nrow), Some(nnz)) =
875 (self.num_columns(), self.num_rows(), self.num_non_zeros())
876 {
877 shared::write_mtx_file(mtx_file, |buf| {
878 let (indptr, data, indices) = self.open_csc_triplets()?;
879 let indptr = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
880
881 shared::write_mtx(
882 buf,
883 (nrow, ncol, nnz),
884 &indptr,
885 shared::MTX_STREAM_BLOCK,
886 |s, e| {
887 let subset = Self::create_subset(s..e);
888 Ok((
889 data.retrieve_array_subset::<Vec<f32>>(&subset)?,
890 indices.retrieve_array_subset::<Vec<u64>>(&subset)?,
891 ))
892 },
893 )
894 })
895 } else {
896 Err(anyhow!("Unable to figure out the size of the backend data"))
897 }
898 }
899
900 fn register_row_names_file(&mut self, row_name_file: &str) {
903 let _ = self.register_names_file(
904 "/row_names",
905 row_name_file,
906 0..self.max_row_name_idx,
907 ROW_SEP,
908 );
909 }
910
911 fn register_row_names_vec(&mut self, rows: &[Box<str>]) {
914 let _ = self.register_names_vec("/row_names", rows);
915 }
916
917 fn register_column_names_file(&mut self, column_name_file: &str) {
920 let _ = self.register_names_file(
921 "/column_names",
922 column_name_file,
923 0..self.max_column_name_idx,
924 COLUMN_SEP,
925 );
926 }
927
928 fn register_column_names_vec(&mut self, columns: &[Box<str>]) {
931 let _ = self.register_names_vec("/column_names", columns);
932 }
933
934 fn num_rows(&self) -> Option<usize> {
936 self.shape
937 .nrow
938 .or_else(|| Self::_num_rows(self.read_store.clone()))
939 }
940
941 fn num_columns(&self) -> Option<usize> {
943 self.shape
944 .ncol
945 .or_else(|| Self::_num_columns(self.read_store.clone()))
946 }
947
948 fn num_non_zeros(&self) -> Option<usize> {
950 self.shape
951 .nnz
952 .or_else(|| Self::_num_nnz(self.read_store.clone()))
953 }
954
955 fn register_names_file(
961 &mut self,
962 key: &str,
963 name_file: &str,
964 name_columns: Range<usize>,
965 name_sep: &str,
966 ) -> anyhow::Result<()> {
967 let names = parse_name_file(name_file, name_columns, name_sep)?;
968 self.new_filled_vector(key, data_type::string(), &names)?;
969 Ok(())
970 }
971
972 fn register_names_vec(&mut self, key: &str, names: &[Box<str>]) -> anyhow::Result<()> {
976 let names_vec: Vec<String> = names.iter().map(|x| x.to_string()).collect();
977 self.new_filled_vector(key, data_type::string(), &names_vec)?;
978 Ok(())
979 }
980
981 fn row_names(&self) -> anyhow::Result<Vec<Box<str>>> {
982 self.retrieve_registered_names("/row_names")
983 }
984
985 fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
986 self.retrieve_registered_names("/column_names")
987 }
988
989 fn retrieve_registered_names(&self, key: &str) -> anyhow::Result<Vec<Box<str>>> {
992 Ok(self
993 ._retrieve_vector::<String>(key)?
994 .into_iter()
995 .map(|s| s.into_boxed_str())
996 .collect())
997 }
998
999 fn read_triplets_by_single_column(
1003 &self,
1004 j_data: usize,
1005 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1006 let indptr = &self.by_column_indptr;
1007
1008 let nrow = self
1009 .num_rows()
1010 .ok_or(anyhow!("can't figure out the number of rows"))?;
1011 index_audit::check_requested_slots("column", [j_data], indptr.len())?;
1012
1013 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
1014 let ncol_out = 1;
1015 let jj = 0;
1016
1017 let mut ret = Vec::new();
1018 index_audit::emit_slot(
1019 "preloaded column",
1020 j_data,
1021 indptr,
1022 indices,
1023 data,
1024 nrow,
1025 &mut ret,
1026 |ii, x| (ii, jj, x),
1027 )?;
1028
1029 Ok((nrow, ncol_out, ret))
1030 } else {
1031 let ncol_out = 1;
1032 let jj = 0;
1033
1034 let start = indptr[j_data];
1036 let end = indptr[j_data + 1];
1037 anyhow::ensure!(
1038 start <= end,
1039 "column read: slot {j_data} points at [{start}, {end})"
1040 );
1041
1042 let tagged = [(jj, start, end)];
1045 let ret = self.read_csc_ranges(if start < end { &tagged } else { &[] }, nrow)?;
1046
1047 Ok((nrow, ncol_out, ret))
1048 }
1049 }
1050
1051 fn read_triplets_by_columns(
1055 &self,
1056 columns: Self::IndexIter,
1057 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1058 let indptr = &self.by_column_indptr;
1059 let columns_vec = columns.into_iter().collect::<Vec<usize>>();
1060
1061 let nrow = self
1062 .num_rows()
1063 .ok_or(anyhow!("can't figure out the number of rows"))?;
1064
1065 let ncol = self
1066 .num_columns()
1067 .ok_or(anyhow!("can't figure out the number of columns"))?;
1068
1069 let ncol_out = columns_vec.len();
1070 index_audit::check_requested_slots(
1071 "column",
1072 columns_vec.iter().copied().filter(|&j| j < ncol),
1073 indptr.len(),
1074 )?;
1075
1076 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
1077 if let Some(&bad) = columns_vec.iter().find(|&&j| j >= ncol) {
1078 anyhow::bail!("read column {bad}: outside {ncol} columns");
1079 }
1080 let min_start = columns_vec
1081 .iter()
1082 .map(|&j_data| indptr[j_data])
1083 .min()
1084 .unwrap_or(0);
1085
1086 let max_end = columns_vec
1087 .iter()
1088 .map(|&j_data| indptr[j_data + 1])
1089 .max()
1090 .unwrap_or(0);
1091
1092 let mut ret: Vec<(u64, u64, f32)> =
1093 Vec::with_capacity(max_end.saturating_sub(min_start) as usize);
1094
1095 for (jj, &j_data) in columns_vec.iter().enumerate() {
1096 let jj = jj as u64;
1097 index_audit::emit_slot(
1098 "preloaded column",
1099 j_data,
1100 indptr,
1101 indices,
1102 data,
1103 nrow,
1104 &mut ret,
1105 |ii, x| (ii, jj, x),
1106 )?;
1107 }
1108
1109 Ok((nrow, ncol_out, ret))
1110 } else {
1111 let mut tagged: Vec<(u64, u64, u64)> = columns_vec
1115 .iter()
1116 .enumerate()
1117 .filter_map(|(jj, &j_data)| {
1118 if j_data >= ncol {
1119 return None;
1120 }
1121 let start = indptr[j_data];
1122 let end = indptr[j_data + 1];
1123 (start < end).then_some((jj as u64, start, end))
1124 })
1125 .collect();
1126 tagged.sort_by_key(|&(_, start, _)| start);
1127
1128 let ret = self.read_csc_ranges(&tagged, nrow)?;
1129 Ok((nrow, ncol_out, ret))
1130 }
1131 }
1132
1133 fn csc_column_arrays(&self) -> Option<(&[u64], &[u64], &[f32])> {
1134 match (
1135 self.by_column_data.as_ref(),
1136 self.by_column_indices.as_ref(),
1137 ) {
1138 (Some(data), Some(indices)) if !self.by_column_indptr.is_empty() => Some((
1139 self.by_column_indptr.as_slice(),
1140 indices.as_slice(),
1141 data.as_slice(),
1142 )),
1143 _ => None,
1144 }
1145 }
1146
1147 fn read_triplets_by_rows(
1151 &self,
1152 rows: Self::IndexIter,
1153 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1154 let indptr = &self.by_row_indptr;
1155
1156 let rows_vec = rows.into_iter().collect::<Vec<_>>();
1157
1158 let (nrow, ncol) = match (self.num_rows(), self.num_columns()) {
1159 (Some(nrow), Some(ncol)) => (nrow, ncol),
1160 _ => return Err(anyhow!("Unable to figure out the size of the backend data")),
1161 };
1162 let nrow_out = rows_vec.len();
1163 index_audit::check_requested_slots(
1164 "row",
1165 rows_vec.iter().copied().filter(|&i| i < nrow),
1166 indptr.len(),
1167 )?;
1168
1169 if let (Some(data), Some(indices)) = (&self.by_row_data, &self.by_row_indices) {
1170 let mut nnz_total: usize = 0;
1171 let valid: Vec<(u64, usize)> = rows_vec
1172 .iter()
1173 .enumerate()
1174 .filter_map(|(ii, &i_data)| {
1175 if i_data >= nrow {
1176 return None;
1177 }
1178 nnz_total += indptr[i_data + 1].saturating_sub(indptr[i_data]) as usize;
1179 Some((ii as u64, i_data))
1180 })
1181 .collect();
1182
1183 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(nnz_total);
1184 for (ii, i_data) in valid {
1185 index_audit::emit_slot(
1186 "preloaded row",
1187 i_data,
1188 indptr,
1189 indices,
1190 data,
1191 ncol,
1192 &mut ret,
1193 |jj, x| (ii, jj, x),
1194 )?;
1195 }
1196 return Ok((nrow_out, ncol, ret));
1197 }
1198
1199 let mut tagged: Vec<(u64, u64, u64)> = rows_vec
1201 .iter()
1202 .enumerate()
1203 .filter_map(|(ii, &i_data)| {
1204 if i_data >= nrow {
1205 return None;
1206 }
1207 let start = indptr[i_data];
1208 let end = indptr[i_data + 1];
1209 (start < end).then_some((ii as u64, start, end))
1210 })
1211 .collect();
1212 tagged.sort_by_key(|&(_, start, _)| start);
1213
1214 let data_cache = self.cache_for(&self.by_row_data_cache, KEY_BY_ROW_DATA)?;
1215 let indices_cache = self.cache_for(&self.by_row_indices_cache, KEY_BY_ROW_INDICES)?;
1216
1217 let opts = zarrs::array::CodecOptions::default();
1218 let ret = shared::coalesce_and_emit(
1219 &tagged,
1220 ncol,
1221 |ii, jj, val| (ii, jj, val),
1222 |s, e| {
1223 let subset = Self::create_subset(s..e);
1224 let data_buf = <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1225 Vec<f32>,
1226 >(data_cache, &subset, &opts)?;
1227 let indices_buf =
1228 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<Vec<u64>>(
1229 indices_cache,
1230 &subset,
1231 &opts,
1232 )?;
1233 Ok((data_buf, indices_buf))
1234 },
1235 )?;
1236 Ok((nrow_out, ncol, ret))
1237 }
1238 fn record_csr_dataset_backend(
1247 &mut self,
1248 csr_cols: &[u64],
1249 csr_vals: &[f32],
1250 csr_rowptr: &[u64],
1251 ) -> anyhow::Result<()> {
1252 let key = "/by_row";
1254 self._add_group(key)?;
1255
1256 let key = "/by_row/data";
1257 self.new_filled_vector(key, data_type::float32(), csr_vals)?;
1258 let key = "/by_row/indices";
1259 self.new_filled_vector(key, data_type::uint64(), csr_cols)?;
1260 let key = "/by_row/indptr";
1261 self.new_filled_vector(key, data_type::uint64(), csr_rowptr)?;
1262
1263 Ok(())
1264 }
1265
1266 fn record_csc_dataset_backend(
1276 &mut self,
1277 csc_rows: &[u64],
1278 csc_vals: &[f32],
1279 csc_colptr: &[u64],
1280 ) -> anyhow::Result<()> {
1281 let key = "/by_column";
1283 self._add_group(key)?;
1284
1285 let key = "/by_column/data";
1286 self.new_filled_vector(key, data_type::float32(), csc_vals)?;
1287 let key = "/by_column/indices";
1288 self.new_filled_vector(key, data_type::uint64(), csc_rows)?;
1289 let key = "/by_column/indptr";
1290 self.new_filled_vector(key, data_type::uint64(), csc_colptr)?;
1291
1292 Ok(())
1293 }
1294
1295 fn cs_create(&mut self, key: CsKey, len: usize) -> anyhow::Result<()> {
1296 let (group, path, dt, elem_bytes) = match key {
1297 CsKey::CscData => (
1298 "/by_column",
1299 "/by_column/data",
1300 data_type::float32(),
1301 std::mem::size_of::<f32>(),
1302 ),
1303 CsKey::CscIndices => (
1304 "/by_column",
1305 "/by_column/indices",
1306 data_type::uint64(),
1307 std::mem::size_of::<u64>(),
1308 ),
1309 CsKey::CscIndptr => (
1310 "/by_column",
1311 "/by_column/indptr",
1312 data_type::uint64(),
1313 std::mem::size_of::<u64>(),
1314 ),
1315 CsKey::CsrData => (
1316 "/by_row",
1317 "/by_row/data",
1318 data_type::float32(),
1319 std::mem::size_of::<f32>(),
1320 ),
1321 CsKey::CsrIndices => (
1322 "/by_row",
1323 "/by_row/indices",
1324 data_type::uint64(),
1325 std::mem::size_of::<u64>(),
1326 ),
1327 CsKey::CsrIndptr => (
1328 "/by_row",
1329 "/by_row/indptr",
1330 data_type::uint64(),
1331 std::mem::size_of::<u64>(),
1332 ),
1333 };
1334 self._add_group(group)?;
1335 self.create_shaped_vector(path, dt, elem_bytes, len)
1336 }
1337
1338 fn cs_write_u64(&mut self, key: CsKey, offset: u64, data: &[u64]) -> anyhow::Result<()> {
1339 let path = match key {
1340 CsKey::CscIndices => "/by_column/indices",
1341 CsKey::CscIndptr => "/by_column/indptr",
1342 CsKey::CsrIndices => "/by_row/indices",
1343 CsKey::CsrIndptr => "/by_row/indptr",
1344 CsKey::CscData | CsKey::CsrData => {
1345 return Err(anyhow!("cs_write_u64 called on f32 slot {:?}", key));
1346 }
1347 };
1348 self.write_slab_u64(path, offset, data)
1349 }
1350
1351 fn cs_write_f32(&mut self, key: CsKey, offset: u64, data: &[f32]) -> anyhow::Result<()> {
1352 let path = match key {
1353 CsKey::CscData => "/by_column/data",
1354 CsKey::CsrData => "/by_row/data",
1355 _ => {
1356 return Err(anyhow!("cs_write_f32 called on u64 slot {:?}", key));
1357 }
1358 };
1359 self.write_slab_f32(path, offset, data)
1360 }
1361}