Skip to content

Commit

Permalink
Reworked align::Type into more exhaustive structure
Browse files Browse the repository at this point in the history
Signed-off-by: Douwe Schulte <d.schulte@uu.nl>
  • Loading branch information
douweschulte committed Dec 20, 2023
1 parent fbaefae commit 0d0aefa
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 21 deletions.
160 changes: 149 additions & 11 deletions src/align/align_type.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,161 @@
use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// The type of alignment to perform
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Serialize, Deserialize,
)]
pub enum Type {
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct Type {
state: u8,
}

impl Type {
/// Global alignment, which tries to find the best alignment to link both sequences fully to each other, like the Needleman Wunsch algorithm
#[default]
Global,
pub const GLOBAL: Self = Self::new(true, true, true, true);
/// Local alignment, which tries to find the best patch of both sequences to align to each other, this could lead to trailing ends on both sides of both sequences, like the Smith Waterman
Local,
/// Hybrid alignment, the second sequence will be fully aligned to the first sequence, this could lead to trailing ends on the first sequence but not on the second.
GlobalForB,
pub const LOCAL: Self = Self::new(false, false, false, false);
/// Hybrid alignment, the first sequence will be fully aligned to the second sequence, this could lead to trailing ends on the second sequence but not on the first.
GlobalForA,
pub const GLOBAL_A: Self = Self::new(true, false, true, false);
/// Hybrid alignment, the second sequence will be fully aligned to the first sequence, this could lead to trailing ends on the first sequence but not on the second.
pub const GLOBAL_B: Self = Self::new(false, true, false, true);
/// Hybrid alignment, globally align the left (start) side of the alignment
pub const GLOBAL_LEFT: Self = Self::new(true, true, false, false);
/// Hybrid alignment, globally align the right (end) side of the alignment
pub const GLOBAL_RIGHT: Self = Self::new(false, false, true, true);
/// Hybrid alignment, extend sequence a with sequence b (▀▀██▄▄)
pub const EXTEND_A: Self = Self::new(false, true, true, false);
/// Hybrid alignment, extend sequence b with sequence a (▄▄██▀▀)
pub const EXTEND_B: Self = Self::new(true, false, false, true);

const LEFT_A: u8 = 0;
const LEFT_B: u8 = 1;
const RIGHT_A: u8 = 2;
const RIGHT_B: u8 = 3;

/// Create a new alignment type
#[allow(clippy::fn_params_excessive_bools)]
pub const fn new(left_a: bool, left_b: bool, right_a: bool, right_b: bool) -> Self {
const fn fu8(v: bool) -> u8 {
if v {
1
} else {
0
}
}
Self {
state: (fu8(left_a) << Self::LEFT_A)
| (fu8(left_b) << Self::LEFT_B)
| (fu8(right_a) << Self::RIGHT_A)
| (fu8(right_b) << Self::RIGHT_B),
}
}

/// Check if left a is global
pub const fn left_a(&self) -> bool {
self.state >> Self::LEFT_A & 1 == 1
}
/// Check if left b is global
pub const fn left_b(&self) -> bool {
self.state >> Self::LEFT_B & 1 == 1
}
/// Check if right a is global
pub const fn right_a(&self) -> bool {
self.state >> Self::RIGHT_A & 1 == 1
}
/// Check if right b is global
pub const fn right_b(&self) -> bool {
self.state >> Self::RIGHT_B & 1 == 1
}

/// Get a descriptive name for this alignment type
pub const fn description(&self) -> &'static str {
match (self.left_a(), self.left_b(), self.right_a(), self.right_b()) {
(true, true, true, true) => "global",
(false, false, false, false) => "local",
(true, false, true, false) => "global A",
(false, true, false, true) => "global B",
(true, true, false, false) => "global left",
(false, false, true, true) => "global right",
(false, true, true, false) => "extend A",
(true, false, false, true) => "extend B",
_ => "special",
}
}

/// Get a concise symbol for this alignment type (four quadrants, each filled if that spot is global)
pub const fn symbol(&self) -> char {
match (self.left_a(), self.left_b(), self.right_a(), self.right_b()) {
(false, false, false, false) => ' ',
(true, false, false, false) => '▘',
(false, true, false, false) => '▖',
(false, false, true, false) => '▝',
(false, false, false, true) => '▗',
(true, false, true, false) => '▀',
(false, true, false, true) => '▄',
(true, true, false, false) => '▌',
(false, false, true, true) => '▐',
(false, true, true, false) => '▞',
(true, false, false, true) => '▚',
(false, true, true, true) => '▟',
(true, false, true, true) => '▜',
(true, true, false, true) => '▙',
(true, true, true, false) => '▛',
(true, true, true, true) => '█',
}
}
}

impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.symbol(), self.description())
}
}

