diff --git a/lib_ts_chainalign/src/alignment/sequences.rs b/lib_ts_chainalign/src/alignment/sequences.rs index 0b6afbce..be8d09ce 100644 --- a/lib_ts_chainalign/src/alignment/sequences.rs +++ b/lib_ts_chainalign/src/alignment/sequences.rs @@ -42,6 +42,8 @@ impl AlignmentSequences { start: AlignmentCoordinates, end: AlignmentCoordinates, ) -> Self { + debug_assert!(start.is_primary()); + debug_assert!(end.is_primary()); Self { seq1, seq2, diff --git a/lib_ts_chainalign/src/anchors.rs b/lib_ts_chainalign/src/anchors.rs index aedb7b94..ae65b971 100644 --- a/lib_ts_chainalign/src/anchors.rs +++ b/lib_ts_chainalign/src/anchors.rs @@ -1,10 +1,11 @@ use std::{fmt::Display, time::Instant}; -use lib_tsalign::a_star_aligner::alignment_geometry::AlignmentRange; use log::{info, trace}; use crate::{ - alignment::{sequences::AlignmentSequences, ts_kind::TsKind}, + alignment::{ + coordinates::AlignmentCoordinates, sequences::AlignmentSequences, ts_kind::TsKind, + }, anchors::{ index::AnchorIndex, kmer_matches::find_kmer_matches, @@ -30,20 +31,15 @@ pub struct Anchors { } impl Anchors { - pub fn new( - sequences: &AlignmentSequences, - range: AlignmentRange, - k: u32, - rc_fn: &dyn Fn(u8) -> u8, - ) -> Self { + pub fn new(sequences: &AlignmentSequences, k: u32, rc_fn: &dyn Fn(u8) -> u8) -> Self { if k <= 8 { - Self::new_with_kmer_store::(sequences, range, k, rc_fn) + Self::new_with_kmer_store::(sequences, k, rc_fn) } else if k <= 16 { - Self::new_with_kmer_store::(sequences, range, k, rc_fn) + Self::new_with_kmer_store::(sequences, k, rc_fn) } else if k <= 32 { - Self::new_with_kmer_store::(sequences, range, k, rc_fn) + Self::new_with_kmer_store::(sequences, k, rc_fn) } else if k <= 64 { - Self::new_with_kmer_store::(sequences, range, k, rc_fn) + Self::new_with_kmer_store::(sequences, k, rc_fn) } else { panic!("Only k-mer sizes up to 64 are supported, but got {k}"); } @@ -51,7 +47,6 @@ impl Anchors { fn new_with_kmer_store( sequences: &AlignmentSequences, - range: AlignmentRange, k: u32, rc_fn: &dyn Fn(u8) -> u8, ) -> Self { @@ -63,18 +58,24 @@ impl Anchors { let s1_rc: Vec<_> = s1.iter().copied().rev().map(rc_fn).collect(); let s2_rc: Vec<_> = s2.iter().copied().rev().map(rc_fn).collect(); - let s1_kmer_count = - (range.reference_limit() - range.reference_offset() + 1).saturating_sub(k); - let s2_kmer_count = (range.query_limit() - range.query_offset() + 1).saturating_sub(k); + let s1_kmer_count = (sequences.primary_end().primary_ordinate_a().unwrap() + - sequences.primary_start().primary_ordinate_a().unwrap() + + 1) + .saturating_sub(k); + let s2_kmer_count = (sequences.primary_end().primary_ordinate_b().unwrap() + - sequences.primary_start().primary_ordinate_b().unwrap() + + 1) + .saturating_sub(k); // Compute k-mers. - let mut s1_kmers: Vec<_> = (range.reference_offset() - ..range.reference_offset() + s1_kmer_count) + let mut s1_kmers: Vec<_> = (sequences.primary_start().primary_ordinate_a().unwrap() + ..sequences.primary_start().primary_ordinate_a().unwrap() + s1_kmer_count) .map(|offset| (Kmer::::from(&s1[offset..offset + k]), offset)) .collect(); s1_kmers.sort(); let s1_kmers = s1_kmers; - let mut s2_kmers: Vec<_> = (range.query_offset()..range.query_offset() + s2_kmer_count) + let mut s2_kmers: Vec<_> = (sequences.primary_start().primary_ordinate_b().unwrap() + ..sequences.primary_start().primary_ordinate_b().unwrap() + s2_kmer_count) .map(|offset| (Kmer::::from(&s2[offset..offset + k]), offset)) .collect(); s2_kmers.sort(); @@ -170,6 +171,16 @@ impl Anchors { .map(|(index, anchor)| (index.into(), anchor)) } + pub fn primary_index_from_start_coordinates( + &self, + start: AlignmentCoordinates, + ) -> Option { + let target_anchor = PrimaryAnchor::new_from_start(&start); + self.enumerate_primaries() + .filter_map(|(index, anchor)| (anchor == target_anchor).then_some(index)) + .next() + } + fn secondary_anchor_vec(&self, ts_kind: TsKind) -> &Vec { &self.secondaries[ts_kind.index()] } @@ -192,6 +203,16 @@ impl Anchors { .enumerate() .map(|(index, anchor)| (index.into(), anchor)) } + + pub fn secondary_index_from_start_coordinates( + &self, + start: AlignmentCoordinates, + ) -> Option { + let target_anchor = SecondaryAnchor::new_from_start(&start); + self.enumerate_secondaries(start.ts_kind().unwrap()) + .filter_map(|(index, anchor)| (anchor == target_anchor).then_some(index)) + .next() + } } impl Display for Anchors { diff --git a/lib_ts_chainalign/src/anchors/tests.rs b/lib_ts_chainalign/src/anchors/tests.rs index 7cede306..2f5600f3 100644 --- a/lib_ts_chainalign/src/anchors/tests.rs +++ b/lib_ts_chainalign/src/anchors/tests.rs @@ -1,5 +1,3 @@ -use lib_tsalign::a_star_aligner::alignment_geometry::AlignmentRange; - use crate::{ alignment::{sequences::AlignmentSequences, ts_kind::TsKind}, anchors::{Anchors, PrimaryAnchor, SecondaryAnchor}, @@ -18,10 +16,9 @@ fn rc_fn(c: u8) -> u8 { #[test] fn test_coordinates() { let sequences = AlignmentSequences::new_complete(b"ACAC".to_vec(), b"ACGT".to_vec()); - let range = AlignmentRange::new_complete(sequences.seq1().len(), sequences.seq2().len()); let k = 2; - let anchors = Anchors::new(&sequences, range, k, &rc_fn); + let anchors = Anchors::new(&sequences, k, &rc_fn); assert_eq!(anchors.primary, [(0, 0), (2, 0)].map(PrimaryAnchor::from)); assert!(anchors.secondary_anchor_vec(TsKind::TS11).is_empty()); assert_eq!( @@ -41,10 +38,9 @@ fn test_coordinates() { #[test] fn test_coordinates_rev() { let sequences = AlignmentSequences::new_complete(b"ACGT".to_vec(), b"ACAC".to_vec()); - let range = AlignmentRange::new_complete(sequences.seq1().len(), sequences.seq2().len()); let k = 2; - let anchors = Anchors::new(&sequences, range, k, &rc_fn); + let anchors = Anchors::new(&sequences, k, &rc_fn); assert_eq!(anchors.primary, [(0, 0), (0, 2)].map(PrimaryAnchor::from)); assert!(anchors.secondary_anchor_vec(TsKind::TS22).is_empty()); assert_eq!( diff --git a/lib_ts_chainalign/src/chaining_cost_function.rs b/lib_ts_chainalign/src/chaining_cost_function.rs index b4756ff4..f1435697 100644 --- a/lib_ts_chainalign/src/chaining_cost_function.rs +++ b/lib_ts_chainalign/src/chaining_cost_function.rs @@ -19,6 +19,8 @@ use crate::{ }; mod cost_array; +#[cfg(test)] +mod tests; pub struct ChainingCostFunction { primary: ChainingCostArray, diff --git a/lib_ts_chainalign/src/chaining_cost_function/tests.rs b/lib_ts_chainalign/src/chaining_cost_function/tests.rs new file mode 100644 index 00000000..1e14656e --- /dev/null +++ b/lib_ts_chainalign/src/chaining_cost_function/tests.rs @@ -0,0 +1,294 @@ +use generic_a_star::cost::U32Cost; +use num_traits::{Bounded, Zero}; + +use crate::{ + alignment::{ + coordinates::AlignmentCoordinates, sequences::AlignmentSequences, ts_kind::TsKind, + }, + anchors::Anchors, + chaining_cost_function::ChainingCostFunction, + chaining_lower_bounds::ChainingLowerBounds, + costs::{AlignmentCosts, GapAffineCosts, TsLimits}, +}; + +fn dna_rc_fn(c: u8) -> u8 { + match c { + b'A' => b'T', + b'C' => b'G', + b'G' => b'C', + b'T' => b'A', + c => panic!("Unsupported character: {c}"), + } +} + +fn create_lower_bounds(max_match_run: u32) -> ChainingLowerBounds { + ChainingLowerBounds::new( + 20, + max_match_run, + AlignmentCosts::new( + GapAffineCosts::new(2u8.into(), 3u8.into(), 1u8.into()), + GapAffineCosts::new(4u8.into(), 6u8.into(), 2u8.into()), + 2u8.into(), + TsLimits::new_unlimited(), + ), + ) +} + +fn create_chaining_cost_function( + sequences: &AlignmentSequences, + max_match_run: u32, +) -> (Anchors, ChainingCostFunction) { + let anchors = Anchors::new(sequences, max_match_run.checked_add(1).unwrap(), &dna_rc_fn); + let cost_function = ChainingCostFunction::new_from_lower_bounds( + &create_lower_bounds(max_match_run), + &anchors, + sequences, + U32Cost::zero(), + &dna_rc_fn, + ); + (anchors, cost_function) +} + +#[test] +fn test_start_end_direct() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(5, 5), + AlignmentCoordinates::new_primary(5, 5), + ); + let (_, cost_function) = create_chaining_cost_function(&sequences, 4); + assert!(cost_function.start_to_end().is_zero()); +} + +#[test] +fn test_start_anchor_direct() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 4); + assert!( + cost_function + .primary_from_start( + anchors + .primary_index_from_start_coordinates(sequences.primary_start()) + .unwrap() + ) + .is_zero() + ); +} + +#[test] +fn test_anchor_end_direct() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 4); + assert!( + cost_function + .primary_to_end( + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(4, 4)) + .unwrap() + ) + .is_zero() + ); +} + +#[test] +fn test_start_end_indirect_lt_k() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (_, cost_function) = create_chaining_cost_function(&sequences, 8); + assert!(cost_function.start_to_end().is_zero()); +} + +#[test] +fn test_start_end_indirect_geq_k() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (_, cost_function) = create_chaining_cost_function(&sequences, 7); + assert!(!cost_function.start_to_end().is_zero()); +} + +#[test] +fn test_start_anchor_indirect() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 4); + assert!( + !cost_function + .primary_from_start( + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(2, 2)) + .unwrap() + ) + .is_zero() + ); +} + +#[test] +fn test_anchor_end_indirect() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 4); + assert!( + !cost_function + .primary_to_end( + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(3, 3)) + .unwrap() + ) + .is_zero() + ); +} + +#[test] +fn test_anchor_anchor_direct_primary() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 2); + assert_eq!( + cost_function.primary( + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(1, 1)) + .unwrap(), + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(4, 4)) + .unwrap(), + ), + U32Cost::max_value(), + ); +} + +#[test] +fn test_anchor_anchor_indirect_primary() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 2); + assert_eq!( + cost_function.primary( + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(1, 1)) + .unwrap(), + anchors + .primary_index_from_start_coordinates(AlignmentCoordinates::new_primary(5, 5)) + .unwrap(), + ), + 2u8.into(), + ); +} + +#[test] +fn test_anchor_anchor_direct_secondary() { + let seq1 = b"GAAAAAAAAG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 2); + assert_eq!( + cost_function.secondary( + anchors + .secondary_index_from_start_coordinates(AlignmentCoordinates::new_secondary( + 9, + 1, + TsKind::TS12 + )) + .unwrap(), + anchors + .secondary_index_from_start_coordinates(AlignmentCoordinates::new_secondary( + 6, + 4, + TsKind::TS12 + )) + .unwrap(), + TsKind::TS12, + ), + U32Cost::max_value(), + ); +} + +#[test] +fn test_anchor_anchor_indirect_secondary() { + let seq1 = b"GAAAAAAAAG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let (anchors, cost_function) = create_chaining_cost_function(&sequences, 2); + assert_eq!( + cost_function.secondary( + anchors + .secondary_index_from_start_coordinates(AlignmentCoordinates::new_secondary( + 9, + 1, + TsKind::TS12 + )) + .unwrap(), + anchors + .secondary_index_from_start_coordinates(AlignmentCoordinates::new_secondary( + 5, + 5, + TsKind::TS12 + )) + .unwrap(), + TsKind::TS12, + ), + 4u8.into(), + ); +} diff --git a/lib_ts_chainalign/src/costs.rs b/lib_ts_chainalign/src/costs.rs index 70c0b229..8ab63709 100644 --- a/lib_ts_chainalign/src/costs.rs +++ b/lib_ts_chainalign/src/costs.rs @@ -60,6 +60,17 @@ impl GapAffineCosts { } } +impl TsLimits { + pub fn new_unlimited() -> Self { + Self { + jump_12: isize::MIN..isize::MAX, + jump_34: isize::MIN..isize::MAX, + length_23: usize::MIN..usize::MAX, + ancestor_gap: isize::MIN..isize::MAX, + } + } +} + impl AlignmentCosts { pub fn new( primary_costs: GapAffineCosts, diff --git a/lib_ts_chainalign/src/exact_chaining/gap_affine.rs b/lib_ts_chainalign/src/exact_chaining/gap_affine.rs index b053fdcb..1b459cef 100644 --- a/lib_ts_chainalign/src/exact_chaining/gap_affine.rs +++ b/lib_ts_chainalign/src/exact_chaining/gap_affine.rs @@ -37,6 +37,33 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> } } + fn allow_direct_chaining( + &self, + start: AlignmentCoordinates, + end: AlignmentCoordinates, + ) -> bool { + start == self.sequences.primary_start() || end == self.sequences.primary_end() + } + + fn allow_all_matches(&self, start: AlignmentCoordinates, end: AlignmentCoordinates) -> bool { + let minimum_primary_sequence_length = + (self.sequences.primary_end().primary_ordinate_a().unwrap() + - self.sequences.primary_start().primary_ordinate_a().unwrap()) + .min( + self.sequences.primary_end().primary_ordinate_b().unwrap() + - self.sequences.primary_start().primary_ordinate_b().unwrap(), + ); + start == self.sequences.primary_start() + && end == self.sequences.primary_end() + && u32::try_from(minimum_primary_sequence_length).unwrap() <= self.max_match_run + } + + /// Align from start to end. + /// + /// Additionally continue the alignment to all nodes with the same cost as the alignment cost from start to end. + /// + /// Collect all closed nodes into the given output lists. + /// Note that the output lists may contain duplicate anchors with different cost. pub fn align( &mut self, start: AlignmentCoordinates, @@ -47,7 +74,6 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> assert!( start.is_primary() && end.is_primary() || start.is_secondary() && end.is_secondary() ); - let enforce_non_match = true; let context = Context::new( self.cost_table, @@ -55,7 +81,8 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> self.rc_fn, start, end, - enforce_non_match, + self.allow_direct_chaining(start, end), + self.allow_all_matches(start, end), self.max_match_run, ); let mut a_star = AStar::new_with_buffers(context, self.a_star_buffers.take().unwrap()); @@ -72,10 +99,9 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> }; a_star.search_until_with_target_policy(|_, node| node.cost > cost, true); - Self::fill_additional_targets( + self.fill_additional_targets( &a_star, start, - enforce_non_match, additional_primary_targets_output, additional_secondary_targets_output, ); @@ -87,6 +113,7 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> /// Align from start until the cost limit is reached. /// /// Collect all closed nodes into the given output lists. + /// Note that the output lists may contain duplicate anchors with different cost. pub fn align_until_cost_limit( &mut self, start: AlignmentCoordinates, @@ -94,7 +121,6 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> additional_primary_targets_output: &mut impl Extend<(PrimaryAnchor, Cost)>, additional_secondary_targets_output: &mut impl Extend<(SecondaryAnchor, Cost)>, ) { - let enforce_non_match = true; let end = self.sequences.end(start.ts_kind()); debug_assert!( start.is_primary() && end.is_primary() || start.is_secondary() && end.is_secondary() @@ -106,17 +132,17 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> self.rc_fn, start, end, - enforce_non_match, + true, + true, self.max_match_run, ); let mut a_star = AStar::new_with_buffers(context, self.a_star_buffers.take().unwrap()); a_star.initialise(); a_star.search_until_with_target_policy(|_, node| node.cost > cost_limit, true); - Self::fill_additional_targets( + self.fill_additional_targets( &a_star, start, - enforce_non_match, additional_primary_targets_output, additional_secondary_targets_output, ); @@ -124,20 +150,36 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> } fn fill_additional_targets( + &self, a_star: &AStar>, start: AlignmentCoordinates, - enforce_non_match: bool, additional_primary_targets_output: &mut impl Extend<(PrimaryAnchor, Cost)>, additional_secondary_targets_output: &mut impl Extend<(SecondaryAnchor, Cost)>, ) { additional_primary_targets_output.extend( a_star .iter_closed_nodes() + .filter(|node| node.identifier.coordinates.is_primary()) .filter(|node| { - node.identifier.coordinates.is_primary() - && ((node.identifier.has_non_match - == (start != node.identifier.coordinates)) - || !enforce_non_match) + start != node.identifier.coordinates + || self.allow_direct_chaining(start, node.identifier.coordinates) + }) + .filter(|node| { + start == node.identifier.coordinates + || node.identifier.has_non_match + || self.allow_all_matches(start, node.identifier.coordinates) + }) + .filter(|node| { + // Filter duplicates. + let mut pair_identifier = node.identifier; + pair_identifier.has_non_match = !pair_identifier.has_non_match; + a_star + .closed_node(&pair_identifier) + .map(|pair| { + pair.cost > node.cost + || (pair.cost == node.cost && node.identifier.has_non_match) + }) + .unwrap_or(true) }) .map(|node| { ( @@ -149,11 +191,27 @@ impl<'sequences, 'cost_table, 'rc_fn, Cost: AStarCost> additional_secondary_targets_output.extend( a_star .iter_closed_nodes() + .filter(|node| node.identifier.coordinates.is_secondary()) + .filter(|node| { + start != node.identifier.coordinates + || self.allow_direct_chaining(start, node.identifier.coordinates) + }) + .filter(|node| { + start == node.identifier.coordinates + || node.identifier.has_non_match + || self.allow_all_matches(start, node.identifier.coordinates) + }) .filter(|node| { - node.identifier.coordinates.is_secondary() - && ((node.identifier.has_non_match - == (start != node.identifier.coordinates)) - || !enforce_non_match) + // Filter duplicates. + let mut pair_identifier = node.identifier; + pair_identifier.has_non_match = !pair_identifier.has_non_match; + a_star + .closed_node(&pair_identifier) + .map(|pair| { + pair.cost > node.cost + || (pair.cost == node.cost && node.identifier.has_non_match) + }) + .unwrap_or(true) }) .map(|node| { ( diff --git a/lib_ts_chainalign/src/exact_chaining/gap_affine/algo.rs b/lib_ts_chainalign/src/exact_chaining/gap_affine/algo.rs index 22d9697c..6fa77e6e 100644 --- a/lib_ts_chainalign/src/exact_chaining/gap_affine/algo.rs +++ b/lib_ts_chainalign/src/exact_chaining/gap_affine/algo.rs @@ -20,7 +20,8 @@ pub struct Context<'costs, 'sequences, 'rc_fn, Cost> { rc_fn: &'rc_fn dyn Fn(u8) -> u8, start: AlignmentCoordinates, end: AlignmentCoordinates, - enforce_non_match: bool, + allow_direct_chaining: bool, + allow_all_matches: bool, max_match_run: u32, } @@ -41,13 +42,19 @@ pub struct Identifier { } impl<'costs, 'sequences, 'rc_fn, Cost> Context<'costs, 'sequences, 'rc_fn, Cost> { + /// + /// **Parameters:** + /// * `allow_direct_chaining`: allow reaching the target with zero alignment operations. + /// * `allow_all_matches`: allow reaching the target before any non-match was used. This still obeys the `max_match_run` parameter. + #[expect(clippy::too_many_arguments)] pub fn new( costs: &'costs GapAffineCosts, sequences: &'sequences AlignmentSequences, rc_fn: &'rc_fn dyn Fn(u8) -> u8, start: AlignmentCoordinates, end: AlignmentCoordinates, - enforce_non_match: bool, + allow_direct_chaining: bool, + allow_all_matches: bool, max_match_run: u32, ) -> Self { Self { @@ -56,7 +63,8 @@ impl<'costs, 'sequences, 'rc_fn, Cost> Context<'costs, 'sequences, 'rc_fn, Cost> rc_fn, start, end, - enforce_non_match, + allow_direct_chaining, + allow_all_matches, max_match_run, } } @@ -174,8 +182,13 @@ impl AStarContext for Context<'_, '_, '_, Cost> { } fn is_target(&self, node: &Self::Node) -> bool { - node.identifier.coordinates == self.end - && (node.identifier.has_non_match || !self.enforce_non_match) + if node.identifier.coordinates != self.end { + false + } else if self.start == self.end { + self.allow_direct_chaining + } else { + node.identifier.has_non_match || self.allow_all_matches + } } fn cost_limit(&self) -> Option<::Cost> { diff --git a/lib_ts_chainalign/src/exact_chaining/gap_affine/tests.rs b/lib_ts_chainalign/src/exact_chaining/gap_affine/tests.rs index b951b1f4..2fc15afc 100644 --- a/lib_ts_chainalign/src/exact_chaining/gap_affine/tests.rs +++ b/lib_ts_chainalign/src/exact_chaining/gap_affine/tests.rs @@ -1,8 +1,10 @@ use generic_a_star::cost::U32Cost; +use num_traits::{Zero, bounds::UpperBounded}; use crate::alignment::AlignmentType; use crate::alignment::ts_kind::TsKind; use crate::exact_chaining::gap_affine::{AlignmentCoordinates, GapAffineAligner}; +use crate::panic_on_extend::PanicOnExtend; use crate::{alignment::sequences::AlignmentSequences, costs::GapAffineCosts}; fn rc_fn(c: u8) -> u8 { @@ -26,7 +28,7 @@ fn test_start_end() { let start = AlignmentCoordinates::new_primary(0, 0); let end = AlignmentCoordinates::new_primary(4, 5); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -46,7 +48,7 @@ fn test_partial_alignment() { let start = AlignmentCoordinates::new_primary(1, 1); let end = AlignmentCoordinates::new_primary(4, 4); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -70,7 +72,7 @@ fn test_gap_directions() { let start = AlignmentCoordinates::new_primary(1, 1); let end = AlignmentCoordinates::new_primary(11, 11); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -96,7 +98,7 @@ fn test_extremity_gaps() { let start = AlignmentCoordinates::new_primary(3, 3); let end = AlignmentCoordinates::new_primary(10, 10); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -120,7 +122,7 @@ fn test_extremity_substitutions() { let start = AlignmentCoordinates::new_primary(0, 0); let end = AlignmentCoordinates::new_primary(5, 5); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -144,7 +146,7 @@ fn test_substitutions_as_gaps() { let start = AlignmentCoordinates::new_primary(0, 0); let end = AlignmentCoordinates::new_primary(20, 20); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert!( alignment.alignment == vec![(20, AlignmentType::GapA), (20, AlignmentType::GapB),] @@ -164,7 +166,7 @@ fn test_max_match_run_0() { let start = AlignmentCoordinates::new_primary(1, 1); let end = AlignmentCoordinates::new_primary(9, 9); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 0); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert!( alignment.alignment == vec![(8, AlignmentType::GapA), (8, AlignmentType::GapB),] @@ -184,7 +186,7 @@ fn test_max_match_run_1() { let start = AlignmentCoordinates::new_primary(1, 1); let end = AlignmentCoordinates::new_primary(9, 9); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 1); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -213,7 +215,7 @@ fn test_max_match_run_2() { let start = AlignmentCoordinates::new_primary(1, 1); let end = AlignmentCoordinates::new_primary(9, 9); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 2); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); assert_eq!( alignment.alignment, @@ -239,7 +241,7 @@ fn test_secondary_12() { let start = AlignmentCoordinates::new_secondary(9, 1, TsKind::TS12); let end = AlignmentCoordinates::new_secondary(1, 9, TsKind::TS12); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut PanicOnExtend, &mut Vec::new()); assert_eq!( alignment.alignment, @@ -259,7 +261,7 @@ fn test_secondary_21() { let start = AlignmentCoordinates::new_secondary(9, 1, TsKind::TS21); let end = AlignmentCoordinates::new_secondary(1, 9, TsKind::TS21); let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); - let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut Vec::new()); + let (cost, alignment) = aligner.align(start, end, &mut PanicOnExtend, &mut Vec::new()); assert_eq!( alignment.alignment, @@ -267,3 +269,505 @@ fn test_secondary_21() { ); assert_eq!(cost, U32Cost::from(2u8)); } + +#[test] +fn test_start_end_direct() { + let seq1 = b"GAAAAAAATG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(5, 5), + AlignmentCoordinates::new_primary(5, 5), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let (cost, alignment) = aligner.align( + sequences.primary_start(), + sequences.primary_end(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert_eq!(alignment.alignment, vec![]); + assert_eq!(cost, 0u8.into()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_start(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_start() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert_eq!(min_cost, 0u8.into()); +} + +#[test] +fn test_start_anchor_direct() { + let seq1 = b"GAAAAAAATG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(5, 5), + AlignmentCoordinates::new_primary(6, 6), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let (cost, alignment) = aligner.align( + sequences.primary_start(), + sequences.primary_start(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert_eq!(alignment.alignment, vec![]); + assert_eq!(cost, 0u8.into()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_start(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_start() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert_eq!(min_cost, 0u8.into()); +} + +#[test] +fn test_anchor_end_direct() { + let seq1 = b"GAAAAAAATG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(5, 5), + AlignmentCoordinates::new_primary(6, 6), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let (cost, alignment) = aligner.align( + sequences.primary_end(), + sequences.primary_end(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert_eq!(alignment.alignment, vec![]); + assert_eq!(cost, 0u8.into()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 4); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_end(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_end() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert_eq!(min_cost, 0u8.into()); +} + +#[test] +fn test_start_end_indirect_lt_k() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 8); + let (cost, alignment) = aligner.align( + sequences.primary_start(), + sequences.primary_end(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert_eq!(alignment.alignment, vec![(8, AlignmentType::Match)]); + assert_eq!(cost, 0u8.into()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 8); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_start(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_end() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert_eq!(min_cost, 0u8.into()); +} + +#[test] +fn test_start_end_indirect_geq_k() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 7); + let (cost, _alignment) = aligner.align( + sequences.primary_start(), + sequences.primary_end(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert!(!cost.is_zero()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, 7); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_start(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_end() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert!(!min_cost.is_zero()); +} + +#[test] +fn test_start_anchor_indirect() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let end = AlignmentCoordinates::new_primary(2, 2); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, _alignment) = aligner.align( + sequences.primary_start(), + end, + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert!(!cost.is_zero()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + sequences.primary_start(), + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == end { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert!(!min_cost.is_zero()); +} + +#[test] +fn test_anchor_end_indirect() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let start = AlignmentCoordinates::new_primary(8, 8); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, _alignment) = aligner.align( + start, + sequences.primary_end(), + &mut Vec::new(), + &mut PanicOnExtend, + ); + + assert!(!cost.is_zero()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + start, + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == sequences.primary_end() { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert!(!min_cost.is_zero()); +} + +#[test] +fn test_anchor_anchor_direct_primary() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let start = AlignmentCoordinates::new_primary(5, 5); + let end = AlignmentCoordinates::new_primary(5, 5); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); + + assert_eq!(alignment.alignment, vec![]); + assert_eq!(cost, U32Cost::max_value()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + start, + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == end { + Some(cost) + } else { + None + } + }) + .min() + .unwrap_or_else(U32Cost::max_value); + assert_eq!(min_cost, U32Cost::max_value()); +} + +#[test] +fn test_anchor_anchor_indirect_primary() { + let seq1 = b"ATTTTTTTTA".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let start = AlignmentCoordinates::new_primary(5, 5); + let end = AlignmentCoordinates::new_primary(6, 6); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, _alignment) = aligner.align(start, end, &mut Vec::new(), &mut PanicOnExtend); + + assert!(!cost.is_zero()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_primary_targets = Vec::new(); + aligner.align_until_cost_limit( + start, + U32Cost::max_value(), + &mut additional_primary_targets, + &mut PanicOnExtend, + ); + + let min_cost = additional_primary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start() == end { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert!(!min_cost.is_zero()); +} + +#[test] +fn test_anchor_anchor_direct_secondary() { + let seq1 = b"GAAAAAAAAG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let start = AlignmentCoordinates::new_secondary(5, 5, TsKind::TS12); + let end = AlignmentCoordinates::new_secondary(5, 5, TsKind::TS12); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, alignment) = aligner.align(start, end, &mut PanicOnExtend, &mut Vec::new()); + + assert_eq!(alignment.alignment, vec![]); + assert_eq!(cost, U32Cost::max_value()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_secondary_targets = Vec::new(); + aligner.align_until_cost_limit( + start, + U32Cost::max_value(), + &mut PanicOnExtend, + &mut additional_secondary_targets, + ); + + let min_cost = additional_secondary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start(TsKind::TS12) == end { + Some(cost) + } else { + None + } + }) + .min() + .unwrap_or_else(U32Cost::max_value); + assert_eq!(min_cost, U32Cost::max_value()); +} + +#[test] +fn test_anchor_anchor_indirect_secondary() { + let seq1 = b"GAAAAAAAAG".to_vec(); + let seq2 = b"GTTTTTTTTG".to_vec(); + let sequences = AlignmentSequences::new( + seq1, + seq2, + AlignmentCoordinates::new_primary(1, 1), + AlignmentCoordinates::new_primary(9, 9), + ); + let cost_table = + GapAffineCosts::new(U32Cost::from(2u8), U32Cost::from(3u8), U32Cost::from(1u8)); + let start = AlignmentCoordinates::new_secondary(6, 4, TsKind::TS12); + let end = AlignmentCoordinates::new_secondary(5, 5, TsKind::TS12); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let (cost, _alignment) = aligner.align(start, end, &mut PanicOnExtend, &mut Vec::new()); + + assert!(!cost.is_zero()); + + let mut aligner = GapAffineAligner::new(&sequences, &cost_table, &rc_fn, u32::MAX); + let mut additional_secondary_targets = Vec::new(); + aligner.align_until_cost_limit( + start, + U32Cost::max_value(), + &mut PanicOnExtend, + &mut additional_secondary_targets, + ); + + let min_cost = additional_secondary_targets + .into_iter() + .filter_map(|(anchor, cost)| { + if anchor.start(TsKind::TS12) == end { + Some(cost) + } else { + None + } + }) + .min() + .unwrap(); + assert!(!min_cost.is_zero()); +} diff --git a/lib_ts_chainalign/src/lib.rs b/lib_ts_chainalign/src/lib.rs index f090c86d..cea611a2 100644 --- a/lib_ts_chainalign/src/lib.rs +++ b/lib_ts_chainalign/src/lib.rs @@ -93,7 +93,7 @@ pub fn align( ); let k = chaining_lower_bounds.max_match_run() + 1; - let anchors = Anchors::new(&sequences, range.clone(), k, rc_fn); + let anchors = Anchors::new(&sequences, k, rc_fn); trace!("Anchors:\n{anchors}"); let mut chaining_cost_function = ChainingCostFunction::new_from_lower_bounds( chaining_lower_bounds,