Extract phylogenetic sibling logic into new obikphylo crate
Relocate the `siblings` and `cardcomp` modules from `obikindex` to a dedicated `obikphylo` workspace member. Convert inherent methods on `KmerIndex` into extension traits, update import paths across `obikmer`, and add supporting accessor methods to `obikseq` and `obilayeredmap`. This restructuring reduces the public API surface of `obikindex` while organizing phylogenetic iteration, caching, and distance calculation logic under a dedicated crate.
This commit is contained in:
@@ -367,6 +367,50 @@ impl<L: KmerLength> CanonicalKmerOf<L> {
|
||||
pub fn into_kmer(self) -> KmerOf<L> {
|
||||
KmerOf(self.0, PhantomData)
|
||||
}
|
||||
|
||||
/// This k-mer's own minimiser — as a standalone unit, not part of a
|
||||
/// streamed sequence. Enumerates all `L::len() - MLen::len() + 1`
|
||||
/// windows directly on the packed 2-bit representation (no ASCII
|
||||
/// round-trip, no rolling state, no entropy tracking) and keeps the one
|
||||
/// with the lowest [`hash_kmer`] — the same selection rule
|
||||
/// `obiskbuilder::rolling_stat::RollingStat` computes incrementally
|
||||
/// along a sequence, replicated here in `O(k)` pure bit arithmetic for
|
||||
/// a single isolated k-mer (the case a streaming rolling scan is
|
||||
/// unnecessary machinery for). Ties (equal hash) keep the last
|
||||
/// (highest-position) window, matching `RollingStat`'s monotonic-deque
|
||||
/// eviction rule (`>=` pops the previous record) — a formal detail, not
|
||||
/// a practical concern: a `mix64` collision between two distinct
|
||||
/// m-mers is vanishingly unlikely.
|
||||
pub fn minimizer(&self) -> Minimizer {
|
||||
let k = L::len();
|
||||
let ml = MLen::len();
|
||||
let rc = self.revcomp().raw();
|
||||
let mut best_canon: RawKmer = 0;
|
||||
let mut best_hash = u64::MAX;
|
||||
for p in 0..=(k - ml) {
|
||||
let fwd = (self.0 << (2 * p)) >> (KMER_BITS - 2 * ml);
|
||||
let rev = (rc << (2 * (k - ml - p))) >> (KMER_BITS - 2 * ml);
|
||||
let canon = fwd.min(rev);
|
||||
let hash = hash_kmer(canon << (KMER_BITS - 2 * ml));
|
||||
if hash < best_hash {
|
||||
best_hash = hash;
|
||||
best_canon = canon;
|
||||
}
|
||||
}
|
||||
Minimizer::from_raw_unchecked(best_canon << (KMER_BITS - 2 * ml))
|
||||
}
|
||||
|
||||
/// Destination partition for this k-mer, using the project-wide routing
|
||||
/// rule (`minimizer().seq_hash() & mask`) — the same rule
|
||||
/// `KmerPartition`/`RoutableSuperKmer` apply to k-mers read from a
|
||||
/// streamed sequence, here for a standalone k-mer (e.g. a synthetic
|
||||
/// variant generated outside any sequence). `n_partitions` must be a
|
||||
/// power of two.
|
||||
#[inline]
|
||||
pub fn partition(&self, n_partitions: usize) -> usize {
|
||||
let mask = (n_partitions as u64) - 1;
|
||||
(self.minimizer().seq_hash() & mask) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: KmerLength> Sequence for CanonicalKmerOf<L> {
|
||||
|
||||
Reference in New Issue
Block a user