use crate::sparse_io::*;
use hdf5::filters::blosc_set_nthreads;
use legume_numeric::matrix::common_io::*;
use log::info;
use std::ops::Range;
use std::sync::Arc;
use anyhow::anyhow;
use crate::sparse_backend::shared;
use crate::utilities::io_helpers::{chunk_elems, parse_name_file};
const COMPRESSION_LEVEL: u8 = 5;
#[derive(Debug, Clone)]
pub struct SparseMtxData {
backend: Arc<hdf5::File>,
file_name: String,
max_row_name_idx: usize,
max_column_name_idx: usize,
by_column_indptr: Vec<u64>,
streamed_nnz: u64,
by_row_indptr: Vec<u64>,
by_column_indices: Option<Vec<u64>>,
by_column_data: Option<Vec<f32>>,
by_row_indices: Option<Vec<u64>>,
by_row_data: Option<Vec<f32>>,
}
impl SparseMtxData {
pub fn new(backend_file: Option<&str>) -> anyhow::Result<Self> {
let ret = match backend_file {
Some(backend_file) => Self::register_backend_file(backend_file)?,
None => {
let backend_file = create_temp_dir_file(".h5")?;
let backend_file = backend_file.to_str().expect("to_str failed");
Self::register_backend_file(backend_file)?
}
};
Ok(ret)
}
pub fn open(backend_file: &str) -> anyhow::Result<Self> {
let hdf5_backend = hdf5::File::open(backend_file)?;
if let (Some(nrow), Some(ncol), Some(nnz)) = (
Self::_num_rows(&hdf5_backend),
Self::_num_columns(&hdf5_backend),
Self::_num_nnz(&hdf5_backend),
) {
info!("#rows: {}, #columns: {}, #non-zeros: {}", nrow, ncol, nnz);
} else {
anyhow::bail!("Couldn't figure out the size of this sparse matrix data");
}
let mut ret = Self {
backend: hdf5_backend.into(),
file_name: backend_file.to_string(),
max_row_name_idx: MAX_ROW_NAME_IDX,
max_column_name_idx: MAX_COLUMN_NAME_IDX,
by_column_indptr: vec![],
streamed_nnz: 0,
by_row_indptr: vec![],
by_column_indices: None,
by_column_data: None,
by_row_indices: None,
by_row_data: None,
};
ret.read_column_indptr()?;
ret.read_row_indptr()?;
Ok(ret)
}
pub fn from_mtx_file(
mtx_file: &str,
backend_file: Option<&str>,
index_by_row: Option<bool>,
) -> anyhow::Result<Self> {
let mut ret = match backend_file {
Some(backend_file) => {
info!("backend file : {}", backend_file);
Self::register_backend_file(backend_file)?
}
None => {
let backend_file = mtx_file.to_string() + ".h5";
info!("backend file : {}", backend_file);
Self::register_backend_file(backend_file.as_ref())?
}
};
ret.import_mtx_file(mtx_file, index_by_row == Some(true))?;
info!("created sparse backend from {}", mtx_file);
Ok(ret)
}
pub fn from_ndarray(
array: &Array2<f32>,
backend_file: Option<&str>,
index_by_row: Option<bool>,
) -> anyhow::Result<Self> {
let mut ret = match backend_file {
Some(backend_file) => Self::register_backend_file(backend_file)?,
None => {
let backend_file = create_temp_dir_file(".h5")?;
let backend_file = backend_file.to_str().expect("to_str failed");
Self::register_backend_file(backend_file)?
}
};
ret.import_ndarray_by_col(array)?; ret.read_column_indptr()?;
if Some(true) == index_by_row {
ret.import_ndarray_by_row(array)?; ret.read_row_indptr()?; }
Ok(ret)
}
pub fn from_dmatrix(
matrix: &DMatrix<f32>,
backend_file: Option<&str>,
index_by_row: Option<bool>,
) -> anyhow::Result<Self> {
let mut ret = match backend_file {
Some(backend_file) => Self::register_backend_file(backend_file)?,
None => {
let backend_file = create_temp_dir_file(".h5")?;
let backend_file = backend_file.to_str().expect("to_str failed");
Self::register_backend_file(backend_file)?
}
};
ret.import_dmatrix_by_col(matrix)?; ret.read_column_indptr()?;
if Some(true) == index_by_row {
ret.import_dmatrix_by_row(matrix)?; ret.read_row_indptr()?; }
Ok(ret)
}
fn _num_rows(file: &hdf5::File) -> Option<usize> {
file.attr("nrow").ok()?.read_scalar().ok()
}
fn _num_columns(file: &hdf5::File) -> Option<usize> {
file.attr("ncol").ok()?.read_scalar().ok()
}
fn _num_nnz(file: &hdf5::File) -> Option<usize> {
file.attr("nnz").ok()?.read_scalar().ok()
}
fn set_attrs(&mut self, attr_name: &str, value: usize) -> anyhow::Result<()> {
if self.backend.attr(attr_name).is_err() {
self.backend
.new_attr::<usize>()
.create(attr_name)?
.write_scalar(&value)?;
} else if self.backend.attr(attr_name)?.read_scalar::<usize>()? != value {
return Err(anyhow!(format!("{} mismatch", attr_name)));
}
Ok(())
}
fn register_backend_file(hdf5_file: &str) -> anyhow::Result<Self> {
let hdf5_backend = hdf5::File::create(hdf5_file)?;
Ok(Self {
backend: hdf5_backend.into(),
file_name: hdf5_file.to_string(),
max_row_name_idx: MAX_ROW_NAME_IDX,
max_column_name_idx: MAX_COLUMN_NAME_IDX,
by_column_indptr: vec![],
streamed_nnz: 0,
by_row_indptr: vec![],
by_column_indices: None,
by_column_data: None,
by_row_indices: None,
by_row_data: None,
})
}
}
impl SparseIo for SparseMtxData {
type IndexIter = Vec<usize>;
fn initialize_backend(&mut self) -> anyhow::Result<()> {
self.remove_backend_file()?;
self.backend = hdf5::File::create(&self.file_name)?.into();
self.max_column_name_idx = MAX_COLUMN_NAME_IDX;
self.max_row_name_idx = MAX_ROW_NAME_IDX;
self.by_column_indptr = vec![];
self.by_row_indptr = vec![];
Ok(())
}
fn record_mtx_shape(&mut self, mtx_shape: Option<(usize, usize, usize)>) -> anyhow::Result<()> {
if let Some((nrow, ncol, nnz)) = mtx_shape {
self.set_attrs("nrow", nrow)?;
self.set_attrs("ncol", ncol)?;
self.set_attrs("nnz", nnz)?;
self.backend.flush()?;
}
Ok(())
}
fn column_indptr(&self) -> &[u64] {
&self.by_column_indptr
}
fn reopen_backend(&mut self) -> anyhow::Result<()> {
self.backend = hdf5::File::open_rw(&self.file_name)?.into();
self.streamed_nnz = 0;
self.read_column_indptr()?;
self.read_row_indptr()?;
Ok(())
}
fn note_streamed_nnz(&mut self, n: u64) {
self.streamed_nnz += n;
}
fn streamed_nnz(&self) -> u64 {
self.streamed_nnz
}
fn reset_streamed_nnz(&mut self) {
self.streamed_nnz = 0;
}
fn read_column_indptr(&mut self) -> anyhow::Result<()> {
if let Ok(by_column) = self.backend.group("/by_column") {
let indptr = by_column.dataset("indptr")?.read_1d::<u64>()?;
self.by_column_indptr.clear();
self.by_column_indptr.extend(indptr);
}
Ok(())
}
fn read_row_indptr(&mut self) -> anyhow::Result<()> {
if let Ok(by_row) = self.backend.group("/by_row") {
let indptr = by_row.dataset("indptr")?.read_1d::<u64>()?;
self.by_row_indptr.clear();
self.by_row_indptr.extend(indptr);
}
Ok(())
}
fn preload_columns(&mut self) -> anyhow::Result<()> {
if let Some(nnz) = self.num_non_zeros() {
if !crate::sparse_io::preload_within_budget(nnz, "column") {
return Ok(());
}
}
let by_column = self.backend.group("/by_column")?;
let data = by_column.dataset("data")?.read_1d::<f32>()?.to_vec();
let indices = by_column.dataset("indices")?.read_1d::<u64>()?.to_vec();
self.by_column_data = Some(data);
self.by_column_indices = Some(indices);
Ok(())
}
fn clean_preloaded_columns(&mut self) {
self.by_column_data = None;
self.by_column_indices = None;
}
fn preload_rows(&mut self) -> anyhow::Result<()> {
if let Some(nnz) = self.num_non_zeros() {
if !crate::sparse_io::preload_within_budget(nnz, "row") {
return Ok(());
}
}
let by_row = self.backend.group("/by_row")?;
let data = by_row.dataset("data")?.read_1d::<f32>()?.to_vec();
let indices = by_row.dataset("indices")?.read_1d::<u64>()?.to_vec();
self.by_row_data = Some(data);
self.by_row_indices = Some(indices);
Ok(())
}
fn clean_preloaded_rows(&mut self) {
self.by_row_data = None;
self.by_row_indices = None;
}
fn remove_backend_file(&self) -> anyhow::Result<()> {
let backend = std::path::Path::new(&self.file_name);
if backend.exists() {
std::fs::remove_file(backend)?;
}
Ok(())
}
fn get_backend_file_name(&self) -> &str {
&self.file_name
}
fn backend_type(&self) -> SparseIoBackend {
SparseIoBackend::HDF5
}
fn to_mtx_file(&self, mtx_file: &str) -> anyhow::Result<()> {
let by_column = self.backend.group("/by_column")?;
let indptr = by_column.dataset("indptr")?.read_1d::<u64>()?;
let data = by_column.dataset("data")?;
let indices = by_column.dataset("indices")?;
if let (Some(ncol), Some(nrow), Some(nnz)) =
(self.num_columns(), self.num_rows(), self.num_non_zeros())
{
let mut buf = open_buf_writer(mtx_file)?;
shared::write_mtx_header(&mut buf, nrow, ncol, nnz)?;
for jj in 0..ncol {
let start = indptr[jj] as usize;
let end = indptr[jj + 1] as usize;
let data_slice = data.read_slice_1d::<f32, _>(start..end)?;
let indices_slice = indices.read_slice_1d::<u64, _>(start..end)?;
for k in 0..(end - start) {
let val = data_slice[k];
let ii = indices_slice[k] as usize;
writeln!(buf, "{}\t{}\t{}", ii + 1, jj + 1, val)?;
}
}
buf.flush()?;
info!(
"{}: {} rows, {} columns, {} non-zeros",
mtx_file, nrow, ncol, nnz
);
Ok(())
} else {
Err(anyhow!("Unable to figure out the size of the backend data"))
}
}
fn register_row_names_file(&mut self, row_name_file: &str) {
self.register_names_file(
"/row_names",
row_name_file,
0..self.max_row_name_idx,
ROW_SEP,
)
.expect("failed to add row names");
}
fn register_row_names_vec(&mut self, rows: &[Box<str>]) {
self.register_names_vec("/row_names", rows)
.expect("failed to add row names");
}
fn register_column_names_file(&mut self, column_name_file: &str) {
self.register_names_file(
"/column_names",
column_name_file,
0..self.max_column_name_idx,
COLUMN_SEP,
)
.expect("failed to add column names");
}
fn register_column_names_vec(&mut self, columns: &[Box<str>]) {
self.register_names_vec("/column_names", columns)
.expect("failed to add column names");
}
fn num_rows(&self) -> Option<usize> {
Self::_num_rows(&self.backend)
}
fn num_columns(&self) -> Option<usize> {
Self::_num_columns(&self.backend)
}
fn num_non_zeros(&self) -> Option<usize> {
Self::_num_nnz(&self.backend)
}
fn register_names_file(
&mut self,
key: &str,
name_file: &str,
name_columns: Range<usize>,
name_sep: &str,
) -> anyhow::Result<()> {
use hdf5::types::VarLenUnicode;
let _names: Vec<VarLenUnicode> = parse_name_file(name_file, name_columns, name_sep)?
.iter()
.map(|x| x.parse().expect("invalid name"))
.collect();
let root = self.backend.group("/")?;
root.new_dataset::<VarLenUnicode>()
.shape(_names.len())
.chunk([_names.len()])
.create(key)?
.write(&_names)?;
Ok(())
}
fn register_names_vec(&mut self, key: &str, names: &[Box<str>]) -> anyhow::Result<()> {
use hdf5::types::VarLenUnicode;
let _names: Vec<VarLenUnicode> = names
.iter()
.map(|x| x.to_string().parse().expect("invalid name"))
.collect::<Vec<_>>();
let root = self.backend.group("/")?;
root.new_dataset::<VarLenUnicode>()
.shape(_names.len())
.chunk([_names.len()])
.create(key)?
.write(&_names)?;
Ok(())
}
fn row_names(&self) -> anyhow::Result<Vec<Box<str>>> {
self.retrieve_registered_names("/row_names")
}
fn column_names(&self) -> anyhow::Result<Vec<Box<str>>> {
self.retrieve_registered_names("/column_names")
}
fn retrieve_registered_names(&self, key: &str) -> anyhow::Result<Vec<Box<str>>> {
use hdf5::types::VarLenUnicode;
let root = self.backend.group("/")?;
let ret = root.dataset(key)?.read_1d::<VarLenUnicode>()?;
Ok(ret.iter().map(|x| x.to_string().into_boxed_str()).collect())
}
fn read_triplets_by_single_column(
&self,
j_data: usize,
) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
let by_column = self.backend.group("/by_column")?;
debug_assert!(!self.by_column_indptr.is_empty());
let indptr = &self.by_column_indptr;
debug_assert!((j_data + 1) < indptr.len());
let nrow = self
.num_rows()
.ok_or(anyhow!("can't figure out the number of rows"))?;
if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
let ncol_out = 1;
let jj = 0;
let start = indptr[j_data] as usize;
let end = indptr[j_data + 1] as usize;
let ret: Vec<(u64, u64, f32)> = indices[start..end]
.iter()
.zip(data[start..end].iter())
.map(|(&ii, &x_ij)| (ii, jj, x_ij))
.collect();
Ok((nrow, ncol_out, ret))
} else {
let data = by_column.dataset("data")?;
let indices = by_column.dataset("indices")?;
let mut ret = Vec::new();
let ncol_out = 1;
let jj = 0;
debug_assert!((j_data + 1) < indptr.len());
let start = indptr[j_data] as usize;
let end = indptr[j_data + 1] as usize;
if start < end {
let data_slice = data.read_slice_1d::<f32, _>(start..end)?;
let indices_slice = indices.read_slice_1d::<u64, _>(start..end)?;
for k in 0..(end - start) {
let x_ij = data_slice[k];
let ii = indices_slice[k];
debug_assert!((ii as usize) < nrow);
ret.push((ii, jj, x_ij));
}
}
Ok((nrow, ncol_out, ret))
}
}
fn read_triplets_by_columns(
&self,
columns: Self::IndexIter,
) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
let by_column = self.backend.group("/by_column")?;
debug_assert!(!self.by_column_indptr.is_empty());
let indptr = &self.by_column_indptr;
let columns_vec = columns.into_iter().collect::<Vec<usize>>();
let nrow = self
.num_rows()
.ok_or(anyhow!("can't figure out the number of rows"))?;
let ncol = self
.num_columns()
.ok_or(anyhow!("can't figure out the number of columns"))?;
let min_start = columns_vec
.iter()
.map(|&j_data| indptr[j_data])
.min()
.unwrap_or(0);
let max_end = columns_vec
.iter()
.map(|&j_data| indptr[j_data + 1])
.max()
.unwrap_or(0);
if let (Some(data), Some(indices)) = (&self.by_column_data, &self.by_column_indices) {
let ncol_out = columns_vec.len();
let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity((max_end - min_start) as usize);
for (jj, &j_data) in columns_vec.iter().enumerate() {
let jj = jj as u64;
if j_data < ncol {
let start = indptr[j_data] as usize;
let end = indptr[j_data + 1] as usize;
for (&ii, &x_ij) in indices[start..end].iter().zip(data[start..end].iter()) {
ret.push((ii, jj, x_ij));
}
}
}
Ok((nrow, ncol_out, ret))
} else {
let data = by_column.dataset("data")?;
let indices = by_column.dataset("indices")?;
let ncol_out = columns_vec.len();
let mut tagged: Vec<(u64, u64, u64)> = columns_vec
.iter()
.enumerate()
.filter_map(|(jj, &j_data)| {
if j_data >= ncol {
return None;
}
let start = indptr[j_data];
let end = indptr[j_data + 1];
(start < end).then_some((jj as u64, start, end))
})
.collect();
tagged.sort_by_key(|&(_, start, _)| start);
let ret = shared::coalesce_and_emit(
&tagged,
nrow,
|jj, ii, val| (ii, jj, val),
|s, e| {
let data_buf = data.read_slice_1d::<f32, _>((s as usize)..(e as usize))?;
let indices_buf =
indices.read_slice_1d::<u64, _>((s as usize)..(e as usize))?;
Ok((data_buf, indices_buf))
},
)?;
Ok((nrow, ncol_out, ret))
}
}
fn csc_column_arrays(&self) -> Option<(&[u64], &[u64], &[f32])> {
match (
self.by_column_data.as_ref(),
self.by_column_indices.as_ref(),
) {
(Some(data), Some(indices)) if !self.by_column_indptr.is_empty() => Some((
self.by_column_indptr.as_slice(),
indices.as_slice(),
data.as_slice(),
)),
_ => None,
}
}
fn read_triplets_by_rows(
&self,
rows: Self::IndexIter,
) -> anyhow::Result<(usize, usize, Vec<(u64, u64, f32)>)> {
debug_assert!(!self.by_row_indptr.is_empty());
let indptr = &self.by_row_indptr;
let rows_vec = rows.into_iter().collect::<Vec<usize>>();
let (ncol, nrow) = match (self.num_columns(), self.num_rows()) {
(Some(ncol), Some(nrow)) => (ncol, nrow),
_ => return Err(anyhow!("Unable to figure out the size of the backend data")),
};
let nrow_out = rows_vec.len();
if let (Some(data), Some(indices)) = (&self.by_row_data, &self.by_row_indices) {
let mut nnz_total: usize = 0;
let valid: Vec<(u64, usize)> = rows_vec
.iter()
.enumerate()
.filter_map(|(ii, &i_data)| {
if i_data >= nrow {
return None;
}
nnz_total += (indptr[i_data + 1] - indptr[i_data]) as usize;
Some((ii as u64, i_data))
})
.collect();
let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(nnz_total);
for (ii, i_data) in valid {
let start = indptr[i_data] as usize;
let end = indptr[i_data + 1] as usize;
for (&jj, &x_ij) in indices[start..end].iter().zip(data[start..end].iter()) {
ret.push((ii, jj, x_ij));
}
}
return Ok((nrow_out, ncol, ret));
}
let by_row = self.backend.group("/by_row")?;
let data = by_row.dataset("data")?;
let indices = by_row.dataset("indices")?;
let mut tagged: Vec<(u64, u64, u64)> = rows_vec
.iter()
.enumerate()
.filter_map(|(ii, &i_data)| {
if i_data >= nrow {
return None;
}
let start = indptr[i_data];
let end = indptr[i_data + 1];
(start < end).then_some((ii as u64, start, end))
})
.collect();
tagged.sort_by_key(|&(_, start, _)| start);
let ret = shared::coalesce_and_emit(
&tagged,
ncol,
|ii, jj, val| (ii, jj, val),
|s, e| {
let data_buf = data.read_slice_1d::<f32, _>((s as usize)..(e as usize))?;
let indices_buf = indices.read_slice_1d::<u64, _>((s as usize)..(e as usize))?;
Ok((data_buf, indices_buf))
},
)?;
Ok((nrow_out, ncol, ret))
}
fn record_csr_dataset_backend(
&mut self,
csr_cols: &[u64],
csr_vals: &[f32],
csr_rowptr: &[u64],
) -> anyhow::Result<()> {
if self.backend.group("/by_row").is_err() {
let _root = self.backend.create_group("/by_row")?;
}
{
let num_threads = rayon::current_num_threads().max(1);
blosc_set_nthreads(num_threads as u8);
}
let csr = self.backend.group("/by_row")?;
csr.new_dataset::<f32>()
.shape(csr_vals.len())
.chunk([chunk_elems(csr_vals.len(), std::mem::size_of::<f32>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("data")?
.write(&csr_vals)?;
csr.new_dataset::<u64>()
.shape(csr_rowptr.len())
.chunk([chunk_elems(csr_rowptr.len(), std::mem::size_of::<u64>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("indptr")?
.write(&csr_rowptr)?;
csr.new_dataset::<u64>()
.shape(csr_cols.len())
.chunk([chunk_elems(csr_cols.len(), std::mem::size_of::<u64>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("indices")?
.write(&csr_cols)?;
self.backend.flush()?;
Ok(())
}
fn cs_create(&mut self, key: CsKey, len: usize) -> anyhow::Result<()> {
let (group_path, ds_name) = match key {
CsKey::CscData => ("/by_column", "data"),
CsKey::CscIndices => ("/by_column", "indices"),
CsKey::CscIndptr => ("/by_column", "indptr"),
CsKey::CsrData => ("/by_row", "data"),
CsKey::CsrIndices => ("/by_row", "indices"),
CsKey::CsrIndptr => ("/by_row", "indptr"),
};
if self.backend.group(group_path).is_err() {
self.backend.create_group(group_path)?;
}
{
let num_threads = rayon::current_num_threads().max(1);
blosc_set_nthreads(num_threads as u8);
}
let group = self.backend.group(group_path)?;
let shape_len = len.max(1);
match key {
CsKey::CscData | CsKey::CsrData => {
group
.new_dataset::<f32>()
.shape(shape_len)
.chunk([chunk_elems(shape_len, std::mem::size_of::<f32>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create(ds_name)?;
}
_ => {
group
.new_dataset::<u64>()
.shape(shape_len)
.chunk([chunk_elems(shape_len, std::mem::size_of::<u64>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create(ds_name)?;
}
}
self.backend.flush()?;
Ok(())
}
fn cs_write_u64(&mut self, key: CsKey, offset: u64, data: &[u64]) -> anyhow::Result<()> {
if data.is_empty() {
return Ok(());
}
let (group_path, ds_name) = match key {
CsKey::CscIndices => ("/by_column", "indices"),
CsKey::CscIndptr => ("/by_column", "indptr"),
CsKey::CsrIndices => ("/by_row", "indices"),
CsKey::CsrIndptr => ("/by_row", "indptr"),
CsKey::CscData | CsKey::CsrData => {
return Err(anyhow!("cs_write_u64 called on f32 slot {:?}", key));
}
};
let group = self.backend.group(group_path)?;
let ds = group.dataset(ds_name)?;
let lo = offset as usize;
let hi = lo + data.len();
ds.write_slice(data, ndarray::s![lo..hi])?;
Ok(())
}
fn cs_write_f32(&mut self, key: CsKey, offset: u64, data: &[f32]) -> anyhow::Result<()> {
if data.is_empty() {
return Ok(());
}
let (group_path, ds_name) = match key {
CsKey::CscData => ("/by_column", "data"),
CsKey::CsrData => ("/by_row", "data"),
_ => {
return Err(anyhow!("cs_write_f32 called on u64 slot {:?}", key));
}
};
let group = self.backend.group(group_path)?;
let ds = group.dataset(ds_name)?;
let lo = offset as usize;
let hi = lo + data.len();
ds.write_slice(data, ndarray::s![lo..hi])?;
Ok(())
}
fn record_csc_dataset_backend(
&mut self,
csc_rows: &[u64],
csc_vals: &[f32],
csc_colptr: &[u64],
) -> anyhow::Result<()> {
if self.backend.group("/by_column").is_err() {
let _root = self.backend.create_group("/by_column")?;
}
{
let num_threads = rayon::current_num_threads().max(1);
blosc_set_nthreads(num_threads as u8);
}
let csc = self.backend.group("/by_column")?;
csc.new_dataset::<f32>()
.shape(csc_vals.len())
.chunk([chunk_elems(csc_vals.len(), std::mem::size_of::<f32>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("data")?
.write(&csc_vals)?;
csc.new_dataset::<u64>()
.shape(csc_colptr.len())
.chunk([chunk_elems(csc_colptr.len(), std::mem::size_of::<u64>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("indptr")?
.write(&csc_colptr)?;
csc.new_dataset::<u64>()
.shape(csc_rows.len())
.chunk([chunk_elems(csc_rows.len(), std::mem::size_of::<u64>())])
.blosc_blosclz(COMPRESSION_LEVEL, true)
.create("indices")?
.write(&csc_rows)?;
self.backend.flush()?;
Ok(())
}
}