use std::cmp::{max, min};
use std::collections::BTreeSet;
use std::marker::PhantomData;
use std::ops::{RangeFrom, RangeTo};
use crate::aligner::offsets::OffsetType;
use crate::aligner::scoring::{AlignmentCosts, GetAlignmentCosts, Score};
use crate::aligner::aln_graph::{AlignmentGraphNode, AlignState};
use crate::aligner::astar::AstarVisited;
use crate::bubbles::index::{BubbleIndex, NodeBubbleMap};
use crate::graphs::NodeIndexType;
pub struct ReachedBubbleExitsMatch<'a, V, N, O>
where V: AstarVisited<N, O> + GetAlignmentCosts,
N: NodeIndexType,
O: OffsetType,
{
visited: &'a V,
reached_offsets: &'a BTreeSet<O>,
seq_len: usize,
dummy: PhantomData<N>,
}
impl<'a, V, N, O> ReachedBubbleExitsMatch<'a, V, N, O>
where V: AstarVisited<N, O> + GetAlignmentCosts,
N: NodeIndexType,
O: OffsetType,
{
pub fn new(visited: &'a V, reached_offsets: &'a BTreeSet<O>, seq_len: usize) -> Self {
Self {
visited,
reached_offsets,
seq_len,
dummy: PhantomData,
}
}
pub fn can_improve_bubble(
&self, bubble_index: &BubbleIndex<N>,
bubble: &NodeBubbleMap<N>,
aln_node: &AlignmentGraphNode<N, O>,
aln_state: AlignState,
current_score: &Score
) -> bool {
if self.reached_offsets.is_empty() {
return true;
}
if aln_node.node() == bubble.bubble_exit {
return true;
}
let target_offset_min = aln_node.offset() + O::new(bubble.min_dist_to_exit);
let target_offset_max = aln_node.offset() + O::new(bubble.max_dist_to_exit);
let min_dist_to_end = bubble_index.get_min_dist_to_end(bubble.bubble_exit)
.saturating_sub(1);
if target_offset_max.as_usize() > self.seq_len {
return true;
}
let mut prev_reached = self.reached_offsets
.range(RangeTo { end: target_offset_min }).next_back();
let mut last_offset = None;
for next_reached in self.reached_offsets
.range(target_offset_min..=target_offset_max)
{
let offset1 = prev_reached.map_or(
target_offset_min,
|v| max(target_offset_min, *v + O::one())
);
if aln_state == AlignState::Deletion {
let next_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*next_reached
), AlignState::Match);
if next_reached_cost + self.visited.get_costs().gap_open() > *current_score {
return true
}
} else if aln_state == AlignState::Deletion2 {
let next_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*next_reached
), AlignState::Match);
if next_reached_cost + self.visited.get_costs().gap_open2() > *current_score {
return true
}
}
if let Some(prev) = prev_reached {
if aln_state == AlignState::Insertion {
let prev_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*prev
), AlignState::Match);
if prev_reached_cost + self.visited.get_costs().gap_open() > *current_score {
return true
}
} else if aln_state == AlignState::Insertion2 {
let prev_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*prev
), AlignState::Match);
if prev_reached_cost + self.visited.get_costs().gap_open2() > *current_score {
return true
}
}
}
if self
.can_improve_at_offset(bubble.bubble_exit, offset1, current_score, prev_reached, Some(next_reached), min_dist_to_end)
{
return true;
}
let offset2 = min(target_offset_max, max(target_offset_min, *next_reached - O::one()));
if offset2 != offset1 {
if self
.can_improve_at_offset(bubble.bubble_exit, offset2, current_score, prev_reached, Some(next_reached), min_dist_to_end)
{
return true;
}
}
prev_reached = Some(next_reached);
last_offset = Some(offset2);
}
let next_reached = self.reached_offsets
.range(RangeFrom { start: target_offset_max.saturating_add(&O::one()) })
.next();
if last_offset.is_none()
&& self.can_improve_at_offset(bubble.bubble_exit, target_offset_min, current_score, prev_reached, next_reached, min_dist_to_end)
{
return true;
}
if last_offset.map_or(true, |v| v < target_offset_max)
&& self.can_improve_at_offset(bubble.bubble_exit, target_offset_max, current_score, prev_reached, next_reached, min_dist_to_end)
{
return true;
}
if let Some(prev) = prev_reached {
if aln_state == AlignState::Insertion {
let prev_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*prev
), AlignState::Match);
if prev_reached_cost + self.visited.get_costs().gap_open() > *current_score {
return true
}
} else if aln_state == AlignState::Insertion2 {
let prev_reached_cost = self.visited.get_score(&AlignmentGraphNode::new(
bubble.bubble_exit,
*prev
), AlignState::Match);
if prev_reached_cost + self.visited.get_costs().gap_open2() > *current_score {
return true
}
}
}
false
}
fn can_improve_at_offset(
&self,
bubble_node: N,
offset_to_check: O,
score: &Score,
reached_bubble_left: Option<&O>,
reached_bubble_right: Option<&O>,
min_dist_to_end: usize,
) -> bool {
let implicit_score = match (reached_bubble_left, reached_bubble_right) {
(None, None) => None,
(Some(offset_left), Some(offset_right)) => {
let left_score = self.visited.get_score(&AlignmentGraphNode::new(bubble_node, *offset_left), AlignState::Match);
let right_score = self.visited.get_score(&AlignmentGraphNode::new(bubble_node, *offset_right), AlignState::Match);
let gap_from_left = offset_to_check - *offset_left;
let gap_from_right = *offset_right - offset_to_check;
let implicit_score_from_left = left_score
+ self.visited.get_costs().gap_cost(AlignState::Match, gap_from_left.as_usize());
let implicit_score_from_right = right_score
+ self.visited.get_costs().gap_cost(AlignState::Match, gap_from_right.as_usize());
if gap_from_right.as_usize() > min_dist_to_end {
Some(implicit_score_from_left)
} else {
Some(min(implicit_score_from_left, implicit_score_from_right))
}
},
(None, Some(offset_right)) => {
let right_score = self.visited.get_score(&AlignmentGraphNode::new(bubble_node, *offset_right), AlignState::Match);
let gap_from_right = *offset_right - offset_to_check;
let implicit_score_from_right = right_score
+ self.visited.get_costs().gap_cost(AlignState::Match, gap_from_right.as_usize());
if gap_from_right.as_usize() > min_dist_to_end {
None
} else {
Some(implicit_score_from_right)
}
},
(Some(offset_left), None) => {
let left_score = self.visited.get_score(&AlignmentGraphNode::new(bubble_node, *offset_left), AlignState::Match);
let gap_from_left = offset_to_check - *offset_left;
let implicit_score_from_left = left_score
+ self.visited.get_costs().gap_cost(AlignState::Match, gap_from_left.as_usize());
Some(implicit_score_from_left)
}
};
implicit_score.map_or(true, |s| *score < s)
}
}