feat: add sparse on-disk format for presence matrices

The index packing API now accepts a `sparse` parameter to generate `PersistentSparseBitMatrix` files alongside existing dense matrices. The sibling cache automatically detects this format via an `is_multi.prsb` marker file and routes queries identically to the dense variant. A new `--sparse` CLI flag exposes the option, with tests verifying end-to-end pipeline correctness and storage equivalence.
This commit is contained in:
Eric Coissac
2026-08-16 21:51:02 +02:00
parent 50f4820cb9
commit 3ba26b3dc1
14 changed files with 279 additions and 24 deletions
+16 -4
View File
@@ -122,7 +122,7 @@ impl KmerIndex {
fs::File::create(output.join(SENTINEL_INDEXED)).map_err(OKIError::Io)?;
let idx = KmerIndex::open(output)?;
let t_pack = Stage::start("pack");
idx.pack_matrices()?;
idx.pack_matrices(false)?;
rep.push(t_pack.stop());
Ok(idx)
}
@@ -274,8 +274,14 @@ impl KmerIndex {
///
/// Reduces per-query file-open overhead from O(n_genomes) to O(1) per partition.
/// Column files are kept in place; packed files take priority when opening.
pub fn pack_matrices(&self) -> OKIResult<()> {
use obicompactvec::{pack_bit_matrix, pack_compact_int_matrix};
///
/// If `sparse` is set, presence matrices go one step further, from the
/// dense `.pbmx` form into `obicompactvec::PersistentSparseBitMatrix`'s
/// on-disk format (see `docmd/architecture/siblings.md`) — count
/// matrices are unaffected, sparse count matrices aren't implemented
/// (see the sparse-matrix design plan's "Explicitly deferred").
pub fn pack_matrices(&self, sparse: bool) -> OKIResult<()> {
use obicompactvec::{pack_bit_matrix, pack_compact_int_matrix, pack_sparse_bit_matrix};
use obilayeredmap::meta::PartitionMeta;
let n = self.n_partitions();
@@ -292,7 +298,13 @@ impl KmerIndex {
let layer_dir = index_dir.join(format!("layer_{l}"));
let presence_dir = layer_dir.join("presence");
let counts_dir = layer_dir.join("counts");
if presence_dir.exists() { pack_bit_matrix(&presence_dir).map_err(OKIError::Io)?; }
if presence_dir.exists() {
if sparse {
pack_sparse_bit_matrix(&presence_dir).map_err(OKIError::Io)?;
} else {
pack_bit_matrix(&presence_dir).map_err(OKIError::Io)?;
}
}
if counts_dir.exists() { pack_compact_int_matrix(&counts_dir).map_err(OKIError::Io)?; }
}
Ok(())
+1 -1
View File
@@ -249,7 +249,7 @@ impl KmerIndex {
let pb = spinner("pack");
pb.set_message("consolidating column files …");
let dst2 = KmerIndex::open(output)?;
dst2.pack_matrices()?;
dst2.pack_matrices(false)?;
pb.finish_and_clear();
rep.push(t.stop());
}
+1 -1
View File
@@ -118,7 +118,7 @@ impl KmerIndex {
self.meta.write(&self.root_path)?;
let t_pack = Stage::start("pack");
self.pack_matrices()?;
self.pack_matrices(false)?;
rep.push(t_pack.stop());
Ok(())
}