impl FromStr for Type {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"0" | "0000" | " " | "l" | "local" => Ok(Self::new(false, false, false, false)),
"8" | "1000" | "▘" => Ok(Self::new(true, false, false, false)),
"4" | "0100" | "▖" => Ok(Self::new(false, true, false, false)),
"2" | "0010" | "▝" => Ok(Self::new(false, false, true, false)),
"1" | "0001" | "▗" => Ok(Self::new(false, false, false, true)),
"10" | "1010" | "▀" | "a" | "global a" | "global_a" => {
Ok(Self::new(true, false, true, false))
}
"5" | "0101" | "▄" | "b" | "global b" | "global_b" => {
Ok(Self::new(false, true, false, true))
}
"12" | "1100" | "▌" | "left" | "global left" | "global_left" => {
Ok(Self::new(true, true, false, false))
}
"3" | "0011" | "▐" | "right" | "global right" | "global_right" => {
Ok(Self::new(false, false, true, true))
}
"6" | "0110" | "▞" | "ea" | "extend a" | "extend_a" => {
Ok(Self::new(false, true, true, false))
}
"9" | "1001" | "▚" | "eb" | "extend b" | "extend_b" => {
Ok(Self::new(true, false, false, true))
}
"7" | "0111" | "▟" => Ok(Self::new(false, true, true, true)),
"11" | "1011" | "▜" => Ok(Self::new(true, false, true, true)),
"13" | "1101" | "▙" => Ok(Self::new(true, true, false, true)),
"14" | "1110" | "▛" => Ok(Self::new(true, true, true, false)),
"15" | "1111" | "█" | "g" | "global" => Ok(Self::new(true, true, true, true)),
_ => Err(()),
}
}
}

impl Default for Type {
/// Defaults to global alignment
fn default() -> Self {
Self::GLOBAL
}
}

impl Type {
pub(super) const fn global(self) -> bool {
!matches!(self, Self::Local)
!matches!(self, Self::LOCAL)
}
}
4 changes: 2 additions & 2 deletions src/align/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl Alignment {
}

fn mass_a(&self) -> Option<MolecularFormula> {
if self.ty == Type::Global {
if self.ty.left_a() && self.ty.right_a() {
self.seq_a.formula()
} else {
let mut placed_a = vec![false; self.seq_a.ambiguous_modifications.len()];
Expand All @@ -129,7 +129,7 @@ impl Alignment {
}

fn mass_b(&self) -> Option<MolecularFormula> {
if self.ty == Type::Global {
if self.ty.left_b() && self.ty.right_b() {
self.seq_b.formula()
} else {
let mut placed_b = vec![false; self.seq_b.ambiguous_modifications.len()];
Expand Down
20 changes: 12 additions & 8 deletions src/align/mass_alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn align<const STEPS: usize>(
let masses_a = calculate_masses(STEPS, &seq_a);
let masses_b = calculate_masses(STEPS, &seq_b);

if ty == Type::Global || ty == Type::GlobalForB {
if ty.left_b() {
#[allow(clippy::cast_possible_wrap)]
// b is always less than seq_b
for index_b in 0..=seq_b.len() {
Expand All @@ -34,7 +34,7 @@ pub fn align<const STEPS: usize>(
);
}
}
if ty == Type::Global || ty == Type::GlobalForA {
if ty.left_a() {
#[allow(clippy::cast_possible_wrap)]
// a is always less than seq_a
for (index_a, row) in matrix.iter_mut().enumerate() {
Expand Down Expand Up @@ -116,31 +116,35 @@ pub fn align<const STEPS: usize>(
}
}

// loop back
if ty == Type::Global {
// Find end spot on right side
if ty.right_a() && ty.right_b() {
high = (
matrix[seq_a.len()][seq_b.len()].score,
seq_a.len(),
seq_b.len(),
);
} else if ty == Type::GlobalForB {
} else if ty.right_b() {
let value = (0..=seq_a.len())
.map(|v| (v, matrix[v][seq_b.len()].score))
.max_by(|a, b| a.1.cmp(&b.1))
.unwrap_or_default();
high = (value.1, value.0, seq_b.len());
} else if ty == Type::GlobalForA {
} else if ty.right_a() {
let value = (0..=seq_b.len())
.map(|v| (v, matrix[seq_a.len()][v].score))
.max_by(|a, b| a.1.cmp(&b.1))
.unwrap_or_default();
high = (value.1, seq_a.len(), value.0);
}

let mut path = Vec::new();
let high_score = high.0;
while ty == Type::Global || !(high.1 == 0 && high.2 == 0) {

// Loop back to left side
while ty.left_a() && ty.left_b() || !(high.1 == 0 && high.2 == 0) {
let value = matrix[high.1][high.2].clone();
if value.step_a == 0 && value.step_b == 0 || ty == Type::Local && value.score < 0 {
if value.step_a == 0 && value.step_b == 0 || !ty.left_a() && !ty.left_b() && value.score < 0
{
break;
}
high = (
Expand Down

0 comments on commit 0d0aefa

Please sign in to comment.