use crate::{
alignment::AlignmentIndices,
data::types::cigar::{Cigar, Ciglet},
};
use std::{hint::cold_path, ops::Range};
#[derive(Clone, Eq, PartialEq, Default)]
pub struct AlignmentStates(pub(crate) Vec<Ciglet>);
impl AlignmentStates {
#[inline]
#[must_use]
pub fn new() -> Self {
AlignmentStates(Vec::new())
}
#[inline]
#[must_use]
pub fn with_capacity(n: usize) -> Self {
AlignmentStates(Vec::with_capacity(n))
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[Ciglet] {
self.0.as_slice()
}
#[inline]
#[must_use]
pub fn as_mut_slice(&mut self) -> &mut [Ciglet] {
self.0.as_mut_slice()
}
#[inline]
#[must_use]
pub fn as_mut_vec(&mut self) -> &mut Vec<Ciglet> {
&mut self.0
}
pub fn add_state(&mut self, op: u8) {
self.add_ciglet(Ciglet { inc: 1, op });
}
pub fn prepend_state(&mut self, op: u8) {
self.prepend_ciglet(Ciglet { inc: 1, op });
}
pub fn add_ciglet(&mut self, ciglet: Ciglet) {
if ciglet.inc > 0 {
if let Some(c) = self.0.last_mut()
&& c.op == ciglet.op
{
c.inc += ciglet.inc;
} else {
self.0.push(ciglet);
}
}
}
pub fn prepend_ciglet(&mut self, ciglet: Ciglet) {
if ciglet.inc > 0 {
if let Some(c) = self.0.first_mut()
&& c.op == ciglet.op
{
c.inc += ciglet.inc;
} else {
self.0.insert(0, ciglet);
}
}
}
#[inline]
pub fn add_inc_op(&mut self, inc: usize, op: u8) {
self.add_ciglet(Ciglet { inc, op });
}
#[inline]
pub fn prepend_inc_op(&mut self, inc: usize, op: u8) {
self.prepend_ciglet(Ciglet { inc, op });
}
pub(crate) fn new_no_gaps(aligned_range: Range<usize>, query_len: usize) -> Self {
let mut states = AlignmentStates::with_capacity(3);
states.soft_clip(aligned_range.start);
states.add_inc_op(aligned_range.end - aligned_range.start, b'M');
states.soft_clip(query_len - aligned_range.end);
states
}
pub(crate) fn extend_from_ciglets<I>(&mut self, ciglets: I)
where
I: IntoIterator<Item = Ciglet>, {
let mut ciglets = ciglets.into_iter();
let Some(first_ciglet) = ciglets.next() else { return };
self.add_ciglet(first_ciglet);
self.0.extend(ciglets);
}
pub fn soft_clip(&mut self, inc: usize) {
if inc > 0 {
if let Some(c) = self.0.last_mut()
&& c.op == b'S'
{
c.inc += inc;
} else {
self.0.push(Ciglet { inc, op: b'S' });
}
}
}
pub fn prepend_soft_clip(&mut self, inc: usize) {
self.prepend_ciglet(Ciglet { inc, op: b'S' });
}
#[must_use]
pub fn to_cigar_unchecked(&self) -> Cigar {
Cigar::from_ciglets_unchecked(self.0.iter().copied())
}
#[must_use]
pub fn from_ciglets_unchecked<I: IntoIterator<Item = Ciglet>>(ciglets: I) -> Self {
Self(ciglets.into_iter().collect())
}
#[must_use]
pub fn from_cigar_unchecked(cigar: &Cigar) -> Self {
Self(cigar.iter().collect())
}
#[inline]
pub fn make_reverse(&mut self) {
self.0.reverse();
}
#[inline]
#[must_use]
pub fn to_reverse(&self) -> Self {
Self::from_ciglets_unchecked(self.into_iter().rev())
}
#[inline]
pub fn iter(&self) -> std::slice::Iter<'_, Ciglet> {
self.0.iter()
}
#[must_use]
pub fn to_verbose_sequence_matching(&self, reference: &[u8], query: &[u8], mut ref_index: usize) -> Self {
let mut out = AlignmentStates::with_capacity(self.0.len());
let mut query_index = 0;
for ciglet in self {
if ciglet.op == b'M' {
let query_slice = &query[query_index..query_index + ciglet.inc];
let ref_slice = &reference[ref_index..ref_index + ciglet.inc];
for (query_base, ref_base) in query_slice.iter().zip(ref_slice) {
out.add_state(if query_base == ref_base { b'=' } else { b'X' });
}
query_index += ciglet.inc;
ref_index += ciglet.inc;
} else {
out.add_ciglet(ciglet);
(query_index, ref_index) = (query_index, ref_index).increment_idxs_by(ciglet);
}
}
out
}
}
pub struct AlignmentIter<'a, I>
where
I: Iterator<Item = Ciglet>, {
reference_buffer: &'a [u8],
query_buffer: &'a [u8],
ciglets: I,
inc: usize,
op: u8,
}
impl<'a, I> AlignmentIter<'a, I>
where
I: Iterator<Item = Ciglet>,
{
#[inline]
#[must_use]
pub(crate) fn new(
reference: &'a [u8], query: &'a [u8], ciglets: impl IntoIterator<Item = Ciglet, IntoIter = I>, ref_index: usize,
) -> Self {
let reference_buffer = &reference[ref_index..];
let query_buffer = query;
let mut ciglets = ciglets.into_iter();
let Ciglet { inc, op } = ciglets.next().unwrap_or(Ciglet { inc: 0, op: b'M' });
AlignmentIter {
reference_buffer,
query_buffer,
ciglets,
inc,
op,
}
}
#[inline]
#[must_use]
fn get_next_op(&mut self) -> Option<u8> {
if self.inc > 0 {
self.inc -= 1;
Some(self.op)
} else {
let Ciglet { inc, op } = self.ciglets.next()?;
self.inc = inc;
self.op = op;
self.get_next_op()
}
}
#[inline]
#[must_use]
fn skip_to_next_ciglet(&mut self) -> Option<()> {
let Ciglet { inc, op } = self.ciglets.next()?;
self.inc = inc;
self.op = op;
Some(())
}
#[inline]
#[must_use]
fn advance_reference(&mut self) -> u8 {
let out = self.reference_buffer[0];
self.reference_buffer = &self.reference_buffer[1..];
out
}
#[inline]
#[must_use]
fn advance_query(&mut self) -> u8 {
let out = self.query_buffer[0];
self.query_buffer = &self.query_buffer[1..];
out
}
}
impl<I> Iterator for AlignmentIter<'_, I>
where
I: Iterator<Item = Ciglet>,
{
type Item = (Option<u8>, Option<u8>);
fn next(&mut self) -> Option<Self::Item> {
let op = self.get_next_op()?;
match op {
b'M' | b'=' | b'X' => Some((Some(self.advance_reference()), Some(self.advance_query()))),
b'D' => Some((Some(self.advance_reference()), None)),
b'I' => Some((None, Some(self.advance_query()))),
b'S' => {
self.query_buffer = &self.query_buffer[self.inc + 1..];
self.skip_to_next_ciglet()?;
self.next()
}
b'N' => Some((Some(self.advance_reference()), Some(b'N'))),
b'H' | b'P' => {
self.skip_to_next_ciglet()?;
self.next()
}
_ => panic!("CIGAR op '{op}' not supported.\n"),
}
}
}
pub trait StatesSequence {
#[must_use]
fn peek_op(&mut self) -> Option<u8>;
#[must_use]
fn peek_back_op(&mut self) -> Option<u8>;
#[must_use]
fn is_empty(&self) -> bool;
fn next_ciglet(&mut self) -> Option<Ciglet>;
fn next_ciglet_back(&mut self) -> Option<Ciglet>;
#[inline]
fn next_if_op(&mut self, f: impl FnOnce(u8) -> bool) -> Option<Ciglet> {
if f(self.peek_op()?) { self.next_ciglet() } else { None }
}
#[inline]
fn next_back_if_op(&mut self, f: impl FnOnce(u8) -> bool) -> Option<Ciglet> {
if f(self.peek_back_op()?) {
self.next_ciglet_back()
} else {
None
}
}
#[inline]
fn remove_clipping_front(&mut self) -> usize {
let hard_clipping = self.next_if_op(|op| op == b'H').map_or(0, |ciglet| ciglet.inc);
let soft_clipping = self.next_if_op(|op| op == b'S').map_or(0, |ciglet| ciglet.inc);
hard_clipping + soft_clipping
}
#[inline]
fn remove_clipping_back(&mut self) -> usize {
let hard_clipping = self.next_back_if_op(|op| op == b'H').map_or(0, |ciglet| ciglet.inc);
let soft_clipping = self.next_back_if_op(|op| op == b'S').map_or(0, |ciglet| ciglet.inc);
hard_clipping + soft_clipping
}
}
pub trait StatesSequenceMut<'a>: StatesSequence {
fn next_ciglet_mut(&mut self) -> Option<&'a mut Ciglet>;
fn next_ciglet_back_mut(&mut self) -> Option<&'a mut Ciglet>;
#[inline]
fn next_if_op_mut(&mut self, f: impl FnOnce(u8) -> bool) -> Option<&'a mut Ciglet> {
if f(self.peek_op()?) { self.next_ciglet_mut() } else { None }
}
#[inline]
fn next_back_if_op_mut(&mut self, f: impl FnOnce(u8) -> bool) -> Option<&'a mut Ciglet> {
if f(self.peek_back_op()?) {
self.next_ciglet_back_mut()
} else {
None
}
}
}
impl StatesSequence for &[Ciglet] {
#[inline]
fn peek_op(&mut self) -> Option<u8> {
loop {
let ciglet = self.first()?;
if ciglet.inc == 0 {
cold_path();
self.split_off_first();
} else {
return Some(ciglet.op);
}
}
}
#[inline]
fn peek_back_op(&mut self) -> Option<u8> {
loop {
let ciglet = self.last()?;
if ciglet.inc == 0 {
cold_path();
self.split_off_last();
} else {
return Some(ciglet.op);
}
}
}
#[inline]
fn is_empty(&self) -> bool {
(*self).is_empty()
}
#[inline]
fn next_ciglet(&mut self) -> Option<Ciglet> {
loop {
let ciglet = self.split_off_first()?;
if ciglet.inc == 0 {
cold_path();
} else {
return Some(*ciglet);
}
}
}
#[inline]
fn next_ciglet_back(&mut self) -> Option<Ciglet> {
loop {
let ciglet = self.split_off_last()?;
if ciglet.inc == 0 {
cold_path();
} else {
return Some(*ciglet);
}
}
}
}
impl StatesSequence for &mut [Ciglet] {
#[inline]
fn peek_op(&mut self) -> Option<u8> {
self.as_ref().peek_op()
}
#[inline]
fn peek_back_op(&mut self) -> Option<u8> {
self.as_ref().peek_back_op()
}
#[inline]
fn is_empty(&self) -> bool {
self.as_ref().is_empty()
}
#[inline]
fn next_ciglet(&mut self) -> Option<Ciglet> {
self.next_ciglet_mut().copied()
}
#[inline]
fn next_ciglet_back(&mut self) -> Option<Ciglet> {
self.next_ciglet_back_mut().copied()
}
}
impl<'a> StatesSequenceMut<'a> for &'a mut [Ciglet] {
#[inline]
fn next_ciglet_mut(&mut self) -> Option<&'a mut Ciglet> {
loop {
let ciglet = self.split_off_first_mut()?;
if ciglet.inc == 0 {
cold_path();
} else {
return Some(ciglet);
}
}
}
#[inline]
fn next_ciglet_back_mut(&mut self) -> Option<&'a mut Ciglet> {
loop {
let ciglet = self.split_off_last_mut()?;
if ciglet.inc == 0 {
cold_path();
} else {
return Some(ciglet);
}
}
}
}