Add unitig extraction command to obikdump and obikmer2

Introduces a new unitig extraction feature that reads k-mers from an index, filters them by group metadata, and partitions the computation using rayon. The implementation constructs per-partition de Bruijn graphs, merges them, computes node degrees, and writes the resulting sequences as FASTA. A corresponding CLI command is added to obikmer2 to expose this functionality with configurable filtering and progress reporting.
This commit is contained in:
Eric Coissac
2026-08-26 14:40:02 +02:00
parent 768fa0183d
commit 96dfb5300b
7 changed files with 129 additions and 5 deletions
+7 -5
View File
@@ -4,8 +4,10 @@ version = "0.1.0"
edition = "2024"
[dependencies]
obikindex = { path = "../obikindex" }
obikfilter = { path = "../obikfilter" }
obikidxcache = { path = "../obikidxcache" }
obikseq = { path = "../obikseq" }
rayon = "1"
obikindex = { path = "../obikindex" }
obikfilter = { path = "../obikfilter" }
obikidxcache = { path = "../obikidxcache" }
obikseq = { path = "../obikseq" }
obidebruinj = { path = "../obidebruinj" }
obifastwrite = { path = "../obifastwrite" }
rayon = "1"
+2
View File
@@ -6,5 +6,7 @@
//! reverse), same pattern as `obikindexer`/`obikquery`.
mod dump;
mod unitig;
pub use dump::IndexDump;
pub use unitig::IndexUnitigs;
+68
View File
@@ -0,0 +1,68 @@
//! Assembling the full k-mer set of a *complete* source index into unitigs
//! and writing them as FASTA — the same `IndexCache`-per-partition,
//! `FilteredPartitionIter` read path as [`crate::IndexDump::dump`], feeding
//! an `obidebruinj::GraphDeBruijn` instead of a CSV row. `KmerIndex` is a
//! foreign type, so this is an extension trait rather than an inherent `impl`.
use std::io::Write;
use obidebruinj::GraphDeBruijn;
use obifastwrite::write_unitig;
use obikfilter::{FilteredPartitionIter, KmerFilter};
use obikidxcache::index_cache::IndexCache;
use obikindex::{KmerIndex, OKIError, OKIResult};
use rayon::prelude::*;
pub trait IndexUnitigs {
/// Build the de Bruijn graph from every partition's filtered kmers, then
/// write the resulting unitigs as FASTA to `out`. Returns the number of
/// unitigs written.
fn write_unitigs<W: Write + Send, F: Fn() + Send + Sync>(
&self,
out: &mut W,
filters: &[Box<dyn KmerFilter>],
on_partition: F,
) -> OKIResult<usize>;
}
impl IndexUnitigs for KmerIndex {
fn write_unitigs<W: Write + Send, F: Fn() + Send + Sync>(
&self,
out: &mut W,
filters: &[Box<dyn KmerFilter>],
on_partition: F,
) -> OKIResult<usize> {
let k = self.kmer_size();
let n_genomes = self.meta().genomes().map_err(OKIError::Io)?.len().max(1);
let use_counts = self.meta().config.with_counts;
let n = self.n_partitions();
let g = (0..n)
.into_par_iter()
.try_fold(GraphDeBruijn::new, |mut local_g, i| -> OKIResult<GraphDeBruijn> {
let cache = IndexCache::new(self, Some(vec![i]));
cache.iter_partition_kmers(i, use_counts, n_genomes, filters, |kmer, _row| {
local_g.push(kmer);
true
})?;
on_partition();
Ok(local_g)
})
.try_reduce(GraphDeBruijn::new, |mut a, b| {
a.merge(b);
Ok(a)
})?;
g.compute_degrees_and_mark_starts();
let mut n_written = 0usize;
g.try_for_each_unitig(|unitig| {
write_unitig(unitig, k, 0, n_written, out)?;
n_written += 1;
Ok(())
})
.map_err(OKIError::Io)?;
Ok(n_written)
}
}