use rayon::prelude::*;
use mafft_tree::{
DistanceMatrix, musclesupg, ClusterMethod, ktuple_distance, Topology,
};
use mafft_types::ScoringContext;
use crate::progressive::{MergeOrAlign, MultipleAlignment};
pub fn add_sequences(
existing: &MultipleAlignment,
new_sequences: &[Vec<u8>],
new_names: &[String],
scoring: &ScoringContext,
use_fft: bool,
) -> MultipleAlignment {
if new_sequences.is_empty() {
return existing.clone();
}
let n_existing = existing.nseq();
let n_new = new_sequences.len();
if n_existing == 0 {
return crate::progressive::progressive_align(
new_sequences,
new_names,
&musclesupg(&compute_ktuple_dm(new_sequences), ClusterMethod::default()),
scoring,
use_fft,
None,
);
}
let stripped_existing = commongappick(&existing.sequences);
let mut all_seqs: Vec<Vec<u8>> = stripped_existing;
for s in new_sequences {
let raw: Vec<u8> = s.iter().filter(|&&c| c != b'-').copied().collect();
all_seqs.push(raw);
}
let mut all_names: Vec<String> = existing.names.clone();
all_names.extend(new_names.iter().cloned());
let ungapped: Vec<Vec<u8>> = all_seqs
.iter()
.map(|s| s.iter().filter(|&&c| c != b'-').copied().collect())
.collect();
let dm = compute_ktuple_dm(&ungapped);
let topo = musclesupg(&dm, ClusterMethod::default());
let mergeoralign = compute_mergeoralign(&topo, n_existing, n_new);
crate::progressive::progressive_align_with_mergeoralign_n(
&all_seqs,
&all_names,
&topo,
&mergeoralign,
scoring,
use_fft,
n_existing,
)
}
pub fn add_sequences_keeplength(
existing: &MultipleAlignment,
new_sequences: &[Vec<u8>],
new_names: &[String],
scoring: &ScoringContext,
use_fft: bool,
) -> MultipleAlignment {
let (msa, _) = add_sequences_keeplength_with_map(
existing, new_sequences, new_names, scoring, use_fft,
);
msa
}
pub fn add_sequences_keeplength_with_map(
existing: &MultipleAlignment,
new_sequences: &[Vec<u8>],
new_names: &[String],
scoring: &ScoringContext,
use_fft: bool,
) -> (MultipleAlignment, Vec<Vec<(usize, usize)>>) {
if new_sequences.is_empty() {
return (existing.clone(), Vec::new());
}
let target_width = existing.sequences.first().map(|s| s.len()).unwrap_or(0);
let n_existing = existing.nseq();
let nadd = new_sequences.len();
let mut full = add_sequences(existing, new_sequences, new_names, scoring, use_fft);
let width = full.sequences.first().map(|s| s.len()).unwrap_or(0);
let mut keep = vec![false; width];
for col in 0..width {
let mut any_existing_residue = false;
for s in full.sequences.iter().take(n_existing) {
if let Some(&c) = s.get(col) {
if c != b'-' {
any_existing_residue = true;
break;
}
}
}
keep[col] = any_existing_residue;
}
let mut deletelist: Vec<Vec<(usize, usize)>> = Vec::with_capacity(nadd);
for i in 0..nadd {
let aligned = &full.sequences[n_existing + i];
let mut entries: Vec<(usize, usize)> = Vec::new();
let mut addbk_pos: usize = 0;
let mut run_start: usize = 0;
let mut run_len: usize = 0;
for (col, &c) in aligned.iter().enumerate() {
if c == b'-' {
continue;
}
if keep[col] {
if run_len > 0 {
entries.push((run_start, run_len));
run_len = 0;
}
} else {
if run_len == 0 { run_start = addbk_pos; }
run_len += 1;
}
addbk_pos += 1;
}
if run_len > 0 {
entries.push((run_start, run_len));
}
deletelist.push(entries);
}
for s in full.sequences.iter_mut() {
let mut filtered: Vec<u8> = Vec::with_capacity(target_width);
for col in 0..s.len() {
if keep[col] { filtered.push(s[col]); }
}
*s = filtered;
}
(full, deletelist)
}
pub fn compute_mergeoralign(
topo: &Topology,
n_existing: usize,
_n_new: usize,
) -> Vec<MergeOrAlign> {
let mut tags: Vec<MergeOrAlign> = Vec::with_capacity(topo.steps.len());
for step in &topo.steps {
let left_all_new = step.left.iter().all(|&i| i >= n_existing);
let right_all_new = step.right.iter().all(|&i| i >= n_existing);
let tag = match (left_all_new, right_all_new) {
(false, false) => MergeOrAlign::SkipExisting, (true, false) => MergeOrAlign::NewLeft, (false, true) => MergeOrAlign::NewRight, (true, true) => MergeOrAlign::Wide, };
tags.push(tag);
}
tags
}
pub fn commongappick(sequences: &[Vec<u8>]) -> Vec<Vec<u8>> {
if sequences.is_empty() { return Vec::new(); }
let width = sequences[0].len();
let mut keep = vec![false; width];
for col in 0..width {
for s in sequences {
let c = s.get(col).copied().unwrap_or(b'-');
if c != b'-' {
keep[col] = true;
break;
}
}
}
sequences
.iter()
.map(|s| {
let mut out = Vec::with_capacity(width);
for col in 0..s.len() {
if keep[col] { out.push(s[col]); }
}
out
})
.collect()
}
fn compute_ktuple_dm(sequences: &[Vec<u8>]) -> DistanceMatrix {
let nseq = sequences.len();
let pairs: Vec<(usize, usize, f64)> = (0..nseq)
.into_par_iter()
.flat_map(|i| {
let seqs = sequences;
((i + 1)..nseq).into_par_iter().map(move |j| {
let d = ktuple_distance(&seqs[i], &seqs[j], 6);
(i, j, d)
})
})
.collect();
let mut dm = DistanceMatrix::new(nseq);
for (i, j, d) in pairs {
dm.set(i, j, d);
}
dm
}
#[cfg(test)]
mod tests {
use super::*;
use mafft_scoring::build_context;
use mafft_tree::JoinStep;
use mafft_types::{ScoringModel, SeqType};
fn make_existing_alignment() -> MultipleAlignment {
MultipleAlignment {
sequences: vec![
b"ACDEFGHIK".to_vec(),
b"ACDEF-HIK".to_vec(),
b"ACD---HIK".to_vec(),
],
names: vec!["s1".into(), "s2".into(), "s3".into()],
score: 0.0,
step_trace: Vec::new(), guide_tree: None, first_pass_sequences: None, distance_matrix: None,
}
}
#[test]
fn add_single_sequence() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let existing = make_existing_alignment();
let new_seqs = vec![b"ACDEFGHIKLM".to_vec()];
let new_names = vec!["new1".into()];
let result = add_sequences(&existing, &new_seqs, &new_names, &scoring, false);
assert_eq!(result.nseq(), 4);
let w = result.sequences[0].len();
for s in &result.sequences { assert_eq!(s.len(), w); }
}
#[test]
fn add_no_sequences_returns_existing() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let existing = make_existing_alignment();
let result = add_sequences(&existing, &[], &[], &scoring, false);
assert_eq!(result.nseq(), 3);
}
#[test]
fn keeplength_preserves_existing_width() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let existing = make_existing_alignment();
let target_width = existing.sequences[0].len();
let new_seqs = vec![b"ACDEFGHIK".to_vec()];
let new_names = vec!["new1".into()];
let result = add_sequences_keeplength(&existing, &new_seqs, &new_names, &scoring, false);
assert_eq!(result.nseq(), 4);
for s in &result.sequences {
assert_eq!(s.len(), target_width);
}
}
#[test]
fn mergeoralign_three_existing_one_new() {
let mut topo = Topology::new(4);
topo.steps.push(JoinStep {
left: vec![0], right: vec![1], left_length: 0.0, right_length: 0.0,
});
topo.steps.push(JoinStep {
left: vec![0, 1], right: vec![2], left_length: 0.0, right_length: 0.0,
});
topo.steps.push(JoinStep {
left: vec![0, 1, 2], right: vec![3], left_length: 0.0, right_length: 0.0,
});
let tags = compute_mergeoralign(&topo, 3, 1);
assert_eq!(tags.len(), 3);
assert!(matches!(tags[0], MergeOrAlign::SkipExisting));
assert!(matches!(tags[1], MergeOrAlign::SkipExisting));
assert!(matches!(tags[2], MergeOrAlign::NewRight));
}
#[test]
fn mergeoralign_mixed_subtree_is_n_not_2() {
let mut topo = Topology::new(4);
topo.steps.push(JoinStep {
left: vec![2], right: vec![3], left_length: 0.0, right_length: 0.0,
});
topo.steps.push(JoinStep {
left: vec![0], right: vec![2, 3], left_length: 0.0, right_length: 0.0,
});
topo.steps.push(JoinStep {
left: vec![0, 2, 3], right: vec![1], left_length: 0.0, right_length: 0.0,
});
let tags = compute_mergeoralign(&topo, 2, 2);
assert!(matches!(tags[0], MergeOrAlign::Wide));
assert!(matches!(tags[1], MergeOrAlign::NewRight));
assert!(matches!(tags[2], MergeOrAlign::SkipExisting));
}
}