use crate::alignment::{Alignment, BackTrackable, BacktrackMatrix, ScalarProfile};
use std::ops::Add;
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn nw_scalar_score<const S: usize>(reference: &[u8], query: &ScalarProfile<S>) -> i32 {
let mut h_row = vec![0; query.seq.len()];
let mut h = query.gap_open;
for h_val in &mut h_row {
*h_val = h;
h += query.gap_extend;
}
let mut e_row = vec![0; query.seq.len()];
let mut e = 2 * query.gap_open;
for e_val in &mut e_row {
*e_val = e;
e += query.gap_extend;
}
for (r, reference_base) in reference.iter().copied().enumerate() {
let mut f = 2 * query.gap_open + r as i32 * query.gap_extend;
let mut h = if r == 0 {
0
} else {
query.gap_open + query.gap_extend * (r - 1) as i32
};
for c in 0..query.seq.len() {
let match_score = i32::from(query.matrix.get_weight(reference_base, query.seq[c]));
h += match_score;
let mut e = e_row[c];
h = h.max(e).max(f);
e = e.add(query.gap_extend).max(h + query.gap_open);
f = f.add(query.gap_extend).max(h + query.gap_open);
(h, h_row[c]) = (h_row[c], h);
e_row[c] = e;
}
}
h_row[h_row.len() - 1]
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn nw_scalar_align<const S: usize>(reference: &[u8], query: &ScalarProfile<S>) -> Alignment<i32> {
let mut h_row = vec![0; query.seq.len()];
let mut h = query.gap_open;
for h_val in &mut h_row {
*h_val = h;
h += query.gap_extend;
}
let mut e_row = vec![0; query.seq.len()];
let mut e = 2 * query.gap_open;
for e_val in &mut e_row {
*e_val = e;
e += query.gap_extend;
}
let mut backtrack = BacktrackMatrix::new(reference.len(), query.seq.len());
for (r, reference_base) in reference.iter().copied().enumerate() {
let mut f = 2 * query.gap_open + r as i32 * query.gap_extend;
let mut h = if r == 0 {
0
} else {
query.gap_open + query.gap_extend * (r - 1) as i32
};
for c in 0..query.seq.len() {
backtrack.move_to(r, c);
let match_score = i32::from(query.matrix.get_weight(reference_base, query.seq[c]));
h += match_score;
let mut e = e_row[c];
h = h.max(e).max(f);
if e == h {
backtrack.up();
}
if f == h {
backtrack.left();
}
let next_diag = h_row[c];
h_row[c] = h;
h += query.gap_open;
e = e.add(query.gap_extend).max(h);
f = f.add(query.gap_extend).max(h);
if e > h {
backtrack.up_extending();
}
if f > h {
backtrack.left_extending();
}
h = next_diag;
e_row[c] = e;
}
}
backtrack.to_alignment_global(h_row[h_row.len() - 1], reference.len(), query.seq.len())
}
#[cfg(feature = "alignment-diagnostics")]
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
pub fn nw_score_from_path<const S: usize>(
ciglets: impl IntoIterator<Item = crate::data::cigar::Ciglet>, reference: &[u8], query: &ScalarProfile<S>,
) -> Result<i32, super::ScoringError> {
use std::cmp::Ordering::{Equal, Greater, Less};
let mut score = 0;
let mut r = 0;
let mut q = 0;
for crate::data::cigar::Ciglet { inc, op } in ciglets {
match op {
b'M' | b'=' | b'X' => {
for _ in 0..inc {
let Some(reference_base) = reference.get(r).copied() else {
return Err(super::ScoringError::ReferenceEnded);
};
let Some(query_base) = query.seq.get(q).copied() else {
return Err(super::ScoringError::QueryEnded);
};
score += i32::from(query.matrix.get_weight(reference_base, query_base));
q += 1;
r += 1;
}
}
b'I' => {
score += query.gap_open + query.gap_extend * (inc - 1) as i32;
q += inc;
}
b'D' => {
score += query.gap_open + query.gap_extend * (inc - 1) as i32;
r += inc;
}
b'N' => r += inc,
b'P' => {}
op => return Err(super::ScoringError::InvalidCigarOp(op)),
}
}
match q.cmp(&query.seq.len()) {
Less => return Err(super::ScoringError::FullQueryNotUsed),
Greater => return Err(super::ScoringError::QueryEnded),
Equal => {}
}
match r.cmp(&reference.len()) {
Less => return Err(super::ScoringError::FullReferenceNotUsed),
Greater => return Err(super::ScoringError::ReferenceEnded),
Equal => {}
}
Ok(score)
}