pub type TextSlice<'a> = &'a [u8];
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentOperation {
Match,
Subst,
Del,
Ins,
Xclip(usize),
Yclip(usize),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentMode {
Local,
Semiglobal,
Global,
Custom,
}
impl Default for AlignmentMode {
fn default() -> Self {
AlignmentMode::Global
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Default)]
pub struct Alignment {
pub score: i32,
pub ystart: usize,
pub xstart: usize,
pub yend: usize,
pub xend: usize,
pub ylen: usize,
pub xlen: usize,
pub operations: Vec<AlignmentOperation>,
pub mode: AlignmentMode,
}
impl Alignment {
pub fn cigar(&self, hard_clip: bool) -> String {
match self.mode {
AlignmentMode::Global => panic!(" Cigar fn not supported for Global Alignment mode"),
AlignmentMode::Local => panic!(" Cigar fn not supported for Local Alignment mode"),
_ => {}
}
let clip_str = if hard_clip { "H" } else { "S" };
let add_op = |op: AlignmentOperation, k, cigar: &mut String| match op {
AlignmentOperation::Match => cigar.push_str(&format!("{}{}", k, "=")),
AlignmentOperation::Subst => cigar.push_str(&format!("{}{}", k, "X")),
AlignmentOperation::Del => cigar.push_str(&format!("{}{}", k, "D")),
AlignmentOperation::Ins => cigar.push_str(&format!("{}{}", k, "I")),
_ => {}
};
let mut cigar = "".to_owned();
if self.operations.is_empty() {
return cigar;
}
let mut last = self.operations[0];
if self.xstart > 0 {
cigar.push_str(&format!("{}{}", self.xstart, clip_str))
}
let mut k = 1;
for &op in self.operations[1..].iter() {
if op == last {
k += 1;
} else {
add_op(last, k, &mut cigar);
k = 1;
}
last = op;
}
add_op(last, k, &mut cigar);
if self.xlen > self.xend {
cigar.push_str(&format!("{}{}", self.xlen - self.xend, clip_str))
}
cigar
}
pub fn pretty(&self, x: TextSlice, y: TextSlice) -> String {
let mut x_pretty = String::new();
let mut y_pretty = String::new();
let mut inb_pretty = String::new();
if !self.operations.is_empty() {
let mut x_i: usize;
let mut y_i: usize;
match self.mode {
AlignmentMode::Custom => {
x_i = 0;
y_i = 0;
}
_ => {
x_i = self.xstart;
y_i = self.ystart;
for k in x.iter().take(self.xstart) {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
inb_pretty.push(' ');
y_pretty.push(' ')
}
for k in y.iter().take(self.ystart) {
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
inb_pretty.push(' ');
x_pretty.push(' ')
}
}
}
for i in 0..self.operations.len() {
match self.operations[i] {
AlignmentOperation::Match => {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
x_i += 1;
inb_pretty.push_str("|");
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
y_i += 1;
}
AlignmentOperation::Subst => {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
x_i += 1;
inb_pretty.push('\\');
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
y_i += 1;
}
AlignmentOperation::Del => {
x_pretty.push('-');
inb_pretty.push('x');
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
y_i += 1;
}
AlignmentOperation::Ins => {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
x_i += 1;
inb_pretty.push('+');
y_pretty.push('-');
}
AlignmentOperation::Xclip(len) => for k in x.iter().take(len) {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
x_i += 1;
inb_pretty.push(' ');
y_pretty.push(' ')
},
AlignmentOperation::Yclip(len) => for k in y.iter().take(len) {
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
y_i += 1;
inb_pretty.push(' ');
x_pretty.push(' ')
},
}
}
match self.mode {
AlignmentMode::Custom => {}
_ => {
for k in x.iter().take(self.xlen).skip(x_i) {
x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
inb_pretty.push(' ');
y_pretty.push(' ')
}
for k in y.iter().take(self.ylen).skip(y_i) {
y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[*k])));
inb_pretty.push(' ');
x_pretty.push(' ')
}
}
}
}
let mut s = String::new();
let mut idx = 0;
let step = 100;
use std::cmp::min;
assert_eq!(x_pretty.len(), inb_pretty.len());
assert_eq!(y_pretty.len(), inb_pretty.len());
let ml = x_pretty.len();
while idx < ml {
let rng = idx..min(idx + step, ml);
s.push_str(&x_pretty[rng.clone()]);
s.push_str("\n");
s.push_str(&inb_pretty[rng.clone()]);
s.push_str("\n");
s.push_str(&y_pretty[rng]);
s.push_str("\n");
s.push_str("\n\n");
idx += step;
}
s
}
pub fn path(&self) -> Vec<(usize, usize, AlignmentOperation)> {
let mut path = Vec::new();
if !self.operations.is_empty() {
let last = match self.mode {
AlignmentMode::Custom => (self.xlen, self.ylen),
_ => (self.xend, self.yend),
};
let mut x_i = last.0;
let mut y_i = last.1;
let mut ops = self.operations.clone();
ops.reverse();
for i in ops {
path.push((x_i, y_i, i));
match i {
AlignmentOperation::Match => {
x_i -= 1;
y_i -= 1;
}
AlignmentOperation::Subst => {
x_i -= 1;
y_i -= 1;
}
AlignmentOperation::Del => {
y_i -= 1;
}
AlignmentOperation::Ins => {
x_i -= 1;
}
AlignmentOperation::Xclip(len) => {
x_i -= len;
}
AlignmentOperation::Yclip(len) => {
y_i -= len;
}
}
}
}
path.reverse();
path
}
pub fn filter_clip_operations(&mut self) {
use self::AlignmentOperation::{Del, Ins, Match, Subst};
self.operations
.retain(|x| (*x == Match || *x == Subst || *x == Ins || *x == Del));
}
pub fn y_aln_len(&self) -> usize {
self.yend - self.ystart
}
pub fn x_aln_len(&self) -> usize {
self.xend - self.xstart
}
}
#[cfg(test)]
mod tests {
use super::AlignmentOperation::*;
use super::*;
#[test]
fn test_cigar() {
let alignment = Alignment {
score: 5,
xstart: 3,
ystart: 0,
xend: 9,
yend: 10,
ylen: 10,
xlen: 10,
operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del],
mode: AlignmentMode::Semiglobal,
};
assert_eq!(alignment.cigar(false), "3S3=1X2I2D1S");
let alignment = Alignment {
score: 5,
xstart: 0,
ystart: 5,
xend: 4,
yend: 10,
ylen: 10,
xlen: 5,
operations: vec![Yclip(5), Match, Subst, Subst, Ins, Del, Del, Xclip(1)],
mode: AlignmentMode::Custom,
};
assert_eq!(alignment.cigar(false), "1=2X1I2D1S");
assert_eq!(alignment.cigar(true), "1=2X1I2D1H");
let alignment = Alignment {
score: 5,
xstart: 0,
ystart: 5,
xend: 3,
yend: 8,
ylen: 10,
xlen: 3,
operations: vec![Yclip(5), Subst, Match, Subst, Yclip(2)],
mode: AlignmentMode::Custom,
};
assert_eq!(alignment.cigar(false), "1X1=1X");
let alignment = Alignment {
score: 5,
xstart: 0,
ystart: 5,
xend: 3,
yend: 8,
ylen: 10,
xlen: 3,
operations: vec![Subst, Match, Subst],
mode: AlignmentMode::Semiglobal,
};
assert_eq!(alignment.cigar(false), "1X1=1X");
}
#[test]
fn test_pretty() {
let alignment = Alignment {
score: 1,
xstart: 0,
ystart: 2,
xend: 3,
yend: 5,
ylen: 7,
xlen: 2,
operations: vec![Subst, Match, Match],
mode: AlignmentMode::Semiglobal,
};
let pretty = concat!(" GAT \n", " \\|| \n", "CTAATCC\n", "\n\n");
assert_eq!(alignment.pretty(b"GAT", b"CTAATCC"), pretty);
let alignment = Alignment {
score: 5,
xstart: 0,
ystart: 5,
xend: 4,
yend: 10,
ylen: 10,
xlen: 5,
operations: vec![Yclip(5), Match, Subst, Subst, Ins, Del, Del, Xclip(1)],
mode: AlignmentMode::Custom,
};
let pretty = concat!(" AAAA--A\n |\\\\+xx \nTTTTTTTT-TT \n\n\n");
assert_eq!(alignment.pretty(b"AAAAA", b"TTTTTTTTTT"), pretty);
}
}