use std::cmp::min;
type IndexAndAlignments = (usize, Vec<(Option<usize>, Option<usize>)>);
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Trace {
Stop,
Left,
Up,
MatchDiagonal,
MisMatchDiagonal,
}
#[derive(PartialEq, Eq, Debug)]
pub enum NeedlemanWunschError {
MatrixTooLarge { cells: usize, max_cells: usize },
}
fn simple_score<T: PartialEq>(a: T, b: T) -> i32 {
if a == b { 1 } else { -5 }
}
pub struct NeedlemanWunsch<T: PartialEq + Clone> {
pub first_sequence: Vec<T>,
pub second_sequence: Vec<T>,
pub first_extra: Vec<T>,
pub second_extra: Vec<T>,
pub mismatches: Vec<(usize, usize)>,
pub num_mismatches: usize,
}
impl<T: PartialEq + Clone> Default for NeedlemanWunsch<T> {
fn default() -> NeedlemanWunsch<T> {
NeedlemanWunsch {
first_sequence: vec![],
second_sequence: vec![],
first_extra: vec![],
second_extra: vec![],
mismatches: vec![],
num_mismatches: 0,
}
}
}
impl<T: PartialEq + Clone> NeedlemanWunsch<T> {
pub fn match_sequences_base(&mut self) -> IndexAndAlignments {
self.match_sequences(None, None, None)
}
pub fn generate_midpoint_schedule(
&mut self,
start_index: usize,
alignment_difference: Vec<(Option<usize>, Option<usize>)>,
) -> Vec<T> {
let mut midpoint_schedule: Vec<T> = vec![];
let total_swaps = self.num_mismatches / 2;
let available_full_swaps = self.mismatches.len();
let mut current_swaps = 0;
let mut full_swaps = 0;
{
let mut i = 0;
while i < start_index {
midpoint_schedule.push(self.first_sequence[i].clone());
i += 1;
}
}
{
let mut i = 0;
while i < alignment_difference.len() {
if alignment_difference[i].0.is_none() && alignment_difference[i].1.is_none() {
if full_swaps < available_full_swaps && current_swaps < total_swaps {
midpoint_schedule
.push(self.first_sequence[self.mismatches[full_swaps].0].clone());
} else if full_swaps < available_full_swaps {
midpoint_schedule
.push(self.second_sequence[self.mismatches[full_swaps].1].clone());
}
current_swaps += 1;
full_swaps += 1;
} else if alignment_difference[i].0.is_some() && current_swaps < total_swaps {
midpoint_schedule
.push(self.first_sequence[alignment_difference[i].0.unwrap()].clone());
current_swaps += 1;
} else if alignment_difference[i].1.is_some() && current_swaps >= total_swaps {
midpoint_schedule
.push(self.second_sequence[alignment_difference[i].1.unwrap()].clone());
current_swaps += 1;
}
i += 1;
}
}
midpoint_schedule
}
pub fn match_sequences(
&mut self,
scoring_function: Option<fn(T, T) -> i32>,
gap_penalty: Option<i32>,
mismatch_penalty: Option<i32>,
) -> IndexAndAlignments {
self.match_sequences_bounded(scoring_function, gap_penalty, mismatch_penalty, usize::MAX)
.expect("Needleman-Wunsch matrix dimensions overflowed")
}
pub fn match_sequences_bounded(
&mut self,
scoring_function: Option<fn(T, T) -> i32>,
gap_penalty: Option<i32>,
mismatch_penalty: Option<i32>,
max_matrix_cells: usize,
) -> Result<IndexAndAlignments, NeedlemanWunschError> {
let gap_penalty: i32 = gap_penalty.unwrap_or(-1);
let mismatch_penalty: i32 = mismatch_penalty.unwrap_or(-2);
let scoring_function = scoring_function.unwrap_or(simple_score);
self.mismatches.clear();
self.num_mismatches = 0;
let mut start_index = 0;
let max_length: usize = min(self.first_sequence.len(), self.second_sequence.len());
while start_index < max_length
&& scoring_function(
self.first_sequence[start_index].clone(),
self.second_sequence[start_index].clone(),
) > 0
{
start_index += 1;
}
let row = self.first_sequence.len() + 1 - start_index;
let col = self.second_sequence.len() + 1 - start_index;
let cells = row
.checked_mul(col)
.ok_or(NeedlemanWunschError::MatrixTooLarge {
cells: usize::MAX,
max_cells: max_matrix_cells,
})?;
if cells > max_matrix_cells {
return Err(NeedlemanWunschError::MatrixTooLarge {
cells,
max_cells: max_matrix_cells,
});
}
let mut previous_scores = (0..col).map(|j| gap_penalty * j as i32).collect::<Vec<_>>();
let mut current_scores = vec![0; col];
let mut tracing_matrix = vec![Trace::Stop; cells];
for trace in tracing_matrix.iter_mut().take(col).skip(1) {
*trace = Trace::Left;
}
for i in 1..row {
current_scores[0] = gap_penalty * i as i32;
tracing_matrix[i * col] = Trace::Up;
for j in 1..col {
let match_value: i32 = scoring_function(
self.first_sequence[i - 1 + start_index].clone(),
self.second_sequence[j - 1 + start_index].clone(),
);
let diagonal_score = previous_scores[j - 1]
+ if match_value > 0 {
match_value
} else {
mismatch_penalty
};
let horizontal_score = current_scores[j - 1] + gap_penalty;
let vertical_score = previous_scores[j] + gap_penalty;
let (score, trace) =
if diagonal_score >= horizontal_score && diagonal_score >= vertical_score {
(
diagonal_score,
if match_value > 0 {
Trace::MatchDiagonal
} else {
Trace::MisMatchDiagonal
},
)
} else if horizontal_score >= vertical_score {
(horizontal_score, Trace::Left)
} else {
(vertical_score, Trace::Up)
};
current_scores[j] = score;
tracing_matrix[i * col + j] = trace;
}
std::mem::swap(&mut previous_scores, &mut current_scores);
}
let mut aligned_seq: Vec<(Option<usize>, Option<usize>)> = vec![];
let (mut i, mut j) = (row - 1, col - 1);
while i > 0 || j > 0 {
match tracing_matrix[i * col + j] {
Trace::MatchDiagonal => {
aligned_seq.push((Some(i - 1 + start_index), Some(j - 1 + start_index)));
i -= 1;
j -= 1;
}
Trace::Up => {
aligned_seq.push((Some(i - 1 + start_index), None));
i -= 1;
self.num_mismatches += 1;
}
Trace::Left => {
aligned_seq.push((None, Some(j - 1 + start_index)));
j -= 1;
self.num_mismatches += 1;
}
Trace::MisMatchDiagonal => {
aligned_seq.push((None, None));
self.num_mismatches += 2;
self.mismatches
.push((i - 1 + start_index, j - 1 + start_index));
i -= 1;
j -= 1;
}
Trace::Stop => unreachable!("traceback stopped before reaching matrix origin"),
}
}
self.mismatches.reverse();
aligned_seq.reverse();
Ok((start_index, aligned_seq))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_complete_match() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![10],
second_sequence: vec![10],
..Default::default()
};
assert_eq!(sw_object.match_sequences_base(), (1, vec![]));
}
#[test]
fn test_single_complete_mismatch() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![0],
second_sequence: vec![1],
..Default::default()
};
assert_eq!(sw_object.match_sequences_base(), (0, vec![(None, None)],));
}
#[test]
fn test_partial_one_off_match() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![3, 3, 4, 4, 3, 1, 2, 4, 1],
second_sequence: vec![4, 3, 4, 4, 1, 2, 3, 3],
..Default::default()
};
assert_eq!(
sw_object.match_sequences_base(),
(
0,
vec![
(None, None),
(Some(1), Some(1)),
(Some(2), Some(2)),
(Some(3), Some(3)),
(Some(4), None),
(Some(5), Some(4)),
(Some(6), Some(5)),
(None, None),
(None, None)
]
)
);
}
#[test]
fn test_gap_match() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![1, 2, 3],
second_sequence: vec![1, 4, 3],
..Default::default()
};
assert_eq!(
sw_object.match_sequences_base(),
(1, vec![(None, None), (Some(2), Some(2))])
);
}
#[test]
fn test_gap_start_match() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![2, 4, 3],
second_sequence: vec![1, 4, 3],
..Default::default()
};
assert_eq!(
sw_object.match_sequences_base(),
(
0,
vec![(None, None), (Some(1), Some(1)), (Some(2), Some(2))],
)
);
}
#[test]
fn bounded_alignment_rejects_large_matrices() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![1; 100],
second_sequence: vec![2; 100],
..Default::default()
};
assert_eq!(
sw_object.match_sequences_bounded(None, None, None, 10_000),
Err(NeedlemanWunschError::MatrixTooLarge {
cells: 10_201,
max_cells: 10_000,
})
);
}
#[test]
fn alignment_includes_unmatched_suffix() {
let mut sw_object = NeedlemanWunsch {
first_sequence: vec![1, 2],
second_sequence: vec![1, 2, 3],
..Default::default()
};
assert_eq!(sw_object.match_sequences_base(), (2, vec![(None, Some(2))]));
}
}