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 crate::zarr_io::make_stage(zarr_file)?;
249 let store = Arc::new(FilesystemStore::new(zarr_file)?);
250 let root = GroupBuilder::new().build(store.clone(), "/")?;
251 root.store_metadata()?;
252
253 Ok(Self {
254 read_store: store.clone(),
255 write_store: Some(store),
256 file_name: zarr_file.to_string(),
257 max_row_name_idx: MAX_ROW_NAME_IDX,
258 max_column_name_idx: MAX_COLUMN_NAME_IDX,
259 by_column_indptr: vec![],
260 streamed_nnz: 0,
261 by_row_indptr: vec![],
262 by_column_indices: None,
263 by_column_data: None,
264 by_row_indices: None,
265 by_row_data: None,
266 column_preload: None,
267 row_preload: None,
268 by_column_data_cache: Arc::new(OnceLock::new()),
269 by_column_indices_cache: Arc::new(OnceLock::new()),
270 by_row_data_cache: Arc::new(OnceLock::new()),
271 by_row_indices_cache: Arc::new(OnceLock::new()),
272 })
273 }
274
275 fn new_filled_vector<V>(&mut self, key: &str, dt: DataType, vec: &[V]) -> anyhow::Result<()>
288 where
289 V: zarrs::array::Element,
290 {
291 use zarrs::array::codec::ZstdCodec;
292 use zarrs::array::ArrayBuilder;
293 use zarrs::array::FillValue;
294
295 let ws = self.write_store()?;
296
297 let nelem = vec.len();
298 let chunk_size = chunk_elems(nelem, std::mem::size_of::<V>());
299
300 let fill = if dt == data_type::float32() {
301 FillValue::from(zarrs::array::ZARR_NAN_F32)
302 } else if dt == data_type::uint64() {
303 FillValue::from(0u64)
304 } else if dt == data_type::string() {
305 FillValue::from("")
306 } else {
307 FillValue::from(0)
308 };
309
310 let array = ArrayBuilder::new(
311 vec![vec.len() as u64], vec![chunk_size as u64], dt, fill, )
316 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
317 .build(ws.clone(), key)?;
318
319 array.store_metadata()?;
320
321 let subset = Self::create_subset(0..vec.len() as u64);
322 array.store_array_subset(&subset, vec)?;
323
324 Ok(())
325 }
326
327 fn _open_vector(
328 &self,
329 key: &str,
330 ) -> anyhow::Result<zarrs::array::Array<dyn ZReadStorageTraits>> {
331 use zarrs::array::Array as ZArray;
332 let ret = ZArray::open(self.read_store.clone(), key)?;
333 Ok(ret)
334 }
335
336 fn create_shaped_vector(
342 &mut self,
343 key: &str,
344 dt: DataType,
345 elem_bytes: usize,
346 nelem: usize,
347 ) -> anyhow::Result<()> {
348 use zarrs::array::codec::ZstdCodec;
349 use zarrs::array::ArrayBuilder;
350 use zarrs::array::FillValue;
351
352 let ws = self.write_store()?;
353
354 let chunk_size = chunk_elems(nelem, elem_bytes);
355
356 let fill = if dt == data_type::float32() {
357 FillValue::from(zarrs::array::ZARR_NAN_F32)
358 } else if dt == data_type::uint64() {
359 FillValue::from(0u64)
360 } else {
361 FillValue::from(0)
362 };
363
364 let array = ArrayBuilder::new(
365 vec![nelem.max(1) as u64],
366 vec![chunk_size.max(1) as u64],
367 dt,
368 fill,
369 )
370 .bytes_to_bytes_codecs(vec![Arc::new(ZstdCodec::new(COMPRESSION_LEVEL, false))])
371 .build(ws.clone(), key)?;
372
373 array.store_metadata()?;
374 Ok(())
375 }
376
377 fn _open_writable_vector(
381 &self,
382 key: &str,
383 ) -> anyhow::Result<zarrs::array::Array<FilesystemStore>> {
384 use zarrs::array::Array as ZArray;
385 let ws = self.write_store()?.clone();
386 let ret = ZArray::open(ws, key)?;
387 Ok(ret)
388 }
389
390 fn write_slab_u64(&mut self, key: &str, offset: u64, data: &[u64]) -> anyhow::Result<()> {
393 if data.is_empty() {
394 return Ok(());
395 }
396 let array = self._open_writable_vector(key)?;
397 let subset = Self::create_subset(offset..offset + data.len() as u64);
398 array.store_array_subset(&subset, data)?;
399 Ok(())
400 }
401
402 fn write_slab_f32(&mut self, key: &str, offset: u64, data: &[f32]) -> anyhow::Result<()> {
404 if data.is_empty() {
405 return Ok(());
406 }
407 let array = self._open_writable_vector(key)?;
408 let subset = Self::create_subset(offset..offset + data.len() as u64);
409 array.store_array_subset(&subset, data)?;
410 Ok(())
411 }
412
413 #[allow(clippy::type_complexity)]
414 fn open_csc_triplets(
415 &self,
416 ) -> anyhow::Result<(
417 zarrs::array::Array<dyn ZReadStorageTraits>,
418 zarrs::array::Array<dyn ZReadStorageTraits>,
419 zarrs::array::Array<dyn ZReadStorageTraits>,
420 )> {
421 Ok((
422 self._open_vector("/by_column/indptr")?,
423 self._open_vector("/by_column/data")?,
424 self._open_vector("/by_column/indices")?,
425 ))
426 }
427
428 #[inline]
430 fn create_subset(range: Range<u64>) -> ArraySubset {
431 ArraySubset::new_with_ranges(&[range])
432 }
433
434 fn open_chunk_cache(
440 read_store: &Arc<dyn ZReadStorageTraits>,
441 key: &str,
442 ) -> anyhow::Result<ChunkCacheDecodedLruChunkLimit> {
443 use zarrs::array::Array as ZArray;
444 use zarrs::storage::ReadableStorageTraits;
445
446 let storage_readable: Arc<dyn ReadableStorageTraits> = read_store.clone().readable();
447 let arr = ZArray::open(read_store.clone(), key)?;
448 let arr_arc = Arc::new(arr.with_storage(storage_readable));
449 Ok(ChunkCacheDecodedLruChunkLimit::new(
450 arr_arc,
451 cache_chunk_cap(),
452 ))
453 }
454
455 fn cache_for<'a>(
459 &'a self,
460 cell: &'a OnceLock<ChunkCacheDecodedLruChunkLimit>,
461 key: &str,
462 ) -> anyhow::Result<&'a ChunkCacheDecodedLruChunkLimit> {
463 if let Some(cache) = cell.get() {
464 return Ok(cache);
465 }
466 let _ = cell.set(Self::open_chunk_cache(&self.read_store, key)?);
467 Ok(cell.get().expect("OnceLock populated above"))
468 }
469
470 fn _retrieve_vector<V>(&self, key: &str) -> anyhow::Result<Vec<V>>
471 where
472 V: zarrs::array::ElementOwned,
473 {
474 let data = self._open_vector(key)?;
475 let ntot = data.shape()[0];
476 let subset = Self::create_subset(0..ntot);
477 Ok(data.retrieve_array_subset::<Vec<V>>(&subset)?)
478 }
479
480 fn _set_group_attr<V>(
486 store: Arc<FilesystemStore>,
487 group_name: &str,
488 attr_name: &str,
489 value: &V,
490 ) -> anyhow::Result<()>
491 where
492 V: serde::Serialize,
493 {
494 use zarrs::group::Group;
495 let mut group = Group::open(store, group_name)?;
496
497 let new_value = serde_json::to_value(value)?;
498 group
499 .attributes_mut()
500 .insert((*attr_name).to_string(), new_value);
501 group.store_metadata()?;
502 Ok(())
503 }
504
505 fn _get_group_attr<V>(
507 store: Arc<dyn ZReadStorageTraits>,
508 group_name: &str,
509 attr_name: &str,
510 ) -> Option<V>
511 where
512 V: serde::de::DeserializeOwned,
513 {
514 zarrs::group::Group::open(store, group_name)
515 .ok()
516 .and_then(|grp| grp.attributes().get(attr_name).cloned())
517 .and_then(|attr| serde_json::from_value(attr).ok())
518 }
519
520 fn _num_nnz(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
521 Self::_get_group_attr::<usize>(store, "/", "nnz")
522 }
523
524 fn _num_rows(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
525 Self::_get_group_attr::<usize>(store, "/", "nrow")
526 }
527
528 fn _num_columns(store: Arc<dyn ZReadStorageTraits>) -> Option<usize> {
529 Self::_get_group_attr::<usize>(store, "/", "ncol")
530 }
531
532 fn _add_group(&mut self, group_name: &str) -> anyhow::Result<()> {
534 use zarrs::group::Group;
535 let ws = self.write_store()?;
536
537 if Group::open(ws.clone(), group_name).is_err() {
538 let new_group = zarrs::group::GroupBuilder::new().build(ws.clone(), group_name)?;
539 new_group.store_metadata()?;
540 }
541
542 Ok(())
543 }
544}
545
546impl SparseIo for SparseMtxData {
547 type IndexIter = Vec<usize>;
548
549 fn read_row_indptr(&mut self) -> anyhow::Result<()> {
551 use zarrs::array::Array as Zarray;
552 let key = "/by_row/indptr";
553 if let Ok(indptr) = Zarray::open(self.read_store.clone(), key) {
554 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
555 self.by_row_indptr.clear();
556 self.by_row_indptr.extend(indptr_vec);
557 }
558 Ok(())
559 }
560
561 fn column_indptr(&self) -> &[u64] {
563 &self.by_column_indptr
564 }
565
566 fn row_indptr(&self) -> &[u64] {
567 &self.by_row_indptr
568 }
569
570 fn metadata(&self) -> Metadata {
571 Self::_get_group_attr::<Metadata>(self.read_store.clone(), "/", meta::ATTR)
572 .unwrap_or_default()
573 }
574
575 fn set_metadata(&mut self, values: &Metadata) -> anyhow::Result<()> {
576 let mut root = zarrs::group::Group::open(self.write_store()?.clone(), "/")?;
577 if values.is_empty() {
578 root.attributes_mut().remove(meta::ATTR);
579 } else {
580 root.attributes_mut()
581 .insert(meta::ATTR.to_string(), serde_json::to_value(values)?);
582 }
583 root.store_metadata()?;
584 Ok(())
585 }
586
587 fn reopen_backend(&mut self) -> anyhow::Result<()> {
588 let store = Arc::new(FilesystemStore::new(&self.file_name)?);
594 self.read_store = store.clone();
595 self.write_store = Some(store);
596 self.by_column_data_cache = Arc::new(OnceLock::new());
597 self.by_column_indices_cache = Arc::new(OnceLock::new());
598 self.by_row_data_cache = Arc::new(OnceLock::new());
599 self.by_row_indices_cache = Arc::new(OnceLock::new());
600 self.streamed_nnz = 0;
601 self.read_column_indptr()?;
602 self.read_row_indptr()?;
603 Ok(())
604 }
605
606 fn note_streamed_nnz(&mut self, n: u64) {
607 self.streamed_nnz += n;
608 }
609
610 fn streamed_nnz(&self) -> u64 {
611 self.streamed_nnz
612 }
613
614 fn reset_streamed_nnz(&mut self) {
615 self.streamed_nnz = 0;
616 }
617
618 fn read_column_indptr(&mut self) -> anyhow::Result<()> {
619 use zarrs::array::Array as ZArray;
620 let key = "/by_column/indptr";
621 if let Ok(indptr) = ZArray::open(self.read_store.clone(), key) {
622 let indptr_vec = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
623 self.by_column_indptr.clear();
624 self.by_column_indptr.extend(indptr_vec);
625 }
626 Ok(())
627 }
628
629 fn clean_preloaded_columns(&mut self) {
630 self.by_column_data = None;
631 self.by_column_indices = None;
632 self.column_preload = None;
633 }
634
635 fn preload_columns(&mut self) -> anyhow::Result<()> {
637 if self.by_column_data.is_some() && self.by_column_indices.is_some() {
638 return Ok(());
639 }
640 let reservation = match self.num_non_zeros() {
641 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "column") {
642 Some(r) => Some(r),
643 None => return Ok(()),
644 },
645 None => None,
646 };
647 use zarrs::array::Array as ZArray;
648
649 let key = "/by_column/data";
650 let data = ZArray::open(self.read_store.clone(), key)?;
651 let key = "/by_column/indices";
652 let indices = ZArray::open(self.read_store.clone(), key)?;
653
654 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
655 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
656
657 index_audit::check_preload(
658 index_audit::Major::Column,
659 self.num_rows(),
660 self.num_columns(),
661 &self.by_column_indptr,
662 &indices,
663 data.len(),
664 )?;
665
666 self.by_column_indices = Some(indices);
667 self.by_column_data = Some(data);
668 self.column_preload = reservation;
669 Ok(())
670 }
671
672 fn clean_preloaded_rows(&mut self) {
673 self.by_row_data = None;
674 self.by_row_indices = None;
675 self.row_preload = None;
676 }
677
678 fn preload_rows(&mut self) -> anyhow::Result<()> {
680 if self.by_row_data.is_some() && self.by_row_indices.is_some() {
681 return Ok(());
682 }
683 let reservation = match self.num_non_zeros() {
684 Some(nnz) => match crate::sparse_io::reserve_preload(nnz, "row") {
685 Some(r) => Some(r),
686 None => return Ok(()),
687 },
688 None => None,
689 };
690 use zarrs::array::Array as ZArray;
691
692 let data = ZArray::open(self.read_store.clone(), KEY_BY_ROW_DATA)?;
693 let indices = ZArray::open(self.read_store.clone(), KEY_BY_ROW_INDICES)?;
694
695 let data = data.retrieve_array_subset::<Vec<f32>>(&data.subset_all())?;
696 let indices = indices.retrieve_array_subset::<Vec<u64>>(&indices.subset_all())?;
697
698 index_audit::check_preload(
699 index_audit::Major::Row,
700 self.num_rows(),
701 self.num_columns(),
702 &self.by_row_indptr,
703 &indices,
704 data.len(),
705 )?;
706
707 self.by_row_indices = Some(indices);
708 self.by_row_data = Some(data);
709 self.row_preload = reservation;
710 Ok(())
711 }
712
713 fn record_mtx_shape(&mut self, mtx_shape: Option<(usize, usize, usize)>) -> anyhow::Result<()> {
715 if let Some((nrow, ncol, nnz)) = mtx_shape {
716 let ws = self.write_store()?;
717 let read_store = self.read_store.clone();
718
719 let check_set_attr = |attr_name: &str, value: usize| -> anyhow::Result<()> {
720 let old_value = Self::_get_group_attr::<usize>(read_store.clone(), "/", attr_name);
721 let new_value = serde_json::to_value(value)?;
722
723 match old_value {
724 Some(old_value) => {
725 if old_value != new_value {
726 return Err(anyhow!("{} mismatch", attr_name));
727 }
728 }
729 _ => {
730 Self::_set_group_attr(ws.clone(), "/", attr_name, &new_value)?;
731 }
732 }
733 Ok(())
734 };
735
736 check_set_attr("nrow", nrow)?;
737 check_set_attr("ncol", ncol)?;
738 check_set_attr("nnz", nnz)?;
739 }
740 Ok(())
741 }
742
743 fn initialize_backend(&mut self) -> anyhow::Result<()> {
745 use zarrs::group::GroupBuilder;
746
747 self.remove_backend_file()?;
748 let zarr_file = &self.file_name;
749 let store = Arc::new(FilesystemStore::new(zarr_file)?);
750 let root = GroupBuilder::new().build(store.clone(), "/")?;
751 root.store_metadata()?;
752
753 self.read_store = store.clone();
754 self.write_store = Some(store);
755 self.file_name = zarr_file.to_string();
756 self.max_column_name_idx = MAX_COLUMN_NAME_IDX;
757 self.max_row_name_idx = MAX_ROW_NAME_IDX;
758 self.by_column_indptr = vec![];
759 self.by_row_indptr = vec![];
760
761 Ok(())
762 }
763
764 fn remove_backend_file(&self) -> anyhow::Result<()> {
766 let backend = std::path::Path::new(&self.file_name);
767 if backend.exists() {
768 if backend.is_file() {
769 std::fs::remove_file(backend)?;
770 } else {
771 std::fs::remove_dir_all(backend)?;
772 }
773 }
774 Ok(())
775 }
776
777 fn get_backend_file_name(&self) -> &str {
779 &self.file_name
780 }
781
782 fn backend_type(&self) -> SparseIoBackend {
783 SparseIoBackend::Zarr
784 }
785
786 fn to_mtx_file(&self, mtx_file: &str) -> anyhow::Result<()> {
789 if let (Some(ncol), Some(nrow), Some(nnz)) =
790 (self.num_columns(), self.num_rows(), self.num_non_zeros())
791 {
792 let (nrow, ncol, nnz) = (nrow, ncol, nnz);
793
794 let mut buf = open_buf_writer(mtx_file)?;
795 shared::write_mtx_header(&mut buf, nrow, ncol, nnz)?;
796
797 let (indptr, data, indices) = self.open_csc_triplets()?;
798 let indptr = indptr.retrieve_array_subset::<Vec<u64>>(&indptr.subset_all())?;
799 crate::sparse_io::index_audit::check_indptr("to_mtx_file", &indptr, ncol, nnz)?;
800
801 let total_nnz = indptr[ncol];
808 let mut jj = 0usize; let mut pos = 0u64;
810 while pos < total_nnz {
811 let end = (pos + MTX_STREAM_BLOCK).min(total_nnz);
812 let subset = Self::create_subset(pos..end);
813 let data_block = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
814 let indices_block = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
815
816 for (k, (&val, &ii)) in data_block.iter().zip(&indices_block).enumerate() {
817 let global = pos + k as u64;
818 while jj + 1 < indptr.len() && indptr[jj + 1] <= global {
820 jj += 1;
821 }
822 writeln!(buf, "{}\t{}\t{}", ii as usize + 1, jj + 1, val)?;
824 }
825 pos = end;
826 }
827 buf.flush()?;
828 Ok(())
829 } else {
830 Err(anyhow!("Unable to figure out the size of the backend data"))
831 }
832 }
833
834 fn register_row_names_file(&mut self, row_name_file: &str) {
837 let _ = self.register_names_file(
838 "/row_names",
839 row_name_file,
840 0..self.max_row_name_idx,
841 ROW_SEP,
842 );
843 }
844
845 fn register_row_names_vec(&mut self, rows: &[Box<str>]) {
848 let _ = self.register_names_vec("/row_names", rows);
849 }
850
851 fn register_column_names_file(&mut self, column_name_file: &str) {
854 let _ = self.register_names_file(
855 "/column_names",
856 column_name_file,
857 0..self.max_column_name_idx,
858 COLUMN_SEP,
859 );
860 }
861
862 fn register_column_names_vec(&mut self, columns: &[Box<str>]) {
865 let _ = self.register_names_vec("/column_names", columns);
866 }
867
868 fn num_rows(&self) -> Option<usize> {
870 Self::_num_rows(self.read_store.clone())
871 }
872
873 fn num_columns(&self) -> Option<usize> {
875 Self::_num_columns(self.read_store.clone())
876 }
877
878 fn num_non_zeros(&self) -> Option<usize> {
880 Self::_num_nnz(self.read_store.clone())
881 }
882
883 fn register_names_file(
889 &mut self,
890 key: &str,
891 name_file: &str,
892 name_columns: Range<usize>,
893 name_sep: &str,
894 ) -> anyhow::Result<()> {
895 let names = parse_name_file(name_file, name_columns, name_sep)?;
896 self.new_filled_vector(key, data_type::string(), &names)?;
897 Ok(())
898 }
899
900 fn register_names_vec(&mut self, key: &str, names: &[Box<str>]) -> anyhow::Result<()> {
904 let names_vec: Vec<String> = names.iter().map(|x| x.to_string()).collect();
905 self.new_filled_vector(key, data_type::string(), &names_vec)?;
906 Ok(())
907 }
908
909 fn row_names(&self) -> anyhow::Result<Vec<Box<str>>> {
910 self.retrieve_registered_names("/row_names")
911 }
912
913 fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
914 self.retrieve_registered_names("/column_names")
915 }
916
917 fn retrieve_registered_names(&self, key: &str) -> anyhow::Result<Vec<Box<str>>> {
920 Ok(self
921 ._retrieve_vector::<String>(key)?
922 .into_iter()
923 .map(|s| s.into_boxed_str())
924 .collect())
925 }
926
927 fn read_triplets_by_single_column(
931 &self,
932 j_data: usize,
933 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
934 use zarrs::array::Array as ZArray;
935
936 let indptr = &self.by_column_indptr;
937
938 let nrow = self
939 .num_rows()
940 .ok_or(anyhow!("can't figure out the number of rows"))?;
941 index_audit::check_requested_slots("column", [j_data], indptr.len())?;
942
943 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
944 let ncol_out = 1;
945 let jj = 0;
946
947 let mut ret = Vec::new();
948 index_audit::emit_slot(
949 "preloaded column",
950 j_data,
951 indptr,
952 indices,
953 data,
954 nrow,
955 &mut ret,
956 |ii, x| (ii, jj, x),
957 )?;
958
959 Ok((nrow, ncol_out, ret))
960 } else {
961 let key = "/by_column/data";
962 let data = ZArray::open(self.read_store.clone(), key)?;
963 let key = "/by_column/indices";
964 let indices = ZArray::open(self.read_store.clone(), key)?;
965
966 let ncol_out = 1;
967 let jj = 0;
968
969 let start = indptr[j_data];
971 let end = indptr[j_data + 1];
972 anyhow::ensure!(
973 start <= end,
974 "column read: slot {j_data} points at [{start}, {end})"
975 );
976
977 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity((end - start) as usize);
978
979 if start < end {
980 let subset = Self::create_subset(start..end);
981 let data_slice = data.retrieve_array_subset::<Vec<f32>>(&subset)?;
982 let indices_slice = indices.retrieve_array_subset::<Vec<u64>>(&subset)?;
983
984 for k in 0..(end - start) {
985 let x_ij = data_slice[k as usize];
986 let ii = indices_slice[k as usize];
987 if ii >= nrow as u64 {
988 return Err(index_audit::inner_out_of_range(
989 "column read",
990 (start + k) as usize,
991 ii,
992 nrow,
993 ));
994 }
995 ret.push((ii, jj, x_ij));
996 }
997 }
998
999 Ok((nrow, ncol_out, ret))
1000 }
1001 }
1002
1003 fn read_triplets_by_columns(
1007 &self,
1008 columns: Self::IndexIter,
1009 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1010 let indptr = &self.by_column_indptr;
1011 let columns_vec = columns.into_iter().collect::<Vec<usize>>();
1012
1013 let nrow = self
1014 .num_rows()
1015 .ok_or(anyhow!("can't figure out the number of rows"))?;
1016
1017 let ncol = self
1018 .num_columns()
1019 .ok_or(anyhow!("can't figure out the number of columns"))?;
1020
1021 let ncol_out = columns_vec.len();
1022 index_audit::check_requested_slots(
1023 "column",
1024 columns_vec.iter().copied().filter(|&j| j < ncol),
1025 indptr.len(),
1026 )?;
1027
1028 if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
1029 if let Some(&bad) = columns_vec.iter().find(|&&j| j >= ncol) {
1030 anyhow::bail!("read column {bad}: outside {ncol} columns");
1031 }
1032 let min_start = columns_vec
1033 .iter()
1034 .map(|&j_data| indptr[j_data])
1035 .min()
1036 .unwrap_or(0);
1037
1038 let max_end = columns_vec
1039 .iter()
1040 .map(|&j_data| indptr[j_data + 1])
1041 .max()
1042 .unwrap_or(0);
1043
1044 let mut ret: Vec<(u64, u64, f32)> =
1045 Vec::with_capacity(max_end.saturating_sub(min_start) as usize);
1046
1047 for (jj, &j_data) in columns_vec.iter().enumerate() {
1048 let jj = jj as u64;
1049 index_audit::emit_slot(
1050 "preloaded column",
1051 j_data,
1052 indptr,
1053 indices,
1054 data,
1055 nrow,
1056 &mut ret,
1057 |ii, x| (ii, jj, x),
1058 )?;
1059 }
1060
1061 Ok((nrow, ncol_out, ret))
1062 } else {
1063 let mut tagged: Vec<(u64, u64, u64)> = columns_vec
1067 .iter()
1068 .enumerate()
1069 .filter_map(|(jj, &j_data)| {
1070 if j_data >= ncol {
1071 return None;
1072 }
1073 let start = indptr[j_data];
1074 let end = indptr[j_data + 1];
1075 (start < end).then_some((jj as u64, start, end))
1076 })
1077 .collect();
1078 tagged.sort_by_key(|&(_, start, _)| start);
1079
1080 let data_cache = self.cache_for(&self.by_column_data_cache, KEY_BY_COLUMN_DATA)?;
1081 let indices_cache =
1082 self.cache_for(&self.by_column_indices_cache, KEY_BY_COLUMN_INDICES)?;
1083
1084 let opts = zarrs::array::CodecOptions::default();
1085 let ret = shared::coalesce_and_emit(
1086 &tagged,
1087 nrow,
1088 |jj, ii, val| (ii, jj, val),
1089 |s, e| {
1090 let subset = Self::create_subset(s..e);
1091 let data_buf =
1092 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1093 Vec<f32>,
1094 >(data_cache, &subset, &opts)?;
1095 let indices_buf =
1096 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1097 Vec<u64>,
1098 >(indices_cache, &subset, &opts)?;
1099 Ok((data_buf, indices_buf))
1100 },
1101 )?;
1102 Ok((nrow, ncol_out, ret))
1103 }
1104 }
1105
1106 fn csc_column_arrays(&self) -> Option<(&[u64], &[u64], &[f32])> {
1107 match (
1108 self.by_column_data.as_ref(),
1109 self.by_column_indices.as_ref(),
1110 ) {
1111 (Some(data), Some(indices)) if !self.by_column_indptr.is_empty() => Some((
1112 self.by_column_indptr.as_slice(),
1113 indices.as_slice(),
1114 data.as_slice(),
1115 )),
1116 _ => None,
1117 }
1118 }
1119
1120 fn read_triplets_by_rows(
1124 &self,
1125 rows: Self::IndexIter,
1126 ) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
1127 let indptr = &self.by_row_indptr;
1128
1129 let rows_vec = rows.into_iter().collect::<Vec<_>>();
1130
1131 let (nrow, ncol) = match (self.num_rows(), self.num_columns()) {
1132 (Some(nrow), Some(ncol)) => (nrow, ncol),
1133 _ => return Err(anyhow!("Unable to figure out the size of the backend data")),
1134 };
1135 let nrow_out = rows_vec.len();
1136 index_audit::check_requested_slots(
1137 "row",
1138 rows_vec.iter().copied().filter(|&i| i < nrow),
1139 indptr.len(),
1140 )?;
1141
1142 if let (Some(data), Some(indices)) = (&self.by_row_data, &self.by_row_indices) {
1143 let mut nnz_total: usize = 0;
1144 let valid: Vec<(u64, usize)> = rows_vec
1145 .iter()
1146 .enumerate()
1147 .filter_map(|(ii, &i_data)| {
1148 if i_data >= nrow {
1149 return None;
1150 }
1151 nnz_total += indptr[i_data + 1].saturating_sub(indptr[i_data]) as usize;
1152 Some((ii as u64, i_data))
1153 })
1154 .collect();
1155
1156 let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(nnz_total);
1157 for (ii, i_data) in valid {
1158 index_audit::emit_slot(
1159 "preloaded row",
1160 i_data,
1161 indptr,
1162 indices,
1163 data,
1164 ncol,
1165 &mut ret,
1166 |jj, x| (ii, jj, x),
1167 )?;
1168 }
1169 return Ok((nrow_out, ncol, ret));
1170 }
1171
1172 let mut tagged: Vec<(u64, u64, u64)> = rows_vec
1174 .iter()
1175 .enumerate()
1176 .filter_map(|(ii, &i_data)| {
1177 if i_data >= nrow {
1178 return None;
1179 }
1180 let start = indptr[i_data];
1181 let end = indptr[i_data + 1];
1182 (start < end).then_some((ii as u64, start, end))
1183 })
1184 .collect();
1185 tagged.sort_by_key(|&(_, start, _)| start);
1186
1187 let data_cache = self.cache_for(&self.by_row_data_cache, KEY_BY_ROW_DATA)?;
1188 let indices_cache = self.cache_for(&self.by_row_indices_cache, KEY_BY_ROW_INDICES)?;
1189
1190 let opts = zarrs::array::CodecOptions::default();
1191 let ret = shared::coalesce_and_emit(
1192 &tagged,
1193 ncol,
1194 |ii, jj, val| (ii, jj, val),
1195 |s, e| {
1196 let subset = Self::create_subset(s..e);
1197 let data_buf = <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<
1198 Vec<f32>,
1199 >(data_cache, &subset, &opts)?;
1200 let indices_buf =
1201 <_ as zarrs::array::chunk_cache::ChunkCache>::retrieve_array_subset::<Vec<u64>>(
1202 indices_cache,
1203 &subset,
1204 &opts,
1205 )?;
1206 Ok((data_buf, indices_buf))
1207 },
1208 )?;
1209 Ok((nrow_out, ncol, ret))
1210 }
1211 fn record_csr_dataset_backend(
1220 &mut self,
1221 csr_cols: &[u64],
1222 csr_vals: &[f32],
1223 csr_rowptr: &[u64],
1224 ) -> anyhow::Result<()> {
1225 let key = "/by_row";
1227 self._add_group(key)?;
1228
1229 let key = "/by_row/data";
1230 self.new_filled_vector(key, data_type::float32(), csr_vals)?;
1231 let key = "/by_row/indices";
1232 self.new_filled_vector(key, data_type::uint64(), csr_cols)?;
1233 let key = "/by_row/indptr";
1234 self.new_filled_vector(key, data_type::uint64(), csr_rowptr)?;
1235
1236 Ok(())
1237 }
1238
1239 fn record_csc_dataset_backend(
1249 &mut self,
1250 csc_rows: &[u64],
1251 csc_vals: &[f32],
1252 csc_colptr: &[u64],
1253 ) -> anyhow::Result<()> {
1254 let key = "/by_column";
1256 self._add_group(key)?;
1257
1258 let key = "/by_column/data";
1259 self.new_filled_vector(key, data_type::float32(), csc_vals)?;
1260 let key = "/by_column/indices";
1261 self.new_filled_vector(key, data_type::uint64(), csc_rows)?;
1262 let key = "/by_column/indptr";
1263 self.new_filled_vector(key, data_type::uint64(), csc_colptr)?;
1264
1265 Ok(())
1266 }
1267
1268 fn cs_create(&mut self, key: CsKey, len: usize) -> anyhow::Result<()> {
1269 let (group, path, dt, elem_bytes) = match key {
1270 CsKey::CscData => (
1271 "/by_column",
1272 "/by_column/data",
1273 data_type::float32(),
1274 std::mem::size_of::<f32>(),
1275 ),
1276 CsKey::CscIndices => (
1277 "/by_column",
1278 "/by_column/indices",
1279 data_type::uint64(),
1280 std::mem::size_of::<u64>(),
1281 ),
1282 CsKey::CscIndptr => (
1283 "/by_column",
1284 "/by_column/indptr",
1285 data_type::uint64(),
1286 std::mem::size_of::<u64>(),
1287 ),
1288 CsKey::CsrData => (
1289 "/by_row",
1290 "/by_row/data",
1291 data_type::float32(),
1292 std::mem::size_of::<f32>(),
1293 ),
1294 CsKey::CsrIndices => (
1295 "/by_row",
1296 "/by_row/indices",
1297 data_type::uint64(),
1298 std::mem::size_of::<u64>(),
1299 ),
1300 CsKey::CsrIndptr => (
1301 "/by_row",
1302 "/by_row/indptr",
1303 data_type::uint64(),
1304 std::mem::size_of::<u64>(),
1305 ),
1306 };
1307 self._add_group(group)?;
1308 self.create_shaped_vector(path, dt, elem_bytes, len)
1309 }
1310
1311 fn cs_write_u64(&mut self, key: CsKey, offset: u64, data: &[u64]) -> anyhow::Result<()> {
1312 let path = match key {
1313 CsKey::CscIndices => "/by_column/indices",
1314 CsKey::CscIndptr => "/by_column/indptr",
1315 CsKey::CsrIndices => "/by_row/indices",
1316 CsKey::CsrIndptr => "/by_row/indptr",
1317 CsKey::CscData | CsKey::CsrData => {
1318 return Err(anyhow!("cs_write_u64 called on f32 slot {:?}", key));
1319 }
1320 };
1321 self.write_slab_u64(path, offset, data)
1322 }
1323
1324 fn cs_write_f32(&mut self, key: CsKey, offset: u64, data: &[f32]) -> anyhow::Result<()> {
1325 let path = match key {
1326 CsKey::CscData => "/by_column/data",
1327 CsKey::CsrData => "/by_row/data",
1328 _ => {
1329 return Err(anyhow!("cs_write_f32 called on u64 slot {:?}", key));
1330 }
1331 };
1332 self.write_slab_f32(path, offset, data)
1333 }
1334}