Push zunrplorkwkt #70
@@ -13,20 +13,30 @@ use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
use obicompactvec::{PersistentCompactIntMatrix, PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder};
|
||||
use obicompactvec::{
|
||||
PersistentIntMatrix, PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let layer_dir = std::env::args().nth(1).expect("usage: compare_sparse_dense_count <layer_dir>");
|
||||
let layer_dir = std::env::args()
|
||||
.nth(1)
|
||||
.expect("usage: compare_sparse_dense_count <layer_dir>");
|
||||
let layer_dir = Path::new(&layer_dir);
|
||||
|
||||
let t0 = Instant::now();
|
||||
let dense = PersistentCompactIntMatrix::open(layer_dir)?;
|
||||
println!("dense ouverte en {:?} ({} lignes x {} colonnes)", t0.elapsed(), dense.n(), dense.n_cols());
|
||||
let dense = PersistentIntMatrix::open(layer_dir)?;
|
||||
println!(
|
||||
"dense ouverte en {:?} ({} lignes x {} colonnes)",
|
||||
t0.elapsed(),
|
||||
dense.n(),
|
||||
dense.n_cols()
|
||||
);
|
||||
|
||||
let out_dir = tempfile::tempdir()?;
|
||||
|
||||
let t0 = Instant::now();
|
||||
let sparse = PersistentSparseCompactIntMatrixBuilder::build_from_dense(&dense, out_dir.path())?.finish()?;
|
||||
let sparse = PersistentSparseCompactIntMatrixBuilder::build_from_dense(&dense, out_dir.path())?
|
||||
.finish()?;
|
||||
println!("build_from_dense: {:?}", t0.elapsed());
|
||||
|
||||
compare(&dense, &sparse)?;
|
||||
@@ -36,7 +46,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compare(dense: &PersistentCompactIntMatrix, sparse: &PersistentSparseCompactIntMatrix) -> Result<(), Box<dyn Error>> {
|
||||
fn compare(
|
||||
dense: &PersistentIntMatrix,
|
||||
sparse: &PersistentSparseCompactIntMatrix,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let n = dense.n();
|
||||
let n_cols = dense.n_cols();
|
||||
assert_eq!(n, sparse.n(), "n mismatch");
|
||||
@@ -63,7 +76,10 @@ fn compare(dense: &PersistentCompactIntMatrix, sparse: &PersistentSparseCompactI
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("comparaison case-a-case: {:?} ({total_cells} cellules)", t0.elapsed());
|
||||
println!(
|
||||
"comparaison case-a-case: {:?} ({total_cells} cellules)",
|
||||
t0.elapsed()
|
||||
);
|
||||
|
||||
if let Some((slot, col, d, s)) = first_mismatch {
|
||||
eprintln!("PREMIER MISMATCH: slot={slot} col={col} dense={d} sparse={s}");
|
||||
@@ -78,7 +94,7 @@ fn compare(dense: &PersistentCompactIntMatrix, sparse: &PersistentSparseCompactI
|
||||
/// for what's actually stored) — sparsity, singleton-row fraction, and the
|
||||
/// value-magnitude buckets this crate's overflow encoding is tuned around
|
||||
/// (< 127, < 255, >= 255).
|
||||
fn content_stats(dense: &PersistentCompactIntMatrix) {
|
||||
fn content_stats(dense: &PersistentIntMatrix) {
|
||||
let n = dense.n();
|
||||
let n_cols = dense.n_cols();
|
||||
let total_cells = n as u64 * n_cols as u64;
|
||||
@@ -128,7 +144,10 @@ fn content_stats(dense: &PersistentCompactIntMatrix) {
|
||||
100.0 * singleton_rows as f64 / n as f64
|
||||
);
|
||||
if nonzero_cells > 0 {
|
||||
println!("valeur moyenne (non-nulles): {:.2}", sum as f64 / nonzero_cells as f64);
|
||||
println!(
|
||||
"valeur moyenne (non-nulles): {:.2}",
|
||||
sum as f64 / nonzero_cells as f64
|
||||
);
|
||||
println!("valeur max: {max_value}");
|
||||
println!(
|
||||
"valeurs < 127: {under_127} ({:.3}% des non-nulles)",
|
||||
@@ -162,7 +181,7 @@ fn dir_size(dir: &Path) -> std::io::Result<u64> {
|
||||
fn compaction_stats(
|
||||
dense_layer_dir: &Path,
|
||||
sparse_dir: &Path,
|
||||
dense: &PersistentCompactIntMatrix,
|
||||
dense: &PersistentIntMatrix,
|
||||
_sparse: &PersistentSparseCompactIntMatrix,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let dense_size = dir_size(&dense_layer_dir.join("counts"))?;
|
||||
|
||||
+288
-111
@@ -5,17 +5,19 @@ use std::path::{Path, PathBuf};
|
||||
use memmap2::Mmap;
|
||||
use ndarray::{Array1, Array2};
|
||||
|
||||
use crate::bitmatrix::{fill_row_generic, par_col_reduce, pairwise2_matrix, pairwise_matrix, row_generic};
|
||||
use crate::bitmatrix::{
|
||||
fill_row_generic, pairwise_matrix, pairwise2_matrix, par_col_reduce, row_generic,
|
||||
};
|
||||
use crate::builder::PersistentCompactIntVecBuilder;
|
||||
use crate::colgroup::{chunked_presence_count, ColGroup, MatrixGroupOps};
|
||||
use crate::colgroup::{ColGroup, MatrixGroupOps, chunked_presence_count};
|
||||
use crate::format::{HEADER_SIZE, OVERFLOW_ENTRY_SIZE};
|
||||
use crate::meta::MatrixMeta;
|
||||
use crate::reader::PersistentCompactIntVec;
|
||||
use crate::sparse_intmatrix::{is_present as sparse_is_present, PersistentSparseCompactIntMatrix};
|
||||
use crate::sparse_intmatrix::{PersistentSparseCompactIntMatrix, is_present as sparse_is_present};
|
||||
use crate::storage_kind::StorageKind;
|
||||
use crate::tempbitvec::{TempBitVec, TempBitVecBuilder};
|
||||
use crate::tempintvec::{TempCompactIntVec, TempCompactIntVecBuilder};
|
||||
use crate::views::{nonzero_triples, IntSliceView};
|
||||
use crate::views::{IntSliceView, nonzero_triples};
|
||||
|
||||
pub(crate) fn col_path(dir: &Path, col: usize) -> PathBuf {
|
||||
dir.join(format!("col_{col:06}.pciv"))
|
||||
@@ -25,7 +27,7 @@ pub(crate) fn col_path(dir: &Path, col: usize) -> PathBuf {
|
||||
|
||||
pub struct ColumnarCompactIntMatrix {
|
||||
cols: Vec<PersistentCompactIntVec>,
|
||||
n: usize,
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl ColumnarCompactIntMatrix {
|
||||
@@ -38,11 +40,17 @@ impl ColumnarCompactIntMatrix {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn n(&self) -> usize { self.n }
|
||||
pub(crate) fn n(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn n_cols(&self) -> usize { self.cols.len() }
|
||||
pub(crate) fn n_cols(&self) -> usize {
|
||||
self.cols.len()
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn col(&self, c: usize) -> &PersistentCompactIntVec { &self.cols[c] }
|
||||
pub(crate) fn col(&self, c: usize) -> &PersistentCompactIntVec {
|
||||
&self.cols[c]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn row(&self, slot: usize) -> Box<[u32]> {
|
||||
@@ -63,34 +71,64 @@ impl ColumnarCompactIntMatrix {
|
||||
}
|
||||
|
||||
pub(crate) fn partial_bray_dist_matrix(&self) -> Array2<u64> {
|
||||
pairwise_matrix(self.n_cols(), |i, j| self.col(i).partial_bray_dist(self.col(j)))
|
||||
pairwise_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i).partial_bray_dist(self.col(j))
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_euclidean_dist_matrix(&self) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols(), |i, j| self.col(i).partial_euclidean_dist(self.col(j)))
|
||||
pairwise_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i).partial_euclidean_dist(self.col(j))
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_threshold_jaccard_dist_matrix(&self, threshold: u32) -> (Array2<u64>, Array2<u64>) {
|
||||
pairwise2_matrix(self.n_cols(), |i, j| self.col(i).partial_threshold_jaccard_dist(self.col(j), threshold))
|
||||
pub(crate) fn partial_threshold_jaccard_dist_matrix(
|
||||
&self,
|
||||
threshold: u32,
|
||||
) -> (Array2<u64>, Array2<u64>) {
|
||||
pairwise2_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i)
|
||||
.partial_threshold_jaccard_dist(self.col(j), threshold)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_relfreq_bray_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i).partial_relfreq_bray_dist(self.col(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col(i).partial_relfreq_bray_dist(
|
||||
self.col(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_relfreq_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pub(crate) fn partial_relfreq_euclidean_dist_matrix(
|
||||
&self,
|
||||
col_sums: &Array1<u64>,
|
||||
) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i).partial_relfreq_euclidean_dist(self.col(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col(i).partial_relfreq_euclidean_dist(
|
||||
self.col(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_hellinger_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pub(crate) fn partial_hellinger_euclidean_dist_matrix(
|
||||
&self,
|
||||
col_sums: &Array1<u64>,
|
||||
) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols(), |i, j| {
|
||||
self.col(i).partial_hellinger_euclidean_dist(self.col(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col(i).partial_hellinger_euclidean_dist(
|
||||
self.col(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn append_column(dir: &Path, value_of: impl Fn(usize) -> u32) -> io::Result<()> {
|
||||
let mut meta = MatrixMeta::load(dir)?;
|
||||
let mut b = PersistentCompactIntVecBuilder::new(meta.n, &col_path(dir, meta.n_cols))?;
|
||||
for slot in 0..meta.n { b.set(slot, value_of(slot)); }
|
||||
for slot in 0..meta.n {
|
||||
b.set(slot, value_of(slot));
|
||||
}
|
||||
b.close()?;
|
||||
meta.n_cols += 1;
|
||||
meta.save(dir)
|
||||
@@ -99,19 +137,19 @@ impl ColumnarCompactIntMatrix {
|
||||
|
||||
// ── PackedCompactIntMatrix ────────────────────────────────────────────────────
|
||||
|
||||
const PCMX_MAGIC: [u8; 4] = *b"PCMX";
|
||||
const PCMX_HEADER: usize = 24; // magic(4) + pad(4) + n_rows(8) + n_cols(8)
|
||||
const PCMX_MAGIC: [u8; 4] = *b"PCMX";
|
||||
const PCMX_HEADER: usize = 24; // magic(4) + pad(4) + n_rows(8) + n_cols(8)
|
||||
|
||||
struct ColInfo {
|
||||
primary_start: usize,
|
||||
data_offset: usize,
|
||||
n_overflow: usize,
|
||||
data_offset: usize,
|
||||
n_overflow: usize,
|
||||
}
|
||||
|
||||
pub struct PackedCompactIntMatrix {
|
||||
mmap: Mmap,
|
||||
n_rows: usize,
|
||||
n_cols: usize,
|
||||
mmap: Mmap,
|
||||
n_rows: usize,
|
||||
n_cols: usize,
|
||||
columns: Vec<ColInfo>,
|
||||
}
|
||||
|
||||
@@ -119,43 +157,65 @@ impl PackedCompactIntMatrix {
|
||||
pub(crate) fn open(path: &Path) -> io::Result<Self> {
|
||||
let mmap = unsafe { Mmap::map(&File::open(path)?)? };
|
||||
if mmap.len() < PCMX_HEADER {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "PCMX file too short"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"PCMX file too short",
|
||||
));
|
||||
}
|
||||
if &mmap[0..4] != &PCMX_MAGIC {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "bad PCMX magic"));
|
||||
}
|
||||
let n_rows = u64::from_le_bytes(mmap[8..16].try_into().unwrap()) as usize;
|
||||
let n_rows = u64::from_le_bytes(mmap[8..16].try_into().unwrap()) as usize;
|
||||
let n_cols = u64::from_le_bytes(mmap[16..24].try_into().unwrap()) as usize;
|
||||
|
||||
let mut columns = Vec::with_capacity(n_cols);
|
||||
for c in 0..n_cols {
|
||||
let off_pos = PCMX_HEADER + c * 8;
|
||||
let col_base = u64::from_le_bytes(mmap[off_pos..off_pos+8].try_into().unwrap()) as usize;
|
||||
let n_ov = u64::from_le_bytes(mmap[col_base+16..col_base+24].try_into().unwrap()) as usize;
|
||||
let n_pciv = u64::from_le_bytes(mmap[col_base+8..col_base+16].try_into().unwrap()) as usize;
|
||||
let off_pos = PCMX_HEADER + c * 8;
|
||||
let col_base =
|
||||
u64::from_le_bytes(mmap[off_pos..off_pos + 8].try_into().unwrap()) as usize;
|
||||
let n_ov =
|
||||
u64::from_le_bytes(mmap[col_base + 16..col_base + 24].try_into().unwrap()) as usize;
|
||||
let n_pciv =
|
||||
u64::from_le_bytes(mmap[col_base + 8..col_base + 16].try_into().unwrap()) as usize;
|
||||
let primary_start = col_base + HEADER_SIZE;
|
||||
let data_offset = primary_start + n_pciv;
|
||||
columns.push(ColInfo { primary_start, data_offset, n_overflow: n_ov });
|
||||
let data_offset = primary_start + n_pciv;
|
||||
columns.push(ColInfo {
|
||||
primary_start,
|
||||
data_offset,
|
||||
n_overflow: n_ov,
|
||||
});
|
||||
}
|
||||
Ok(Self { mmap, n_rows, n_cols, columns })
|
||||
Ok(Self {
|
||||
mmap,
|
||||
n_rows,
|
||||
n_cols,
|
||||
columns,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn col_view(&self, c: usize) -> IntSliceView<'_> {
|
||||
let ci = &self.columns[c];
|
||||
let primary = &self.mmap[ci.primary_start..ci.primary_start + self.n_rows];
|
||||
let overflow_raw = &self.mmap[ci.data_offset..ci.data_offset + ci.n_overflow * OVERFLOW_ENTRY_SIZE];
|
||||
let primary = &self.mmap[ci.primary_start..ci.primary_start + self.n_rows];
|
||||
let overflow_raw =
|
||||
&self.mmap[ci.data_offset..ci.data_offset + ci.n_overflow * OVERFLOW_ENTRY_SIZE];
|
||||
IntSliceView::new(primary, overflow_raw, ci.n_overflow, self.n_rows)
|
||||
}
|
||||
|
||||
pub(crate) fn col_persist(&self, c: usize, path: &Path) -> io::Result<PersistentCompactIntVecBuilder> {
|
||||
pub(crate) fn col_persist(
|
||||
&self,
|
||||
c: usize,
|
||||
path: &Path,
|
||||
) -> io::Result<PersistentCompactIntVecBuilder> {
|
||||
let view = self.col_view(c);
|
||||
let overflow: std::collections::HashMap<usize, u32> = view.overflow_entries().collect();
|
||||
PersistentCompactIntVecBuilder::from_raw_primary(view.primary_bytes(), overflow, path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get(&self, col: usize, slot: usize) -> u32 { self.col_view(col).get(slot) }
|
||||
pub(crate) fn get(&self, col: usize, slot: usize) -> u32 {
|
||||
self.col_view(col).get(slot)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn fill_row(&self, slot: usize, buf: &mut [u32]) {
|
||||
@@ -180,27 +240,55 @@ impl PackedCompactIntMatrix {
|
||||
// via `PersistentCompactIntVec`. No formula is re-derived here.
|
||||
|
||||
pub(crate) fn partial_bray_dist_matrix(&self) -> Array2<u64> {
|
||||
pairwise_matrix(self.n_cols, |i, j| self.col_view(i).partial_bray_dist(self.col_view(j)))
|
||||
pairwise_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i).partial_bray_dist(self.col_view(j))
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_euclidean_dist_matrix(&self) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols, |i, j| self.col_view(i).partial_euclidean_dist(self.col_view(j)))
|
||||
pairwise_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i).partial_euclidean_dist(self.col_view(j))
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_threshold_jaccard_dist_matrix(&self, t: u32) -> (Array2<u64>, Array2<u64>) {
|
||||
pairwise2_matrix(self.n_cols, |i, j| self.col_view(i).partial_threshold_jaccard_dist(self.col_view(j), t))
|
||||
pub(crate) fn partial_threshold_jaccard_dist_matrix(
|
||||
&self,
|
||||
t: u32,
|
||||
) -> (Array2<u64>, Array2<u64>) {
|
||||
pairwise2_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i)
|
||||
.partial_threshold_jaccard_dist(self.col_view(j), t)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_relfreq_bray_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i).partial_relfreq_bray_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col_view(i).partial_relfreq_bray_dist(
|
||||
self.col_view(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_relfreq_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pub(crate) fn partial_relfreq_euclidean_dist_matrix(
|
||||
&self,
|
||||
col_sums: &Array1<u64>,
|
||||
) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i).partial_relfreq_euclidean_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col_view(i).partial_relfreq_euclidean_dist(
|
||||
self.col_view(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
pub(crate) fn partial_hellinger_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
pub(crate) fn partial_hellinger_euclidean_dist_matrix(
|
||||
&self,
|
||||
col_sums: &Array1<u64>,
|
||||
) -> Array2<f64> {
|
||||
pairwise_matrix(self.n_cols, |i, j| {
|
||||
self.col_view(i).partial_hellinger_euclidean_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
|
||||
self.col_view(i).partial_hellinger_euclidean_dist(
|
||||
self.col_view(j),
|
||||
col_sums[i] as f64,
|
||||
col_sums[j] as f64,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -238,7 +326,9 @@ pub fn pack_compact_int_matrix(dir: &Path) -> io::Result<()> {
|
||||
// the columnar files are newer and must be (re-)packed, overwriting the
|
||||
// stale one — never silently discarded as "leftover cleanup".
|
||||
if packed_int_matrix_n_cols(&packed_path).ok() == Some(meta.n_cols) {
|
||||
for c in 0..meta.n_cols { let _ = fs::remove_file(col_path(dir, c)); }
|
||||
for c in 0..meta.n_cols {
|
||||
let _ = fs::remove_file(col_path(dir, c));
|
||||
}
|
||||
let _ = fs::remove_file(dir.join("meta.json"));
|
||||
return Ok(());
|
||||
}
|
||||
@@ -250,32 +340,41 @@ pub fn pack_compact_int_matrix(dir: &Path) -> io::Result<()> {
|
||||
let header_size = (PCMX_HEADER + n_cols * 8) as u64;
|
||||
let mut col_offset = header_size;
|
||||
let mut offsets = Vec::with_capacity(n_cols);
|
||||
for &size in &col_sizes { offsets.push(col_offset); col_offset += size; }
|
||||
for &size in &col_sizes {
|
||||
offsets.push(col_offset);
|
||||
col_offset += size;
|
||||
}
|
||||
let tmp_path = dir.join("matrix.pcmx.tmp");
|
||||
let mut out = BufWriter::new(File::create(&tmp_path)?);
|
||||
out.write_all(&PCMX_MAGIC)?;
|
||||
out.write_all(&[0u8; 4])?;
|
||||
out.write_all(&(meta.n as u64).to_le_bytes())?;
|
||||
out.write_all(&(n_cols as u64).to_le_bytes())?;
|
||||
for &off in &offsets { out.write_all(&off.to_le_bytes())?; }
|
||||
for c in 0..n_cols { io::copy(&mut File::open(col_path(dir, c))?, &mut out)?; }
|
||||
for &off in &offsets {
|
||||
out.write_all(&off.to_le_bytes())?;
|
||||
}
|
||||
for c in 0..n_cols {
|
||||
io::copy(&mut File::open(col_path(dir, c))?, &mut out)?;
|
||||
}
|
||||
out.flush()?;
|
||||
drop(out);
|
||||
fs::rename(&tmp_path, &packed_path)?;
|
||||
for c in 0..n_cols { fs::remove_file(col_path(dir, c))?; }
|
||||
for c in 0..n_cols {
|
||||
fs::remove_file(col_path(dir, c))?;
|
||||
}
|
||||
fs::remove_file(dir.join("meta.json"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── PersistentCompactIntMatrix — public enum ──────────────────────────────────
|
||||
|
||||
pub enum PersistentCompactIntMatrix {
|
||||
pub enum PersistentIntMatrix {
|
||||
Columnar(ColumnarCompactIntMatrix),
|
||||
Packed(PackedCompactIntMatrix),
|
||||
Sparse(PersistentSparseCompactIntMatrix),
|
||||
}
|
||||
|
||||
impl PersistentCompactIntMatrix {
|
||||
impl PersistentIntMatrix {
|
||||
/// Checks (in order): `counts/matrix.pcmx` → Packed; `counts/meta.json`
|
||||
/// → Columnar; `counts/singleton_values.pciv` → Sparse. Mirrors
|
||||
/// `PersistentBitMatrix::open`'s own Packed → Columnar → Sparse
|
||||
@@ -283,17 +382,24 @@ impl PersistentCompactIntMatrix {
|
||||
pub fn open(layer_dir: &Path) -> io::Result<Self> {
|
||||
let counts_dir = layer_dir.join("counts");
|
||||
if counts_dir.join("matrix.pcmx").exists() {
|
||||
return Ok(Self::Packed(PackedCompactIntMatrix::open(&counts_dir.join("matrix.pcmx"))?));
|
||||
return Ok(Self::Packed(PackedCompactIntMatrix::open(
|
||||
&counts_dir.join("matrix.pcmx"),
|
||||
)?));
|
||||
}
|
||||
if MatrixMeta::load(&counts_dir).is_ok() {
|
||||
return Ok(Self::Columnar(ColumnarCompactIntMatrix::open(&counts_dir)?));
|
||||
}
|
||||
if sparse_is_present(&counts_dir) {
|
||||
return Ok(Self::Sparse(PersistentSparseCompactIntMatrix::open(&counts_dir)?));
|
||||
return Ok(Self::Sparse(PersistentSparseCompactIntMatrix::open(
|
||||
&counts_dir,
|
||||
)?));
|
||||
}
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("no count matrix found in {} — run 'obikmer upgrade'", layer_dir.display()),
|
||||
format!(
|
||||
"no count matrix found in {} — run 'obikmer upgrade'",
|
||||
layer_dir.display()
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -303,8 +409,8 @@ impl PersistentCompactIntMatrix {
|
||||
pub fn storage_kind(&self) -> StorageKind {
|
||||
match self {
|
||||
Self::Columnar(_) => StorageKind::Columnar,
|
||||
Self::Packed(_) => StorageKind::Packed,
|
||||
Self::Sparse(_) => StorageKind::Sparse,
|
||||
Self::Packed(_) => StorageKind::Packed,
|
||||
Self::Sparse(_) => StorageKind::Sparse,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,11 +436,19 @@ impl PersistentCompactIntMatrix {
|
||||
|
||||
#[inline]
|
||||
pub fn n(&self) -> usize {
|
||||
match self { Self::Columnar(m) => m.n(), Self::Packed(m) => m.n_rows, Self::Sparse(m) => m.n() }
|
||||
match self {
|
||||
Self::Columnar(m) => m.n(),
|
||||
Self::Packed(m) => m.n_rows,
|
||||
Self::Sparse(m) => m.n(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn n_cols(&self) -> usize {
|
||||
match self { Self::Columnar(m) => m.n_cols(), Self::Packed(m) => m.n_cols, Self::Sparse(m) => m.n_cols() }
|
||||
match self {
|
||||
Self::Columnar(m) => m.n_cols(),
|
||||
Self::Packed(m) => m.n_cols,
|
||||
Self::Sparse(m) => m.n_cols(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -349,30 +463,38 @@ impl PersistentCompactIntMatrix {
|
||||
pub fn col_view(&self, c: usize) -> IntSliceView<'_> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.col(c).view(),
|
||||
Self::Packed(m) => m.col_view(c),
|
||||
Self::Sparse(_) => panic!("col_view() not available on Sparse PersistentCompactIntMatrix"),
|
||||
Self::Packed(m) => m.col_view(c),
|
||||
Self::Sparse(_) => {
|
||||
panic!("col_view() not available on Sparse PersistentCompactIntMatrix")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn col_persist(&self, c: usize, path: &Path) -> io::Result<PersistentCompactIntVecBuilder> {
|
||||
match self {
|
||||
Self::Columnar(m) => PersistentCompactIntVecBuilder::build_from(m.col(c), path),
|
||||
Self::Packed(m) => m.col_persist(c, path),
|
||||
Self::Sparse(_) => Err(io::Error::new(io::ErrorKind::Unsupported,
|
||||
"col_persist not available on Sparse PersistentCompactIntMatrix")),
|
||||
Self::Packed(m) => m.col_persist(c, path),
|
||||
Self::Sparse(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"col_persist not available on Sparse PersistentCompactIntMatrix",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn row(&self, slot: usize) -> Box<[u32]> {
|
||||
match self { Self::Columnar(m) => m.row(slot), Self::Packed(m) => m.row(slot), Self::Sparse(m) => m.row(slot) }
|
||||
match self {
|
||||
Self::Columnar(m) => m.row(slot),
|
||||
Self::Packed(m) => m.row(slot),
|
||||
Self::Sparse(m) => m.row(slot),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn fill_row(&self, slot: usize, buf: &mut [u32]) {
|
||||
match self {
|
||||
Self::Columnar(m) => m.fill_row(slot, buf),
|
||||
Self::Packed(m) => m.fill_row(slot, buf),
|
||||
Self::Sparse(m) => m.fill_row(slot, buf),
|
||||
Self::Packed(m) => m.fill_row(slot, buf),
|
||||
Self::Sparse(m) => m.fill_row(slot, buf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,73 +540,86 @@ impl PersistentCompactIntMatrix {
|
||||
/// `Columnar`/`Packed` reuse the shared sorted-slot batching in
|
||||
/// `views::nonzero_triples`. Boxed, not `impl Iterator`, because the
|
||||
/// match arms are genuinely different concrete types.
|
||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
pub fn nonzero_iter<'a>(
|
||||
&'a self,
|
||||
slots: &'a [usize],
|
||||
) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
match self {
|
||||
Self::Sparse(m) => Box::new(m.nonzero_iter(slots)),
|
||||
Self::Columnar(_) | Self::Packed(_) => {
|
||||
Box::new(nonzero_triples(slots, self.n_cols(), |c| self.col_view(c), false))
|
||||
}
|
||||
Self::Columnar(_) | Self::Packed(_) => Box::new(nonzero_triples(
|
||||
slots,
|
||||
self.n_cols(),
|
||||
|c| self.col_view(c),
|
||||
false,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sum(&self) -> Array1<u64> {
|
||||
match self { Self::Columnar(m) => m.sum(), Self::Packed(m) => m.sum(), Self::Sparse(m) => m.sum() }
|
||||
match self {
|
||||
Self::Columnar(m) => m.sum(),
|
||||
Self::Packed(m) => m.sum(),
|
||||
Self::Sparse(m) => m.sum(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn count_nonzero(&self) -> Array1<u64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.count_nonzero(),
|
||||
Self::Packed(m) => m.count_nonzero(),
|
||||
Self::Sparse(m) => m.count_nonzero(),
|
||||
Self::Packed(m) => m.count_nonzero(),
|
||||
Self::Sparse(m) => m.count_nonzero(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_bray_dist_matrix(&self) -> Array2<u64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_bray_dist_matrix(),
|
||||
Self::Packed(m) => m.partial_bray_dist_matrix(),
|
||||
Self::Sparse(m) => CountPartials::partial_bray(m),
|
||||
Self::Packed(m) => m.partial_bray_dist_matrix(),
|
||||
Self::Sparse(m) => CountPartials::partial_bray(m),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_euclidean_dist_matrix(&self) -> Array2<f64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_euclidean_dist_matrix(),
|
||||
Self::Packed(m) => m.partial_euclidean_dist_matrix(),
|
||||
Self::Sparse(m) => CountPartials::partial_euclidean(m),
|
||||
Self::Packed(m) => m.partial_euclidean_dist_matrix(),
|
||||
Self::Sparse(m) => CountPartials::partial_euclidean(m),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_threshold_jaccard_dist_matrix(&self, threshold: u32) -> (Array2<u64>, Array2<u64>) {
|
||||
pub fn partial_threshold_jaccard_dist_matrix(
|
||||
&self,
|
||||
threshold: u32,
|
||||
) -> (Array2<u64>, Array2<u64>) {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_threshold_jaccard_dist_matrix(threshold),
|
||||
Self::Packed(m) => m.partial_threshold_jaccard_dist_matrix(threshold),
|
||||
Self::Sparse(m) => CountPartials::partial_threshold_jaccard(m, threshold),
|
||||
Self::Packed(m) => m.partial_threshold_jaccard_dist_matrix(threshold),
|
||||
Self::Sparse(m) => CountPartials::partial_threshold_jaccard(m, threshold),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_relfreq_bray_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_relfreq_bray_dist_matrix(col_sums),
|
||||
Self::Packed(m) => m.partial_relfreq_bray_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_relfreq_bray(m, col_sums),
|
||||
Self::Packed(m) => m.partial_relfreq_bray_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_relfreq_bray(m, col_sums),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_relfreq_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_relfreq_euclidean_dist_matrix(col_sums),
|
||||
Self::Packed(m) => m.partial_relfreq_euclidean_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_relfreq_euclidean(m, col_sums),
|
||||
Self::Packed(m) => m.partial_relfreq_euclidean_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_relfreq_euclidean(m, col_sums),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn partial_hellinger_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
|
||||
match self {
|
||||
Self::Columnar(m) => m.partial_hellinger_euclidean_dist_matrix(col_sums),
|
||||
Self::Packed(m) => m.partial_hellinger_euclidean_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_hellinger(m, col_sums),
|
||||
Self::Packed(m) => m.partial_hellinger_euclidean_dist_matrix(col_sums),
|
||||
Self::Sparse(m) => CountPartials::partial_hellinger(m, col_sums),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
@@ -497,40 +632,60 @@ impl PersistentCompactIntMatrix {
|
||||
|
||||
use crate::traits::{ColumnWeights, CountPartials};
|
||||
|
||||
impl ColumnWeights for PersistentCompactIntMatrix {
|
||||
impl ColumnWeights for PersistentIntMatrix {
|
||||
#[inline]
|
||||
fn col_weights(&self) -> Array1<u64> { self.sum() }
|
||||
fn col_weights(&self) -> Array1<u64> {
|
||||
self.sum()
|
||||
}
|
||||
#[inline]
|
||||
fn partial_kmer_counts(&self) -> Array1<u64> { self.count_nonzero() }
|
||||
fn partial_kmer_counts(&self) -> Array1<u64> {
|
||||
self.count_nonzero()
|
||||
}
|
||||
}
|
||||
|
||||
impl CountPartials for PersistentCompactIntMatrix {
|
||||
impl CountPartials for PersistentIntMatrix {
|
||||
#[inline]
|
||||
fn partial_bray(&self) -> Array2<u64> { self.partial_bray_dist_matrix() }
|
||||
fn partial_bray(&self) -> Array2<u64> {
|
||||
self.partial_bray_dist_matrix()
|
||||
}
|
||||
#[inline]
|
||||
fn partial_euclidean(&self) -> Array2<f64> { self.partial_euclidean_dist_matrix() }
|
||||
fn partial_euclidean(&self) -> Array2<f64> {
|
||||
self.partial_euclidean_dist_matrix()
|
||||
}
|
||||
#[inline]
|
||||
fn partial_threshold_jaccard(&self, t: u32) -> (Array2<u64>, Array2<u64>) { self.partial_threshold_jaccard_dist_matrix(t) }
|
||||
fn partial_threshold_jaccard(&self, t: u32) -> (Array2<u64>, Array2<u64>) {
|
||||
self.partial_threshold_jaccard_dist_matrix(t)
|
||||
}
|
||||
#[inline]
|
||||
fn partial_relfreq_bray(&self, g: &Array1<u64>) -> Array2<f64> { self.partial_relfreq_bray_dist_matrix(g) }
|
||||
fn partial_relfreq_bray(&self, g: &Array1<u64>) -> Array2<f64> {
|
||||
self.partial_relfreq_bray_dist_matrix(g)
|
||||
}
|
||||
#[inline]
|
||||
fn partial_relfreq_euclidean(&self, g: &Array1<u64>) -> Array2<f64> { self.partial_relfreq_euclidean_dist_matrix(g) }
|
||||
fn partial_relfreq_euclidean(&self, g: &Array1<u64>) -> Array2<f64> {
|
||||
self.partial_relfreq_euclidean_dist_matrix(g)
|
||||
}
|
||||
#[inline]
|
||||
fn partial_hellinger(&self, g: &Array1<u64>) -> Array2<f64> { self.partial_hellinger_euclidean_dist_matrix(g) }
|
||||
fn partial_hellinger(&self, g: &Array1<u64>) -> Array2<f64> {
|
||||
self.partial_hellinger_euclidean_dist_matrix(g)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Builder ───────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct PersistentCompactIntMatrixBuilder {
|
||||
dir: PathBuf,
|
||||
n: usize,
|
||||
dir: PathBuf,
|
||||
n: usize,
|
||||
n_cols: usize,
|
||||
}
|
||||
|
||||
impl PersistentCompactIntMatrixBuilder {
|
||||
pub fn new(n: usize, dir: &Path) -> io::Result<Self> {
|
||||
fs::create_dir_all(dir)?;
|
||||
Ok(Self { dir: dir.to_path_buf(), n, n_cols: 0 })
|
||||
Ok(Self {
|
||||
dir: dir.to_path_buf(),
|
||||
n,
|
||||
n_cols: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resume appending columns to a Columnar matrix directory already
|
||||
@@ -541,13 +696,21 @@ impl PersistentCompactIntMatrixBuilder {
|
||||
/// on-disk column naming or `MatrixMeta` themselves.
|
||||
pub fn resume(dir: &Path) -> io::Result<Self> {
|
||||
let meta = MatrixMeta::load(dir)?;
|
||||
Ok(Self { dir: dir.to_path_buf(), n: meta.n, n_cols: meta.n_cols })
|
||||
Ok(Self {
|
||||
dir: dir.to_path_buf(),
|
||||
n: meta.n,
|
||||
n_cols: meta.n_cols,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn n(&self) -> usize { self.n }
|
||||
pub fn n(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
#[inline]
|
||||
pub fn n_cols(&self) -> usize { self.n_cols }
|
||||
pub fn n_cols(&self) -> usize {
|
||||
self.n_cols
|
||||
}
|
||||
pub fn add_col(&mut self) -> io::Result<PersistentCompactIntVecBuilder> {
|
||||
let path = col_path(&self.dir, self.n_cols);
|
||||
self.n_cols += 1;
|
||||
@@ -569,14 +732,22 @@ impl PersistentCompactIntMatrixBuilder {
|
||||
}
|
||||
|
||||
pub fn close(self) -> io::Result<()> {
|
||||
MatrixMeta { n: self.n, n_cols: self.n_cols }.save(&self.dir)
|
||||
MatrixMeta {
|
||||
n: self.n,
|
||||
n_cols: self.n_cols,
|
||||
}
|
||||
.save(&self.dir)
|
||||
}
|
||||
}
|
||||
|
||||
// ── MatrixGroupOps ────────────────────────────────────────────────────────────
|
||||
|
||||
impl MatrixGroupOps for PersistentCompactIntMatrix {
|
||||
fn partial_group_presence_count(&self, g: &ColGroup, threshold: u32) -> io::Result<TempCompactIntVec> {
|
||||
impl MatrixGroupOps for PersistentIntMatrix {
|
||||
fn partial_group_presence_count(
|
||||
&self,
|
||||
g: &ColGroup,
|
||||
threshold: u32,
|
||||
) -> io::Result<TempCompactIntVec> {
|
||||
chunked_presence_count(self.n(), &g.indices, |b, c| {
|
||||
b.inc_predicate_fast(self.col_view(c), |v| v >= threshold)
|
||||
})
|
||||
@@ -585,7 +756,9 @@ impl MatrixGroupOps for PersistentCompactIntMatrix {
|
||||
fn partial_group_sum(&self, g: &ColGroup) -> io::Result<TempCompactIntVec> {
|
||||
let n = self.n();
|
||||
let mut result = TempCompactIntVecBuilder::new(n)?;
|
||||
for &c in &g.indices { result.add(self.col_view(c)); }
|
||||
for &c in &g.indices {
|
||||
result.add(self.col_view(c));
|
||||
}
|
||||
result.freeze()
|
||||
}
|
||||
|
||||
@@ -603,7 +776,9 @@ impl MatrixGroupOps for PersistentCompactIntMatrix {
|
||||
let mut result = TempCompactIntVecBuilder::new(n)?;
|
||||
if let Some((&first, rest)) = g.indices.split_first() {
|
||||
result.add(self.col_view(first));
|
||||
for &c in rest { result.min(self.col_view(c)); }
|
||||
for &c in rest {
|
||||
result.min(self.col_view(c));
|
||||
}
|
||||
}
|
||||
result.freeze()
|
||||
}
|
||||
@@ -611,7 +786,9 @@ impl MatrixGroupOps for PersistentCompactIntMatrix {
|
||||
fn partial_group_max(&self, g: &ColGroup) -> io::Result<TempCompactIntVec> {
|
||||
let n = self.n();
|
||||
let mut result = TempCompactIntVecBuilder::new(n)?;
|
||||
for &c in &g.indices { result.max(self.col_view(c)); }
|
||||
for &c in &g.indices {
|
||||
result.max(self.col_view(c));
|
||||
}
|
||||
result.freeze()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
mod bitvec;
|
||||
mod bitmatrix;
|
||||
mod bitvec;
|
||||
mod builder;
|
||||
mod colgroup;
|
||||
mod eliasfano;
|
||||
mod fixedintvec;
|
||||
mod format;
|
||||
mod rankselect;
|
||||
mod intmatrix;
|
||||
mod layer_meta;
|
||||
mod matrix_builder;
|
||||
mod meta;
|
||||
mod mmap_file;
|
||||
mod rankselect;
|
||||
mod reader;
|
||||
mod sparse_intmatrix;
|
||||
mod storage_kind;
|
||||
mod tempbitvec;
|
||||
mod tempintvec;
|
||||
mod views;
|
||||
pub mod traits;
|
||||
mod views;
|
||||
|
||||
pub use bitmatrix::{
|
||||
PersistentBitMatrix, PersistentBitMatrixBuilder, PersistentSparseBitMatrix,
|
||||
PersistentSparseBitMatrixBuilder, pack_bit_matrix, pack_sparse_bit_matrix,
|
||||
};
|
||||
pub use bitvec::{BitIter, PersistentBitVec, PersistentBitVecBuilder};
|
||||
pub use fixedintvec::{PersistentFixedIntVec, PersistentFixedIntVecBuilder, bit_width_for_range};
|
||||
pub use rankselect::{PersistentRankSelectBitVec, PersistentRankSelectBitVecBuilder};
|
||||
pub use eliasfano::{EliasFano, EliasFanoBuilder};
|
||||
pub use bitmatrix::{PersistentBitMatrix, PersistentBitMatrixBuilder, PersistentSparseBitMatrix, PersistentSparseBitMatrixBuilder, pack_bit_matrix, pack_sparse_bit_matrix};
|
||||
pub use builder::PersistentCompactIntVecBuilder;
|
||||
pub use colgroup::{ColGroup, FilterMask, MatrixGroupOps, eval_filter_mask};
|
||||
pub use intmatrix::{PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder, pack_compact_int_matrix};
|
||||
pub use eliasfano::{EliasFano, EliasFanoBuilder};
|
||||
pub use fixedintvec::{PersistentFixedIntVec, PersistentFixedIntVecBuilder, bit_width_for_range};
|
||||
pub use intmatrix::{
|
||||
PersistentCompactIntMatrixBuilder, PersistentIntMatrix, pack_compact_int_matrix,
|
||||
};
|
||||
|
||||
pub use layer_meta::LayerMeta;
|
||||
pub use matrix_builder::{ColBuilder, MatrixBuilder};
|
||||
pub use reader::{PersistentCompactIntVec, Iter as CompactIntVecIter};
|
||||
pub use sparse_intmatrix::{PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder, pack_sparse_compact_int_matrix};
|
||||
pub use rankselect::{PersistentRankSelectBitVec, PersistentRankSelectBitVecBuilder};
|
||||
pub use reader::{Iter as CompactIntVecIter, PersistentCompactIntVec};
|
||||
pub use sparse_intmatrix::{
|
||||
PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder,
|
||||
pack_sparse_compact_int_matrix,
|
||||
};
|
||||
pub use storage_kind::StorageKind;
|
||||
pub use tempbitvec::{TempBitVec, TempBitVecBuilder};
|
||||
pub use tempintvec::{TempCompactIntVec, TempCompactIntVecBuilder};
|
||||
pub use traits::{BinaryMatrix, BitPartials, ColumnWeights, CountPartials};
|
||||
pub use views::{BitSliceView, BitSliceIter, IntSliceView, IntSliceViewIter};
|
||||
pub use views::{BitSliceIter, BitSliceView, IntSliceView, IntSliceViewIter};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/mod.rs"]
|
||||
|
||||
@@ -35,11 +35,17 @@ use crate::builder::PersistentCompactIntVecBuilder;
|
||||
use crate::eliasfano::{EliasFano, EliasFanoBuilder};
|
||||
use crate::reader::PersistentCompactIntVec;
|
||||
use crate::traits::{BitPartials, ColumnWeights, CountPartials};
|
||||
use crate::{PersistentCompactIntMatrix, PersistentSparseBitMatrix, PersistentSparseBitMatrixBuilder};
|
||||
use crate::{PersistentIntMatrix, PersistentSparseBitMatrix, PersistentSparseBitMatrixBuilder};
|
||||
|
||||
fn singleton_values_path(dir: &Path) -> PathBuf { dir.join("singleton_values.pciv") }
|
||||
fn multi_values_path(dir: &Path) -> PathBuf { dir.join("multi_values.pciv") }
|
||||
fn multi_offsets_base(dir: &Path) -> PathBuf { dir.join("multi_offsets") }
|
||||
fn singleton_values_path(dir: &Path) -> PathBuf {
|
||||
dir.join("singleton_values.pciv")
|
||||
}
|
||||
fn multi_values_path(dir: &Path) -> PathBuf {
|
||||
dir.join("multi_values.pciv")
|
||||
}
|
||||
fn multi_offsets_base(dir: &Path) -> PathBuf {
|
||||
dir.join("multi_offsets")
|
||||
}
|
||||
|
||||
/// Lightweight disk probe: true iff a sparse count matrix has already been
|
||||
/// written at `dir` — the marker [`PersistentCompactIntMatrix::open`]/
|
||||
@@ -73,9 +79,13 @@ impl PersistentSparseCompactIntMatrix {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn n(&self) -> usize { self.support.n() }
|
||||
pub fn n(&self) -> usize {
|
||||
self.support.n()
|
||||
}
|
||||
#[inline]
|
||||
pub fn n_cols(&self) -> usize { self.support.n_cols() }
|
||||
pub fn n_cols(&self) -> usize {
|
||||
self.support.n_cols()
|
||||
}
|
||||
|
||||
/// Calls `f(col, value)` once per non-zero column at `slot`, in
|
||||
/// ascending column order — the shared decode branch behind every
|
||||
@@ -104,7 +114,11 @@ impl PersistentSparseCompactIntMatrix {
|
||||
/// absent).
|
||||
pub fn get(&self, c: usize, slot: usize) -> u32 {
|
||||
let mut found = 0u32;
|
||||
self.for_each_cell_in_row(slot, |g, v| if g == c { found = v; });
|
||||
self.for_each_cell_in_row(slot, |g, v| {
|
||||
if g == c {
|
||||
found = v;
|
||||
}
|
||||
});
|
||||
found
|
||||
}
|
||||
|
||||
@@ -138,27 +152,32 @@ impl PersistentSparseCompactIntMatrix {
|
||||
/// Yields every `(idx into slots, col, value)` triple, row by row, in
|
||||
/// `slots` order — mirrors
|
||||
/// [`PersistentSparseBitMatrix::nonzero_iter`].
|
||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator<Item = (usize, usize, u32)> + 'a {
|
||||
pub fn nonzero_iter<'a>(
|
||||
&'a self,
|
||||
slots: &'a [usize],
|
||||
) -> impl Iterator<Item = (usize, usize, u32)> + 'a {
|
||||
let mut next_slot = 0usize;
|
||||
let mut cur_idx = 0usize;
|
||||
let mut buf: Vec<(usize, u32)> = Vec::new();
|
||||
let mut buf_pos = 0usize;
|
||||
|
||||
std::iter::from_fn(move || loop {
|
||||
if buf_pos < buf.len() {
|
||||
let (g, v) = buf[buf_pos];
|
||||
buf_pos += 1;
|
||||
return Some((cur_idx, g, v));
|
||||
std::iter::from_fn(move || {
|
||||
loop {
|
||||
if buf_pos < buf.len() {
|
||||
let (g, v) = buf[buf_pos];
|
||||
buf_pos += 1;
|
||||
return Some((cur_idx, g, v));
|
||||
}
|
||||
if next_slot >= slots.len() {
|
||||
return None;
|
||||
}
|
||||
cur_idx = next_slot;
|
||||
let slot = slots[next_slot];
|
||||
next_slot += 1;
|
||||
buf.clear();
|
||||
self.for_each_cell_in_row(slot, |g, v| buf.push((g, v)));
|
||||
buf_pos = 0;
|
||||
}
|
||||
if next_slot >= slots.len() {
|
||||
return None;
|
||||
}
|
||||
cur_idx = next_slot;
|
||||
let slot = slots[next_slot];
|
||||
next_slot += 1;
|
||||
buf.clear();
|
||||
self.for_each_cell_in_row(slot, |g, v| buf.push((g, v)));
|
||||
buf_pos = 0;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -203,7 +222,11 @@ impl PersistentSparseCompactIntMatrix {
|
||||
fn count_geq(&self, threshold: u32) -> Array1<u64> {
|
||||
let mut counts = vec![0u64; self.n_cols()];
|
||||
for slot in 0..self.n() {
|
||||
self.for_each_cell_in_row(slot, |g, v| if v >= threshold { counts[g] += 1; });
|
||||
self.for_each_cell_in_row(slot, |g, v| {
|
||||
if v >= threshold {
|
||||
counts[g] += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
Array1::from(counts)
|
||||
}
|
||||
@@ -261,9 +284,13 @@ impl PersistentSparseCompactIntMatrix {
|
||||
|
||||
impl ColumnWeights for PersistentSparseCompactIntMatrix {
|
||||
#[inline]
|
||||
fn col_weights(&self) -> Array1<u64> { self.sum() }
|
||||
fn col_weights(&self) -> Array1<u64> {
|
||||
self.sum()
|
||||
}
|
||||
#[inline]
|
||||
fn partial_kmer_counts(&self) -> Array1<u64> { self.count_nonzero() }
|
||||
fn partial_kmer_counts(&self) -> Array1<u64> {
|
||||
self.count_nonzero()
|
||||
}
|
||||
}
|
||||
|
||||
impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
@@ -278,7 +305,9 @@ impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
fn partial_bray(&self) -> Array2<u64> {
|
||||
let mut m = self.row_major_pairwise(|_, a, _, b| (a as u64).min(b as u64));
|
||||
let sum = self.sum();
|
||||
for i in 0..self.n_cols() { m[[i, i]] = sum[i]; }
|
||||
for i in 0..self.n_cols() {
|
||||
m[[i, i]] = sum[i];
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
@@ -329,7 +358,8 @@ impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
// left to patch.
|
||||
return BitPartials::partial_jaccard(&self.support);
|
||||
}
|
||||
let mut inter = self.row_major_pairwise(|_, a, _, b| (a >= threshold && b >= threshold) as u64);
|
||||
let mut inter =
|
||||
self.row_major_pairwise(|_, a, _, b| (a >= threshold && b >= threshold) as u64);
|
||||
let mut union = Array2::<u64>::zeros((n, n));
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
@@ -363,7 +393,11 @@ impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
});
|
||||
let sum = self.sum();
|
||||
for i in 0..self.n_cols() {
|
||||
m[[i, i]] = if global[i] > 0 { sum[i] as f64 / global[i] as f64 } else { 0.0 };
|
||||
m[[i, i]] = if global[i] > 0 {
|
||||
sum[i] as f64 / global[i] as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
}
|
||||
m
|
||||
}
|
||||
@@ -382,9 +416,21 @@ impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
if i != j {
|
||||
let sa = global[i] as f64;
|
||||
let sb = global[j] as f64;
|
||||
let sq_a = if sa > 0.0 { sq[i] as f64 / (sa * sa) } else { 0.0 };
|
||||
let sq_b = if sb > 0.0 { sq[j] as f64 / (sb * sb) } else { 0.0 };
|
||||
let cross = if sa > 0.0 && sb > 0.0 { dot[[i, j]] / (sa * sb) } else { 0.0 };
|
||||
let sq_a = if sa > 0.0 {
|
||||
sq[i] as f64 / (sa * sa)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let sq_b = if sb > 0.0 {
|
||||
sq[j] as f64 / (sb * sb)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let cross = if sa > 0.0 && sb > 0.0 {
|
||||
dot[[i, j]] / (sa * sb)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
m[[i, j]] = sq_a + sq_b - 2.0 * cross;
|
||||
}
|
||||
}
|
||||
@@ -409,7 +455,11 @@ impl CountPartials for PersistentSparseCompactIntMatrix {
|
||||
let sb = global[j] as f64;
|
||||
let pa_sum = if sa > 0.0 { sum[i] as f64 / sa } else { 0.0 };
|
||||
let pb_sum = if sb > 0.0 { sum[j] as f64 / sb } else { 0.0 };
|
||||
let cross = if sa > 0.0 && sb > 0.0 { sqrt_dot[[i, j]] / (sa * sb).sqrt() } else { 0.0 };
|
||||
let cross = if sa > 0.0 && sb > 0.0 {
|
||||
sqrt_dot[[i, j]] / (sa * sb).sqrt()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
m[[i, j]] = pa_sum + pb_sum - 2.0 * cross;
|
||||
}
|
||||
}
|
||||
@@ -461,7 +511,6 @@ impl PersistentSparseCompactIntMatrixBuilder {
|
||||
self.support.push_row(cols);
|
||||
}
|
||||
|
||||
|
||||
/// Builds a sparse matrix from an already-built dense
|
||||
/// [`PersistentCompactIntMatrix`] (`Columnar` or `Packed`) — same
|
||||
/// batched-transpose shape as
|
||||
@@ -475,7 +524,7 @@ impl PersistentSparseCompactIntMatrixBuilder {
|
||||
/// pairs are appended to its own buffer in strictly ascending column
|
||||
/// order "for free", matching `push_row`'s ascending-`cols` contract
|
||||
/// without an explicit sort.
|
||||
pub fn build_from_dense(dense: &PersistentCompactIntMatrix, dir: &Path) -> io::Result<Self> {
|
||||
pub fn build_from_dense(dense: &PersistentIntMatrix, dir: &Path) -> io::Result<Self> {
|
||||
let n = dense.n();
|
||||
let n_cols = dense.n_cols();
|
||||
let mut builder = Self::new(n, n_cols, dir)?;
|
||||
@@ -500,23 +549,34 @@ impl PersistentSparseCompactIntMatrixBuilder {
|
||||
}
|
||||
|
||||
pub fn close(self) -> io::Result<()> {
|
||||
let Self { support, dir, singleton_values, multi_values, multi_row_offsets } = self;
|
||||
let Self {
|
||||
support,
|
||||
dir,
|
||||
singleton_values,
|
||||
multi_values,
|
||||
multi_row_offsets,
|
||||
} = self;
|
||||
support.close()?;
|
||||
|
||||
let mut sv = PersistentCompactIntVecBuilder::new(singleton_values.len(), &singleton_values_path(&dir))?;
|
||||
let mut sv = PersistentCompactIntVecBuilder::new(
|
||||
singleton_values.len(),
|
||||
&singleton_values_path(&dir),
|
||||
)?;
|
||||
for (i, &v) in singleton_values.iter().enumerate() {
|
||||
sv.set(i, v);
|
||||
}
|
||||
sv.close()?;
|
||||
|
||||
let mut mv = PersistentCompactIntVecBuilder::new(multi_values.len(), &multi_values_path(&dir))?;
|
||||
let mut mv =
|
||||
PersistentCompactIntVecBuilder::new(multi_values.len(), &multi_values_path(&dir))?;
|
||||
for (i, &v) in multi_values.iter().enumerate() {
|
||||
mv.set(i, v);
|
||||
}
|
||||
mv.close()?;
|
||||
|
||||
let universe = multi_values.len() as u64 + 1;
|
||||
let mut mo = EliasFanoBuilder::new(multi_row_offsets.len(), universe, &multi_offsets_base(&dir))?;
|
||||
let mut mo =
|
||||
EliasFanoBuilder::new(multi_row_offsets.len(), universe, &multi_offsets_base(&dir))?;
|
||||
for &o in &multi_row_offsets {
|
||||
mo.push(o);
|
||||
}
|
||||
@@ -544,7 +604,7 @@ impl PersistentSparseCompactIntMatrixBuilder {
|
||||
/// half-written one). Idempotent — a no-op if `singleton_values.pciv`
|
||||
/// already exists.
|
||||
pub fn pack_sparse_compact_int_matrix(dir: &Path) -> io::Result<()> {
|
||||
use crate::intmatrix::{col_path, ColumnarCompactIntMatrix, PackedCompactIntMatrix};
|
||||
use crate::intmatrix::{ColumnarCompactIntMatrix, PackedCompactIntMatrix, col_path};
|
||||
|
||||
if is_present(dir) {
|
||||
return Ok(());
|
||||
@@ -552,12 +612,12 @@ pub fn pack_sparse_compact_int_matrix(dir: &Path) -> io::Result<()> {
|
||||
|
||||
let packed_path = dir.join("matrix.pcmx");
|
||||
if packed_path.exists() {
|
||||
let dense = PersistentCompactIntMatrix::Packed(PackedCompactIntMatrix::open(&packed_path)?);
|
||||
let dense = PersistentIntMatrix::Packed(PackedCompactIntMatrix::open(&packed_path)?);
|
||||
PersistentSparseCompactIntMatrixBuilder::build_from_dense(&dense, dir)?.close()?;
|
||||
drop(dense);
|
||||
fs::remove_file(&packed_path)?;
|
||||
} else {
|
||||
let dense = PersistentCompactIntMatrix::Columnar(ColumnarCompactIntMatrix::open(dir)?);
|
||||
let dense = PersistentIntMatrix::Columnar(ColumnarCompactIntMatrix::open(dir)?);
|
||||
let n_cols = dense.n_cols();
|
||||
PersistentSparseCompactIntMatrixBuilder::build_from_dense(&dense, dir)?.close()?;
|
||||
drop(dense);
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::{
|
||||
ColGroup, MatrixGroupOps,
|
||||
PersistentBitMatrix, PersistentBitMatrixBuilder,
|
||||
PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder,
|
||||
ColGroup, MatrixGroupOps, PersistentBitMatrix, PersistentBitMatrixBuilder,
|
||||
PersistentIntMatrix, PersistentCompactIntMatrixBuilder,
|
||||
};
|
||||
use crate::{PersistentBitVecBuilder, PersistentCompactIntVec, PersistentCompactIntVecBuilder};
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_int_matrix(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentCompactIntMatrix) {
|
||||
fn make_int_matrix(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentIntMatrix) {
|
||||
let n = cols.first().map_or(0, |c| c.len());
|
||||
let dir = tempdir().unwrap();
|
||||
let mut b = PersistentCompactIntMatrixBuilder::new(n, &dir.path().join("counts")).unwrap();
|
||||
for &col in cols {
|
||||
let mut cb = b.add_col().unwrap();
|
||||
for (slot, &v) in col.iter().enumerate() { cb.set(slot, v); }
|
||||
for (slot, &v) in col.iter().enumerate() {
|
||||
cb.set(slot, v);
|
||||
}
|
||||
cb.close().unwrap();
|
||||
}
|
||||
b.close().unwrap();
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
(dir, m)
|
||||
}
|
||||
|
||||
@@ -30,7 +31,9 @@ fn make_bit_matrix(cols: &[&[bool]]) -> (tempfile::TempDir, PersistentBitMatrix)
|
||||
let mut b = PersistentBitMatrixBuilder::new(n, &presence).unwrap();
|
||||
for &col in cols {
|
||||
let mut cb = b.add_col().unwrap();
|
||||
for (slot, &v) in col.iter().enumerate() { cb.set(slot, v); }
|
||||
for (slot, &v) in col.iter().enumerate() {
|
||||
cb.set(slot, v);
|
||||
}
|
||||
cb.close().unwrap();
|
||||
}
|
||||
b.close().unwrap();
|
||||
@@ -115,9 +118,13 @@ fn mask_with_zeros_selected_slots() {
|
||||
// count vec [10, 20, 30, 40], mask [T, F, T, F] → [10, 0, 30, 0]
|
||||
let dir = tempdir().unwrap();
|
||||
let mut v = PersistentCompactIntVecBuilder::new(4, &dir.path().join("v.pciv")).unwrap();
|
||||
v.set(0, 10); v.set(1, 20); v.set(2, 30); v.set(3, 40);
|
||||
v.set(0, 10);
|
||||
v.set(1, 20);
|
||||
v.set(2, 30);
|
||||
v.set(3, 40);
|
||||
let mut mask = PersistentBitVecBuilder::new(4, &dir.path().join("m.pbiv")).unwrap();
|
||||
mask.set(0, true); mask.set(2, true);
|
||||
mask.set(0, true);
|
||||
mask.set(2, true);
|
||||
v.mask_with(mask.view());
|
||||
v.close().unwrap();
|
||||
let r = PersistentCompactIntVec::open(&dir.path().join("v.pciv")).unwrap();
|
||||
@@ -132,9 +139,12 @@ fn mask_with_overflow_slot_zeroed() {
|
||||
// overflow slot (value 500) masked out → removed from overflow, primary=0
|
||||
let dir = tempdir().unwrap();
|
||||
let mut v = PersistentCompactIntVecBuilder::new(3, &dir.path().join("v.pciv")).unwrap();
|
||||
v.set(0, 10); v.set(1, 500); v.set(2, 5);
|
||||
v.set(0, 10);
|
||||
v.set(1, 500);
|
||||
v.set(2, 5);
|
||||
let mut mask = PersistentBitVecBuilder::new(3, &dir.path().join("m.pbiv")).unwrap();
|
||||
mask.set(0, true); mask.set(2, true); // slot 1 masked out
|
||||
mask.set(0, true);
|
||||
mask.set(2, true); // slot 1 masked out
|
||||
v.mask_with(mask.view());
|
||||
v.close().unwrap();
|
||||
let r = PersistentCompactIntVec::open(&dir.path().join("v.pciv")).unwrap();
|
||||
@@ -142,14 +152,20 @@ fn mask_with_overflow_slot_zeroed() {
|
||||
assert_eq!(r.get(1), 0);
|
||||
assert_eq!(r.get(2), 5);
|
||||
let ov: Vec<_> = r.view().overflow_entries().collect();
|
||||
assert!(ov.is_empty(), "overflow entry for masked-out slot should be gone");
|
||||
assert!(
|
||||
ov.is_empty(),
|
||||
"overflow entry for masked-out slot should be gone"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_with_all_ones_is_noop() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut v = PersistentCompactIntVecBuilder::new(4, &dir.path().join("v.pciv")).unwrap();
|
||||
v.set(0, 300); v.set(1, 1); v.set(2, 0); v.set(3, 42);
|
||||
v.set(0, 300);
|
||||
v.set(1, 1);
|
||||
v.set(2, 0);
|
||||
v.set(3, 42);
|
||||
let mask = PersistentBitVecBuilder::new_ones(4, &dir.path().join("m.pbiv")).unwrap();
|
||||
v.mask_with(mask.view());
|
||||
v.close().unwrap();
|
||||
@@ -167,9 +183,9 @@ fn bit_partial_group_presence_count() {
|
||||
// col0=[T,F,T,F], col1=[T,T,F,F], col2=[F,T,T,F]
|
||||
// group {0,1,2}: counts = [2, 2, 2, 0]
|
||||
let (_d, m) = make_bit_matrix(&[
|
||||
&[true, false, true, false],
|
||||
&[true, true, false, false],
|
||||
&[false,true, true, false],
|
||||
&[true, false, true, false],
|
||||
&[true, true, false, false],
|
||||
&[false, true, true, false],
|
||||
]);
|
||||
let g = ColGroup::new("g", vec![0, 1, 2]);
|
||||
let result = m.partial_group_presence_count(&g, 1).unwrap();
|
||||
@@ -184,10 +200,7 @@ fn bit_partial_group_presence_count() {
|
||||
#[test]
|
||||
fn bit_partial_group_any() {
|
||||
// col0=[T,F,F], col1=[F,F,T], group {0,1}: any = [T, F, T]
|
||||
let (_d, m) = make_bit_matrix(&[
|
||||
&[true, false, false],
|
||||
&[false, false, true],
|
||||
]);
|
||||
let (_d, m) = make_bit_matrix(&[&[true, false, false], &[false, false, true]]);
|
||||
let g = ColGroup::new("g", vec![0, 1]);
|
||||
let result = m.partial_group_any(&g, 1).unwrap();
|
||||
assert_eq!(result.get(0), true);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::{pack_compact_int_matrix, pack_sparse_compact_int_matrix, PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder, PersistentCompactIntVec, PersistentCompactIntVecBuilder, StorageKind};
|
||||
use crate::traits::CountPartials;
|
||||
use crate::{
|
||||
PersistentCompactIntMatrixBuilder, PersistentCompactIntVec, PersistentCompactIntVecBuilder,
|
||||
PersistentIntMatrix, StorageKind, pack_compact_int_matrix, pack_sparse_compact_int_matrix,
|
||||
};
|
||||
|
||||
fn make_matrix(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentCompactIntMatrix) {
|
||||
fn make_matrix(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentIntMatrix) {
|
||||
let n = cols.first().map_or(0, |c| c.len());
|
||||
let dir = tempdir().unwrap();
|
||||
let mut b = PersistentCompactIntMatrixBuilder::new(n, &dir.path().join("counts")).unwrap();
|
||||
@@ -15,7 +18,7 @@ fn make_matrix(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentCompactIntMatri
|
||||
cb.close().unwrap();
|
||||
}
|
||||
b.close().unwrap();
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
(dir, m)
|
||||
}
|
||||
|
||||
@@ -31,7 +34,7 @@ fn single_col_roundtrip() {
|
||||
col.close().unwrap();
|
||||
b.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 1);
|
||||
assert_eq!(m.n(), 4);
|
||||
assert_eq!(&*m.row(0), &[10u32]);
|
||||
@@ -45,14 +48,18 @@ fn two_cols_roundtrip() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut b = PersistentCompactIntMatrixBuilder::new(3, &dir.path().join("counts")).unwrap();
|
||||
let mut col0 = b.add_col().unwrap();
|
||||
col0.set(0, 1); col0.set(1, 2); col0.set(2, 3);
|
||||
col0.set(0, 1);
|
||||
col0.set(1, 2);
|
||||
col0.set(2, 3);
|
||||
col0.close().unwrap();
|
||||
let mut col1 = b.add_col().unwrap();
|
||||
col1.set(0, 10); col1.set(1, 20); col1.set(2, 30);
|
||||
col1.set(0, 10);
|
||||
col1.set(1, 20);
|
||||
col1.set(2, 30);
|
||||
col1.close().unwrap();
|
||||
b.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 2);
|
||||
assert_eq!(&*m.row(0), &[1u32, 10]);
|
||||
assert_eq!(&*m.row(1), &[2u32, 20]);
|
||||
@@ -66,7 +73,9 @@ fn resume_continues_n_cols_and_appends_columns() {
|
||||
|
||||
let mut b = PersistentCompactIntMatrixBuilder::new(3, &counts).unwrap();
|
||||
let mut col0 = b.add_col().unwrap();
|
||||
col0.set(0, 1); col0.set(1, 2); col0.set(2, 3);
|
||||
col0.set(0, 1);
|
||||
col0.set(1, 2);
|
||||
col0.set(2, 3);
|
||||
col0.close().unwrap();
|
||||
b.close().unwrap();
|
||||
|
||||
@@ -74,11 +83,13 @@ fn resume_continues_n_cols_and_appends_columns() {
|
||||
assert_eq!(resumed.n(), 3);
|
||||
assert_eq!(resumed.n_cols(), 1);
|
||||
let mut col1 = resumed.add_col().unwrap();
|
||||
col1.set(0, 10); col1.set(1, 20); col1.set(2, 30);
|
||||
col1.set(0, 10);
|
||||
col1.set(1, 20);
|
||||
col1.set(2, 30);
|
||||
col1.close().unwrap();
|
||||
resumed.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 2);
|
||||
assert_eq!(&*m.row(0), &[1u32, 10]);
|
||||
assert_eq!(&*m.row(1), &[2u32, 20]);
|
||||
@@ -90,17 +101,21 @@ fn resume_twice_keeps_appending() {
|
||||
let dir = tempdir().unwrap();
|
||||
let counts = dir.path().join("counts");
|
||||
|
||||
PersistentCompactIntMatrixBuilder::new(2, &counts).unwrap().close().unwrap();
|
||||
PersistentCompactIntMatrixBuilder::new(2, &counts)
|
||||
.unwrap()
|
||||
.close()
|
||||
.unwrap();
|
||||
|
||||
for v in [1u32, 2, 3] {
|
||||
let mut b = PersistentCompactIntMatrixBuilder::resume(&counts).unwrap();
|
||||
let mut col = b.add_col().unwrap();
|
||||
col.set(0, v); col.set(1, v * 10);
|
||||
col.set(0, v);
|
||||
col.set(1, v * 10);
|
||||
col.close().unwrap();
|
||||
b.close().unwrap();
|
||||
}
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 3);
|
||||
assert_eq!(&*m.row(0), &[1u32, 2, 3]);
|
||||
assert_eq!(&*m.row(1), &[10u32, 20, 30]);
|
||||
@@ -111,11 +126,12 @@ fn col_accessor() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut b = PersistentCompactIntMatrixBuilder::new(2, &dir.path().join("counts")).unwrap();
|
||||
let mut col0 = b.add_col().unwrap();
|
||||
col0.set(0, 5); col0.set(1, 7);
|
||||
col0.set(0, 5);
|
||||
col0.set(1, 7);
|
||||
col0.close().unwrap();
|
||||
b.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.col(0).get(0), 5);
|
||||
assert_eq!(m.col(0).get(1), 7);
|
||||
}
|
||||
@@ -126,7 +142,7 @@ fn zero_cols_roundtrip() {
|
||||
let b = PersistentCompactIntMatrixBuilder::new(10, &dir.path().join("counts")).unwrap();
|
||||
b.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 0);
|
||||
assert_eq!(m.n(), 10);
|
||||
}
|
||||
@@ -139,7 +155,9 @@ fn bray_dist_matrix_symmetry_and_diagonal() {
|
||||
let (_d, m) = make_matrix(&[&[1, 0, 1], &[1, 1, 0], &[0, 1, 1]]);
|
||||
let dm = m.bray_dist_matrix();
|
||||
let n = m.n_cols();
|
||||
for i in 0..n { assert_eq!(dm[[i, i]], 0.0, "diagonal"); }
|
||||
for i in 0..n {
|
||||
assert_eq!(dm[[i, i]], 0.0, "diagonal");
|
||||
}
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
assert!((dm[[i, j]] - dm[[j, i]]).abs() < 1e-12, "symmetry");
|
||||
@@ -174,7 +192,7 @@ fn jaccard_dist_matrix_values_match_pairwise() {
|
||||
#[test]
|
||||
fn partial_bray_dist_matrix_consistent() {
|
||||
let (_d, m) = make_matrix(&[&[1, 0, 1], &[1, 1, 0], &[0, 1, 1]]);
|
||||
let sum_min = m.partial_bray_dist_matrix();
|
||||
let sum_min = m.partial_bray_dist_matrix();
|
||||
let col_sums = m.sum();
|
||||
let n = m.n_cols();
|
||||
|
||||
@@ -189,7 +207,11 @@ fn partial_bray_dist_matrix_consistent() {
|
||||
for i in 0..n {
|
||||
for j in i + 1..n {
|
||||
let denom = col_sums[i] + col_sums[j];
|
||||
let dist = if denom == 0 { 0.0 } else { 1.0 - 2.0 * sum_min[[i, j]] as f64 / denom as f64 };
|
||||
let dist = if denom == 0 {
|
||||
0.0
|
||||
} else {
|
||||
1.0 - 2.0 * sum_min[[i, j]] as f64 / denom as f64
|
||||
};
|
||||
let expected = m.col(i).bray_dist(m.col(j));
|
||||
assert!((dist - expected).abs() < 1e-12, "[{i},{j}]");
|
||||
}
|
||||
@@ -249,13 +271,18 @@ fn partial_relfreq_bray_matches_full() {
|
||||
let (_d, m) = make_matrix(&[&[1, 0, 2], &[0, 1, 1], &[1, 1, 0]]);
|
||||
let col_sums = m.sum();
|
||||
let partial = m.partial_relfreq_bray_dist_matrix(&col_sums);
|
||||
let full = m.relfreq_bray_dist_matrix();
|
||||
let full = m.relfreq_bray_dist_matrix();
|
||||
let n = m.n_cols();
|
||||
// partial[i,j] = sum_min_relfreq; full[i,j] = 1 - sum_min_relfreq (off-diagonal only)
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
if i == j { continue; }
|
||||
assert!((partial[[i, j]] - (1.0 - full[[i, j]])).abs() < 1e-12, "[{i},{j}]");
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
assert!(
|
||||
(partial[[i, j]] - (1.0 - full[[i, j]])).abs() < 1e-12,
|
||||
"[{i},{j}]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,12 +292,15 @@ fn partial_relfreq_euclidean_matches_full() {
|
||||
let (_d, m) = make_matrix(&[&[3, 0], &[0, 4], &[1, 1]]);
|
||||
let col_sums = m.sum();
|
||||
let partial = m.partial_relfreq_euclidean_dist_matrix(&col_sums);
|
||||
let full = m.relfreq_euclidean_dist_matrix();
|
||||
let full = m.relfreq_euclidean_dist_matrix();
|
||||
let n = m.n_cols();
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
// partial = squared euclidean; full = sqrt(partial)
|
||||
assert!((partial[[i, j]].sqrt() - full[[i, j]]).abs() < 1e-12, "[{i},{j}]");
|
||||
assert!(
|
||||
(partial[[i, j]].sqrt() - full[[i, j]]).abs() < 1e-12,
|
||||
"[{i},{j}]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,12 +310,15 @@ fn partial_hellinger_matches_full() {
|
||||
let (_d, m) = make_matrix(&[&[3, 0], &[0, 4], &[1, 1]]);
|
||||
let col_sums = m.sum();
|
||||
let partial = m.partial_hellinger_euclidean_dist_matrix(&col_sums);
|
||||
let full = m.hellinger_dist_matrix();
|
||||
let full = m.hellinger_dist_matrix();
|
||||
let n = m.n_cols();
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
// partial / sqrt(2) gives the Hellinger distance
|
||||
assert!((partial[[i, j]].sqrt() / std::f64::consts::SQRT_2 - full[[i, j]]).abs() < 1e-12, "[{i},{j}]");
|
||||
assert!(
|
||||
(partial[[i, j]].sqrt() / std::f64::consts::SQRT_2 - full[[i, j]]).abs() < 1e-12,
|
||||
"[{i},{j}]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +328,7 @@ fn col_view_packed_values() {
|
||||
// Build Columnar with overflow values (≥ 255), pack, reopen as Packed, exercise col_view().
|
||||
let (dir, _col) = make_matrix(&[&[10, 300, 500], &[200, 50, 1000]]);
|
||||
pack_compact_int_matrix(&dir.path().join("counts")).unwrap();
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
|
||||
// col 0: [10, 300, 500] — two overflow slots
|
||||
let v0 = m.col_view(0);
|
||||
@@ -326,10 +359,10 @@ fn col_view_packed_matches_columnar() {
|
||||
// Re-build in a separate dir so we can pack without touching m_col's files.
|
||||
let (dir_pack, _) = make_matrix(data);
|
||||
pack_compact_int_matrix(&dir_pack.path().join("counts")).unwrap();
|
||||
let m_pack = PersistentCompactIntMatrix::open(dir_pack.path()).unwrap();
|
||||
let m_pack = PersistentIntMatrix::open(dir_pack.path()).unwrap();
|
||||
|
||||
for c in 0..data.len() {
|
||||
let col_ref = m_col.col(c);
|
||||
let col_ref = m_col.col(c);
|
||||
let col_view = m_pack.col_view(c);
|
||||
assert_eq!(col_view.len(), col_ref.len());
|
||||
for s in 0..col_ref.len() {
|
||||
@@ -337,7 +370,7 @@ fn col_view_packed_matches_columnar() {
|
||||
}
|
||||
assert_eq!(col_view.sum(), col_ref.sum(), "col={c} sum");
|
||||
let mut ov_view: Vec<(usize, u32)> = col_view.overflow_entries().collect();
|
||||
let mut ov_ref: Vec<(usize, u32)> = col_ref.view().overflow_entries().collect();
|
||||
let mut ov_ref: Vec<(usize, u32)> = col_ref.view().overflow_entries().collect();
|
||||
ov_view.sort_unstable_by_key(|&(s, _)| s);
|
||||
ov_ref.sort_unstable_by_key(|&(s, _)| s);
|
||||
assert_eq!(ov_view, ov_ref, "col={c} overflow_entries");
|
||||
@@ -355,7 +388,7 @@ fn nonzero_iter_matches_row() {
|
||||
let (dir_col, m_col) = make_matrix(data);
|
||||
let (dir_pack, _) = make_matrix(data);
|
||||
pack_compact_int_matrix(&dir_pack.path().join("counts")).unwrap();
|
||||
let m_pack = PersistentCompactIntMatrix::open(dir_pack.path()).unwrap();
|
||||
let m_pack = PersistentIntMatrix::open(dir_pack.path()).unwrap();
|
||||
|
||||
let slots = [4usize, 0, 3, 1];
|
||||
let mut expected: Vec<(usize, usize, u32)> = Vec::new();
|
||||
@@ -390,7 +423,7 @@ fn sparse_roundtrip_matches_columnar() {
|
||||
let (dir_col, m_col) = make_matrix(data);
|
||||
let (dir_sparse, _) = make_matrix(data);
|
||||
pack_sparse_compact_int_matrix(&dir_sparse.path().join("counts")).unwrap();
|
||||
let m_sparse = PersistentCompactIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
let m_sparse = PersistentIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
assert_eq!(m_sparse.storage_kind(), StorageKind::Sparse);
|
||||
|
||||
assert_eq!(m_sparse.n(), m_col.n());
|
||||
@@ -441,7 +474,7 @@ fn sparse_count_partials_match_dense() {
|
||||
let (dir_col, m_col) = make_matrix(data);
|
||||
let (dir_sparse, _) = make_matrix(data);
|
||||
pack_sparse_compact_int_matrix(&dir_sparse.path().join("counts")).unwrap();
|
||||
let m_sparse = PersistentCompactIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
let m_sparse = PersistentIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
assert_eq!(m_sparse.storage_kind(), StorageKind::Sparse);
|
||||
|
||||
let n = m_col.n_cols();
|
||||
@@ -461,7 +494,11 @@ fn sparse_count_partials_match_dense() {
|
||||
let eucl_sparse = m_sparse.partial_euclidean_dist_matrix();
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
close(eucl_col[[i, j]], eucl_sparse[[i, j]], &format!("partial_euclidean[{i},{j}]"));
|
||||
close(
|
||||
eucl_col[[i, j]],
|
||||
eucl_sparse[[i, j]],
|
||||
&format!("partial_euclidean[{i},{j}]"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,9 +506,16 @@ fn sparse_count_partials_match_dense() {
|
||||
// (support shortcut), and >1 (general row-major path).
|
||||
for threshold in [0u32, 1, 2, 3] {
|
||||
let (inter_col, union_col) = m_col.partial_threshold_jaccard_dist_matrix(threshold);
|
||||
let (inter_sparse, union_sparse) = m_sparse.partial_threshold_jaccard_dist_matrix(threshold);
|
||||
assert_eq!(inter_col, inter_sparse, "partial_threshold_jaccard inter, threshold={threshold}");
|
||||
assert_eq!(union_col, union_sparse, "partial_threshold_jaccard union, threshold={threshold}");
|
||||
let (inter_sparse, union_sparse) =
|
||||
m_sparse.partial_threshold_jaccard_dist_matrix(threshold);
|
||||
assert_eq!(
|
||||
inter_col, inter_sparse,
|
||||
"partial_threshold_jaccard inter, threshold={threshold}"
|
||||
);
|
||||
assert_eq!(
|
||||
union_col, union_sparse,
|
||||
"partial_threshold_jaccard union, threshold={threshold}"
|
||||
);
|
||||
}
|
||||
|
||||
// partial_relfreq_bray
|
||||
@@ -479,7 +523,11 @@ fn sparse_count_partials_match_dense() {
|
||||
let rfb_sparse = m_sparse.partial_relfreq_bray_dist_matrix(&global);
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
close(rfb_col[[i, j]], rfb_sparse[[i, j]], &format!("partial_relfreq_bray[{i},{j}]"));
|
||||
close(
|
||||
rfb_col[[i, j]],
|
||||
rfb_sparse[[i, j]],
|
||||
&format!("partial_relfreq_bray[{i},{j}]"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +536,11 @@ fn sparse_count_partials_match_dense() {
|
||||
let rfe_sparse = m_sparse.partial_relfreq_euclidean_dist_matrix(&global);
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
close(rfe_col[[i, j]], rfe_sparse[[i, j]], &format!("partial_relfreq_euclidean[{i},{j}]"));
|
||||
close(
|
||||
rfe_col[[i, j]],
|
||||
rfe_sparse[[i, j]],
|
||||
&format!("partial_relfreq_euclidean[{i},{j}]"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +549,11 @@ fn sparse_count_partials_match_dense() {
|
||||
let hel_sparse = m_sparse.partial_hellinger_euclidean_dist_matrix(&global);
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
close(hel_col[[i, j]], hel_sparse[[i, j]], &format!("partial_hellinger[{i},{j}]"));
|
||||
close(
|
||||
hel_col[[i, j]],
|
||||
hel_sparse[[i, j]],
|
||||
&format!("partial_hellinger[{i},{j}]"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +570,7 @@ fn sparse_roundtrip_from_packed() {
|
||||
let (dir_sparse, _) = make_matrix(data);
|
||||
pack_compact_int_matrix(&dir_sparse.path().join("counts")).unwrap();
|
||||
pack_sparse_compact_int_matrix(&dir_sparse.path().join("counts")).unwrap();
|
||||
let m_sparse = PersistentCompactIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
let m_sparse = PersistentIntMatrix::open(dir_sparse.path()).unwrap();
|
||||
assert_eq!(m_sparse.storage_kind(), StorageKind::Sparse);
|
||||
for slot in 0..m_col.n() {
|
||||
assert_eq!(&*m_sparse.row(slot), &*m_col.row(slot), "slot={slot}");
|
||||
@@ -540,7 +596,10 @@ fn partial_relfreq_bray_additive_across_split() {
|
||||
let n = m_full.n_cols();
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
assert!((combined[[i, j]] - full_partial[[i, j]]).abs() < 1e-12, "[{i},{j}]");
|
||||
assert!(
|
||||
(combined[[i, j]] - full_partial[[i, j]]).abs() < 1e-12,
|
||||
"[{i},{j}]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,10 +607,12 @@ fn partial_relfreq_bray_additive_across_split() {
|
||||
// ── collect_slots_values tests ────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dir = tempdir().unwrap();
|
||||
let path = dir.path().join("c.pciv");
|
||||
let mut b = PersistentCompactIntVecBuilder::new(counts.len(), &path).unwrap();
|
||||
for (i, &v) in counts.iter().enumerate() { b.set(i, v); }
|
||||
for (i, &v) in counts.iter().enumerate() {
|
||||
b.set(i, v);
|
||||
}
|
||||
b.close().unwrap();
|
||||
let r = PersistentCompactIntVec::open(&path).unwrap();
|
||||
(dir, r)
|
||||
@@ -592,7 +653,10 @@ fn pciv_collect_slots_values_empty() {
|
||||
fn pciv_collect_slots_values_out_of_bounds_panics() {
|
||||
let (_dir, v) = make_pciv(&[10u32, 20]);
|
||||
let result = std::panic::catch_unwind(|| v.collect_slots_values(&[0, 2]));
|
||||
assert!(result.is_err(), "collect_slots_values should panic on out-of-bounds slot");
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"collect_slots_values should panic on out-of-bounds slot"
|
||||
);
|
||||
}
|
||||
|
||||
// IntSliceView collect_slots_values (same logic, exercised through the view)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::{
|
||||
pack_compact_int_matrix, pack_sparse_compact_int_matrix, ColumnWeights, PersistentCompactIntMatrix,
|
||||
PersistentCompactIntMatrixBuilder, PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder,
|
||||
ColumnWeights, PersistentIntMatrix, PersistentCompactIntMatrixBuilder,
|
||||
PersistentSparseCompactIntMatrix, PersistentSparseCompactIntMatrixBuilder,
|
||||
pack_compact_int_matrix, pack_sparse_compact_int_matrix,
|
||||
};
|
||||
|
||||
/// Builds a dense `PersistentCompactIntMatrix` from column-major `u32` data
|
||||
/// — mirrors `tests/intmatrix.rs`'s own `make_matrix` helper.
|
||||
fn make_dense(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentCompactIntMatrix) {
|
||||
fn make_dense(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentIntMatrix) {
|
||||
let n = cols.first().map_or(0, |c| c.len());
|
||||
let dir = tempdir().unwrap();
|
||||
let counts_dir = dir.path().join("counts");
|
||||
@@ -20,17 +21,21 @@ fn make_dense(cols: &[&[u32]]) -> (tempfile::TempDir, PersistentCompactIntMatrix
|
||||
cb.close().unwrap();
|
||||
}
|
||||
b.close().unwrap();
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
(dir, m)
|
||||
}
|
||||
|
||||
/// Builds a sparse int matrix directly from row-major `u32` data (one
|
||||
/// slice per row, `n_cols` values each) — mirrors `tests/sparse.rs`'s
|
||||
/// `make_sparse` helper.
|
||||
fn make_sparse(rows: &[&[u32]], n_cols: usize) -> (tempfile::TempDir, PersistentSparseCompactIntMatrix) {
|
||||
fn make_sparse(
|
||||
rows: &[&[u32]],
|
||||
n_cols: usize,
|
||||
) -> (tempfile::TempDir, PersistentSparseCompactIntMatrix) {
|
||||
let dir = tempdir().unwrap();
|
||||
let sparse_dir = dir.path().join("sparse");
|
||||
let mut b = PersistentSparseCompactIntMatrixBuilder::new(rows.len(), n_cols, &sparse_dir).unwrap();
|
||||
let mut b =
|
||||
PersistentSparseCompactIntMatrixBuilder::new(rows.len(), n_cols, &sparse_dir).unwrap();
|
||||
let mut cols = Vec::new();
|
||||
let mut values = Vec::new();
|
||||
for row in rows {
|
||||
@@ -54,11 +59,11 @@ fn basic_roundtrip_singletons_and_multi() {
|
||||
// shares its *support* (columns {0,1}) with another but has different
|
||||
// values — the whole point of not deduplicating values.
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[5, 0, 0, 0], // singleton: genome 0, value 5
|
||||
&[0, 0, 3, 0], // singleton: genome 2, value 3
|
||||
&[7, 2, 0, 0], // multi: {0:7, 1:2}
|
||||
&[0, 0, 4, 9], // multi: {2:4, 3:9}
|
||||
&[1, 6, 0, 0], // multi: same support {0,1} as row 2, different values
|
||||
&[5, 0, 0, 0], // singleton: genome 0, value 5
|
||||
&[0, 0, 3, 0], // singleton: genome 2, value 3
|
||||
&[7, 2, 0, 0], // multi: {0:7, 1:2}
|
||||
&[0, 0, 4, 9], // multi: {2:4, 3:9}
|
||||
&[1, 6, 0, 0], // multi: same support {0,1} as row 2, different values
|
||||
];
|
||||
let (_dir, m) = make_sparse(&rows, 4);
|
||||
assert_eq!(m.n(), 5);
|
||||
@@ -70,11 +75,7 @@ fn basic_roundtrip_singletons_and_multi() {
|
||||
|
||||
#[test]
|
||||
fn get_matches_row() {
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[3, 0, 5],
|
||||
&[0, 8, 0],
|
||||
&[1, 2, 4],
|
||||
];
|
||||
let rows: Vec<&[u32]> = vec![&[3, 0, 5], &[0, 8, 0], &[1, 2, 4]];
|
||||
let (_dir, m) = make_sparse(&rows, 3);
|
||||
for (slot, row) in rows.iter().enumerate() {
|
||||
for (c, &expected) in row.iter().enumerate() {
|
||||
@@ -85,11 +86,7 @@ fn get_matches_row() {
|
||||
|
||||
#[test]
|
||||
fn fill_row_matches_row() {
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[9, 0, 2],
|
||||
&[0, 4, 0],
|
||||
&[1, 1, 1],
|
||||
];
|
||||
let rows: Vec<&[u32]> = vec![&[9, 0, 2], &[0, 4, 0], &[1, 1, 1]];
|
||||
let (_dir, m) = make_sparse(&rows, 3);
|
||||
let mut buf = vec![0u32; 3];
|
||||
for slot in 0..3 {
|
||||
@@ -120,11 +117,7 @@ fn fill_sub_matrix_matches_row() {
|
||||
|
||||
#[test]
|
||||
fn nonzero_iter_matches_row() {
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[9, 0, 2],
|
||||
&[0, 4, 0],
|
||||
&[1, 1, 1],
|
||||
];
|
||||
let rows: Vec<&[u32]> = vec![&[9, 0, 2], &[0, 4, 0], &[1, 1, 1]];
|
||||
let (_dir, m) = make_sparse(&rows, 3);
|
||||
let slots = [2usize, 0, 1];
|
||||
let mut expected: Vec<(usize, usize, u32)> = Vec::new();
|
||||
@@ -143,12 +136,7 @@ fn nonzero_iter_matches_row() {
|
||||
|
||||
#[test]
|
||||
fn sum_and_count_nonzero_match_naive() {
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[9, 0, 2],
|
||||
&[0, 4, 0],
|
||||
&[1, 1, 1],
|
||||
&[0, 0, 6],
|
||||
];
|
||||
let rows: Vec<&[u32]> = vec![&[9, 0, 2], &[0, 4, 0], &[1, 1, 1], &[0, 0, 6]];
|
||||
let (_dir, m) = make_sparse(&rows, 3);
|
||||
let mut expected_sum = [0u64; 3];
|
||||
let mut expected_count = [0u64; 3];
|
||||
@@ -193,14 +181,21 @@ fn reopen_after_close_matches_original() {
|
||||
let rows: Vec<Vec<u32>> = (0..500)
|
||||
.map(|i| {
|
||||
(0..37)
|
||||
.map(|c| if (i * 7 + c * 3) % 11 == 0 { ((i + c) % 250 + 1) as u32 } else { 0 })
|
||||
.map(|c| {
|
||||
if (i * 7 + c * 3) % 11 == 0 {
|
||||
((i + c) % 250 + 1) as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let dir = tempdir().unwrap();
|
||||
let sparse_dir = dir.path().join("sparse");
|
||||
{
|
||||
let mut b = PersistentSparseCompactIntMatrixBuilder::new(rows.len(), 37, &sparse_dir).unwrap();
|
||||
let mut b =
|
||||
PersistentSparseCompactIntMatrixBuilder::new(rows.len(), 37, &sparse_dir).unwrap();
|
||||
let mut cols = Vec::new();
|
||||
let mut values = Vec::new();
|
||||
for row in &rows {
|
||||
@@ -229,10 +224,7 @@ fn reopen_after_close_matches_original() {
|
||||
fn overflow_values_roundtrip() {
|
||||
// Values >= 255 exercise the PersistentCompactIntVec overflow path in
|
||||
// both the singleton and multi value streams.
|
||||
let rows: Vec<&[u32]> = vec![
|
||||
&[1000, 0, 0],
|
||||
&[0, 50_000, 300],
|
||||
];
|
||||
let rows: Vec<&[u32]> = vec![&[1000, 0, 0], &[0, 50_000, 300]];
|
||||
let (_dir, m) = make_sparse(&rows, 3);
|
||||
for (slot, &expected) in rows.iter().enumerate() {
|
||||
assert_eq!(&*m.row(slot), expected, "row {slot}");
|
||||
@@ -246,14 +238,13 @@ fn overflow_values_roundtrip() {
|
||||
/// `tests::sparse::pack_sparse_bit_matrix_transposes_columnar_directly`.
|
||||
#[test]
|
||||
fn pack_sparse_compact_int_matrix_transposes_columnar_directly() {
|
||||
let cols: &[&[u32]] = &[
|
||||
&[9, 0, 2, 0],
|
||||
&[0, 4, 0, 7],
|
||||
&[1, 1, 300, 1],
|
||||
];
|
||||
let cols: &[&[u32]] = &[&[9, 0, 2, 0], &[0, 4, 0, 7], &[1, 1, 300, 1]];
|
||||
let (dir, dense) = make_dense(cols);
|
||||
let counts_dir = dir.path().join("counts");
|
||||
assert!(!counts_dir.join("matrix.pcmx").exists(), "still Columnar before packing");
|
||||
assert!(
|
||||
!counts_dir.join("matrix.pcmx").exists(),
|
||||
"still Columnar before packing"
|
||||
);
|
||||
|
||||
let expected_rows: Vec<Box<[u32]>> = (0..dense.n()).map(|s| dense.row(s)).collect();
|
||||
let n_cols = dense.n_cols();
|
||||
@@ -262,9 +253,18 @@ fn pack_sparse_compact_int_matrix_transposes_columnar_directly() {
|
||||
pack_sparse_compact_int_matrix(&counts_dir).unwrap();
|
||||
|
||||
assert!(counts_dir.join("singleton_values.pciv").exists());
|
||||
assert!(!counts_dir.join("meta.json").exists(), "columnar meta.json cleaned up");
|
||||
assert!(!counts_dir.join("col_000000.pciv").exists(), "columnar column files cleaned up");
|
||||
assert!(!counts_dir.join("matrix.pcmx").exists(), "never materialised");
|
||||
assert!(
|
||||
!counts_dir.join("meta.json").exists(),
|
||||
"columnar meta.json cleaned up"
|
||||
);
|
||||
assert!(
|
||||
!counts_dir.join("col_000000.pciv").exists(),
|
||||
"columnar column files cleaned up"
|
||||
);
|
||||
assert!(
|
||||
!counts_dir.join("matrix.pcmx").exists(),
|
||||
"never materialised"
|
||||
);
|
||||
|
||||
let sparse = PersistentSparseCompactIntMatrix::open(&counts_dir).unwrap();
|
||||
assert_eq!(sparse.n(), expected_rows.len());
|
||||
@@ -282,10 +282,7 @@ fn pack_sparse_compact_int_matrix_transposes_columnar_directly() {
|
||||
/// it's still used as the transpose source and cleaned up afterward.
|
||||
#[test]
|
||||
fn pack_sparse_compact_int_matrix_from_already_packed_matrix() {
|
||||
let cols: &[&[u32]] = &[
|
||||
&[9, 0, 2],
|
||||
&[0, 4, 300],
|
||||
];
|
||||
let cols: &[&[u32]] = &[&[9, 0, 2], &[0, 4, 300]];
|
||||
let (dir, dense) = make_dense(cols);
|
||||
let counts_dir = dir.path().join("counts");
|
||||
let expected_rows: Vec<Box<[u32]>> = (0..dense.n()).map(|s| dense.row(s)).collect();
|
||||
@@ -296,7 +293,10 @@ fn pack_sparse_compact_int_matrix_from_already_packed_matrix() {
|
||||
assert!(counts_dir.join("matrix.pcmx").exists());
|
||||
|
||||
pack_sparse_compact_int_matrix(&counts_dir).unwrap();
|
||||
assert!(!counts_dir.join("matrix.pcmx").exists(), "packed intermediate cleaned up");
|
||||
assert!(
|
||||
!counts_dir.join("matrix.pcmx").exists(),
|
||||
"packed intermediate cleaned up"
|
||||
);
|
||||
|
||||
let sparse = PersistentSparseCompactIntMatrix::open(&counts_dir).unwrap();
|
||||
assert_eq!(sparse.n_cols(), n_cols);
|
||||
|
||||
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::layer::utils::LAYERNAME_SUFFIX;
|
||||
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentCompactIntMatrix};
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentIntMatrix};
|
||||
use obikseq::CanonicalKmer;
|
||||
|
||||
use crate::index::error::OKIResult;
|
||||
@@ -63,7 +63,7 @@ pub enum KmerLayer {
|
||||
Count {
|
||||
dir: PathBuf,
|
||||
id: usize,
|
||||
layer: TypedLayer<PersistentCompactIntMatrix>,
|
||||
layer: TypedLayer<PersistentIntMatrix>,
|
||||
},
|
||||
Presence {
|
||||
dir: PathBuf,
|
||||
@@ -99,7 +99,7 @@ impl KmerLayer {
|
||||
already_open => return Ok(already_open),
|
||||
};
|
||||
if dir.join(COUNTS_DIR).exists() {
|
||||
let layer = TypedLayer::<PersistentCompactIntMatrix>::open(&dir)?;
|
||||
let layer = TypedLayer::<PersistentIntMatrix>::open(&dir)?;
|
||||
Ok(KmerLayer::Count { dir, id, layer })
|
||||
} else {
|
||||
let layer = TypedLayer::<PersistentBitMatrix>::open(&dir)?;
|
||||
@@ -221,7 +221,10 @@ impl KmerLayer {
|
||||
/// format-agnostic column-major fetch, delegating to whichever matrix
|
||||
/// this layer actually holds (see `obicompactvec::PersistentBitMatrix`/
|
||||
/// `PersistentCompactIntMatrix::nonzero_iter`).
|
||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
pub fn nonzero_iter<'a>(
|
||||
&'a self,
|
||||
slots: &'a [usize],
|
||||
) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
match self {
|
||||
KmerLayer::Presence { layer, .. } => layer.nonzero_iter(slots),
|
||||
KmerLayer::Count { layer, .. } => layer.nonzero_iter(slots),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use obicompactvec::PersistentSparseBitMatrixBuilder;
|
||||
use obikseq::{set_k, Unitig};
|
||||
use obikseq::{Unitig, set_k};
|
||||
use obiskio::DEFAULT_BLOCK_BITS;
|
||||
use tempfile::tempdir;
|
||||
|
||||
@@ -13,7 +13,8 @@ fn write_unitigs(dir: &Path, seqs: &[&[u8]]) {
|
||||
}
|
||||
|
||||
fn all_canonical_kmers(dir: &Path) -> Vec<CanonicalKmer> {
|
||||
UnitigFileReader::open_sequential(&dir.join(UNITIGS_FILE)).unwrap()
|
||||
UnitigFileReader::open_sequential(&dir.join(UNITIGS_FILE))
|
||||
.unwrap()
|
||||
.iter_indexed_canonical_kmers()
|
||||
.map(|(kmer, _, _)| kmer)
|
||||
.collect()
|
||||
@@ -34,13 +35,17 @@ fn canonical_kmer_iter_matches_reader() {
|
||||
let dir = tempdir().unwrap();
|
||||
write_unitigs(dir.path(), &[b"AAAACGT", b"TTTTGCA"]);
|
||||
|
||||
let from_iter: Vec<CanonicalKmer> = obiskio::CanonicalKmerIter::new(&dir.path().join(UNITIGS_FILE))
|
||||
.unwrap()
|
||||
.collect();
|
||||
let from_iter: Vec<CanonicalKmer> =
|
||||
obiskio::CanonicalKmerIter::new(&dir.path().join(UNITIGS_FILE))
|
||||
.unwrap()
|
||||
.collect();
|
||||
let from_reader: Vec<CanonicalKmer> = all_canonical_kmers(dir.path());
|
||||
|
||||
assert_eq!(from_iter.len(), from_reader.len(), "different kmer counts");
|
||||
assert_eq!(from_iter, from_reader, "CanonicalKmerIter and UnitigFileReader disagree");
|
||||
assert_eq!(
|
||||
from_iter, from_reader,
|
||||
"CanonicalKmerIter and UnitigFileReader disagree"
|
||||
);
|
||||
}
|
||||
|
||||
// ── Generic `TypedLayer<D>` over dense vs. sparse presence storage ───────────────
|
||||
@@ -64,9 +69,14 @@ fn presence_layer_generic_over_sparse_matches_dense() {
|
||||
// kmer iff `(kmer's raw bits + g)` is even. Doesn't need to be
|
||||
// biologically meaningful, just the same on both sides of the
|
||||
// dense/sparse comparison below.
|
||||
TypedLayer::<PersistentBitMatrix>::build_presence(dir.path(), DEFAULT_BLOCK_BITS, &mode, n_genomes, |kmer, g| {
|
||||
(kmer.raw().wrapping_add(g as u64)) % 2 == 0
|
||||
}).unwrap();
|
||||
TypedLayer::<PersistentBitMatrix>::build_presence(
|
||||
dir.path(),
|
||||
DEFAULT_BLOCK_BITS,
|
||||
&mode,
|
||||
n_genomes,
|
||||
|kmer, g| (kmer.raw().wrapping_add(g as u64)) % 2 == 0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dense_layer = TypedLayer::<PersistentBitMatrix>::open(dir.path()).unwrap();
|
||||
assert!(dense_layer.n_cols() >= 1);
|
||||
@@ -76,10 +86,13 @@ fn presence_layer_generic_over_sparse_matches_dense() {
|
||||
// same directory, distinct filenames — see
|
||||
// `DevDocMD/architecture/siblings.md`'s sparse-matrix section).
|
||||
let dense_matrix = obicompactvec::PersistentBitMatrix::open(dir.path()).unwrap();
|
||||
PersistentSparseBitMatrixBuilder::build_from_dense(&dense_matrix, &dir.path().join(PRESENCE_DIR))
|
||||
.unwrap()
|
||||
.close()
|
||||
.unwrap();
|
||||
PersistentSparseBitMatrixBuilder::build_from_dense(
|
||||
&dense_matrix,
|
||||
&dir.path().join(PRESENCE_DIR),
|
||||
)
|
||||
.unwrap()
|
||||
.close()
|
||||
.unwrap();
|
||||
|
||||
let sparse_layer = TypedLayer::<PersistentSparseBitMatrix>::open(dir.path()).unwrap();
|
||||
|
||||
@@ -88,7 +101,10 @@ fn presence_layer_generic_over_sparse_matches_dense() {
|
||||
let n = dense_layer.n();
|
||||
assert_eq!(n, sparse_layer.n());
|
||||
let slots: Vec<usize> = (0..n).collect();
|
||||
assert_eq!(dense_layer.sub_matrix(&slots), sparse_layer.sub_matrix(&slots));
|
||||
assert_eq!(
|
||||
dense_layer.sub_matrix(&slots),
|
||||
sparse_layer.sub_matrix(&slots)
|
||||
);
|
||||
|
||||
// The matrix-agnostic, MPHF-only surface (from the generic
|
||||
// `impl<D: LayerData> TypedLayer<D>`) must also agree: same kmer set,
|
||||
@@ -124,15 +140,25 @@ fn count_layer_transparently_reads_sparse_after_pack() {
|
||||
// Deterministic, arbitrary count function — same shape as the presence
|
||||
// test's arbitrary predicate, just producing a small count instead of a
|
||||
// bool.
|
||||
TypedLayer::<()>::build_with_matrix(dir.path(), DEFAULT_BLOCK_BITS, &mode, false, n_genomes, |kmer| {
|
||||
(0..n_genomes)
|
||||
.map(|g| ((kmer.raw().wrapping_add(g as u64)) % 5) as u32)
|
||||
.collect()
|
||||
})
|
||||
TypedLayer::<()>::build_with_matrix(
|
||||
dir.path(),
|
||||
DEFAULT_BLOCK_BITS,
|
||||
&mode,
|
||||
false,
|
||||
n_genomes,
|
||||
|kmer| {
|
||||
(0..n_genomes)
|
||||
.map(|g| ((kmer.raw().wrapping_add(g as u64)) % 5) as u32)
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dense_layer = TypedLayer::<PersistentCompactIntMatrix>::open(dir.path()).unwrap();
|
||||
assert_eq!(dense_layer.storage_kind(), obicompactvec::StorageKind::Columnar);
|
||||
let dense_layer = TypedLayer::<PersistentIntMatrix>::open(dir.path()).unwrap();
|
||||
assert_eq!(
|
||||
dense_layer.storage_kind(),
|
||||
obicompactvec::StorageKind::Columnar
|
||||
);
|
||||
let n = dense_layer.n();
|
||||
let slots: Vec<usize> = (0..n).collect();
|
||||
let dense_sub = dense_layer.sub_matrix(&slots);
|
||||
@@ -146,8 +172,11 @@ fn count_layer_transparently_reads_sparse_after_pack() {
|
||||
// in production.
|
||||
obicompactvec::pack_sparse_compact_int_matrix(&dir.path().join(COUNTS_DIR)).unwrap();
|
||||
|
||||
let sparse_layer = TypedLayer::<PersistentCompactIntMatrix>::open(dir.path()).unwrap();
|
||||
assert_eq!(sparse_layer.storage_kind(), obicompactvec::StorageKind::Sparse);
|
||||
let sparse_layer = TypedLayer::<PersistentIntMatrix>::open(dir.path()).unwrap();
|
||||
assert_eq!(
|
||||
sparse_layer.storage_kind(),
|
||||
obicompactvec::StorageKind::Sparse
|
||||
);
|
||||
assert_eq!(sparse_layer.n(), n);
|
||||
assert_eq!(sparse_layer.n_cols(), dense_layer.n_cols());
|
||||
assert_eq!(sparse_layer.sub_matrix(&slots), dense_sub);
|
||||
@@ -167,13 +196,20 @@ fn count_layer_reports_count_content_and_columnar_storage() {
|
||||
set_k(4);
|
||||
let dir = tempdir().unwrap();
|
||||
write_unitigs(dir.path(), &[b"AAAACGT"]);
|
||||
TypedLayer::<PersistentCompactIntMatrix>::build(dir.path(), DEFAULT_BLOCK_BITS, &IndexMode::Exact, |_| 1)
|
||||
.unwrap();
|
||||
let layer = TypedLayer::<PersistentCompactIntMatrix>::open(dir.path()).unwrap();
|
||||
TypedLayer::<PersistentIntMatrix>::build(dir.pat
|
||||
h(), DEFAUL
|
||||
T_BLOCK_BITS, &Index
|
||||
Mode::Exact, |_| 1)
|
||||
,
|
||||
).unwrap();
|
||||
let layer = TypedLayer::<PersistentIntMatrix>::open(dir.path()).unwrap();
|
||||
|
||||
assert_eq!(layer.content(), LayerContent::Count);
|
||||
assert_eq!(layer.storage_kind(), obicompactvec::StorageKind::Columnar);
|
||||
assert_eq!(layer.evidence_kind(), crate::layer::mphf_layer::EvidenceKind::Exact);
|
||||
assert_eq!(
|
||||
layer.evidence_kind(),
|
||||
crate::layer::mphf_layer::EvidenceKind::Exact
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -181,9 +217,14 @@ fn presence_layer_reports_presence_content_and_columnar_storage() {
|
||||
set_k(4);
|
||||
let dir = tempdir().unwrap();
|
||||
write_unitigs(dir.path(), &[b"AAATCTA", b"CTTCGCC", b"TGATACG"]);
|
||||
TypedLayer::<PersistentBitMatrix>::build_presence(dir.path(), DEFAULT_BLOCK_BITS, &IndexMode::Exact, 2, |kmer, g| {
|
||||
(kmer.raw().wrapping_add(g as u64)) % 2 == 0
|
||||
}).unwrap();
|
||||
TypedLayer::<PersistentBitMatrix>::build_presence(
|
||||
dir.path(),
|
||||
DEFAULT_BLOCK_BITS,
|
||||
&IndexMode::Exact,
|
||||
2,
|
||||
|kmer, g| (kmer.raw().wrapping_add(g as u64)) % 2 == 0,
|
||||
)
|
||||
.unwrap();
|
||||
let layer = TypedLayer::<PersistentBitMatrix>::open(dir.path()).unwrap();
|
||||
|
||||
assert_eq!(layer.content(), LayerContent::Presence);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// use crate::layer::utils::layer_dir;
|
||||
use obicompactvec::{
|
||||
BinaryMatrix, ColBuilder, MatrixBuilder, PersistentBitMatrix, PersistentBitMatrixBuilder,
|
||||
PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder, PersistentSparseBitMatrix,
|
||||
PersistentCompactIntMatrixBuilder, PersistentIntMatrix, PersistentSparseBitMatrix,
|
||||
};
|
||||
use obikseq::CanonicalKmer;
|
||||
use obiskio::{UnitigFileReader, UnitigFileWriter};
|
||||
@@ -47,10 +47,10 @@ impl LayerData for () {
|
||||
fn read(&self, _slot: usize) {}
|
||||
}
|
||||
|
||||
impl LayerData for PersistentCompactIntMatrix {
|
||||
impl LayerData for PersistentIntMatrix {
|
||||
type Item = Box<[u32]>;
|
||||
fn open(layer_dir: &Path) -> OKIResult<Self> {
|
||||
PersistentCompactIntMatrix::open(layer_dir).map_err(OKIError::Io)
|
||||
PersistentIntMatrix::open(layer_dir).map_err(OKIError::Io)
|
||||
}
|
||||
fn read(&self, slot: usize) -> Box<[u32]> {
|
||||
self.row(slot)
|
||||
@@ -112,7 +112,7 @@ pub trait HasLayerContent {
|
||||
const CONTENT: LayerContent;
|
||||
}
|
||||
|
||||
impl HasLayerContent for PersistentCompactIntMatrix {
|
||||
impl HasLayerContent for PersistentIntMatrix {
|
||||
const CONTENT: LayerContent = LayerContent::Count;
|
||||
}
|
||||
|
||||
@@ -135,9 +135,9 @@ pub trait HasStorageKind {
|
||||
fn storage_kind(&self) -> obicompactvec::StorageKind;
|
||||
}
|
||||
|
||||
impl HasStorageKind for PersistentCompactIntMatrix {
|
||||
impl HasStorageKind for PersistentIntMatrix {
|
||||
fn storage_kind(&self) -> obicompactvec::StorageKind {
|
||||
PersistentCompactIntMatrix::storage_kind(self)
|
||||
PersistentIntMatrix::storage_kind(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ impl TypedLayer<()> {
|
||||
|
||||
// ── Mode 2 — count matrix ─────────────────────────────────────────────────────
|
||||
|
||||
impl TypedLayer<PersistentCompactIntMatrix> {
|
||||
impl TypedLayer<PersistentIntMatrix> {
|
||||
pub fn build(
|
||||
out_dir: &Path,
|
||||
block_bits: u8,
|
||||
@@ -377,12 +377,12 @@ impl TypedLayer<PersistentCompactIntMatrix> {
|
||||
|
||||
// ── Mode 2 — count matrix column append ──────────────────────────────────────
|
||||
|
||||
impl TypedLayer<PersistentCompactIntMatrix> {
|
||||
impl TypedLayer<PersistentIntMatrix> {
|
||||
pub fn append_genome_column(
|
||||
layer_dir: &Path,
|
||||
value_of: impl Fn(usize) -> u32,
|
||||
) -> OKIResult<()> {
|
||||
PersistentCompactIntMatrix::append_column(&layer_dir.join(COUNTS_DIR), value_of)
|
||||
PersistentIntMatrix::append_column(&layer_dir.join(COUNTS_DIR), value_of)
|
||||
.map_err(OKIError::Io)
|
||||
}
|
||||
|
||||
@@ -418,7 +418,10 @@ impl TypedLayer<PersistentCompactIntMatrix> {
|
||||
|
||||
/// Every nonzero `(idx into slots, col, value)` triple among `slots` —
|
||||
/// delegates to `PersistentCompactIntMatrix::nonzero_iter`.
|
||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
pub fn nonzero_iter<'a>(
|
||||
&'a self,
|
||||
slots: &'a [usize],
|
||||
) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
self.data.nonzero_iter(slots)
|
||||
}
|
||||
}
|
||||
@@ -477,7 +480,10 @@ impl<D: LayerData<Item = Box<[bool]>> + BinaryMatrix + obicompactvec::ColumnWeig
|
||||
impl TypedLayer<PersistentBitMatrix> {
|
||||
/// Every nonzero `(idx into slots, col, value)` triple among `slots` —
|
||||
/// delegates to `PersistentBitMatrix::nonzero_iter`.
|
||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
pub fn nonzero_iter<'a>(
|
||||
&'a self,
|
||||
slots: &'a [usize],
|
||||
) -> Box<dyn Iterator<Item = (usize, usize, u32)> + 'a> {
|
||||
self.data.nonzero_iter(slots)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::graph_pipeline::{materialize_layer, write_graph_as_unitigs};
|
||||
use cacheline_ef::{CachelineEf, CachelineEfVec};
|
||||
use epserde::prelude::*;
|
||||
use obicompactvec::{PersistentCompactIntMatrix, PersistentCompactIntVec};
|
||||
use obicompactvec::{PersistentIntMatrix, PersistentCompactIntVec};
|
||||
use obidebruinj::GraphDeBruijn;
|
||||
use obikindex::layer::IndexMode;
|
||||
use obikindex::layer::{KmerLayer, TypedLayer};
|
||||
use obikindex::{KmerIndex, OKIError, OKIResult};
|
||||
use crate::graph_pipeline::{materialize_layer, write_graph_as_unitigs};
|
||||
use obiskio::{SKError, SKFileMeta, SKFileReader};
|
||||
use ptr_hash::{PtrHash, bucket_fn::CubicEps, hash::Xx64};
|
||||
|
||||
@@ -139,7 +139,9 @@ impl PrivateBuilder for KmerIndex {
|
||||
mode: &IndexMode,
|
||||
block_bits: u8,
|
||||
) -> Result<usize, SKError> {
|
||||
let layer0 = self.layer0(i).map_err(|e| io::Error::other(e.to_string()))?;
|
||||
let layer0 = self
|
||||
.layer0(i)
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
let layer0_dir = layer0.dir();
|
||||
let dedup_path = layer0.dereplicated_superkmers_path();
|
||||
if !dedup_path.exists() {
|
||||
@@ -193,15 +195,12 @@ impl PrivateBuilder for KmerIndex {
|
||||
let n_kmers = if with_counts {
|
||||
let n = write_graph_as_unitigs(g, layer0_dir)
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
TypedLayer::<PersistentCompactIntMatrix>::build(
|
||||
layer0_dir,
|
||||
block_bits,
|
||||
mode,
|
||||
|kmer| match (&mphf1_opt, &counts1_opt) {
|
||||
TypedLayer::<PersistentIntMatrix>::build(layer0_dir, block_bits, mode, |kmer| {
|
||||
match (&mphf1_opt, &counts1_opt) {
|
||||
(Some(mphf), Some(counts)) => counts.get(mphf.index(&kmer.raw())),
|
||||
_ => 1,
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
n
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ fn presence(mode: MergeMode) -> bool {
|
||||
mod matrix_builder_tests {
|
||||
use tempfile::tempdir;
|
||||
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentCompactIntMatrix};
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentIntMatrix};
|
||||
|
||||
use super::{ColBuilder, MatrixBuilder};
|
||||
|
||||
@@ -90,7 +90,7 @@ mod matrix_builder_tests {
|
||||
col.close().unwrap();
|
||||
mb.close().unwrap();
|
||||
|
||||
let m = PersistentCompactIntMatrix::open(dir.path()).unwrap();
|
||||
let m = PersistentIntMatrix::open(dir.path()).unwrap();
|
||||
assert_eq!(m.n_cols(), 2);
|
||||
assert_eq!(&*m.row(0), &[0u32, 7]);
|
||||
assert_eq!(&*m.row(1), &[0u32, 42]);
|
||||
@@ -422,8 +422,13 @@ pub(crate) fn merge_partition(
|
||||
move |data: Pass2Data,
|
||||
push: &PipelineSender<Result<Pass2Data, PipelineError>>,
|
||||
delta: &PipelineSender<isize>| {
|
||||
if let Pass2Data::SrcLayer((col_offset, src_n, unitigs_path, src_layer, _guard)) =
|
||||
data
|
||||
if let Pass2Data::SrcLayer((
|
||||
col_offset,
|
||||
src_n,
|
||||
unitigs_path,
|
||||
src_layer,
|
||||
_guard,
|
||||
)) = data
|
||||
{
|
||||
// _guard dropped at end of block, releasing the slot.
|
||||
// `src_layer` (MPHF + matrix) was already opened up
|
||||
@@ -455,8 +460,10 @@ pub(crate) fn merge_partition(
|
||||
}
|
||||
}
|
||||
if !batch.is_empty() {
|
||||
push.send(Ok(Pass2Data::RawBatch((col_offset, src_n, src_layer, batch))))
|
||||
.ok();
|
||||
push.send(Ok(Pass2Data::RawBatch((
|
||||
col_offset, src_n, src_layer, batch,
|
||||
))))
|
||||
.ok();
|
||||
count += 1;
|
||||
}
|
||||
delta.send(count - 1).ok();
|
||||
@@ -480,8 +487,9 @@ pub(crate) fn merge_partition(
|
||||
// per kmer — matches the underlying matrix's own
|
||||
// column-major layout.
|
||||
let slots = src_layer.hash_batch(&kmers);
|
||||
let mut cols: Vec<Vec<u32>> =
|
||||
(0..src_n).map(|_| Vec::with_capacity(kmers.len())).collect();
|
||||
let mut cols: Vec<Vec<u32>> = (0..src_n)
|
||||
.map(|_| Vec::with_capacity(kmers.len()))
|
||||
.collect();
|
||||
src_layer.fill_sub_matrix(&slots, &mut cols);
|
||||
|
||||
// Membership against dst, grouped by dst layer
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentIntMatrix};
|
||||
use obikidxcache::LayeredStore;
|
||||
use obikindex::OKIResult;
|
||||
use obikindex::layer::open_data;
|
||||
use obikidxcache::LayeredStore;
|
||||
use obicompactvec::{PersistentBitMatrix, PersistentCompactIntMatrix};
|
||||
|
||||
use obikindex::load_meta;
|
||||
use obikindex::KmerIndex;
|
||||
use obikindex::load_meta;
|
||||
|
||||
impl KmerIndex {
|
||||
/// Open all count matrices for partition `part`, one per layer.
|
||||
/// Layers without a `counts/` directory are skipped.
|
||||
pub fn count_store(&self, part: usize) -> OKIResult<LayeredStore<PersistentCompactIntMatrix>> {
|
||||
pub fn count_store(&self, part: usize) -> OKIResult<LayeredStore<PersistentIntMatrix>> {
|
||||
let index_dir = self.index_dir(part);
|
||||
if !index_dir.exists() {
|
||||
return Ok(LayeredStore::new(vec![]));
|
||||
|
||||
@@ -11,8 +11,8 @@ use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use obicompactvec::{
|
||||
ColGroup, MatrixBuilder, MatrixGroupOps, PersistentBitMatrix,
|
||||
PersistentCompactIntMatrix, TempBitVec, TempCompactIntVec,
|
||||
ColGroup, MatrixBuilder, MatrixGroupOps, PersistentBitMatrix, PersistentIntMatrix, TempBitVec,
|
||||
TempCompactIntVec,
|
||||
};
|
||||
use obikindex::layer::{KmerLayer, LayerContent};
|
||||
use obikindex::{KmerIndex, OKIError, OKIResult};
|
||||
@@ -39,13 +39,15 @@ impl AggOp {
|
||||
/// `--aggregate-op`.
|
||||
pub fn parse(s: &str) -> Result<Self, String> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"any" => Ok(AggOp::Any),
|
||||
"all" => Ok(AggOp::All),
|
||||
"any" => Ok(AggOp::Any),
|
||||
"all" => Ok(AggOp::All),
|
||||
"none" => Ok(AggOp::None),
|
||||
"sum" => Ok(AggOp::Sum),
|
||||
"min" => Ok(AggOp::Min),
|
||||
"max" => Ok(AggOp::Max),
|
||||
other => Err(format!("unknown aggregation operator: {other}; valid: any, all, none, sum, min, max")),
|
||||
"sum" => Ok(AggOp::Sum),
|
||||
"min" => Ok(AggOp::Min),
|
||||
"max" => Ok(AggOp::Max),
|
||||
other => Err(format!(
|
||||
"unknown aggregation operator: {other}; valid: any, all, none, sum, min, max"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +77,11 @@ enum AggResult {
|
||||
Int(TempCompactIntVec),
|
||||
}
|
||||
|
||||
fn compute_group(mat: &dyn MatrixGroupOps, spec: &OutputCol, threshold: u32) -> io::Result<AggResult> {
|
||||
fn compute_group(
|
||||
mat: &dyn MatrixGroupOps,
|
||||
spec: &OutputCol,
|
||||
threshold: u32,
|
||||
) -> io::Result<AggResult> {
|
||||
let g = ColGroup::new(spec.label.clone(), spec.indices.clone());
|
||||
Ok(match spec.op {
|
||||
AggOp::Any => AggResult::Bit(mat.partial_group_any(&g, threshold)?),
|
||||
@@ -145,18 +151,23 @@ pub(crate) fn select_partition(
|
||||
|
||||
let group_mat: Box<dyn MatrixGroupOps> = match src_layer.content() {
|
||||
LayerContent::Count => {
|
||||
Box::new(PersistentCompactIntMatrix::open(src_layer.dir()).map_err(OKIError::Io)?)
|
||||
Box::new(PersistentIntMatrix::open(src_layer.dir()).map_err(OKIError::Io)?)
|
||||
}
|
||||
LayerContent::Presence => {
|
||||
Box::new(PersistentBitMatrix::open(src_layer.dir()).map_err(OKIError::Io)?)
|
||||
}
|
||||
};
|
||||
|
||||
let data_subdir = if output_presence { "presence" } else { "counts" };
|
||||
let data_subdir = if output_presence {
|
||||
"presence"
|
||||
} else {
|
||||
"counts"
|
||||
};
|
||||
let data_dir = dst_layer_dir.join(data_subdir);
|
||||
fs::create_dir_all(&data_dir).map_err(OKIError::Io)?;
|
||||
|
||||
let mut builder = MatrixBuilder::new(output_presence, n, &data_dir).map_err(OKIError::Io)?;
|
||||
let mut builder =
|
||||
MatrixBuilder::new(output_presence, n, &data_dir).map_err(OKIError::Io)?;
|
||||
for spec in specs {
|
||||
let r = compute_group(group_mat.as_ref(), spec, threshold).map_err(OKIError::Io)?;
|
||||
add_result(&mut builder, r).map_err(OKIError::Io)?;
|
||||
|
||||
Reference in New Issue
Block a user