refactor(obikselect): implement Algorithm trait for selection
Shifts the selection logic from a direct method into a dedicated `Select` struct implementing the `Algorithm` trait. Introduces a two-phase initialization pattern with explicit builder methods and closure-based progress callbacks. Updates the crate's dependency graph to include `obikalgorithm`, refactors layer processing with unified matrix dispatch, and implements dual directory routing for partition execution.
This commit is contained in:
Generated
+1
@@ -1746,6 +1746,7 @@ name = "obikselect"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"obicompactvec",
|
"obicompactvec",
|
||||||
|
"obikalgorithm",
|
||||||
"obikindex",
|
"obikindex",
|
||||||
"obisys",
|
"obisys",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
obikindex = { path = "../obikindex" }
|
obikindex = { path = "../obikindex" }
|
||||||
|
obikalgorithm = { path = "../obikalgorithm" }
|
||||||
obicompactvec = { path = "../obicompactvec" }
|
obicompactvec = { path = "../obicompactvec" }
|
||||||
obisys = { path = "../obisys" }
|
obisys = { path = "../obisys" }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
//! Genome-column selection/aggregation on an `obikindex::KmerIndex`:
|
//! Genome-column selection/aggregation on an `obikindex::KmerIndex`:
|
||||||
//! projecting/aggregating genome columns (`AggOp`) into new output columns
|
//! projecting/aggregating genome columns (`AggOp`) into new output columns
|
||||||
//! (`OutputCol`), either into a new index (`select`) or in place
|
//! (`OutputCol`) of a brand new index. Orthogonal to per-kmer filtering
|
||||||
//! (`select_in_place`). Orthogonal to per-kmer filtering (`obikfilter`),
|
//! (`obikfilter`), which operates on rows, not columns. Kept as a separate
|
||||||
//! which operates on rows, not columns. Kept as a separate crate — not a
|
//! crate — not a module of `obikindex` — so the dependency runs one way
|
||||||
//! module of `obikindex` — so the dependency runs one way only.
|
//! only. Structured on the model of `obikindexer::algorithms`/
|
||||||
|
//! `obikmerge`: [`Select`] implements `obikalgorithm::Algorithm` (two-phase
|
||||||
|
//! `new` + setters, then `run`).
|
||||||
|
|
||||||
mod select;
|
mod select;
|
||||||
mod select_layer;
|
mod select_layer;
|
||||||
|
|
||||||
|
pub use select::Select;
|
||||||
pub use select_layer::{AggOp, OutputCol};
|
pub use select_layer::{AggOp, OutputCol};
|
||||||
|
|||||||
+126
-132
@@ -1,154 +1,148 @@
|
|||||||
use std::path::Path;
|
//! Projecting/aggregating the genome columns of a source
|
||||||
use std::sync::Arc;
|
//! `obikindex::KmerIndex` into a brand new one, according to a list of
|
||||||
|
//! [`crate::OutputCol`] specs. Structured exactly like `obikmerge::Merge`:
|
||||||
|
//! two-phase construction (`new` + setters), then
|
||||||
|
//! `obikalgorithm::Algorithm::run`, via `obikindex::IndexBuilder`
|
||||||
|
//! (`create_skeleton` + `finalize_indexed`) — no in-place variant, same as
|
||||||
|
//! `Merge`.
|
||||||
|
|
||||||
use obikindex::IndexBuilder;
|
use std::io;
|
||||||
use crate::OutputCol;
|
use std::path::PathBuf;
|
||||||
use obisys::{Reporter, Stage, progress_bar, PartitionRunner};
|
|
||||||
|
use obikalgorithm::Algorithm;
|
||||||
|
use obikindex::{GenomeInfo, IndexBuilder, IndexState, KmerIndex};
|
||||||
|
use obisys::{PartitionRunner, Progress, Reporter, Stage};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use obikindex::{OKIError, OKIResult};
|
use crate::select_layer::select_partition;
|
||||||
use obikindex::KmerIndex;
|
use crate::OutputCol;
|
||||||
use obikindex::{GenomeInfo, IndexMeta};
|
|
||||||
use obikindex::IndexState;
|
|
||||||
|
|
||||||
impl KmerIndex {
|
pub struct Select<'a> {
|
||||||
/// Create a new index at `output` by projecting/aggregating the genome columns
|
src: &'a KmerIndex,
|
||||||
/// of `src` according to `specs`.
|
output: PathBuf,
|
||||||
///
|
specs: &'a [OutputCol],
|
||||||
/// `output_presence` — if true, output uses bit matrices (0/1), regardless of
|
|
||||||
/// whether the source stores counts. The caller is responsible for ensuring all
|
|
||||||
/// specs use logical operators when `output_presence=true` on a count source.
|
|
||||||
pub fn select<P: AsRef<Path>>(
|
|
||||||
output: P,
|
|
||||||
src: &KmerIndex,
|
|
||||||
specs: &[OutputCol],
|
|
||||||
threshold: u32,
|
threshold: u32,
|
||||||
output_presence: bool,
|
output_presence: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
rep: &mut Reporter,
|
sparse: bool,
|
||||||
) -> OKIResult<Self> {
|
reporter: Reporter,
|
||||||
let output = output.as_ref();
|
on_progress: Option<Box<dyn FnMut(Progress) + Send + 'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
if src.state()? != IndexState::Indexed {
|
impl<'a> Select<'a> {
|
||||||
return Err(OKIError::NotIndexed(src.root_path.clone()));
|
pub fn new(
|
||||||
}
|
src: &'a KmerIndex,
|
||||||
|
output: impl Into<PathBuf>,
|
||||||
KmerIndex::clear_output_for_create(output, force)?;
|
specs: &'a [OutputCol],
|
||||||
|
|
||||||
let mut config = src.meta.config.clone();
|
|
||||||
config.with_counts = !output_presence;
|
|
||||||
let genomes: Vec<GenomeInfo> = specs
|
|
||||||
.iter()
|
|
||||||
.map(|s| GenomeInfo::new(s.label.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let n_src_genomes = src.meta.genomes().map_err(OKIError::Io)?.len();
|
|
||||||
let n_partitions = src.n_partitions();
|
|
||||||
|
|
||||||
let dst_partition = KmerIndex::create_skeleton(output, config, genomes)?;
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"select: {} partition(s), {} source genome(s) → {} output column(s)",
|
|
||||||
n_partitions,
|
|
||||||
n_src_genomes,
|
|
||||||
specs.len(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let t = Stage::start("select");
|
|
||||||
let pb = progress_bar("select", n_partitions as u64, "partitions");
|
|
||||||
|
|
||||||
let order: Vec<usize> = (0..n_partitions).collect();
|
|
||||||
let runner = PartitionRunner::new();
|
|
||||||
runner
|
|
||||||
.run(
|
|
||||||
&order,
|
|
||||||
|i| {
|
|
||||||
dst_partition.select_partition(
|
|
||||||
src,
|
|
||||||
i,
|
|
||||||
specs,
|
|
||||||
n_src_genomes,
|
|
||||||
threshold,
|
|
||||||
output_presence,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|_, _, _| {
|
|
||||||
pb.inc(1);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(OKIError::Partition)?;
|
|
||||||
|
|
||||||
pb.finish_and_clear();
|
|
||||||
rep.push(t.stop());
|
|
||||||
|
|
||||||
KmerIndex::finalize_indexed(output, rep, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rewrite the genome columns of this index in-place according to `specs`.
|
|
||||||
///
|
|
||||||
/// The MPHF and unitig files are unchanged; only data matrices are rewritten.
|
|
||||||
pub fn select_in_place(
|
|
||||||
&mut self,
|
|
||||||
specs: &[OutputCol],
|
|
||||||
threshold: u32,
|
|
||||||
output_presence: bool,
|
output_presence: bool,
|
||||||
rep: &mut Reporter,
|
) -> Self {
|
||||||
) -> OKIResult<()> {
|
Self {
|
||||||
if self.state()? != IndexState::Indexed {
|
src,
|
||||||
return Err(OKIError::NotIndexed(self.root_path.clone()));
|
output: output.into(),
|
||||||
|
specs,
|
||||||
|
threshold: 0,
|
||||||
|
output_presence,
|
||||||
|
force: false,
|
||||||
|
sparse: true,
|
||||||
|
reporter: Reporter::new(),
|
||||||
|
on_progress: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let n_src_genomes = self.meta.genomes().map_err(OKIError::Io)?.len();
|
/// Minimum count to consider a genome as "carrying" a k-mer, for
|
||||||
let n_partitions = self.n_partitions();
|
/// logical (`Any`/`All`/`None`) aggregation ops (default: `0`).
|
||||||
|
pub fn threshold(mut self, v: u32) -> Self {
|
||||||
|
self.threshold = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
info!(
|
/// Remove a pre-existing index at `output` before creating the
|
||||||
"select (in-place): {} partition(s), {} source genome(s) → {} output column(s)",
|
/// selected one (default: `false`, fails if one already exists).
|
||||||
n_partitions,
|
pub fn force(mut self, v: bool) -> Self {
|
||||||
n_src_genomes,
|
self.force = v;
|
||||||
specs.len(),
|
self
|
||||||
);
|
}
|
||||||
|
|
||||||
let t = Stage::start("select");
|
/// Pack the output's presence matrices in the compact sparse format
|
||||||
let pb = progress_bar("select", n_partitions as u64, "partitions");
|
/// rather than dense (default: `true`).
|
||||||
|
pub fn sparse(mut self, v: bool) -> Self {
|
||||||
|
self.sparse = v;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
let order: Vec<usize> = (0..n_partitions).collect();
|
/// Progress callback, called once per completed partition.
|
||||||
let runner = PartitionRunner::new();
|
pub fn on_progress(mut self, cb: impl FnMut(Progress) + Send + 'a) -> Self {
|
||||||
runner
|
self.on_progress = Some(Box::new(cb));
|
||||||
.run(
|
self
|
||||||
&order,
|
}
|
||||||
|i| {
|
|
||||||
self.select_partition(
|
|
||||||
self,
|
|
||||||
i,
|
|
||||||
specs,
|
|
||||||
n_src_genomes,
|
|
||||||
threshold,
|
|
||||||
output_presence,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|_, _, _| {
|
|
||||||
pb.inc(1);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(OKIError::Partition)?;
|
|
||||||
|
|
||||||
pb.finish_and_clear();
|
/// Per-stage timing report accumulated during `run` — call after `run` returns.
|
||||||
rep.push(t.stop());
|
pub fn reporter(&self) -> &Reporter {
|
||||||
|
&self.reporter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut config = self.meta.config.clone();
|
impl Algorithm for Select<'_> {
|
||||||
config.with_counts = !output_presence;
|
type Output = KmerIndex;
|
||||||
let genomes: Vec<GenomeInfo> = specs
|
|
||||||
|
fn run(&mut self) -> obikalgorithm::Result<KmerIndex> {
|
||||||
|
let src = self.src;
|
||||||
|
let output = self.output.clone();
|
||||||
|
|
||||||
|
if src.state()? != IndexState::Indexed {
|
||||||
|
return Err(format!("{}: source index is not fully built", src.dir().display()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_src_genomes = src.meta().genomes()?.len();
|
||||||
|
info!(
|
||||||
|
"select: {} partition(s), {} source genome(s) → {} output column(s), output={}",
|
||||||
|
src.n_partitions(),
|
||||||
|
n_src_genomes,
|
||||||
|
self.specs.len(),
|
||||||
|
if self.output_presence { "presence" } else { "count" },
|
||||||
|
);
|
||||||
|
|
||||||
|
KmerIndex::clear_output_for_create(&output, self.force)?;
|
||||||
|
|
||||||
|
let mut config = src.meta().config.clone();
|
||||||
|
config.with_counts = !self.output_presence;
|
||||||
|
let genomes: Vec<GenomeInfo> = self
|
||||||
|
.specs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| GenomeInfo::new(s.label.clone()))
|
.map(|s| GenomeInfo::new(s.label.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
self.meta.rewrite_config(config, genomes).map_err(OKIError::Io)?;
|
|
||||||
self.meta = Arc::new(IndexMeta::open(self).map_err(OKIError::Io)?);
|
let t = Stage::start("select");
|
||||||
|
let dst = KmerIndex::create_skeleton(&output, config, genomes)?;
|
||||||
|
|
||||||
|
let n_partitions = src.n_partitions();
|
||||||
|
let order: Vec<usize> = (0..n_partitions).collect();
|
||||||
|
let runner = PartitionRunner::new();
|
||||||
|
let on_progress = &mut self.on_progress;
|
||||||
|
let mut done: u64 = 0;
|
||||||
|
|
||||||
|
runner
|
||||||
|
.run(
|
||||||
|
&order,
|
||||||
|
|i| select_partition(&dst, src, i, self.specs, self.threshold, self.output_presence),
|
||||||
|
|_, _, _| {
|
||||||
|
done += 1;
|
||||||
|
if let Some(cb) = on_progress.as_mut() {
|
||||||
|
cb(Progress {
|
||||||
|
position: done,
|
||||||
|
total: Some(n_partitions as u64),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||||
|
|
||||||
|
self.reporter.push(t.stop());
|
||||||
|
|
||||||
|
let t = Stage::start("finalize");
|
||||||
|
let dst = KmerIndex::finalize_indexed(&output, &mut self.reporter, self.sparse)?;
|
||||||
|
self.reporter.push(t.stop());
|
||||||
|
|
||||||
let t_pack = Stage::start("pack");
|
Ok(dst)
|
||||||
self.pack_matrices(false)?;
|
|
||||||
rep.push(t_pack.stop());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-233
@@ -1,14 +1,21 @@
|
|||||||
|
//! Per-layer column projection/aggregation — the mechanics
|
||||||
|
//! [`crate::select::Select`] runs once per partition-layer. Generic over
|
||||||
|
//! source content (`Count`/`Presence`) via `obicompactvec::MatrixGroupOps`
|
||||||
|
//! (object-safe: one `&dyn MatrixGroupOps` covers both matrix kinds, so
|
||||||
|
//! this never branches on source content beyond the single `match` that
|
||||||
|
//! opens it) and over destination content via [`DstBuilder`] — no
|
||||||
|
//! duplicated per-op-per-content code path.
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
use obicompactvec::{
|
use obicompactvec::{
|
||||||
ColGroup, MatrixGroupOps, PersistentBitMatrix, PersistentBitMatrixBuilder,
|
ColGroup, MatrixGroupOps, PersistentBitMatrix, PersistentBitMatrixBuilder,
|
||||||
PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder,
|
PersistentCompactIntMatrix, PersistentCompactIntMatrixBuilder, TempBitVec, TempCompactIntVec,
|
||||||
};
|
};
|
||||||
use obikindex::{OKIError, OKIResult};
|
use obikindex::layer::{KmerLayer, LayerContent};
|
||||||
|
use obikindex::{KmerIndex, OKIError, OKIResult};
|
||||||
use obikindex::KmerIndex;
|
|
||||||
|
|
||||||
// ── AggOp ─────────────────────────────────────────────────────────────────────
|
// ── AggOp ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -36,9 +43,67 @@ pub struct OutputCol {
|
|||||||
pub op: AggOp,
|
pub op: AggOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Group aggregation, generic over source content ───────────────────────────
|
||||||
|
|
||||||
|
/// One output column's aggregated values, still content-typed — the shape
|
||||||
|
/// `AggOp` naturally produces (`Any`/`All`/`None` → bits, `Sum`/`Min`/`Max`
|
||||||
|
/// → ints), independent of whether the *source* was itself count or
|
||||||
|
/// presence data.
|
||||||
|
enum AggResult {
|
||||||
|
Bit(TempBitVec),
|
||||||
|
Int(TempCompactIntVec),
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?),
|
||||||
|
AggOp::All => AggResult::Bit(mat.partial_group_all(&g, threshold)?),
|
||||||
|
AggOp::None => AggResult::Bit(mat.partial_group_none(&g, threshold)?),
|
||||||
|
AggOp::Sum => AggResult::Int(mat.partial_group_sum(&g)?),
|
||||||
|
AggOp::Min => AggResult::Int(mat.partial_group_min(&g)?),
|
||||||
|
AggOp::Max => AggResult::Int(mat.partial_group_max(&g)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DstBuilder — destination matrix, generic over output content ─────────────
|
||||||
|
|
||||||
|
enum DstBuilder {
|
||||||
|
Bit(PersistentBitMatrixBuilder),
|
||||||
|
Int(PersistentCompactIntMatrixBuilder),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DstBuilder {
|
||||||
|
fn new(output_presence: bool, n: usize, dir: &Path) -> io::Result<Self> {
|
||||||
|
Ok(if output_presence {
|
||||||
|
DstBuilder::Bit(PersistentBitMatrixBuilder::new(n, dir)?)
|
||||||
|
} else {
|
||||||
|
DstBuilder::Int(PersistentCompactIntMatrixBuilder::new(n, dir)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, r: AggResult) -> io::Result<()> {
|
||||||
|
match (self, r) {
|
||||||
|
(DstBuilder::Bit(b), AggResult::Bit(v)) => b.add_col_from(&v),
|
||||||
|
(DstBuilder::Bit(b), AggResult::Int(v)) => b.add_col_from_int(&v),
|
||||||
|
(DstBuilder::Int(b), AggResult::Bit(v)) => b.add_col_from_bit(&v),
|
||||||
|
(DstBuilder::Int(b), AggResult::Int(v)) => b.add_col_from(&v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(self) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
DstBuilder::Bit(b) => b.close(),
|
||||||
|
DstBuilder::Int(b) => b.close(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Copy all plain files (not subdirectories) from `src_dir` to `dst_dir`.
|
/// Copy all plain files (not subdirectories) from `src_dir` to `dst_dir` —
|
||||||
|
/// carries a layer's kmer identity (mphf/unitigs/evidence/fingerprint)
|
||||||
|
/// across unchanged; only the data matrix (a subdirectory) is rebuilt.
|
||||||
fn copy_layer_files(src_dir: &Path, dst_dir: &Path) -> io::Result<()> {
|
fn copy_layer_files(src_dir: &Path, dst_dir: &Path) -> io::Result<()> {
|
||||||
for entry in fs::read_dir(src_dir)? {
|
for entry in fs::read_dir(src_dir)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
@@ -50,248 +115,54 @@ fn copy_layer_files(src_dir: &Path, dst_dir: &Path) -> io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── fill_builders ─────────────────────────────────────────────────────────────
|
// ── select_partition ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn fill_builders(
|
/// Rewrite partition `i`'s data matrices from `src` into `dst` according to
|
||||||
specs: &[OutputCol],
|
/// `specs`. Every layer's kmer identity is copied as-is; only the genome
|
||||||
src_layer_dir: &Path,
|
/// columns are recomputed.
|
||||||
src_is_count: bool,
|
pub(crate) fn select_partition(
|
||||||
threshold: u32,
|
dst: &KmerIndex,
|
||||||
output_presence: bool,
|
|
||||||
mut dst_bit: Option<&mut PersistentBitMatrixBuilder>,
|
|
||||||
mut dst_int: Option<&mut PersistentCompactIntMatrixBuilder>,
|
|
||||||
) -> OKIResult<()> {
|
|
||||||
if src_is_count {
|
|
||||||
let mat = PersistentCompactIntMatrix::open(src_layer_dir).map_err(OKIError::Io)?;
|
|
||||||
for spec in specs {
|
|
||||||
let g = ColGroup::new(&spec.label, spec.indices.clone());
|
|
||||||
if output_presence {
|
|
||||||
let b = dst_bit.as_deref_mut().unwrap();
|
|
||||||
match spec.op {
|
|
||||||
AggOp::Any => {
|
|
||||||
b.add_col_from(&mat.partial_group_any(&g, threshold).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::All => {
|
|
||||||
b.add_col_from(&mat.partial_group_all(&g, threshold).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::None => {
|
|
||||||
b.add_col_from(&mat.partial_group_none(&g, threshold).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Sum => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_sum(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Min => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_min(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Max => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_max(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(OKIError::Io)?;
|
|
||||||
} else {
|
|
||||||
let b = dst_int.as_deref_mut().unwrap();
|
|
||||||
match spec.op {
|
|
||||||
AggOp::Sum => b.add_col_from(&mat.partial_group_sum(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Min => b.add_col_from(&mat.partial_group_min(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Max => b.add_col_from(&mat.partial_group_max(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Any => b.add_col_from_bit(
|
|
||||||
&mat.partial_group_any(&g, threshold).map_err(OKIError::Io)?,
|
|
||||||
),
|
|
||||||
AggOp::All => b.add_col_from_bit(
|
|
||||||
&mat.partial_group_all(&g, threshold).map_err(OKIError::Io)?,
|
|
||||||
),
|
|
||||||
AggOp::None => b.add_col_from_bit(
|
|
||||||
&mat.partial_group_none(&g, threshold).map_err(OKIError::Io)?,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
.map_err(OKIError::Io)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mat = PersistentBitMatrix::open(src_layer_dir).map_err(OKIError::Io)?;
|
|
||||||
for spec in specs {
|
|
||||||
let g = ColGroup::new(&spec.label, spec.indices.clone());
|
|
||||||
if output_presence {
|
|
||||||
let b = dst_bit.as_deref_mut().unwrap();
|
|
||||||
match spec.op {
|
|
||||||
AggOp::Any => {
|
|
||||||
b.add_col_from(&mat.partial_group_any(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::All => {
|
|
||||||
b.add_col_from(&mat.partial_group_all(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::None => {
|
|
||||||
b.add_col_from(&mat.partial_group_none(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Sum => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_sum(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Min => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_min(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::Max => {
|
|
||||||
b.add_col_from_int(&mat.partial_group_max(&g).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(OKIError::Io)?;
|
|
||||||
} else {
|
|
||||||
let b = dst_int.as_deref_mut().unwrap();
|
|
||||||
match spec.op {
|
|
||||||
AggOp::Sum => b.add_col_from(&mat.partial_group_sum(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Min => b.add_col_from(&mat.partial_group_min(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Max => b.add_col_from(&mat.partial_group_max(&g).map_err(OKIError::Io)?),
|
|
||||||
AggOp::Any => {
|
|
||||||
b.add_col_from_bit(&mat.partial_group_any(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::All => {
|
|
||||||
b.add_col_from_bit(&mat.partial_group_all(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
AggOp::None => {
|
|
||||||
b.add_col_from_bit(&mat.partial_group_none(&g, 1).map_err(OKIError::Io)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(OKIError::Io)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── KmerPartition::select_partition ──────────────────────────────────────────
|
|
||||||
|
|
||||||
impl KmerIndex {
|
|
||||||
/// Rewrite the data matrices of partition `i` in `src` into `self`.
|
|
||||||
///
|
|
||||||
/// `specs` defines the output columns (projection/aggregation).
|
|
||||||
/// `output_presence` — if true, all output builders use bit (0/1) format.
|
|
||||||
/// `in_place` — `self` and `src` share the same root; write to temp dirs then swap.
|
|
||||||
pub fn select_partition(
|
|
||||||
&self,
|
|
||||||
src: &KmerIndex,
|
src: &KmerIndex,
|
||||||
i: usize,
|
i: usize,
|
||||||
specs: &[OutputCol],
|
specs: &[OutputCol],
|
||||||
_n_src_genomes: usize,
|
|
||||||
threshold: u32,
|
threshold: u32,
|
||||||
output_presence: bool,
|
output_presence: bool,
|
||||||
in_place: bool,
|
) -> OKIResult<()> {
|
||||||
) -> OKIResult<()> {
|
let src_partition = src.partition(i)?;
|
||||||
let src_index_dir = src.index_dir(i);
|
let dst_partition = dst.partition(i)?;
|
||||||
if !src_index_dir.exists() {
|
let n_layers = src_partition.n_layers();
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let n_src_layers = src.n_layers(i)?;
|
for l in 0..n_layers {
|
||||||
if n_src_layers == 0 {
|
let src_layer = src_partition.layer(l)?.open()?;
|
||||||
return Ok(());
|
let n = src_layer.n();
|
||||||
}
|
|
||||||
|
|
||||||
let dst_index_dir = self.index_dir(i);
|
let dst_layer_dir = KmerLayer::new(&dst_partition, l)
|
||||||
if !in_place {
|
.create()
|
||||||
fs::create_dir_all(&dst_index_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data_subdir = if output_presence {
|
|
||||||
"presence"
|
|
||||||
} else {
|
|
||||||
"counts"
|
|
||||||
};
|
|
||||||
|
|
||||||
for l in 0..n_src_layers {
|
|
||||||
let src_layer_dir = src.layer_dir(i, l);
|
|
||||||
if !src_layer_dir.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dst_layer_dir = self.layer_dir(i, l);
|
|
||||||
|
|
||||||
let counts_dir = src_layer_dir.join("counts");
|
|
||||||
let presence_dir = src_layer_dir.join("presence");
|
|
||||||
let src_is_count = counts_dir.exists() && !presence_dir.exists();
|
|
||||||
|
|
||||||
// Determine number of slots and detect implicit layers.
|
|
||||||
let n = if counts_dir.exists() {
|
|
||||||
PersistentCompactIntMatrix::open(&src_layer_dir)
|
|
||||||
.map_err(OKIError::Io)?
|
.map_err(OKIError::Io)?
|
||||||
.n()
|
.dir()
|
||||||
} else if presence_dir.exists() {
|
.to_path_buf();
|
||||||
PersistentBitMatrix::open(&src_layer_dir)
|
copy_layer_files(src_layer.dir(), &dst_layer_dir).map_err(OKIError::Io)?;
|
||||||
.map_err(OKIError::Io)?
|
|
||||||
.n()
|
let group_mat: Box<dyn MatrixGroupOps> = match src_layer.content() {
|
||||||
} else {
|
LayerContent::Count => {
|
||||||
// Implicit single-genome layer: no data matrix needed in output either.
|
Box::new(PersistentCompactIntMatrix::open(src_layer.dir()).map_err(OKIError::Io)?)
|
||||||
if !in_place {
|
}
|
||||||
fs::create_dir_all(&dst_layer_dir)?;
|
LayerContent::Presence => {
|
||||||
copy_layer_files(&src_layer_dir, &dst_layer_dir)?;
|
Box::new(PersistentBitMatrix::open(src_layer.dir()).map_err(OKIError::Io)?)
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Choose the output data directory (temp name for in-place).
|
let data_subdir = if output_presence { "presence" } else { "counts" };
|
||||||
let (dst_data_dir, final_data_dir): (PathBuf, PathBuf) = if in_place {
|
let data_dir = dst_layer_dir.join(data_subdir);
|
||||||
let tmp = dst_layer_dir.join(format!("{data_subdir}_new"));
|
fs::create_dir_all(&data_dir).map_err(OKIError::Io)?;
|
||||||
let perm = dst_layer_dir.join(data_subdir);
|
|
||||||
(tmp, perm)
|
|
||||||
} else {
|
|
||||||
let perm = dst_layer_dir.join(data_subdir);
|
|
||||||
(perm.clone(), perm)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !in_place {
|
let mut builder = DstBuilder::new(output_presence, n, &data_dir).map_err(OKIError::Io)?;
|
||||||
fs::create_dir_all(&dst_layer_dir)?;
|
for spec in specs {
|
||||||
copy_layer_files(&src_layer_dir, &dst_layer_dir)?;
|
let r = compute_group(group_mat.as_ref(), spec, threshold).map_err(OKIError::Io)?;
|
||||||
|
builder.add(r).map_err(OKIError::Io)?;
|
||||||
}
|
}
|
||||||
fs::create_dir_all(&dst_data_dir)?;
|
builder.close().map_err(OKIError::Io)?;
|
||||||
|
|
||||||
let (mut dst_bit, mut dst_int) = if output_presence {
|
|
||||||
(
|
|
||||||
Some(PersistentBitMatrixBuilder::new(n, &dst_data_dir).map_err(OKIError::Io)?),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(
|
|
||||||
PersistentCompactIntMatrixBuilder::new(n, &dst_data_dir)
|
|
||||||
.map_err(OKIError::Io)?,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
fill_builders(
|
|
||||||
specs,
|
|
||||||
&src_layer_dir,
|
|
||||||
src_is_count,
|
|
||||||
threshold,
|
|
||||||
output_presence,
|
|
||||||
dst_bit.as_mut(),
|
|
||||||
dst_int.as_mut(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if output_presence {
|
|
||||||
dst_bit.unwrap().close().map_err(OKIError::Io)?;
|
|
||||||
} else {
|
|
||||||
dst_int.unwrap().close().map_err(OKIError::Io)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In-place: swap old data dir for new.
|
|
||||||
if in_place {
|
|
||||||
let old_data_dir = if src_is_count {
|
|
||||||
dst_layer_dir.join("counts")
|
|
||||||
} else {
|
|
||||||
dst_layer_dir.join("presence")
|
|
||||||
};
|
|
||||||
if old_data_dir.exists() {
|
|
||||||
fs::remove_dir_all(&old_data_dir)?;
|
|
||||||
}
|
|
||||||
fs::rename(&dst_data_dir, &final_data_dir)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !in_place {
|
|
||||||
src.partition_meta(i)?
|
|
||||||
.save(&dst_index_dir)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user