use mafft_types::fp::fmadd;
use std::collections::BTreeMap;
use mafft_align::{profile_align, pairwise_align11_ex, fft_profile_align, Profile, GapModel, AlignOp, FftAlignParams};
use mafft_tree::{Topology, sequence_weights, compute_distfromtip};
use mafft_types::ScoringContext;
pub(crate) fn dist2offset(dist: f64, sc: f64) -> f64 {
let v = dist * 0.5 - sc;
if v > 0.0 { 0.0 } else { v }
}
pub(crate) fn make_dynamic_matrix(base: &[Vec<f64>], distfromtip: f64, unalign_level: f64, gap_idx: usize) -> Vec<Vec<f64>> {
let offset = dist2offset(distfromtip * 2.0, unalign_level);
if offset == 0.0 {
return base.iter().map(|r| r.clone()).collect();
}
base.iter()
.enumerate()
.map(|(i, row)| {
row.iter().enumerate()
.map(|(j, &v)| {
if i == gap_idx || j == gap_idx { v } else { fmadd(offset, 600.0, v) }
})
.collect()
})
.collect()
}
#[derive(Debug, Clone)]
pub struct MultipleAlignment {
pub sequences: Vec<Vec<u8>>,
pub names: Vec<String>,
pub score: f64,
pub step_trace: Vec<StepTrace>,
pub guide_tree: Option<mafft_tree::Topology>,
pub first_pass_sequences: Option<Vec<Vec<u8>>>,
pub distance_matrix: Option<mafft_tree::DistanceMatrix>,
}
#[derive(Debug, Clone, Copy)]
pub struct StepTrace {
pub clus1: usize,
pub clus2: usize,
pub width: usize,
pub score: f64,
}
impl MultipleAlignment {
pub fn width(&self) -> usize {
self.sequences.first().map_or(0, |s| s.len())
}
pub fn nseq(&self) -> usize {
self.sequences.len()
}
}
#[derive(Debug, Clone)]
struct CachedProfile {
profile: Profile,
eff: f64,
}
#[derive(Debug, Clone, Copy)]
pub enum MergeOrAlign {
SkipExisting,
NewLeft,
NewRight,
Wide,
}
pub fn progressive_align(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
) -> MultipleAlignment {
progressive_align_with_constraints(
sequences, names, topology, scoring, use_fft, shift_penalty, None, false,
)
}
pub fn progressive_align_unweighted(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
) -> MultipleAlignment {
let weights = vec![1.0f64; sequences.len()];
progressive_align_with_weights_override(
sequences, names, topology, scoring, use_fft, shift_penalty,
None, false, Some(&weights),
)
}
pub fn progressive_align_with_mergeoralign(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
mergeoralign: &[MergeOrAlign],
scoring: &ScoringContext,
use_fft: bool,
) -> MultipleAlignment {
progressive_align_with_mergeoralign_n(
sequences, names, topology, mergeoralign, scoring, use_fft,
sequences.len(), )
}
pub fn progressive_align_with_mergeoralign_n(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
mergeoralign: &[MergeOrAlign],
scoring: &ScoringContext,
use_fft: bool,
n_existing: usize,
) -> MultipleAlignment {
let nseq = sequences.len();
if nseq == 0 {
return MultipleAlignment {
sequences: Vec::new(), names: Vec::new(), score: 0.0, step_trace: Vec::new(), guide_tree: None, first_pass_sequences: None, distance_matrix: None,
};
}
if nseq == 1 {
return MultipleAlignment {
sequences: sequences.to_vec(), names: names.to_vec(), score: 0.0, step_trace: Vec::new(), guide_tree: None, first_pass_sequences: None, distance_matrix: None,
};
}
let weights = sequence_weights(topology);
let mut aligned: Vec<Vec<u8>> = sequences.to_vec();
let mut last_score = 0.0;
let gap = GapModel::new(scoring.gap.open as f64, scoring.gap.extend as f64);
let mut profile_cache: BTreeMap<Vec<usize>, CachedProfile> = BTreeMap::new();
let mut already_aligned: Vec<bool> = (0..nseq).map(|i| i < n_existing).collect();
let mut step_trace: Vec<StepTrace> = Vec::with_capacity(topology.steps.len());
for (step_idx, step) in topology.steps.iter().enumerate() {
let tag = mergeoralign.get(step_idx).copied().unwrap_or(MergeOrAlign::Wide);
match tag {
MergeOrAlign::SkipExisting => {
let width = aligned[step.left[0]].len().max(aligned[step.right[0]].len());
step_trace.push(StepTrace {
clus1: step.left.len(),
clus2: step.right.len(),
width,
score: 0.0,
});
}
MergeOrAlign::NewRight | MergeOrAlign::NewLeft => {
let (existing_grp, new_grp) = match tag {
MergeOrAlign::NewRight => (&step.left[..], &step.right[..]),
MergeOrAlign::NewLeft => (&step.right[..], &step.left[..]),
_ => unreachable!(),
};
let pre_width = aligned[existing_grp[0]].len();
let pre_classification: Vec<bool> = (0..pre_width)
.map(|col| {
existing_grp.iter().all(|&i| {
let c = aligned[i].get(col).copied().unwrap_or(b'-');
c == b'-'
})
})
.collect();
let n_gap_cols = pre_classification.iter().filter(|&&b| b).count();
let pre_rep_full = aligned[existing_grp[0]].clone();
let pre_rep_stripped: Vec<u8>;
if n_gap_cols > 0 {
pre_rep_stripped = pre_rep_full
.iter()
.enumerate()
.filter(|(col, _)| !pre_classification[*col])
.map(|(_, &c)| c)
.collect();
for &i in existing_grp {
let stripped: Vec<u8> = aligned[i]
.iter()
.enumerate()
.filter(|(col, _)| !pre_classification[*col])
.map(|(_, &c)| c)
.collect();
aligned[i] = stripped;
}
} else {
pre_rep_stripped = pre_rep_full.clone();
}
let stripped_width = pre_rep_stripped.len();
last_score = merge_step_cached(
&step.left,
&step.right,
&mut aligned,
&weights,
scoring,
&gap,
use_fft,
&mut profile_cache,
None,
false,
false,
false, true, );
let post_merge_width = aligned[existing_grp[0]].len();
let post_rep = aligned[existing_grp[0]].clone();
let new_merge_gap_set = compute_new_merge_gap_set(&pre_rep_stripped, &post_rep);
let anchor_positions: Vec<usize> = (0..pre_width)
.filter(|&k| !pre_classification[k])
.collect();
debug_assert_eq!(anchor_positions.len(), stripped_width);
let mut gap_cols_before: Vec<Vec<usize>> =
vec![Vec::new(); stripped_width + 1];
{
let mut s = 0usize;
for k in 0..pre_width {
if pre_classification[k] {
gap_cols_before[s].push(k);
} else {
s += 1;
}
}
}
if n_gap_cols > 0 {
let inserts_per_strip_idx: Vec<usize> =
gap_cols_before.iter().map(|v| v.len()).collect();
let active: Vec<usize> =
step.left.iter().chain(step.right.iter()).copied().collect();
for &i in &active {
aligned[i] = restore_common_gaps_to_merged_row(
&aligned[i],
&new_merge_gap_set,
&inserts_per_strip_idx,
);
}
}
let n1 = post_merge_width - stripped_width;
if n1 > 0 || n_gap_cols > 0 {
let active_set: std::collections::HashSet<usize> =
step.left.iter().chain(step.right.iter()).copied().collect();
let other_indices: Vec<usize> = (0..nseq)
.filter(|i| already_aligned[*i] && !active_set.contains(i))
.collect();
let use_port = std::env::var("RS_R6_PORT_OFF").is_err();
let active_w = aligned[existing_grp[0]].len();
let other_widths_consistent = other_indices.iter().all(|&i| {
aligned[i].len() == pre_width
});
if use_port && !other_indices.is_empty() && other_widths_consistent {
let group1_active = aligned[existing_grp[0]].clone();
let post_restore_w = group1_active.len();
let _ = active_w;
let inserts_per_strip_idx: Vec<usize> =
gap_cols_before.iter().map(|v| v.len()).collect();
let mut gaplen = vec![0usize; post_restore_w + 2];
{
let mut pos = 0usize;
let mut s = 0usize;
for q in 0..post_merge_width {
if new_merge_gap_set.contains(&q) {
gaplen[pos] += 1;
} else {
pos += inserts_per_strip_idx.get(s).copied().unwrap_or(0);
pos += 1;
s += 1;
}
}
}
let mut gapmap = vec![0usize; post_restore_w + 2];
{
let mut p = 0usize;
let mut s = 0usize;
for q in 0..post_merge_width {
if new_merge_gap_set.contains(&q) {
p += 1; } else {
let n_common = inserts_per_strip_idx.get(s).copied().unwrap_or(0);
if n_common > 0 {
gapmap[p] = n_common;
}
p += n_common; p += 1; s += 1;
}
}
let n_common = inserts_per_strip_idx.get(anchor_positions.len()).copied().unwrap_or(0);
if n_common > 0 && p < gapmap.len() {
gapmap[p] = n_common;
}
}
apply_c_insertnewgaps(
&mut aligned,
existing_grp,
new_grp,
&other_indices,
&gaplen,
&gapmap,
scoring,
&gap,
);
} else {
for i in other_indices {
let other_pre = aligned[i].clone();
if other_pre.len() == pre_width {
aligned[i] = build_other_post_restore_row(
&other_pre,
&anchor_positions,
&gap_cols_before,
&new_merge_gap_set,
post_merge_width,
);
}
}
}
}
for &i in new_grp {
already_aligned[i] = true;
}
let width = aligned[step.left[0]].len();
let _ = (pre_width, n_gap_cols, stripped_width, post_merge_width, n1);
step_trace.push(StepTrace {
clus1: step.left.len(),
clus2: step.right.len(),
width,
score: last_score,
});
}
MergeOrAlign::Wide => {
last_score = merge_step_cached(
&step.left,
&step.right,
&mut aligned,
&weights,
scoring,
&gap,
use_fft,
&mut profile_cache,
None,
false,
false,
false, true, );
for &i in step.left.iter().chain(step.right.iter()) {
already_aligned[i] = true;
}
let width = aligned[step.left[0]].len().max(aligned[step.right[0]].len());
step_trace.push(StepTrace {
clus1: step.left.len(),
clus2: step.right.len(),
width,
score: last_score,
});
}
}
}
let max_width = aligned.iter().map(|s| s.len()).max().unwrap_or(0);
for seq in &mut aligned {
seq.resize(max_width, b'-');
}
MultipleAlignment {
sequences: aligned, names: names.to_vec(), score: last_score, step_trace,
guide_tree: None, first_pass_sequences: None, distance_matrix: None,
}
}
fn compute_new_merge_gap_set(
pre_strip: &[u8],
post_merge: &[u8],
) -> std::collections::HashSet<usize> {
let mut gap_set = std::collections::HashSet::new();
let mut p = 0usize;
for (q, &c) in post_merge.iter().enumerate() {
if p < pre_strip.len() && pre_strip[p] == c {
p += 1;
} else {
gap_set.insert(q);
}
}
gap_set
}
fn restore_common_gaps_to_merged_row(
row: &[u8],
new_merge_gap_set: &std::collections::HashSet<usize>,
inserts_per_strip_idx: &[usize],
) -> Vec<u8> {
let total_inserts: usize = inserts_per_strip_idx.iter().sum();
let mut out = Vec::with_capacity(row.len() + total_inserts);
let mut s = 0usize;
for q in 0..row.len() {
if !new_merge_gap_set.contains(&q) {
for _ in 0..inserts_per_strip_idx[s] {
out.push(b'-');
}
out.push(row[q]);
s += 1;
} else {
out.push(row[q]);
}
}
let l_strip = inserts_per_strip_idx.len() - 1;
for _ in 0..inserts_per_strip_idx[l_strip] {
out.push(b'-');
}
out
}
fn commongappick_inplace(mseq: &mut Vec<Vec<u8>>) {
if mseq.is_empty() || mseq[0].is_empty() { return; }
let n = mseq.len();
let len = mseq[0].len();
let mut keep = vec![true; len];
for j in 0..len {
let all_gap = (0..n).all(|i| {
let c = mseq[i].get(j).copied().unwrap_or(b'-');
c == b'-'
});
if all_gap { keep[j] = false; }
}
for row in mseq.iter_mut() {
let new_row: Vec<u8> = row.iter().enumerate()
.filter(|(j, _)| keep[*j])
.map(|(_, &c)| c)
.collect();
*row = new_row;
}
}
fn rs_profilealignment(
mseq0: &mut Vec<Vec<u8>>,
mseq1: &mut Vec<Vec<u8>>,
mseq2: &mut Vec<Vec<u8>>,
scoring: &ScoringContext,
gap: &GapModel,
) {
commongappick_inplace(mseq0);
commongappick_inplace(mseq2);
let n0 = mseq0.len();
let n1 = mseq1.len();
let n2 = mseq2.len();
if n2 == 0 || mseq2[0].is_empty() {
let target_len = if n0 > 0 { mseq0[0].len() } else { 0 };
for row in mseq2.iter_mut() {
*row = vec![b'-'; target_len];
}
return;
}
let alcount0 = mseq0.iter().filter(|r| r.iter().any(|&c| c != b'-')).count().max(1);
let alcount2 = mseq2.iter().filter(|r| r.iter().any(|&c| c != b'-')).count().max(1);
let eff0: Vec<f64> = mseq0.iter().map(|r| {
if r.iter().any(|&c| c != b'-') { 1.0 / alcount0 as f64 } else { 0.0 }
}).collect();
let eff2: Vec<f64> = mseq2.iter().map(|r| {
if r.iter().any(|&c| c != b'-') { 1.0 / alcount2 as f64 } else { 0.0 }
}).collect();
let mseq0_refs: Vec<&[u8]> = mseq0.iter().map(|v| v.as_slice()).collect();
let mseq2_refs: Vec<&[u8]> = mseq2.iter().map(|v| v.as_slice()).collect();
let prof0 = Profile::from_aligned(&mseq0_refs, &eff0, &scoring.amino_map, scoring.nalphabets);
let prof2 = Profile::from_aligned(&mseq2_refs, &eff2, &scoring.amino_map, scoring.nalphabets);
let aln = profile_align(&prof0, &prof2, &scoring.consweight_matrix, gap, true, true);
let mut new_mseq0: Vec<Vec<u8>> = vec![Vec::new(); n0];
let mut new_mseq2: Vec<Vec<u8>> = vec![Vec::new(); n2];
let mut cur_i = vec![0usize; n0];
let mut cur_j = vec![0usize; n2];
for op in &aln.operations {
match op {
AlignOp::Match => {
for i in 0..n0 {
new_mseq0[i].push(mseq0[i].get(cur_i[i]).copied().unwrap_or(b'-'));
cur_i[i] += 1;
}
for j in 0..n2 {
new_mseq2[j].push(mseq2[j].get(cur_j[j]).copied().unwrap_or(b'-'));
cur_j[j] += 1;
}
}
AlignOp::Delete => {
for i in 0..n0 {
new_mseq0[i].push(mseq0[i].get(cur_i[i]).copied().unwrap_or(b'-'));
cur_i[i] += 1;
}
for j in 0..n2 {
new_mseq2[j].push(b'-');
}
}
AlignOp::Insert => {
for i in 0..n0 {
new_mseq0[i].push(b'-');
}
for j in 0..n2 {
new_mseq2[j].push(mseq2[j].get(cur_j[j]).copied().unwrap_or(b'-'));
cur_j[j] += 1;
}
}
}
}
*mseq0 = new_mseq0;
*mseq2 = new_mseq2;
let newlen = if n0 > 0 { mseq0[0].len() } else if n2 > 0 { mseq2[0].len() } else { 0 };
for row in mseq1.iter_mut() {
*row = vec![b'-'; newlen];
}
for j in 0..newlen {
let all_aln0_gap = mseq0.iter().all(|r| r.get(j).copied().unwrap_or(b'-') == b'-');
if !all_aln0_gap { continue; }
let all_aln1_gap = mseq1.iter().all(|r| r.get(j).copied().unwrap_or(b'-') == b'-');
if all_aln1_gap {
for row in mseq1.iter_mut() {
row[j] = b'=';
}
}
}
let _ = n1;
}
pub fn findnewgaps(seq: &[u8]) -> Vec<usize> {
let mut gaplen = vec![0usize; seq.len() + 1];
let mut pos = 0;
for &c in seq {
if c == b'=' { gaplen[pos] += 1; }
else { pos += 1; }
}
gaplen
}
pub fn apply_c_insertnewgaps(
aseq: &mut [Vec<u8>],
existing_grp: &[usize],
new_grp: &[usize],
other_indices: &[usize],
gaplen: &[usize],
gapmap: &[usize],
scoring: &ScoringContext,
gap: &GapModel,
) {
if other_indices.is_empty() {
return; }
let rep = other_indices[0];
let len = aseq[rep].len();
let len0 = len + 1;
let mut out: Vec<Vec<u8>> = (0..aseq.len()).map(|_| Vec::with_capacity(len * 2 + 16)).collect();
let mut posin12 = 0usize;
let mut j = 0usize;
while j < len0 {
if j < gaplen.len() && gaplen[j] > 0 {
let gapshift = gaplen[j];
let mut mseq0: Vec<Vec<u8>> = (0..other_indices.len()).map(|_| Vec::new()).collect();
let mut mseq1: Vec<Vec<u8>> = (0..existing_grp.len()).map(|_| Vec::new()).collect();
let mut mseq2: Vec<Vec<u8>> = (0..new_grp.len()).map(|_| Vec::new()).collect();
for row in mseq0.iter_mut() {
for _ in 0..gapshift { row.push(b'-'); }
}
for (k, &i) in existing_grp.iter().enumerate() {
for kk in 0..gapshift {
let c = aseq[i].get(posin12 + kk).copied().unwrap_or(b'-');
mseq1[k].push(c);
}
}
for (k, &i) in new_grp.iter().enumerate() {
for kk in 0..gapshift {
let c = aseq[i].get(posin12 + kk).copied().unwrap_or(b'-');
mseq2[k].push(c);
}
}
posin12 += gapshift;
let gapshift2 = gapmap.get(posin12).copied().unwrap_or(0);
if gapshift2 > 0 {
for (k, &i) in other_indices.iter().enumerate() {
for kk in 0..gapshift2 {
let c = aseq[i].get(j + kk).copied().unwrap_or(b'-');
mseq0[k].push(c);
}
}
for (k, &i) in existing_grp.iter().enumerate() {
for kk in 0..gapshift2 {
let c = aseq[i].get(posin12 + kk).copied().unwrap_or(b'-');
mseq1[k].push(c);
}
}
for (k, &i) in new_grp.iter().enumerate() {
for kk in 0..gapshift2 {
let c = aseq[i].get(posin12 + kk).copied().unwrap_or(b'-');
mseq2[k].push(c);
}
}
rs_profilealignment(&mut mseq0, &mut mseq1, &mut mseq2, scoring, gap);
j += gapshift2;
posin12 += gapshift2;
}
for (k, &i) in other_indices.iter().enumerate() {
out[i].extend_from_slice(&mseq0[k]);
}
for (k, &i) in existing_grp.iter().enumerate() {
out[i].extend_from_slice(&mseq1[k]);
}
for (k, &i) in new_grp.iter().enumerate() {
out[i].extend_from_slice(&mseq2[k]);
}
}
let mut blocklen = 1;
let mut q = j + 1;
while q < len0 && q < gaplen.len() && gaplen[q] == 0 {
blocklen += 1;
q += 1;
}
for &i in other_indices {
for k in 0..blocklen {
if let Some(&c) = aseq[i].get(j + k) {
if c != 0 { out[i].push(c); }
} else { break; }
}
}
for &i in existing_grp {
for k in 0..blocklen {
if let Some(&c) = aseq[i].get(posin12 + k) {
if c != 0 { out[i].push(c); }
} else { break; }
}
}
for &i in new_grp {
for k in 0..blocklen {
if let Some(&c) = aseq[i].get(posin12 + k) {
if c != 0 { out[i].push(c); }
} else { break; }
}
}
j += blocklen;
posin12 += blocklen;
}
for row in out.iter_mut() {
while row.last() == Some(&0) { row.pop(); }
}
for &i in other_indices.iter().chain(existing_grp).chain(new_grp) {
aseq[i] = std::mem::take(&mut out[i]);
}
}
#[allow(dead_code, clippy::too_many_arguments)]
fn insertnewgaps_with_profilealignment(
aligned: &[Vec<u8>],
other_indices: &[usize],
existing_grp: &[usize],
new_grp: &[usize],
anchor_positions: &[usize],
gap_cols_before: &[Vec<usize>],
new_merge_gap_set: &std::collections::HashSet<usize>,
post_merge_width: usize,
scoring: &ScoringContext,
gap: &GapModel,
) -> Vec<Vec<u8>> {
let stripped_width = anchor_positions.len();
let mut anchor_idx_at_post: Vec<usize> = Vec::with_capacity(post_merge_width);
{
let mut s = 0usize;
for q in 0..post_merge_width {
if !new_merge_gap_set.contains(&q) { s += 1; }
anchor_idx_at_post.push(s); }
}
let mut gap_runs: Vec<(usize, usize)> = Vec::new(); {
let mut q = 0;
while q < post_merge_width {
if new_merge_gap_set.contains(&q) {
let start = q;
while q < post_merge_width && new_merge_gap_set.contains(&q) { q += 1; }
gap_runs.push((start, q - start));
} else {
q += 1;
}
}
}
let other_pre: Vec<Vec<u8>> = other_indices.iter().map(|&i| aligned[i].clone()).collect();
let mut other_out: Vec<Vec<u8>> = vec![Vec::with_capacity(post_merge_width); other_indices.len()];
let mut s = 0usize;
let mut run_idx = 0usize;
let mut q = 0usize;
while s < stripped_width {
if run_idx < gap_runs.len() && gap_runs[run_idx].0 == q {
let (g_start, g_len) = gap_runs[run_idx];
run_idx += 1;
let consume = g_len.min(stripped_width - s);
let src_anchors = &anchor_positions[s..s + consume];
let mseq0: Vec<Vec<u8>> = other_pre.iter().map(|row| {
src_anchors.iter().map(|&k| row.get(k).copied().unwrap_or(b'-')).collect()
}).collect();
let mseq2: Vec<Vec<u8>> = new_grp.iter().map(|&i| {
aligned[i].iter().skip(g_start).take(g_len).copied().collect()
}).collect();
let m_refs: Vec<&[u8]> = mseq0.iter().map(|v| v.as_slice()).collect();
let n_refs: Vec<&[u8]> = mseq2.iter().map(|v| v.as_slice()).collect();
let n0 = m_refs.len();
let _n2 = n_refs.len();
let alcount0 = mseq0.iter().filter(|r| r.iter().any(|&c| c != b'-')).count().max(1);
let alcount2 = mseq2.iter().filter(|r| r.iter().any(|&c| c != b'-')).count().max(1);
let w0: Vec<f64> = mseq0.iter().map(|r| {
if r.iter().any(|&c| c != b'-') { 1.0 / alcount0 as f64 } else { 0.0 }
}).collect();
let w2: Vec<f64> = mseq2.iter().map(|r| {
if r.iter().any(|&c| c != b'-') { 1.0 / alcount2 as f64 } else { 0.0 }
}).collect();
let prof0 = Profile::from_aligned(&m_refs, &w0, &scoring.amino_map, scoring.nalphabets);
let prof2 = Profile::from_aligned(&n_refs, &w2, &scoring.amino_map, scoring.nalphabets);
let aln = profile_align(&prof0, &prof2, &scoring.consweight_matrix, gap, true, true);
let mut cur_i = vec![0usize; n0];
for op in &aln.operations {
for i in 0..n0 {
match op {
AlignOp::Match | AlignOp::Delete => {
let c = mseq0[i].get(cur_i[i]).copied().unwrap_or(b'-');
other_out[i].push(c);
cur_i[i] += 1;
}
AlignOp::Insert => {
other_out[i].push(b'-');
}
}
}
}
s += consume;
q = g_start + g_len;
continue;
}
if s < stripped_width {
for &k_pre in &gap_cols_before[s] {
for (i, row) in other_pre.iter().enumerate() {
other_out[i].push(row.get(k_pre).copied().unwrap_or(b'-'));
}
}
for (i, row) in other_pre.iter().enumerate() {
other_out[i].push(row.get(anchor_positions[s]).copied().unwrap_or(b'-'));
}
s += 1;
q += 1;
}
}
if let Some(tail) = gap_cols_before.get(stripped_width) {
for &k_pre in tail {
for (i, row) in other_pre.iter().enumerate() {
other_out[i].push(row.get(k_pre).copied().unwrap_or(b'-'));
}
}
}
while run_idx < gap_runs.len() {
let (_, g_len) = gap_runs[run_idx];
for _ in 0..g_len {
for row in other_out.iter_mut() {
row.push(b'-');
}
}
run_idx += 1;
}
let _ = (existing_grp, anchor_idx_at_post); other_out
}
fn build_other_post_restore_row(
other_pre: &[u8],
anchor_positions: &[usize],
gap_cols_before: &[Vec<usize>],
new_merge_gap_set: &std::collections::HashSet<usize>,
post_merge_w: usize,
) -> Vec<u8> {
let total_size = other_pre.len() + new_merge_gap_set.len();
let mut out: Vec<u8> = Vec::with_capacity(total_size);
let mut s = 0usize;
for q in 0..post_merge_w {
if new_merge_gap_set.contains(&q) {
out.push(b'-');
} else {
for &k_pre in &gap_cols_before[s] {
out.push(other_pre.get(k_pre).copied().unwrap_or(b'-'));
}
out.push(other_pre.get(anchor_positions[s]).copied().unwrap_or(b'-'));
s += 1;
}
}
let l_strip = anchor_positions.len();
for &k_pre in &gap_cols_before[l_strip] {
out.push(other_pre.get(k_pre).copied().unwrap_or(b'-'));
}
out
}
pub fn progressive_align_partial(
sequences: &[Vec<u8>],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
n_steps: usize,
) -> Vec<Vec<u8>> {
let nseq = sequences.len();
if nseq <= 1 {
return sequences.to_vec();
}
let weights = sequence_weights(topology);
let mut aligned: Vec<Vec<u8>> = sequences.to_vec();
let mut gap = GapModel::new(scoring.gap.open as f64, scoring.gap.extend as f64);
if let Some(shift) = shift_penalty {
gap = gap.with_shift(shift);
}
let mut profile_cache: BTreeMap<Vec<usize>, CachedProfile> = BTreeMap::new();
let limit = n_steps.min(topology.steps.len());
for step in topology.steps.iter().take(limit) {
merge_step_cached(
&step.left, &step.right, &mut aligned, &weights, scoring, &gap, use_fft,
&mut profile_cache, None, false, false,
false, true, );
}
aligned
}
pub fn progressive_align_with_constraints(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
) -> MultipleAlignment {
progressive_align_with_weights_override(
sequences, names, topology, scoring, use_fft, shift_penalty,
constraints, penalize_term_gaps, None,
)
}
pub fn progressive_align_with_weights_override(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
weights_override: Option<&[f64]>,
) -> MultipleAlignment {
progressive_align_full(
sequences, names, topology, scoring, use_fft, shift_penalty,
constraints, penalize_term_gaps, weights_override, 0.0, false, false,
)
}
pub fn progressive_align_full(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
weights_override: Option<&[f64]>,
unalign_level: f64,
legacy_gap_cost: bool,
memsave_dp: bool,
) -> MultipleAlignment {
progressive_align_full_c_compat(
sequences, names, topology, scoring, use_fft, shift_penalty,
constraints, penalize_term_gaps, weights_override, unalign_level,
legacy_gap_cost, memsave_dp, false,
)
}
pub fn progressive_align_full_c_compat(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
weights_override: Option<&[f64]>,
unalign_level: f64,
legacy_gap_cost: bool,
memsave_dp: bool,
c_compat: bool,
) -> MultipleAlignment {
progressive_align_full_c_compat_ex(
sequences, names, topology, scoring, use_fft, shift_penalty,
constraints, penalize_term_gaps, weights_override, unalign_level,
legacy_gap_cost, memsave_dp, c_compat, true,
)
}
pub fn progressive_align_full_c_compat_ex(
sequences: &[Vec<u8>],
names: &[String],
topology: &Topology,
scoring: &ScoringContext,
use_fft: bool,
shift_penalty: Option<f64>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
weights_override: Option<&[f64]>,
unalign_level: f64,
legacy_gap_cost: bool,
memsave_dp: bool,
c_compat: bool,
use_cache: bool,
) -> MultipleAlignment {
let nseq = sequences.len();
if nseq == 0 {
return MultipleAlignment {
sequences: Vec::new(), names: Vec::new(), score: 0.0, step_trace: Vec::new(), guide_tree: None, first_pass_sequences: None, distance_matrix: None,
};
}
if nseq == 1 {
return MultipleAlignment {
sequences: sequences.to_vec(), names: names.to_vec(), score: 0.0, step_trace: Vec::new(), guide_tree: None, first_pass_sequences: None, distance_matrix: None,
};
}
let weights = match weights_override {
Some(w) => w.to_vec(),
None => sequence_weights(topology),
};
let mut aligned: Vec<Vec<u8>> = sequences.to_vec();
let mut last_score = 0.0;
let progressive_extend = if constraints.is_some() {
0.0
} else {
scoring.gap.extend as f64
};
let mut gap = GapModel::new(scoring.gap.open as f64, progressive_extend)
.with_legacy_gap_cost(legacy_gap_cost);
if let Some(shift) = shift_penalty {
gap = gap.with_shift(shift);
}
let mut profile_cache: BTreeMap<Vec<usize>, CachedProfile> = BTreeMap::new();
if c_compat {
mafft_align::reset_cpmx_memo();
}
let distfromtip: Vec<f64> = if unalign_level > 0.0 {
compute_distfromtip(topology)
} else {
Vec::new()
};
let gap_idx = scoring.amino_map[b'-' as usize] as usize;
let dyn_scoring: Vec<ScoringContext> = if unalign_level > 0.0 {
distfromtip
.iter()
.map(|&dft| {
let mut s = scoring.clone();
s.consweight_matrix =
make_dynamic_matrix(&scoring.consweight_matrix, dft, unalign_level, gap_idx);
s
})
.collect()
} else {
Vec::new()
};
let mut step_trace: Vec<StepTrace> = Vec::with_capacity(topology.steps.len());
for (step_idx, step) in topology.steps.iter().enumerate() {
let step_scoring: &ScoringContext = if unalign_level > 0.0 {
&dyn_scoring[step_idx]
} else {
scoring
};
last_score = merge_step_cached(
&step.left, &step.right, &mut aligned, &weights, step_scoring, &gap, use_fft,
&mut profile_cache, constraints, penalize_term_gaps, memsave_dp, c_compat,
use_cache,
);
let width = aligned[step.left[0]].len().max(aligned[step.right[0]].len());
step_trace.push(StepTrace {
clus1: step.left.len(),
clus2: step.right.len(),
width,
score: last_score,
});
if std::env::var("MAFFT_DEBUG_STEPS").is_ok() {
eprintln!("RDBG {} {} {} {} {:.4}",
step_idx, step.left.len(), step.right.len(), width, last_score);
}
if let Ok(f) = std::env::var("RS_PROGRESSIVE_TRACE") {
use std::io::Write;
if let Ok(mut fp) = std::fs::OpenOptions::new().create(true).append(true).open(&f) {
let m1 = step.left[0];
let m2 = step.right[0];
let mut h: u64 = 5381;
for &i in &step.left {
for &c in &aligned[i] { h = h.wrapping_mul(33).wrapping_add(c as u64); }
}
for &i in &step.right {
for &c in &aligned[i] { h = h.wrapping_mul(33).wrapping_add(c as u64); }
}
let _ = writeln!(fp, "step={} m1={} m2={} clus1={} clus2={} width={} pscore={:.6} hash={:x}",
step_idx, m1, m2, step.left.len(), step.right.len(), width, last_score, h);
}
}
if std::env::var("RDBG_PT_STEPS").is_ok() {
eprintln!("RDBG_PT step={} clus1={} clus2={} width={} mem1={:?} mem2={:?}",
step_idx, step.left.len(), step.right.len(), width, step.left, step.right);
}
if let Ok(prefix) = std::env::var("MAFFT_DUMP_CPMX_PREFIX") {
let mut key = step.left.clone();
key.extend_from_slice(&step.right);
key.sort_unstable();
if let Some(cached) = profile_cache.get(&key) {
let fname = format!("{}_step_{}.txt", prefix, step_idx);
if let Ok(mut f) = std::fs::File::create(&fname) {
use std::io::Write;
let prof = &cached.profile;
let cw = prof.length;
let na = prof.nalphabets;
writeln!(f, "step={} width={} nalphabets={} clus1={} clus2={} score={:.6}",
step_idx, cw, na, step.left.len(), step.right.len(), last_score).unwrap();
for k in 0..na {
write!(f, "F[{}]:", k).unwrap();
for j in 0..cw {
write!(f, " {:.18e}", prof.freqs[j][k]).unwrap();
}
writeln!(f).unwrap();
}
write!(f, "G:").unwrap();
for j in 0..cw {
write!(f, " {:.18e}", prof.nongap_freq[j]).unwrap();
}
write!(f, " {:.18e}", 1.0).unwrap();
writeln!(f).unwrap();
write!(f, "O:").unwrap();
for j in 0..cw {
write!(f, " {:.18e}", prof.ogcp[j]).unwrap();
}
writeln!(f).unwrap();
write!(f, "N:").unwrap();
for j in 0..cw {
write!(f, " {:.18e}", prof.fgcp[j]).unwrap();
}
writeln!(f).unwrap();
}
}
}
}
let max_width = aligned.iter().map(|s| s.len()).max().unwrap_or(0);
for seq in &mut aligned {
seq.resize(max_width, b'-');
}
MultipleAlignment {
sequences: aligned, names: names.to_vec(), score: last_score, step_trace,
guide_tree: None, first_pass_sequences: None, distance_matrix: None,
}
}
pub fn merge_two_groups_progressive(
group1: &[usize],
group2: &[usize],
aligned: &mut Vec<Vec<u8>>,
weights: &[f64],
scoring: &ScoringContext,
gap: &GapModel,
use_fft: bool,
penalize_term_gaps: bool,
) -> f64 {
let mut empty_cache: BTreeMap<Vec<usize>, CachedProfile> = BTreeMap::new();
merge_step_cached(
group1, group2, aligned, weights, scoring, gap, use_fft,
&mut empty_cache,
None, penalize_term_gaps,
false, false, false, )
}
fn merge_step_cached(
group1: &[usize],
group2: &[usize],
aligned: &mut Vec<Vec<u8>>,
weights: &[f64],
scoring: &ScoringContext,
gap: &GapModel,
use_fft: bool,
cache: &mut BTreeMap<Vec<usize>, CachedProfile>,
constraints: Option<&mafft_types::LocalHomologyTable>,
penalize_term_gaps: bool,
memsave_dp: bool,
c_compat: bool,
use_cache: bool,
) -> f64 {
let width1 = aligned[group1[0]].len();
let width2 = aligned[group2[0]].len();
let dump_inputs = std::env::var_os("RS_DP_DUMP").is_some();
let dp_dump_step = if dump_inputs {
let s1: Vec<String> = group1.iter().map(|&i| String::from_utf8_lossy(&aligned[i]).into_owned()).collect();
let s2: Vec<String> = group2.iter().map(|&i| String::from_utf8_lossy(&aligned[i]).into_owned()).collect();
let w1: Vec<f64> = group1.iter().map(|&i| weights[i]).collect();
let w2: Vec<f64> = group2.iter().map(|&i| weights[i]).collect();
Some((s1, s2, w1, w2))
} else { None };
let key1 = sorted_key(group1);
let key2 = sorted_key(group2);
let try_cache = use_cache;
let (prof1, eff1) = if try_cache && cache.get(&key1).is_some() {
let cached = cache.get(&key1).unwrap();
(cached.profile.clone(), cached.eff)
} else if c_compat {
build_profile_with_memo(group1, aligned, weights, scoring)
} else {
let (prof, eff) = build_profile_from_seqs(group1, aligned, weights, scoring);
(prof, eff)
};
let (prof2, eff2) = if try_cache && cache.get(&key2).is_some() {
let cached = cache.get(&key2).unwrap();
(cached.profile.clone(), cached.eff)
} else {
let (prof, eff) = build_profile_from_seqs(group2, aligned, weights, scoring);
(prof, eff)
};
let aln = if !use_fft && group1.len() == 1 && group2.len() == 1
&& constraints.is_none()
{
pairwise_align11_ex(
&aligned[group1[0]], &aligned[group2[0]],
&scoring.consweight_matrix, &scoring.amino_map,
scoring.gap.open as f64, scoring.gap.extend as f64,
penalize_term_gaps, penalize_term_gaps,
)
} else if use_fft {
let property_channels = if scoring.seq_type.is_nucleotide() {
None
} else {
let nscored = scoring.nscoredalphabets;
let mut polarity_by_idx = vec![0.0f64; nscored];
let mut volume_by_idx = vec![0.0f64; nscored];
for ch in 0u16..256 {
let idx = scoring.amino_map[ch as usize] as usize;
if idx < nscored {
polarity_by_idx[idx] = scoring.polarity[ch as usize];
volume_by_idx[idx] = scoring.volume[ch as usize];
}
}
Some((polarity_by_idx, volume_by_idx))
};
let fft_params = FftAlignParams {
num_candidates: 20,
segment_params: if scoring.seq_type.is_nucleotide() {
mafft_fft::SegmentParams::dna()
} else {
mafft_fft::SegmentParams::protein()
},
gap: gap.clone(),
head_gap: penalize_term_gaps,
tail_gap: penalize_term_gaps,
num_channels: scoring.nscoredalphabets,
property_channels,
};
fft_profile_align(&prof1, &prof2, &scoring.consweight_matrix, &fft_params)
} else if let Some(table) = constraints {
let g1_seq_refs: Vec<&[u8]> = group1.iter().map(|&i| aligned[i].as_slice()).collect();
let g2_seq_refs: Vec<&[u8]> = group2.iter().map(|&i| aligned[i].as_slice()).collect();
const MINIMUM_WEIGHT: f64 = 0.00001;
let w1: Vec<f64> = group1.iter().map(|&i| weights[i].max(MINIMUM_WEIGHT)).collect();
let w2: Vec<f64> = group2.iter().map(|&i| weights[i].max(MINIMUM_WEIGHT)).collect();
let s1: f64 = w1.iter().sum();
let s2: f64 = w2.iter().sum();
let w1n: Vec<f64> = if s1 > 0.0 { w1.iter().map(|w| w / s1).collect() } else { vec![1.0; group1.len()] };
let w2n: Vec<f64> = if s2 > 0.0 { w2.iter().map(|w| w / s2).collect() } else { vec![1.0; group2.len()] };
let imp = mafft_align::build_imp_matrix(
table,
group1, group2,
&g1_seq_refs, &g2_seq_refs,
&w1n, &w2n,
prof1.length, prof2.length,
mafft_align::FASTATHRESHOLD_DEFAULT,
);
if std::env::var_os("RUST_IMP_DUMP").is_some() {
let s00 = imp.first().and_then(|r| r.first()).copied().unwrap_or(0.0);
let s100 = if imp.len()>100 && imp[100].len()>100 { imp[100][100] } else { 0.0 };
let s300 = if imp.len()>300 && imp[300].len()>300 { imp[300][300] } else { 0.0 };
eprintln!("[RUST_IMP] g1={:?} g2={:?} lgth1={} lgth2={} eff1={:?} eff2={:?} imp[0,0]={:.4} imp[100,100]={:.4} imp[300,300]={:.4}",
group1, group2, prof1.length, prof2.length, w1n, w2n, s00, s100, s300);
for &gi in group1 {
for &gj in group2 {
let regs = table.get(gi, gj);
for (idx, r) in regs.iter().enumerate().take(3) {
eprintln!("[RUST_IMP] lh[{},{}] e{}: opt={:.6} imp={:.6} overlapaa={} s1={} e1={} s2={} e2={}",
gi, gj, idx, r.opt, r.importance, r.overlapaa, r.start1, r.end1, r.start2, r.end2);
}
}
}
}
mafft_align::profile_align_imp(
&prof1, &prof2, &scoring.consweight_matrix, gap,
penalize_term_gaps, penalize_term_gaps, Some(&imp),
)
} else if memsave_dp {
mafft_align::msalignmm(
&prof1, &prof2, &scoring.consweight_matrix, gap,
penalize_term_gaps, penalize_term_gaps,
)
} else {
profile_align(
&prof1, &prof2, &scoring.consweight_matrix, gap,
penalize_term_gaps, penalize_term_gaps,
)
};
let new_width = aln.operations.len();
let mut gaptable1 = Vec::with_capacity(new_width); let mut gaptable2 = Vec::with_capacity(new_width);
for op in &aln.operations {
match op {
AlignOp::Match => { gaptable1.push(b'o'); gaptable2.push(b'o'); }
AlignOp::Delete => { gaptable1.push(b'o'); gaptable2.push(b'-'); }
AlignOp::Insert => { gaptable1.push(b'-'); gaptable2.push(b'o'); }
}
}
let total_eff = eff1 + eff2;
let combined_seqs = group1.len() + group2.len();
if total_eff > 0.0 && combined_seqs > 20 {
let norm_eff1 = eff1 / total_eff;
let norm_eff2 = eff2 / total_eff;
let merged_prof = blend_profiles_exact(
&prof1, &prof2,
norm_eff1, norm_eff2,
&gaptable1, &gaptable2,
scoring.nalphabets,
);
let mut merged_key = group1.to_vec();
merged_key.extend_from_slice(group2);
merged_key.sort();
cache.insert(merged_key, CachedProfile {
profile: merged_prof,
eff: total_eff,
});
}
cache.remove(&key1);
cache.remove(&key2);
let mut cursor1 = 0usize;
let mut cursor2 = 0usize;
let mut new_seqs_g1: Vec<Vec<u8>> = vec![Vec::with_capacity(new_width); group1.len()];
let mut new_seqs_g2: Vec<Vec<u8>> = vec![Vec::with_capacity(new_width); group2.len()];
for op in &aln.operations {
match op {
AlignOp::Match => {
for (gi, &idx) in group1.iter().enumerate() {
new_seqs_g1[gi].push(if cursor1 < width1 { aligned[idx][cursor1] } else { b'-' });
}
for (gi, &idx) in group2.iter().enumerate() {
new_seqs_g2[gi].push(if cursor2 < width2 { aligned[idx][cursor2] } else { b'-' });
}
cursor1 += 1;
cursor2 += 1;
}
AlignOp::Delete => {
for (gi, &idx) in group1.iter().enumerate() {
new_seqs_g1[gi].push(if cursor1 < width1 { aligned[idx][cursor1] } else { b'-' });
}
for gi in 0..group2.len() { new_seqs_g2[gi].push(b'-'); }
cursor1 += 1;
}
AlignOp::Insert => {
for gi in 0..group1.len() { new_seqs_g1[gi].push(b'-'); }
for (gi, &idx) in group2.iter().enumerate() {
new_seqs_g2[gi].push(if cursor2 < width2 { aligned[idx][cursor2] } else { b'-' });
}
cursor2 += 1;
}
}
}
for (gi, &idx) in group1.iter().enumerate() { aligned[idx] = new_seqs_g1[gi].clone(); }
for (gi, &idx) in group2.iter().enumerate() { aligned[idx] = new_seqs_g2[gi].clone(); }
if let Some((s1, s2, w1, w2)) = dp_dump_step {
if let Ok(path) = std::env::var("RS_DP_DUMP") {
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
let out1: Vec<String> = new_seqs_g1.iter().map(|v| String::from_utf8_lossy(v).into_owned()).collect();
let out2: Vec<String> = new_seqs_g2.iter().map(|v| String::from_utf8_lossy(v).into_owned()).collect();
let _ = writeln!(f,
"g1={}\tg2={}\tw1={}\tw2={}\tpen={}\tpen_ex={}\thgp={}\ttgp={}\tfft={}\tcon={}\tout1={}\tout2={}",
s1.join(";"), s2.join(";"),
w1.iter().map(|x| format!("{:.10}", x)).collect::<Vec<_>>().join(","),
w2.iter().map(|x| format!("{:.10}", x)).collect::<Vec<_>>().join(","),
gap.open as i32, gap.extend as i32,
penalize_term_gaps as u8, penalize_term_gaps as u8,
use_fft as u8,
constraints.is_some() as u8,
out1.join(";"), out2.join(";"),
);
}
}
}
aln.score
}
fn sorted_key(group: &[usize]) -> Vec<usize> {
let mut k = group.to_vec();
k.sort();
k
}
fn build_profile_from_seqs(
group: &[usize],
aligned: &[Vec<u8>],
weights: &[f64],
scoring: &ScoringContext,
) -> (Profile, f64) {
let seqs: Vec<&[u8]> = group.iter().map(|&i| aligned[i].as_slice()).collect();
let w: Vec<f64> = group.iter().map(|&i| weights[i]).collect();
let sum: f64 = w.iter().sum();
let wn: Vec<f64> = if sum > 0.0 { w.iter().map(|v| v / sum).collect() } else { vec![1.0; group.len()] };
let prof = Profile::from_aligned(&seqs, &wn, &scoring.amino_map, scoring.nalphabets);
(prof, sum)
}
fn build_profile_with_memo(
group: &[usize],
aligned: &[Vec<u8>],
weights: &[f64],
scoring: &ScoringContext,
) -> (Profile, f64) {
let seqs: Vec<&[u8]> = group.iter().map(|&i| aligned[i].as_slice()).collect();
let w: Vec<f64> = group.iter().map(|&i| weights[i]).collect();
let sum: f64 = w.iter().sum();
let wn: Vec<f64> = if sum > 0.0 { w.iter().map(|v| v / sum).collect() } else { vec![1.0; group.len()] };
let firstmem = group[0] as i32;
let icyc = group.len();
let lgth = seqs.first().map_or(0, |s| s.len());
let prof = Profile::from_aligned_with_memo(
&seqs, &wn, &scoring.amino_map, scoring.nalphabets,
firstmem, icyc, lgth,
);
(prof, sum)
}
pub fn blend_profiles_exact(
prof1: &Profile,
prof2: &Profile,
eff1: f64,
eff2: f64,
gaptable1: &[u8],
gaptable2: &[u8],
nalphabets: usize,
) -> Profile {
let alen = gaptable1.len();
let mut freqs = vec![vec![0.0f64; nalphabets]; alen];
let mut nongap_freq = vec![0.0f64; alen + 1]; let mut ogcp = vec![0.0f64; alen];
let mut fgcp = vec![0.0f64; alen];
{
let mut p = 0usize;
for j in 0..alen {
if gaptable1[j] != b'-' {
if p < prof1.length {
for k in 0..nalphabets.min(prof1.freqs[p].len()) {
freqs[j][k] = fmadd(prof1.freqs[p][k], eff1, freqs[j][k]);
}
}
p += 1;
}
}
}
{
let mut p = 0usize;
for j in 0..alen {
if gaptable2[j] != b'-' {
if p < prof2.length {
for k in 0..nalphabets.min(prof2.freqs[p].len()) {
freqs[j][k] = fmadd(prof2.freqs[p][k], eff2, freqs[j][k]);
}
}
p += 1;
}
}
}
{
let mut p = 0usize;
for j in 0..=alen {
if j < alen && gaptable1[j] == b'-' {
} else {
if p < prof1.nongap_freq.len() {
nongap_freq[j] = fmadd(prof1.nongap_freq[p], eff1, nongap_freq[j]);
}
p += 1;
}
}
}
{
let mut p = 0usize;
for j in 0..alen {
if gaptable2[j] == b'-' {
} else {
if p < prof2.nongap_freq.len() {
nongap_freq[j] = fmadd(prof2.nongap_freq[p], eff2, nongap_freq[j]);
}
p += 1;
}
}
}
nongap_freq[alen] = 1.0;
blend_og_one_side(&mut ogcp, &prof1.ogcp, &prof1.nongap_freq, gaptable1, eff1);
blend_og_one_side(&mut ogcp, &prof2.ogcp, &prof2.nongap_freq, gaptable2, eff2);
blend_fg_one_side(&mut fgcp, &prof1.fgcp, &prof1.nongap_freq, gaptable1, eff1);
blend_fg_one_side(&mut fgcp, &prof2.fgcp, &prof2.nongap_freq, gaptable2, eff2);
let gap_freq: Vec<f64> = nongap_freq[..alen].iter().map(|&nf| (1.0 - nf).max(0.0)).collect();
let nongap_freq_trimmed = nongap_freq[..alen].to_vec();
Profile {
freqs,
gap_freq,
nongap_freq: nongap_freq_trimmed,
ogcp,
fgcp,
length: alen,
nalphabets,
}
}
fn blend_og_one_side(
result: &mut [f64],
ori: &[f64], gf: &[f64], gaptable: &[u8],
eff: f64,
) {
let alen = result.len();
let mut p = 0usize;
for j in 0..alen {
if gaptable[j] == b'-' {
if j == 0 {
result[j] += eff;
} else if gaptable[j - 1] != b'-' && p > 0 {
let gf_val = if p - 1 < gf.len() { gf[p - 1] } else { 1.0 };
result[j] = fmadd(gf_val, eff, result[j]);
}
} else {
if j == 0 || (j > 0 && gaptable[j - 1] != b'-') {
if p < ori.len() {
result[j] = fmadd(ori[p], eff, result[j]);
}
}
p += 1;
}
}
}
fn blend_fg_one_side(
result: &mut [f64],
ori: &[f64], gf: &[f64], gaptable: &[u8],
eff: f64,
) {
let alen = result.len();
let mut p = 0usize;
let next_is_gap = |j: usize| -> bool {
if j + 1 < alen { gaptable[j + 1] == b'-' } else { false }
};
for j in 0..alen {
if gaptable[j] == b'-' {
if j == alen - 1 {
result[j] += eff;
} else if !next_is_gap(j) {
let gf_val = if p < gf.len() { gf[p] } else { 1.0 };
result[j] = fmadd(gf_val, eff, result[j]);
}
} else {
if !next_is_gap(j) {
if p < ori.len() {
result[j] = fmadd(ori[p], eff, result[j]);
}
}
p += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use mafft_tree::{DistanceMatrix, upgma};
use mafft_scoring::build_context;
use mafft_types::{ScoringModel, SeqType};
fn check_alignment(result: &MultipleAlignment, original: &[Vec<u8>]) {
let width = result.width();
assert!(width > 0);
for (i, seq) in result.sequences.iter().enumerate() {
assert_eq!(seq.len(), width, "seq {i} wrong width: {} vs {width}", seq.len());
let ungapped: Vec<u8> = seq.iter().filter(|&&c| c != b'-').cloned().collect();
assert_eq!(ungapped, original[i], "seq {i} residues not preserved");
}
}
#[test]
fn progressive_two_identical() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let seqs = vec![b"ACDEFGHIK".to_vec(), b"ACDEFGHIK".to_vec()];
let names = vec!["s1".into(), "s2".into()];
let mut dm = DistanceMatrix::new(2);
dm.set(0, 1, 0.0);
let topo = upgma(&dm);
let result = progressive_align(&seqs, &names, &topo, &scoring, false, None);
assert_eq!(result.sequences[0], result.sequences[1]);
check_alignment(&result, &seqs);
}
#[test]
fn progressive_three_sequences() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let seqs = vec![
b"ACDEFGHIK".to_vec(),
b"ACDEFHIK".to_vec(),
b"ACDHIK".to_vec(),
];
let names = vec!["s1".into(), "s2".into(), "s3".into()];
let mut dm = DistanceMatrix::new(3);
dm.set(0, 1, 0.1); dm.set(0, 2, 0.3); dm.set(1, 2, 0.2);
let topo = upgma(&dm);
let result = progressive_align(&seqs, &names, &topo, &scoring, false, None);
check_alignment(&result, &seqs);
}
#[test]
fn progressive_six_sequences_preserves_residues() {
let scoring = build_context(ScoringModel::Blosum(62), SeqType::Protein);
let seqs = vec![
b"ACDEFGHIKLMNPQR".to_vec(),
b"ACDEFHIKLMNPQR".to_vec(),
b"ACDEHIKLMNPQR".to_vec(),
b"ACDHIKLMNPQR".to_vec(),
b"ACDHIKLMNP".to_vec(),
b"ACDHIKLM".to_vec(),
];
let names: Vec<String> = (0..6).map(|i| format!("s{i}")).collect();
let mut dm = DistanceMatrix::new(6);
for i in 0..6 { for j in (i+1)..6 { dm.set(i, j, (j-i) as f64 * 0.1); } }
let topo = upgma(&dm);
let result = progressive_align(&seqs, &names, &topo, &scoring, false, None);
check_alignment(&result, &seqs);
}
}