use ztensor::{DType, Error, Result, Rule, Tensor};
#[derive(Debug, Clone)]
pub struct Csr {
pub rows: u64,
pub cols: u64,
pub values: Vec<u8>,
pub dtype: DType,
pub logical: Option<String>,
pub indices: Vec<u64>,
pub indptr: Vec<u64>,
}
pub fn read(tensor: &Tensor<'_>) -> Result<Csr> {
if tensor.layout() != "zt.sparse_csr/1" {
return Err(Error::Unsupported(format!(
"{:?} has layout {:?}, not zt.sparse_csr/1",
tensor.name(),
tensor.layout()
)));
}
let [rows, cols] = tensor.shape()[..] else {
return Err(Error::reject(
Rule::LayoutRule,
format!("{:?}: sparse_csr requires rank-2 shape", tensor.name()),
));
};
let idx_part = tensor.part("indices")?;
let idx_dtype = idx_part.dtype();
let values_part = tensor.part("values")?;
let (dtype, logical) = (
values_part.dtype(),
values_part.logical().map(str::to_string),
);
let indices = widen(&idx_part.bytes()?, idx_dtype);
let indptr = widen(&tensor.part("indptr")?.bytes()?, idx_dtype);
let values = values_part.bytes()?.into_owned();
let nnz = indices.len() as u64;
let name = tensor.name();
let bad = |detail: String| Err(Error::reject(Rule::LayoutData, detail));
if indptr.first() != Some(&0) {
return bad(format!("{name:?}: indptr must start at 0"));
}
if indptr.windows(2).any(|w| w[0] > w[1]) {
return bad(format!("{name:?}: indptr must be non-decreasing"));
}
if indptr.last() != Some(&nnz) {
return bad(format!("{name:?}: indptr must end at nnz ({nnz})"));
}
for r in 0..rows as usize {
let row = &indices[indptr[r] as usize..indptr[r + 1] as usize];
if row.windows(2).any(|w| w[0] >= w[1]) {
return bad(format!("{name:?}: row {r} indices not strictly increasing"));
}
if row.last().is_some_and(|&c| c >= cols) {
return bad(format!("{name:?}: row {r} has an index >= cols ({cols})"));
}
}
Ok(Csr {
rows,
cols,
values,
dtype,
logical,
indices,
indptr,
})
}
fn widen(bytes: &[u8], dtype: DType) -> Vec<u64> {
match dtype {
DType::U32 => bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes(c.try_into().unwrap()) as u64)
.collect(),
_ => bytes
.chunks_exact(8)
.map(|c| u64::from_le_bytes(c.try_into().unwrap()))
.collect(),
}
}