use legume_numeric::matrix::common_io::open_buf_writer;
use std::io::Write;
pub(crate) fn coalesce_and_emit<R, F, D, I>(
tagged: &[(u64, u64, u64)],
inner_bound: usize,
make_triplet: F,
mut retrieve: R,
) -> anyhow::Result<Vec<(u64, u64, f32)>>
where
R: FnMut(u64, u64) -> anyhow::Result<(D, I)>,
D: std::ops::Index<usize, Output = f32>,
I: std::ops::Index<usize, Output = u64>,
F: Fn(u64, u64, f32) -> (u64, u64, f32),
{
let total: u64 = tagged.iter().map(|&(_, s, e)| e - s).sum();
let mut ret: Vec<(u64, u64, f32)> = Vec::with_capacity(total as usize);
const GAP_FUSE: u64 = 65_536;
const MERGED_SPAN_CAP: u64 = 4_000_000;
let mut i = 0;
while i < tagged.len() {
let merged_start = tagged[i].1;
let mut merged_end = tagged[i].2;
let mut j = i + 1;
while j < tagged.len()
&& tagged[j].1 <= merged_end.saturating_add(GAP_FUSE)
&& tagged[j].2.max(merged_end) - merged_start <= MERGED_SPAN_CAP
{
merged_end = merged_end.max(tagged[j].2);
j += 1;
}
let (data_buf, indices_buf) = retrieve(merged_start, merged_end)?;
for &(tag, start, end) in &tagged[i..j] {
let off = (start - merged_start) as usize;
let len = (end - start) as usize;
for k in 0..len {
let inner = indices_buf[off + k];
let val = data_buf[off + k];
if inner >= inner_bound as u64 {
return Err(crate::sparse_io::index_audit::inner_out_of_range(
"sparse read",
(start + k as u64) as usize,
inner,
inner_bound,
));
}
ret.push(make_triplet(tag, inner, val));
}
}
i = j;
}
Ok(ret)
}
pub(crate) const MTX_STREAM_BLOCK: u64 = 1 << 20;
pub(crate) fn write_mtx_header<W: Write>(
buf: &mut W,
nrow: usize,
ncol: usize,
nnz: usize,
) -> anyhow::Result<()> {
writeln!(buf, "%%MatrixMarket matrix coordinate real general")?;
writeln!(buf, "{}\t{}\t{}", nrow, ncol, nnz)?;
Ok(())
}
pub(crate) fn write_mtx<W, R, D, I>(
buf: &mut W,
shape: (usize, usize, usize),
indptr: &[u64],
block: u64,
mut retrieve: R,
) -> anyhow::Result<()>
where
W: Write,
R: FnMut(u64, u64) -> anyhow::Result<(D, I)>,
D: std::ops::Index<usize, Output = f32>,
I: std::ops::Index<usize, Output = u64>,
{
let (nrow, ncol, nnz) = shape;
crate::sparse_io::index_audit::check_indptr("to_mtx_file", indptr, ncol, nnz)?;
write_mtx_header(buf, nrow, ncol, nnz)?;
let total_nnz = indptr[ncol];
let block = block.max(1);
let mut jj = 0usize; let mut pos = 0u64;
while pos < total_nnz {
let end = (pos + block).min(total_nnz);
let (data_block, indices_block) = retrieve(pos, end)?;
for k in 0..(end - pos) as usize {
let global = pos + k as u64;
while indptr[jj + 1] <= global {
jj += 1;
}
let ii = indices_block[k];
if ii >= nrow as u64 {
return Err(crate::sparse_io::index_audit::inner_out_of_range(
"to_mtx_file",
global as usize,
ii,
nrow,
));
}
writeln!(buf, "{}\t{}\t{}", ii + 1, jj + 1, data_block[k])?;
}
pos = end;
}
Ok(())
}
pub(crate) fn write_mtx_file<F>(mtx_file: &str, write: F) -> anyhow::Result<()>
where
F: FnOnce(&mut Box<dyn Write>) -> anyhow::Result<()>,
{
let mut buf = open_buf_writer(mtx_file)?;
let written = write(&mut buf).and_then(|()| Ok(buf.flush()?));
if written.is_err() && std::fs::metadata(mtx_file).is_ok_and(|m| m.is_file()) {
drop(buf);
let _ = std::fs::remove_file(mtx_file);
}
written
}
#[cfg(test)]
#[path = "shared_tests.rs"]
mod tests;