Add phylogenetic export support for TNT, PhyG, and IQ-TREE
Extends the phylogenetic pipeline with exporters for TNT, PhyG, and IQ-TREE that generate executable scripts, cost matrices, and recoded alignments. Adds internal helpers for state indexing, floating-point matrix scaling, and Floyd-Warshall metric closure to satisfy external tool constraints. Adjusts visibility modifiers for sibling iterators and entropy annex structs, and updates design documentation for evolutionary distance metrics.
This commit is contained in:
@@ -27,20 +27,21 @@ mod subsample;
|
||||
|
||||
use obikidxcache::index_cache::IndexCache;
|
||||
|
||||
pub use cardcomp::{cardinality_transition_probs, composition_transition_probs, pairwise_cost_matrix};
|
||||
pub use alignment::SnpAlignment;
|
||||
pub use cardcomp::{
|
||||
cardinality_transition_probs, composition_transition_probs, pairwise_cost_matrix,
|
||||
};
|
||||
pub use pairwise::{BasePairTally, CardinalityTally, RawSnpDistanceOutput};
|
||||
pub use sankoff::SankoffBundle;
|
||||
pub use stats::SiblingAnnexStats;
|
||||
pub use subsample::EntropyBias;
|
||||
pub use subsample::{EntropyBias, SurvivingFamily};
|
||||
|
||||
pub(crate) use alignment::snp_pseudo_alignment;
|
||||
pub(crate) use annex::build_layer_sibling_annex;
|
||||
pub(crate) use entropy::{ensure_layer_entropy_annex, family_entropy, family_entropy_4, iter_full_entropy};
|
||||
pub(crate) use entropy::{ensure_layer_entropy_annex, family_entropy, family_entropy_4};
|
||||
pub(crate) use family_scan::{Selection, scan_layer_families};
|
||||
pub(crate) use sankoff::sankoff_bundle;
|
||||
pub(crate) use stats::{sibling_annex_stats, sibling_family_size_histogram};
|
||||
pub(crate) use subsample::sample_index;
|
||||
|
||||
/// Whether every layer number in `cache` fits in a `FamilyMask` field
|
||||
/// (`< 7`) — a single fact about the whole index, decided once here so
|
||||
@@ -51,5 +52,10 @@ pub(crate) use subsample::sample_index;
|
||||
/// interprets that field (`SiblingExt::build_sibling_annex`,
|
||||
/// `SiblingBuilder::ensure_entropy_annexes`), so they can never disagree.
|
||||
pub(crate) fn is_fast_mode(cache: &IndexCache) -> bool {
|
||||
cache.partitions().filter_map(|p| cache.n_layer(p)).max().unwrap_or(0) <= 7
|
||||
cache
|
||||
.partitions()
|
||||
.filter_map(|p| cache.n_layer(p))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
<= 7
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ const MAX_TOPUP_ROUNDS: usize = 5;
|
||||
/// outlive that callback, but a `SurvivingFamily` is meant to be handed to
|
||||
/// [`sample_index`]'s caller as part of a whole layer's batch, well after
|
||||
/// that callback returns.
|
||||
pub(crate) struct SurvivingFamily {
|
||||
pub struct SurvivingFamily {
|
||||
pub family_idx: usize,
|
||||
pub mask: FamilyMask,
|
||||
pub genome_mask: Vec<u8>,
|
||||
@@ -162,7 +162,10 @@ pub(crate) fn sample_index(
|
||||
}
|
||||
}
|
||||
|
||||
let total_eligible: u64 = layers.iter().map(|(_, _, e, _)| e.view().count_ones()).sum();
|
||||
let total_eligible: u64 = layers
|
||||
.iter()
|
||||
.map(|(_, _, e, _)| e.view().count_ones())
|
||||
.sum();
|
||||
if total_eligible == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
@@ -272,7 +275,9 @@ fn sample_layer(
|
||||
Some(_) => Box::new(circular_entropy_values(&layer_dir, round_start)?),
|
||||
None => Box::new(std::iter::repeat(0.0f32)), // never read: kernel forced to 1.0 below
|
||||
};
|
||||
let candidates = (round_start..n_minorants).chain(0..round_start).zip(entropies);
|
||||
let candidates = (round_start..n_minorants)
|
||||
.chain(0..round_start)
|
||||
.zip(entropies);
|
||||
|
||||
let mut accepted: HashSet<usize> = HashSet::new();
|
||||
for (family_idx, entropy) in candidates {
|
||||
|
||||
@@ -35,41 +35,47 @@ const SENTINEL: f32 = -1.0;
|
||||
|
||||
pub(crate) const ENTROPY_ANNEX_FILE_NAME: &str = "entropy.pent";
|
||||
|
||||
pub(crate) struct EntropyAnnex {
|
||||
pub struct EntropyAnnex {
|
||||
mmap: Mmap,
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl EntropyAnnex {
|
||||
pub(crate) fn open(path: &Path) -> io::Result<Self> {
|
||||
pub fn open(path: &Path) -> io::Result<Self> {
|
||||
let mmap = unsafe { Mmap::map(&File::open(path)?)? };
|
||||
if mmap.len() < HEADER_SIZE {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "PENT file too short"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"PENT file too short",
|
||||
));
|
||||
}
|
||||
if mmap[0..4] != MAGIC {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "bad PENT magic"));
|
||||
}
|
||||
let n = u64::from_le_bytes(mmap[8..16].try_into().unwrap()) as usize;
|
||||
if mmap.len() < HEADER_SIZE + n * 4 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "PENT file truncated"));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"PENT file truncated",
|
||||
));
|
||||
}
|
||||
Ok(Self { mmap, n })
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub fn len(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
|
||||
/// `None` for the sentinel (not yet computed — shouldn't happen against
|
||||
/// a fully-built annex).
|
||||
pub(crate) fn get(&self, idx: usize) -> Option<f32> {
|
||||
pub fn get(&self, idx: usize) -> Option<f32> {
|
||||
let off = HEADER_SIZE + idx * 4;
|
||||
let v = f32::from_le_bytes(self.mmap[off..off + 4].try_into().unwrap());
|
||||
(v >= 0.0).then_some(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EntropyAnnexBuilder {
|
||||
pub struct EntropyAnnexBuilder {
|
||||
mmap: MmapMut,
|
||||
}
|
||||
|
||||
@@ -79,7 +85,10 @@ impl EntropyAnnexBuilder {
|
||||
pub(crate) fn new(n: usize, path: &Path) -> io::Result<Self> {
|
||||
let file_size = HEADER_SIZE + n * 4;
|
||||
let file = OpenOptions::new()
|
||||
.read(true).write(true).create(true).truncate(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
file.set_len(file_size as u64)?;
|
||||
let mut mmap = unsafe { MmapMut::map_mut(&file)? };
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
//! | batch | [`SiblingBatchIter`] | [`MinorantBatchIter`] |
|
||||
//!
|
||||
//! No separate "enumerate" variant (unlike `KmerIter`/`enumerate_kmers`):
|
||||
//! [`SiblingEntry`] already carries `order` for free, since pairing with
|
||||
//! the annex requires it anyway.
|
||||
//! pairing with the annex is a plain positional `zip` internal to
|
||||
//! [`SiblingIter`] (a running counter indexing straight into the annex's
|
||||
//! mmap) — nothing a consumer needs to see, so [`SiblingEntry`] doesn't
|
||||
//! carry it.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -31,19 +33,17 @@ use obikseq::CanonicalKmer;
|
||||
|
||||
use super::{FamilyMask, SiblingAnnex};
|
||||
|
||||
/// One layer entry: a k-mer's position in the layer's iteration order (the
|
||||
/// same index the sibling annex is keyed on — not an MPHF slot), the k-mer
|
||||
/// itself, and its family mask.
|
||||
/// One layer entry: a k-mer and its family mask, paired positionally with
|
||||
/// the sibling annex (see [`SiblingIter`]'s own docs for how).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SiblingEntry {
|
||||
pub order: usize,
|
||||
pub struct SiblingEntry {
|
||||
pub kmer: CanonicalKmer,
|
||||
pub mask: FamilyMask,
|
||||
}
|
||||
|
||||
/// Streams `(order, kmer, mask)` triples for one layer, in iteration order.
|
||||
/// Streams `(kmer, mask)` pairs for one layer, in iteration order.
|
||||
/// Produced by [`SiblingLayerExt::iter_siblings`].
|
||||
pub(crate) struct SiblingIter {
|
||||
pub struct SiblingIter {
|
||||
kmers: KmerIter,
|
||||
annex: Arc<SiblingAnnex>,
|
||||
order: usize,
|
||||
@@ -61,7 +61,7 @@ impl Iterator for SiblingIter {
|
||||
// docs) — shouldn't happen against a fully-built annex, but
|
||||
// skip rather than misalign the two streams if it does.
|
||||
if let Some(mask) = self.annex.get(order) {
|
||||
return Some(SiblingEntry { order, kmer, mask });
|
||||
return Some(SiblingEntry { kmer, mask });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ impl Iterator for SiblingIter {
|
||||
|
||||
/// Batches of [`SiblingIter`]'s entries, `batch_size` at a time — the last
|
||||
/// batch may be shorter. Produced by [`SiblingLayerExt::iter_siblings_batch`].
|
||||
pub(crate) struct SiblingBatchIter {
|
||||
pub struct SiblingBatchIter {
|
||||
inner: SiblingIter,
|
||||
batch_size: usize,
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl Iterator for SiblingBatchIter {
|
||||
/// Like [`SiblingIter`], filtered to the minorant of each family — the
|
||||
/// common case, since a family is tallied once, at its minorant. Produced
|
||||
/// by [`SiblingLayerExt::iter_minorants`].
|
||||
pub(crate) struct MinorantIter {
|
||||
pub struct MinorantIter {
|
||||
inner: SiblingIter,
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Iterator for MinorantIter {
|
||||
/// Batches of [`MinorantIter`]'s entries, `batch_size` at a time — the last
|
||||
/// batch may be shorter. Produced by
|
||||
/// [`SiblingLayerExt::iter_minorants_batch`].
|
||||
pub(crate) struct MinorantBatchIter {
|
||||
pub struct MinorantBatchIter {
|
||||
inner: MinorantIter,
|
||||
batch_size: usize,
|
||||
}
|
||||
@@ -131,7 +131,7 @@ fn collect_batch<I: Iterator>(inner: &mut I, batch_size: usize) -> Option<Vec<I:
|
||||
/// (`LayerData`) rather than implemented once per matrix kind: kmer
|
||||
/// iteration doesn't depend on the data payload, and `TypedLayer<D>` already
|
||||
/// carries `iter_kmers`/`hash_batch`/`fill_sub_matrix_carries` for every `D`.
|
||||
pub(crate) trait SiblingLayerExt {
|
||||
pub trait SiblingLayerExt {
|
||||
/// Zip this layer's k-mers with their sibling-annex entry, in iteration
|
||||
/// order. `annex` must have been built from this same layer (its length
|
||||
/// must match the layer's k-mer count).
|
||||
|
||||
@@ -20,14 +20,18 @@
|
||||
//! reverse.
|
||||
|
||||
pub mod algorithms;
|
||||
pub mod extensions;
|
||||
mod entropy_annex;
|
||||
pub mod extensions;
|
||||
mod helpers;
|
||||
mod iter;
|
||||
pub mod iter;
|
||||
mod siblingannex;
|
||||
|
||||
pub(crate) use entropy_annex::{ENTROPY_ANNEX_FILE_NAME, EntropyAnnex, EntropyAnnexBuilder};
|
||||
pub(crate) use siblingannex::{FamilyMask, SiblingAnnex, SiblingAnnexBuilder};
|
||||
pub(crate) use entropy_annex::ENTROPY_ANNEX_FILE_NAME;
|
||||
pub use entropy_annex::{EntropyAnnex, EntropyAnnexBuilder};
|
||||
pub use iter::{
|
||||
MinorantBatchIter, MinorantIter, SiblingBatchIter, SiblingEntry, SiblingIter, SiblingLayerExt,
|
||||
};
|
||||
pub use siblingannex::{FamilyMask, SiblingAnnex, SiblingAnnexBuilder};
|
||||
|
||||
pub use algorithms::{
|
||||
BasePairTally, CardinalityTally, EntropyBias, RawSnpDistanceOutput, SankoffBundle,
|
||||
|
||||
Reference in New Issue
Block a user