feat: implement per-k-mer sibling counts and central neighbor generation
Introduce the siblingannex module in obicompactvec to store per-slot minorant flags and sibling counts in a memory-mapped annex file. Add a scatter-gather pipeline in obikindex to compute these values across index layers and write them to .psib files. Implement central_canonical_neighbors in obikseq for generating strand-aware k-mer variants around the middle base. Expose rolling statistics in obiskbuilder and update dependency graphs accordingly.
This commit is contained in:
@@ -210,4 +210,46 @@ mod tests {
|
||||
check!(31);
|
||||
check!(32);
|
||||
}
|
||||
|
||||
// ── central_canonical_neighbors ─────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn central_canonical_neighbors_hand_checked_k3() {
|
||||
// k=3, centre = index 1. For "ACG", every one of the 4 central
|
||||
// substitutions ("AAG","ACG","AGG","ATG") happens to stay in forward
|
||||
// orientation when canonicalised (verified by hand: each is already
|
||||
// lexicographically <= its own reverse complement), so this case
|
||||
// exercises the substitution logic without the RC-flip edge case.
|
||||
let ck = KmerOf::<ConstLen<3>>::from_ascii(b"ACG").unwrap().canonical();
|
||||
let neighbours = ck.central_canonical_neighbors();
|
||||
let ascii: Vec<Vec<u8>> = neighbours.iter().map(|n| n.to_ascii()).collect();
|
||||
assert_eq!(ascii, vec![b"AAG".to_vec(), b"ACG".to_vec(), b"AGG".to_vec(), b"ATG".to_vec()]);
|
||||
// The identity substitution (centre unchanged) must reproduce `ck`.
|
||||
assert!(neighbours.contains(&ck));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn central_canonical_neighbors_identity_present_for_various_k() {
|
||||
macro_rules! check {
|
||||
($n:expr) => {{
|
||||
let ck = KmerOf::<ConstLen<$n>>::from_ascii(&make_seq::<$n>())
|
||||
.unwrap()
|
||||
.canonical();
|
||||
let neighbours = ck.central_canonical_neighbors();
|
||||
assert!(
|
||||
neighbours.contains(&ck),
|
||||
"identity substitution missing from central_canonical_neighbors for k={}",
|
||||
$n
|
||||
);
|
||||
// Every returned neighbour must itself already be canonical.
|
||||
for n in &neighbours {
|
||||
assert_eq!(n.into_kmer().canonical(), *n, "neighbour not canonical for k={}", $n);
|
||||
}
|
||||
}};
|
||||
}
|
||||
check!(1);
|
||||
check!(3);
|
||||
check!(5);
|
||||
check!(31);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user