diff --git a/Cargo.lock b/Cargo.lock index 6aad973a..636ffc40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,7 @@ dependencies = [ [[package]] name = "generic_a_star" -version = "5.0.1" +version = "6.0.1" dependencies = [ "binary-heap-plus", "compare", @@ -593,7 +593,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lib_ts_chainalign" -version = "5.0.1" +version = "6.0.1" dependencies = [ "binary-heap-plus", "bincode", @@ -613,7 +613,7 @@ dependencies = [ [[package]] name = "lib_tsalign" -version = "5.0.1" +version = "6.0.1" dependencies = [ "binary-heap-plus", "compact-genome", @@ -634,7 +634,7 @@ dependencies = [ [[package]] name = "lib_tsshow" -version = "5.0.1" +version = "6.0.1" dependencies = [ "clap", "ena", @@ -859,7 +859,7 @@ dependencies = [ [[package]] name = "py_lib_tsalign" -version = "5.0.1" +version = "6.0.1" dependencies = [ "compact-genome", "lib_tsalign", @@ -1066,7 +1066,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "seed_chain" -version = "5.0.1" +version = "6.0.1" dependencies = [ "compact-genome", "generic_a_star", @@ -1444,7 +1444,7 @@ checksum = "2b939c825d0c4295ac520f7b479927c1e5b458c9107a9d6f5ba65bc867bb93a5" [[package]] name = "tsalign" -version = "5.0.1" +version = "6.0.1" dependencies = [ "anyhow", "bincode", @@ -1465,7 +1465,7 @@ dependencies = [ [[package]] name = "tsalign-tests" -version = "5.0.1" +version = "6.0.1" dependencies = [ "anyhow", "clap", diff --git a/generic_a_star/Cargo.toml b/generic_a_star/Cargo.toml index 5f61c8df..64a10d50 100644 --- a/generic_a_star/Cargo.toml +++ b/generic_a_star/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "generic_a_star" description = "A generic implementation of the A* algorithm" -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/lib_ts_chainalign/Cargo.toml b/lib_ts_chainalign/Cargo.toml index 4025d613..ef7c94c4 100644 --- a/lib_ts_chainalign/Cargo.toml +++ b/lib_ts_chainalign/Cargo.toml @@ -2,15 +2,15 @@ name = "lib_ts_chainalign" description = "A chaining-based sequence-to-sequence aligner that accounts for template switches" authors = ["Sebastian Schmidt "] -version = "5.0.1" +version = "6.0.1" license.workspace = true edition.workspace = true rust-version.workspace = true repository.workspace = true [dependencies] -generic_a_star = { version = "5.0.1", path = "../generic_a_star" } -lib_tsalign = { version = "5.0.1", path = "../lib_tsalign" } +generic_a_star = { version = "6.0.1", path = "../generic_a_star" } +lib_tsalign = { version = "6.0.1", path = "../lib_tsalign" } ndarray = { version = "0.17.1", features = ["serde"] } num-traits.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/lib_ts_chainalign/src/chain_align.rs b/lib_ts_chainalign/src/chain_align.rs index 09b703ec..078a3c60 100644 --- a/lib_ts_chainalign/src/chain_align.rs +++ b/lib_ts_chainalign/src/chain_align.rs @@ -18,7 +18,7 @@ use itertools::Itertools; use lib_tsalign::a_star_aligner::{ alignment_geometry::AlignmentRange, alignment_result::AlignmentResult, - template_switch_distance::{EqualCostRange, TemplateSwitchDirection}, + template_switch_distance::{TSMUncertaintyRange, TemplateSwitchDirection}, }; use log::{debug, info, trace}; use rustc_hash::FxHashMapSeed; @@ -409,7 +409,7 @@ fn actually_align< multiplicity, TsAlignAlignmentType::TemplateSwitchEntrance { first_offset: jump, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: ts_kind.descendant.into_tsalign_primary(), ancestor: ts_kind.ancestor.into_tsalign_secondary(), direction: TemplateSwitchDirection::Reverse, diff --git a/lib_tsalign/Cargo.toml b/lib_tsalign/Cargo.toml index 581ddad3..8355b6ba 100644 --- a/lib_tsalign/Cargo.toml +++ b/lib_tsalign/Cargo.toml @@ -2,7 +2,7 @@ name = "lib_tsalign" description = "A sequence-to-sequence aligner that accounts for template switches" authors = ["Sebastian Schmidt "] -version = "5.0.1" +version = "6.0.1" license.workspace = true edition.workspace = true rust-version.workspace = true @@ -26,9 +26,9 @@ thiserror.workspace = true num-traits.workspace = true serde = { workspace = true, features = ["derive"], optional = true } noisy_float = { version = "0.2.0" } -generic_a_star = { version = "5.0.1", path = "../generic_a_star" } +generic_a_star = { version = "6.0.1", path = "../generic_a_star" } log.workspace = true rustc-hash = "2.1.1" -seed_chain = { version = "5.0.1", path = "../seed_chain" } +seed_chain = { version = "6.0.1", path = "../seed_chain" } extend_map = "0.14.3" template-switch-error-free-inners = "0.1.1" diff --git a/lib_tsalign/src/a_star_aligner.rs b/lib_tsalign/src/a_star_aligner.rs index ff6c1367..b7cbdbc5 100644 --- a/lib_tsalign/src/a_star_aligner.rs +++ b/lib_tsalign/src/a_star_aligner.rs @@ -23,7 +23,7 @@ use crate::{ a_star_aligner::{ alignment_result::alignment::Alignment, template_switch_distance::{ - context::DynamicStrategies, + TSMUncertaintyRangeExtensionMode, context::DynamicStrategies, strategies::allow_ts_14_out_of_range::AdditionalExplicitTSMStartsAndEnds, }, }, @@ -222,6 +222,7 @@ pub fn template_switch_distance_a_star_align< memory_limit: Option, force_label_correcting: bool, extend_beyond_range: bool, + tsm_uncertainty_range_extension_mode: TSMUncertaintyRangeExtensionMode, template_switch_count_memory: ::Memory, ) -> AlignmentResult where @@ -276,7 +277,35 @@ where info!("Alignment ranges after extension {range}"); } - info!("Extending template switches"); - result.compute_ts_equal_cost_ranges(reference, query, &range, config); + match tsm_uncertainty_range_extension_mode { + TSMUncertaintyRangeExtensionMode::None => { + debug!("TSM uncertainty range extension is disabled.") + } + + TSMUncertaintyRangeExtensionMode::EqualCost => { + info!("Extending TSM uncertainty ranges without increasing cost"); + result.compute_ts_uncertainty_ranges( + reference, + query, + &range, + config, + tsm_uncertainty_range_extension_mode, + ); + } + + TSMUncertaintyRangeExtensionMode::EqualCostIgnoreGeometry => { + info!( + "Extending TSM uncertainty ranges without increasing cost but ignoring cost increases due to TSM geometry", + ); + result.compute_ts_uncertainty_ranges( + reference, + query, + &range, + config, + tsm_uncertainty_range_extension_mode, + ); + } + } + result } diff --git a/lib_tsalign/src/a_star_aligner/alignment_result.rs b/lib_tsalign/src/a_star_aligner/alignment_result.rs index c7e3979e..2c44406f 100644 --- a/lib_tsalign/src/a_star_aligner/alignment_result.rs +++ b/lib_tsalign/src/a_star_aligner/alignment_result.rs @@ -4,13 +4,14 @@ use a_star_sequences::SequencePair; use alignment::Alignment; use compact_genome::interface::{alphabet::Alphabet, sequence::GenomeSequence}; use generic_a_star::{AStarResult, cost::AStarCost}; -use log::{trace, warn}; +use log::{debug, trace, warn}; use noisy_float::types::{R64, r64}; use num_traits::{Float, Zero}; use crate::{ a_star_aligner::{ - alignment_geometry::AlignmentCoordinates, template_switch_distance::AlignmentType, + alignment_geometry::AlignmentCoordinates, + template_switch_distance::{AlignmentType, TSMUncertaintyRangeExtensionMode}, }, config::TemplateSwitchConfig, }; @@ -286,7 +287,7 @@ impl> } // Compute cost before extending. - let mut current_cost = alignment.compute_cost( + let mut current_cost = alignment.compute_complete_cost( reference, query, range.reference_offset(), @@ -325,7 +326,7 @@ impl> } // Compute cost. - let new_cost = alignment.compute_cost( + let new_cost = alignment.compute_complete_cost( reference, query, new_range.reference_offset(), @@ -380,7 +381,7 @@ impl> } // Compute cost. - let new_cost = alignment.compute_cost( + let new_cost = alignment.compute_complete_cost( reference, query, new_range.reference_offset(), @@ -413,7 +414,7 @@ impl> } #[allow(clippy::too_many_lines)] - pub fn compute_ts_equal_cost_ranges< + pub fn compute_ts_uncertainty_ranges< AlphabetType: Alphabet, SubsequenceType: GenomeSequence + ?Sized, >( @@ -422,6 +423,7 @@ impl> query: &SubsequenceType, range: &AlignmentRange, config: &TemplateSwitchConfig, + mode: TSMUncertaintyRangeExtensionMode, ) { let Self::WithTarget { alignment, @@ -431,6 +433,10 @@ impl> trace!("There is no alignment, therefore we cannot postprocess it."); return; }; + if mode == TSMUncertaintyRangeExtensionMode::None { + debug!("TSM uncertainty range extension is disabled"); + return; + } if config.left_flank_length > 0 || config.right_flank_length > 0 { warn!("TS extension does not support flanks"); return; @@ -438,19 +444,24 @@ impl> let reference_offset = range.reference_offset(); let query_offset = range.query_offset(); + let ignore_geometry_cost = match mode { + TSMUncertaintyRangeExtensionMode::None => unreachable!(), + TSMUncertaintyRangeExtensionMode::EqualCost => false, + TSMUncertaintyRangeExtensionMode::EqualCostIgnoreGeometry => true, + }; for i in 0..alignment.inner_mut().len() { let alignment_type = alignment.inner_mut()[i].1; if let super::template_switch_distance::AlignmentType::TemplateSwitchEntrance { - mut equal_cost_range, + mut uncertainty_range, .. } = alignment_type { - equal_cost_range.min_start = 0; - equal_cost_range.max_start = 0; - equal_cost_range.min_end = 0; - equal_cost_range.max_end = 0; + uncertainty_range.min_start = 0; + uncertainty_range.max_start = 0; + uncertainty_range.min_end = 0; + uncertainty_range.max_end = 0; // Keep track of the current cost, which might get less while extending but never increase let mut current_cost = alignment.compute_cost( @@ -459,8 +470,11 @@ impl> reference_offset, query_offset, config, + ignore_geometry_cost, ); - debug_assert_eq!(current_cost, (statistics.cost.round().raw() as u64).into()); + if !ignore_geometry_cost { + debug_assert_eq!(current_cost, (statistics.cost.round().raw() as u64).into()); + } // Move start to the left { @@ -480,6 +494,7 @@ impl> reference_offset, query_offset, config, + ignore_geometry_cost, ); if new_cost > current_cost { trace!( @@ -488,7 +503,7 @@ impl> break; } current_cost = new_cost; - equal_cost_range.min_start -= 1; + uncertainty_range.min_start -= 1; } } @@ -510,6 +525,7 @@ impl> reference_offset, query_offset, config, + ignore_geometry_cost, ); if new_cost > current_cost { trace!( @@ -518,7 +534,7 @@ impl> break; } current_cost = new_cost; - equal_cost_range.max_start += 1; + uncertainty_range.max_start += 1; } } @@ -538,6 +554,7 @@ impl> reference_offset, query_offset, config, + ignore_geometry_cost, ); if new_cost > current_cost { trace!( @@ -546,7 +563,7 @@ impl> break; } current_cost = new_cost; - equal_cost_range.min_end -= 1; + uncertainty_range.min_end -= 1; } } @@ -566,6 +583,7 @@ impl> reference_offset, query_offset, config, + ignore_geometry_cost, ); if new_cost > current_cost { trace!( @@ -574,18 +592,18 @@ impl> break; } current_cost = new_cost; - equal_cost_range.max_end += 1; + uncertainty_range.max_end += 1; } } let super::template_switch_distance::AlignmentType::TemplateSwitchEntrance { - equal_cost_range: alignment_equal_cost_range, + uncertainty_range: alignment_uncertainty_range, .. } = &mut alignment.inner_mut()[i].1 else { unreachable!() }; - *alignment_equal_cost_range = equal_cost_range; + *alignment_uncertainty_range = uncertainty_range; } } } diff --git a/lib_tsalign/src/a_star_aligner/alignment_result/alignment/template_switch_specifics.rs b/lib_tsalign/src/a_star_aligner/alignment_result/alignment/template_switch_specifics.rs index 69a8006e..508f71a4 100644 --- a/lib_tsalign/src/a_star_aligner/alignment_result/alignment/template_switch_specifics.rs +++ b/lib_tsalign/src/a_star_aligner/alignment_result/alignment/template_switch_specifics.rs @@ -601,6 +601,56 @@ impl Alignment { } } + /// Compute the cost of an alignment. + /// + /// Flanks are not supported. + pub fn compute_complete_cost< + AlphabetType: Alphabet, + SubsequenceType: GenomeSequence + ?Sized, + Cost: AStarCost, + >( + &mut self, + reference: &SubsequenceType, + query: &SubsequenceType, + reference_offset: usize, + query_offset: usize, + config: &TemplateSwitchConfig, + ) -> Cost { + self.compute_cost( + reference, + query, + reference_offset, + query_offset, + config, + false, + ) + } + + /// Compute the cost of an alignment, ignoring the cost of the TSM geometries. + /// + /// Flanks are not supported. + pub fn compute_cost_ignore_geometry< + AlphabetType: Alphabet, + SubsequenceType: GenomeSequence + ?Sized, + Cost: AStarCost, + >( + &mut self, + reference: &SubsequenceType, + query: &SubsequenceType, + reference_offset: usize, + query_offset: usize, + config: &TemplateSwitchConfig, + ) -> Cost { + self.compute_cost( + reference, + query, + reference_offset, + query_offset, + config, + true, + ) + } + /// Compute the cost of an alignment. /// /// Flanks are not supported. @@ -615,6 +665,7 @@ impl Alignment { reference_offset: usize, query_offset: usize, config: &TemplateSwitchConfig, + ignore_geometry_cost: bool, ) -> Cost { let mut cost = Cost::zero(); @@ -760,9 +811,13 @@ impl Alignment { ancestor = ts_ancestor; direction = ts_direction; let base_cost = config.base_cost.get(descendant, ancestor, direction); - let offset_cost = config - .offset_costs(descendant, ancestor) - .evaluate(&first_offset); + let offset_cost = if ignore_geometry_cost { + Cost::zero() + } else { + config + .offset_costs(descendant, ancestor) + .evaluate(&first_offset) + }; let Some(cost_increment) = base_cost.checked_add(&offset_cost) else { return Cost::max_value(); }; @@ -789,47 +844,53 @@ impl Alignment { last_alignment_type, Some(AlignmentType::TemplateSwitchExit { .. }) )); - let length = match descendant { - TemplateSwitchDescendant::Reference => { - let length = descendant_index - reference_index; - reference_index = descendant_index; - query_index = usize::try_from( - isize::try_from(query_index) - .unwrap() - .checked_add(anti_descendant_gap) - .unwrap(), - ) - .unwrap(); - length - } - TemplateSwitchDescendant::Query => { - let length = descendant_index - query_index; - query_index = descendant_index; - reference_index = usize::try_from( - isize::try_from(reference_index) - .unwrap() - .checked_add(anti_descendant_gap) - .unwrap(), - ) - .unwrap(); - length - } - }; - let length_difference = anti_descendant_gap - isize::try_from(length).unwrap(); - let cost_increment = config - .anti_descendant_gap_costs(direction) - .evaluate(&anti_descendant_gap); - let Some(cost_increment) = - cost_increment.checked_add(&config.length_costs.evaluate(&length)) - else { - return Cost::max_value(); - }; - let Some(cost_increment) = cost_increment - .checked_add(&config.length_difference_costs.evaluate(&length_difference)) - else { - return Cost::max_value(); - }; - cost_increment + + if ignore_geometry_cost { + Cost::zero() + } else { + let length = match descendant { + TemplateSwitchDescendant::Reference => { + let length = descendant_index - reference_index; + reference_index = descendant_index; + query_index = usize::try_from( + isize::try_from(query_index) + .unwrap() + .checked_add(anti_descendant_gap) + .unwrap(), + ) + .unwrap(); + length + } + TemplateSwitchDescendant::Query => { + let length = descendant_index - query_index; + query_index = descendant_index; + reference_index = usize::try_from( + isize::try_from(reference_index) + .unwrap() + .checked_add(anti_descendant_gap) + .unwrap(), + ) + .unwrap(); + length + } + }; + let length_difference = + anti_descendant_gap - isize::try_from(length).unwrap(); + let cost_increment = config + .anti_descendant_gap_costs(direction) + .evaluate(&anti_descendant_gap); + let Some(cost_increment) = + cost_increment.checked_add(&config.length_costs.evaluate(&length)) + else { + return Cost::max_value(); + }; + let Some(cost_increment) = cost_increment.checked_add( + &config.length_difference_costs.evaluate(&length_difference), + ) else { + return Cost::max_value(); + }; + cost_increment + } } AlignmentType::AlternativeStart { reference_index: reference_start, @@ -883,8 +944,8 @@ mod tests { a_star_aligner::{ alignment_result::alignment::Alignment, template_switch_distance::{ - AlignmentType, EqualCostRange, TemplateSwitchAncestor, TemplateSwitchDescendant, - TemplateSwitchDirection, + AlignmentType, TSMUncertaintyRange, TemplateSwitchAncestor, + TemplateSwitchDescendant, TemplateSwitchDirection, }, }, config::{BaseCost, TemplateSwitchConfig}, @@ -942,7 +1003,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: -6, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -963,7 +1024,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: -4, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -984,7 +1045,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: -2, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1005,7 +1066,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 0, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1027,7 +1088,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 2, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1096,7 +1157,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 10, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1117,7 +1178,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 10, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1138,7 +1199,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 10, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1159,7 +1220,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 10, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1181,7 +1242,7 @@ mod tests { 1, AlignmentType::TemplateSwitchEntrance { first_offset: 10, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), descendant: TemplateSwitchDescendant::Reference, ancestor: TemplateSwitchAncestor::Query, direction: TemplateSwitchDirection::Reverse, @@ -1289,7 +1350,8 @@ mod tests { query.as_genome_subsequence(), 2, 2, - &CONFIG + &CONFIG, + false, ), START_COSTS[0] ); @@ -1311,7 +1373,8 @@ mod tests { query.as_genome_subsequence(), 2, 2, - &CONFIG + &CONFIG, + false, ), *expected_cost ); @@ -1329,7 +1392,8 @@ mod tests { query.as_genome_subsequence(), 2, 2, - &CONFIG + &CONFIG, + false, ), *START_COSTS.last().unwrap() ); @@ -1354,7 +1418,8 @@ mod tests { query.as_genome_subsequence(), 2, 2, - &CONFIG + &CONFIG, + false, ), *expected_cost ); @@ -1372,7 +1437,8 @@ mod tests { query.as_genome_subsequence(), 1, 1, - &CONFIG + &CONFIG, + false, ), *END_COSTS.last().unwrap() ); @@ -1394,7 +1460,8 @@ mod tests { query.as_genome_subsequence(), 1, 1, - &CONFIG + &CONFIG, + false, ), *expected_cost ); @@ -1412,7 +1479,8 @@ mod tests { query.as_genome_subsequence(), 1, 1, - &CONFIG + &CONFIG, + false, ), *END_COSTS.first().unwrap() ); @@ -1432,7 +1500,8 @@ mod tests { query.as_genome_subsequence(), 1, 1, - &CONFIG + &CONFIG, + false, ), *expected_cost ); diff --git a/lib_tsalign/src/a_star_aligner/configurable_a_star_align.rs b/lib_tsalign/src/a_star_aligner/configurable_a_star_align.rs index 3d989d70..ac7ddd38 100644 --- a/lib_tsalign/src/a_star_aligner/configurable_a_star_align.rs +++ b/lib_tsalign/src/a_star_aligner/configurable_a_star_align.rs @@ -10,6 +10,7 @@ use traitsequence::interface::Sequence; use crate::{ a_star_aligner::{ template_switch_distance::{ + TSMUncertaintyRangeExtensionMode, context::DynamicStrategies, strategies::{ AlignmentStrategySelection, @@ -138,6 +139,7 @@ struct QueryData<'a> { cost_limit: Option, memory_limit: Option, extend_beyond_range: bool, + tsm_uncertainty_range_extension_mode: TSMUncertaintyRangeExtensionMode, } #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -270,6 +272,7 @@ impl Aligner { cost_limit: Option, memory_limit: Option, extend_beyond_range: bool, + tsm_uncertainty_range_extension_mode: TSMUncertaintyRangeExtensionMode, ) -> AlignmentResult { let data = QueryData { reference_name, @@ -281,6 +284,7 @@ impl Aligner { cost_limit, memory_limit, extend_beyond_range, + tsm_uncertainty_range_extension_mode, }; self.align_select_min_length_strategy(data) } @@ -484,6 +488,7 @@ impl Aligner { data.memory_limit, false, data.extend_beyond_range, + data.tsm_uncertainty_range_extension_mode, count_strategy_memory, ) } diff --git a/lib_tsalign/src/a_star_aligner/configurable_a_star_align/tests.rs b/lib_tsalign/src/a_star_aligner/configurable_a_star_align/tests.rs index c2d763f9..ca27b427 100644 --- a/lib_tsalign/src/a_star_aligner/configurable_a_star_align/tests.rs +++ b/lib_tsalign/src/a_star_aligner/configurable_a_star_align/tests.rs @@ -21,7 +21,9 @@ fn test_panic() { &[], None, None, - true); + true, + TSMUncertaintyRangeExtensionMode::None, + ); println!("{res:#?}"); assert!(res.statistics().cost.is_sign_positive()); } @@ -37,5 +39,16 @@ fn test_193() { let mut costs = TemplateSwitchConfig::default(); costs.base_cost.qrr = 4u64.into(); tsa.set_costs(costs.clone()); - let _tsa_res = tsa.align("ref", r, "qry", q, None, &[], None, None, false); + let _tsa_res = tsa.align( + "ref", + r, + "qry", + q, + None, + &[], + None, + None, + false, + TSMUncertaintyRangeExtensionMode::None, + ); } diff --git a/lib_tsalign/src/a_star_aligner/template_switch_distance.rs b/lib_tsalign/src/a_star_aligner/template_switch_distance.rs index 87729712..757235a4 100644 --- a/lib_tsalign/src/a_star_aligner/template_switch_distance.rs +++ b/lib_tsalign/src/a_star_aligner/template_switch_distance.rs @@ -17,7 +17,10 @@ mod identifier; pub mod lower_bounds; pub mod strategies; -pub use alignment_type::{AlignmentType, equal_cost_range::EqualCostRange}; +pub use alignment_type::{ + AlignmentType, + tsm_uncertainty_range::{TSMUncertaintyRange, TSMUncertaintyRangeExtensionMode}, +}; pub use context::Context; pub use identifier::{ GapType, Identifier, TemplateSwitchAncestor, TemplateSwitchDescendant, TemplateSwitchDirection, @@ -289,7 +292,7 @@ impl Node { descendant: *template_switch_descendant, ancestor: *template_switch_ancestor, direction: *template_switch_direction, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), first_offset: *template_switch_first_offset, }, context, @@ -341,7 +344,7 @@ impl Node { descendant: template_switch_descendant, ancestor: template_switch_ancestor, direction: template_switch_direction, - equal_cost_range: EqualCostRange::new_invalid(), + uncertainty_range: TSMUncertaintyRange::new_invalid(), first_offset: successor_template_switch_first_offset, }, context, diff --git a/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type.rs b/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type.rs index 2380236c..9cb34c77 100644 --- a/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type.rs +++ b/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type.rs @@ -1,4 +1,4 @@ -use equal_cost_range::EqualCostRange; +use tsm_uncertainty_range::TSMUncertaintyRange; use crate::a_star_aligner::{ alignment_geometry::AlignmentCoordinates, alignment_result::IAlignmentType, @@ -8,7 +8,7 @@ use super::identifier::{ TemplateSwitchAncestor, TemplateSwitchDescendant, TemplateSwitchDirection, }; -pub mod equal_cost_range; +pub mod tsm_uncertainty_range; #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -48,7 +48,7 @@ pub enum AlignmentType { /// A template switch entrance. TemplateSwitchEntrance { first_offset: isize, - equal_cost_range: EqualCostRange, + uncertainty_range: TSMUncertaintyRange, descendant: TemplateSwitchDescendant, ancestor: TemplateSwitchAncestor, direction: TemplateSwitchDirection, @@ -207,13 +207,13 @@ impl AlignmentType { descendant, ancestor, direction, - equal_cost_range, + uncertainty_range, first_offset, } => Self::TemplateSwitchEntrance { descendant: descendant.inverted(), ancestor: ancestor.inverted(), direction: direction.inverted(), - equal_cost_range: *equal_cost_range, + uncertainty_range: *uncertainty_range, first_offset: *first_offset, }, Self::AlternativeStart { diff --git a/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/equal_cost_range.rs b/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/equal_cost_range.rs deleted file mode 100644 index c5a53675..00000000 --- a/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/equal_cost_range.rs +++ /dev/null @@ -1,41 +0,0 @@ -/// A heuristic range within which the start and end of the TS can be shifted without increasing cost. -/// -/// The range may not be maximal, but is required to be correct. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EqualCostRange { - /// How much the start of the TS can be shifted to the left without increasing cost. - /// - /// This number must not be positive. - pub min_start: i8, - - /// How much the start of the TS can be shifted to the right without increasing cost. - /// - /// This number must not be negative. - pub max_start: i8, - - /// How much the end of the TS can be shifted to the left without increasing cost. - /// - /// This number must not be positive. - pub min_end: i8, - - /// How much the end of the TS can be shifted to the right without increasing cost. - /// - /// This number must not be negative. - pub max_end: i8, -} - -impl EqualCostRange { - pub const fn new_invalid() -> Self { - Self { - min_start: 1, - max_start: -1, - min_end: 1, - max_end: -1, - } - } - - pub fn is_valid(&self) -> bool { - self.min_start <= 0 && self.max_start >= 0 && self.min_end <= 0 && self.max_end >= 0 - } -} diff --git a/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/tsm_uncertainty_range.rs b/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/tsm_uncertainty_range.rs new file mode 100644 index 00000000..7389f38b --- /dev/null +++ b/lib_tsalign/src/a_star_aligner/template_switch_distance/alignment_type/tsm_uncertainty_range.rs @@ -0,0 +1,56 @@ +/// Determines how the TSM uncertainty ranges are extended. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TSMUncertaintyRangeExtensionMode { + /// Keep the TSM uncertainty range empty. + None, + + /// Extend the TSM uncertainty range as far as possible without increasing cost. + EqualCost, + + /// Extend the TSM uncertainty range as far as possible without increasing cost, but ignore cost increases caused by different TSM geometry. + EqualCostIgnoreGeometry, +} + +/// A heuristic range within which the start and end of the TS can be shifted. +/// This can be without increasing cost, or without introducing mismatches, or possibly other criteria in the future. +/// +/// The range may not be maximal, but is required to be correct. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TSMUncertaintyRange { + /// How much the start of the TS can be shifted to the left. + /// + /// This number must not be positive. + pub min_start: i8, + + /// How much the start of the TS can be shifted to the right. + /// + /// This number must not be negative. + pub max_start: i8, + + /// How much the end of the TS can be shifted to the left. + /// + /// This number must not be positive. + pub min_end: i8, + + /// How much the end of the TS can be shifted to the right. + /// + /// This number must not be negative. + pub max_end: i8, +} + +impl TSMUncertaintyRange { + pub const fn new_invalid() -> Self { + Self { + min_start: 1, + max_start: -1, + min_end: 1, + max_end: -1, + } + } + + pub fn is_valid(&self) -> bool { + self.min_start <= 0 && self.max_start >= 0 && self.min_end <= 0 && self.max_end >= 0 + } +} diff --git a/lib_tsalign/src/a_star_aligner/template_switch_distance/display.rs b/lib_tsalign/src/a_star_aligner/template_switch_distance/display.rs index 8af08dae..ddf86a5f 100644 --- a/lib_tsalign/src/a_star_aligner/template_switch_distance/display.rs +++ b/lib_tsalign/src/a_star_aligner/template_switch_distance/display.rs @@ -2,7 +2,8 @@ use std::fmt::{Display, Formatter, Result}; use super::{ AlignmentType, GapType, Identifier, TemplateSwitchAncestor, TemplateSwitchDescendant, - alignment_type::equal_cost_range::EqualCostRange, identifier::TemplateSwitchDirection, + alignment_type::tsm_uncertainty_range::TSMUncertaintyRange, + identifier::TemplateSwitchDirection, }; impl Display for AlignmentType { @@ -22,11 +23,11 @@ impl Display for AlignmentType { descendant, ancestor, direction, - equal_cost_range, + uncertainty_range, first_offset, } => write!( f, - "[TS{descendant}{ancestor}{direction}:{equal_cost_range}:{first_offset}:" + "[TS{descendant}{ancestor}{direction}:{uncertainty_range}:{first_offset}:" ), Self::TemplateSwitchExit { anti_descendant_gap, @@ -80,7 +81,7 @@ impl Display for TemplateSwitchDirection { } } -impl Display for EqualCostRange { +impl Display for TSMUncertaintyRange { fn fmt(&self, f: &mut Formatter<'_>) -> Result { if self.is_valid() { let Self { diff --git a/lib_tsalign/src/tests.rs b/lib_tsalign/src/tests.rs index a89939fa..90d9f92f 100644 --- a/lib_tsalign/src/tests.rs +++ b/lib_tsalign/src/tests.rs @@ -11,6 +11,7 @@ use crate::{ a_star_aligner::{ alignment_geometry::{AlignmentCoordinates, AlignmentRange}, template_switch_distance::{ + TSMUncertaintyRangeExtensionMode, context::DynamicStrategies, strategies::{ AlignmentStrategySelection, @@ -82,6 +83,7 @@ fn test_tsnax_disc1_473() { None, false, true, + TSMUncertaintyRangeExtensionMode::None, (), ); assert_eq!( @@ -128,6 +130,7 @@ fn test_tsnax_disc1_473() { None, false, true, + TSMUncertaintyRangeExtensionMode::None, (), ); println!("{sample_alignment}"); @@ -164,6 +167,7 @@ fn test_tsnax_disc1_473() { None, false, true, + TSMUncertaintyRangeExtensionMode::None, (), ); println!("{sample_alignment}"); @@ -200,6 +204,7 @@ fn test_tsnax_disc1_473() { None, false, true, + TSMUncertaintyRangeExtensionMode::None, (), ); println!("{sample_alignment}"); diff --git a/lib_tsshow/Cargo.toml b/lib_tsshow/Cargo.toml index a5d6e149..4c034356 100644 --- a/lib_tsshow/Cargo.toml +++ b/lib_tsshow/Cargo.toml @@ -3,13 +3,13 @@ name = "lib_tsshow" description = "A visualiser for template-switch alignments" license.workspace = true authors = ["Sebastian Schmidt "] -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true repository.workspace = true [dependencies] -lib_tsalign = { version = "5.0.1", path = "../lib_tsalign" } +lib_tsalign = { version = "6.0.1", path = "../lib_tsalign" } log.workspace = true svg = "0.18.0" resvg = "0.48.1" diff --git a/lib_tsshow/src/svg.rs b/lib_tsshow/src/svg.rs index c56a0c75..adb7abc1 100644 --- a/lib_tsshow/src/svg.rs +++ b/lib_tsshow/src/svg.rs @@ -44,10 +44,16 @@ mod numbers; const SVG_PADDING: f32 = 10.0; const COPY_COLORS: &[&str] = &["#00CC00", "#009900", "#006600", "#003300"]; -const OPTIONAL_INNER_COPY_COLORS: &[&str] = &["#88CC88", "#669966", "#446644", "#223322"]; -const OPTIONAL_SOURCE_COPY_COLORS: &[&str] = &["#0000FF66"]; -const OPTIONAL_INNER_COLOR: &str = "#0000FF"; -const OPTIONAL_SOURCE_COLOR: &str = "#0000FF66"; +const OPTIONAL_INNER_INCREASING_COPY_COLORS: &[&str] = + &["#88CC8866", "#66996666", "#44664466", "#22332266"]; +const OPTIONAL_INNER_DECREASING_COPY_COLORS: &[&str] = + &["#88CCCC", "#669999", "#446666", "#223333"]; +const OPTIONAL_SOURCE_INCREASING_COPY_COLORS: &[&str] = &["#B14DB1"]; +const OPTIONAL_SOURCE_DECREASING_COPY_COLORS: &[&str] = &["#0000FF66"]; +const OPTIONAL_INNER_INCREASING_COLOR: &str = "#B14DB166"; +const OPTIONAL_INNER_DECREASING_COLOR: &str = "#0000FF"; +const OPTIONAL_SOURCE_INCREASING_COLOR: &str = "#B14DB1"; +const OPTIONAL_SOURCE_DECREASING_COLOR: &str = "#0000FF66"; const COMPLEMENT_SOURCE_HIDDEN_COLOR: &str = "grey"; const TS_RUNNING_NUMBER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -68,16 +74,16 @@ pub struct SvgConfig { /// Restrict the context around the template switches to this many characters on each side. /// If `None`, the full sequences will be rendered. pub restrict_context: Option, - pub equal_cost_range_mode: EqualCostRangeMode, + pub uncertainty_range_mode: UncertaintyRangeMode, } #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] -pub enum EqualCostRangeMode { - /// Do not render equal cost ranges. +pub enum UncertaintyRangeMode { + /// Do not render TSM uncertainty ranges. None, - /// Render the equal cost ranges only on the inner sequences. + /// Render the TSM uncertainty ranges only on the inner sequences. InnerOnly, - /// Render the equal cost ranges on all sequences. + /// Render the TSM uncertainty ranges on all sequences. /// This may be messy if there are overlaps. Full, } @@ -111,7 +117,7 @@ pub fn create_ts_svg( reference.len(), query.len(), alignment.iter_flat_cloned(), - config.equal_cost_range_mode, + config.uncertainty_range_mode, )?; if config.render_more_complement { @@ -152,7 +158,7 @@ pub fn create_ts_svg( reference.len(), query.len(), alignment.iter_flat_cloned(), - config.equal_cost_range_mode, + config.uncertainty_range_mode, &mut Vec::new(), ) .map(|mut no_ts_arrangement| { @@ -199,7 +205,6 @@ pub fn create_ts_svg( sp3_ancestor, sp4_reference, sp4_query, - equal_cost_range, .. }, ) in ts_arrangement.template_switches() @@ -257,34 +262,40 @@ pub fn create_ts_svg( } }*/ - let descendant_sp1 = ts_arrangement.descendant_arrangement_char_to_arrangement_column( - match descendant { - TemplateSwitchDescendant::Reference => *sp1_reference, - TemplateSwitchDescendant::Query => *sp1_query, - } - usize::try_from(-equal_cost_range.min_start).unwrap(), - *descendant, - ); + let descendant_sp1 = match descendant { + TemplateSwitchDescendant::Reference => *sp1_reference, + TemplateSwitchDescendant::Query => *sp1_query, + }; + /*let descendant_sp1 = if config.uncertainty_range_mode == UncertaintyRangeMode::Full { + descendant_sp1 - usize::try_from(-uncertainty_range.min_start).unwrap() + } else { + descendant_sp1 + };*/ + let descendant_sp1 = ts_arrangement + .descendant_arrangement_char_to_arrangement_column(descendant_sp1, *descendant); let descendant_sp4 = match descendant { TemplateSwitchDescendant::Reference => *sp4_reference, TemplateSwitchDescendant::Query => *sp4_query, - } + usize::try_from(equal_cost_range.max_end).unwrap(); + }; + /*let descendant_sp4 = if config.uncertainty_range_mode == UncertaintyRangeMode::Full { + descendant_sp4 + usize::try_from(uncertainty_range.max_end).unwrap() + } else { + descendant_sp4 + };*/ let descendant_sp4_minus_one = descendant_sp4.checked_sub(1).map(|column| { ts_arrangement.descendant_arrangement_char_to_arrangement_column(column, *descendant) + 1usize }); + let descendant_sp4_limit = ts_arrangement + .descendant_arrangement_char_to_arrangement_column(descendant_sp4, *descendant) + + 1usize; let descendant_sp4 = ts_arrangement .descendant(*descendant) - .iter( - descendant_sp4_minus_one.unwrap_or(0.into()) - ..ts_arrangement.descendant_arrangement_char_to_arrangement_column( - descendant_sp4, - *descendant, - ) + 1usize, - ) + .iter(descendant_sp4_minus_one.unwrap_or(0.into())..descendant_sp4_limit) .find(|(_, c)| !c.is_blank()) - .unwrap() - .0; + .map(|(column, _)| column) + .unwrap_or_else(|| ts_arrangement.limit_column()); let descendant_row = match descendant { TemplateSwitchDescendant::Reference => TsArrangementRow::Reference, @@ -294,15 +305,15 @@ pub fn create_ts_svg( let forward = sp2_ancestor < sp3_ancestor; let (ancestor_limit, ancestor_offset, inner_row) = match ancestor { TemplateSwitchAncestor::Reference => ( - ts_arrangement.inner_last_non_blank_column(inner_identifier) + 1usize, - ts_arrangement.inner_first_non_blank_column(inner_identifier), + ts_arrangement.inner_last_non_increasing_column(inner_identifier) + 1usize, + ts_arrangement.inner_first_non_increasing_column(inner_identifier), TsArrangementRow::Inner { index: inner_identifier, }, ), TemplateSwitchAncestor::Query => ( - ts_arrangement.inner_last_non_blank_column(inner_identifier) + 1usize, - ts_arrangement.inner_first_non_blank_column(inner_identifier), + ts_arrangement.inner_last_non_increasing_column(inner_identifier) + 1usize, + ts_arrangement.inner_first_non_increasing_column(inner_identifier), TsArrangementRow::Inner { index: inner_identifier, }, @@ -821,6 +832,7 @@ fn render_source_char( SourceChar::OptionalSource { column, lower_case, + increases_inner, copy_depth, } => { let c = source_sequence.char_at(column.into()); @@ -832,7 +844,14 @@ fn render_source_char( Character::new_char( c, - CharacterData::new_colored(copy_color(copy_depth, OptionalChar::Source)), + CharacterData::new_colored(copy_color( + copy_depth, + if *increases_inner { + OptionalChar::SourceIncreasing + } else { + OptionalChar::SourceDecreasing + }, + )), ) } SourceChar::Gap { copy_depth } => Character::new_char( @@ -908,6 +927,7 @@ fn render_inner_char( InnerChar::OptionalInner { column, lower_case, + increases_inner, copy_depth, } => { let c = source_sequence.char_at(column.into()); @@ -918,7 +938,14 @@ fn render_inner_char( }; Character::new_char( c, - CharacterData::new_colored(copy_color(copy_depth, OptionalChar::Inner)), + CharacterData::new_colored(copy_color( + copy_depth, + if *increases_inner { + OptionalChar::InnerIncreasing + } else { + OptionalChar::InnerDecreasing + }, + )), ) } InnerChar::Gap { copy_depth } => Character::new_char( @@ -935,26 +962,40 @@ fn render_label_char(c: char) -> Character { enum OptionalChar { NotOptional, - Source, - Inner, + SourceIncreasing, + SourceDecreasing, + InnerIncreasing, + InnerDecreasing, } fn copy_color(copy_depth: &Option, optional: OptionalChar) -> impl ToString { if let Some(copy_depth) = copy_depth { match optional { OptionalChar::NotOptional => COPY_COLORS[copy_depth % COPY_COLORS.len()], - OptionalChar::Source => { - OPTIONAL_SOURCE_COPY_COLORS[copy_depth % OPTIONAL_SOURCE_COPY_COLORS.len()] + OptionalChar::SourceIncreasing => { + OPTIONAL_SOURCE_INCREASING_COPY_COLORS + [copy_depth % OPTIONAL_SOURCE_INCREASING_COPY_COLORS.len()] } - OptionalChar::Inner => { - OPTIONAL_INNER_COPY_COLORS[copy_depth % OPTIONAL_INNER_COPY_COLORS.len()] + OptionalChar::SourceDecreasing => { + OPTIONAL_SOURCE_DECREASING_COPY_COLORS + [copy_depth % OPTIONAL_SOURCE_DECREASING_COPY_COLORS.len()] + } + OptionalChar::InnerIncreasing => { + OPTIONAL_INNER_INCREASING_COPY_COLORS + [copy_depth % OPTIONAL_INNER_INCREASING_COPY_COLORS.len()] + } + OptionalChar::InnerDecreasing => { + OPTIONAL_INNER_DECREASING_COPY_COLORS + [copy_depth % OPTIONAL_INNER_DECREASING_COPY_COLORS.len()] } } } else { match optional { OptionalChar::NotOptional => "black", - OptionalChar::Source => OPTIONAL_SOURCE_COLOR, - OptionalChar::Inner => OPTIONAL_INNER_COLOR, + OptionalChar::SourceIncreasing => OPTIONAL_SOURCE_INCREASING_COLOR, + OptionalChar::SourceDecreasing => OPTIONAL_SOURCE_DECREASING_COLOR, + OptionalChar::InnerIncreasing => OPTIONAL_INNER_INCREASING_COLOR, + OptionalChar::InnerDecreasing => OPTIONAL_INNER_DECREASING_COLOR, } } } @@ -1026,17 +1067,39 @@ fn legend( .character_height .max(sans_serif_mono::FONT.character_height); - if config.equal_cost_range_mode != EqualCostRangeMode::None { - let uncertainty_label = "BLUE CHARACTERS"; + if config.uncertainty_range_mode != UncertaintyRangeMode::None { + let uncertainty_increasing_label = "PURPLE-ISH CHARACTERS"; result = result.add(svg_string( - uncertainty_label - .chars() - .map(|c| Character::new_char(c, CharacterData::new_colored(OPTIONAL_INNER_COLOR))), + uncertainty_increasing_label.chars().map(|c| { + Character::new_char( + c, + CharacterData::new_colored(OPTIONAL_INNER_INCREASING_COLOR), + ) + }), &SvgLocation { x: 0.0, y }, &typewriter::FONT, )); - label_width = label_width - .max(uncertainty_label.chars().count() as f32 * typewriter::FONT.character_width); + label_width = label_width.max( + uncertainty_increasing_label.chars().count() as f32 * typewriter::FONT.character_width, + ); + y += typewriter::FONT + .character_height + .max(sans_serif_mono::FONT.character_height); + + let uncertainty_decreasing_label = "BLUE CHARACTERS"; + result = result.add(svg_string( + uncertainty_decreasing_label.chars().map(|c| { + Character::new_char( + c, + CharacterData::new_colored(OPTIONAL_INNER_DECREASING_COLOR), + ) + }), + &SvgLocation { x: 0.0, y }, + &typewriter::FONT, + )); + label_width = label_width.max( + uncertainty_decreasing_label.chars().count() as f32 * typewriter::FONT.character_width, + ); } // Explanations. @@ -1071,17 +1134,34 @@ fn legend( .character_height .max(sans_serif_mono::FONT.character_height); - if config.equal_cost_range_mode != EqualCostRangeMode::None { - let uncertainty_explanation = "Equal-cost range of the TSM"; + if config.uncertainty_range_mode != UncertaintyRangeMode::None { + let uncertainty_increasing_explanation = "Uncertainty range of the TSM, increasing part"; + result = result.add(svg_string( + uncertainty_increasing_explanation + .chars() + .map(Character::::new_char_with_default), + &SvgLocation { x: label_width, y }, + &sans_serif_mono::FONT, + )); + explanation_width = explanation_width.max( + uncertainty_increasing_explanation.chars().count() as f32 + * sans_serif_mono::FONT.character_width, + ); + y += typewriter::FONT + .character_height + .max(sans_serif_mono::FONT.character_height); + + let uncertainty_decreasing_explanation = "Uncertainty range of the TSM, decreasing part"; result = result.add(svg_string( - uncertainty_explanation + uncertainty_decreasing_explanation .chars() .map(Character::::new_char_with_default), &SvgLocation { x: label_width, y }, &sans_serif_mono::FONT, )); explanation_width = explanation_width.max( - uncertainty_explanation.chars().count() as f32 * sans_serif_mono::FONT.character_width, + uncertainty_decreasing_explanation.chars().count() as f32 + * sans_serif_mono::FONT.character_width, ); y += typewriter::FONT .character_height diff --git a/lib_tsshow/src/ts_arrangement.rs b/lib_tsshow/src/ts_arrangement.rs index af2859c7..25016219 100644 --- a/lib_tsshow/src/ts_arrangement.rs +++ b/lib_tsshow/src/ts_arrangement.rs @@ -15,7 +15,7 @@ use source::{SourceChar, TsSourceArrangement}; use tagged_vec::TaggedVec; use template_switch::TemplateSwitch; -use crate::{error::Result, svg::EqualCostRangeMode}; +use crate::{error::Result, svg::UncertaintyRangeMode}; pub mod character; pub mod complement; @@ -38,7 +38,7 @@ impl TsArrangement { reference_length: usize, query_length: usize, alignment: impl IntoIterator, - equal_cost_range_mode: EqualCostRangeMode, + uncertainty_range_mode: UncertaintyRangeMode, ) -> Result { let mut template_switches = Vec::new(); let mut source = TsSourceArrangement::new( @@ -47,7 +47,7 @@ impl TsArrangement { reference_length, query_length, alignment, - equal_cost_range_mode, + uncertainty_range_mode, &mut template_switches, )?; let mut complement = TsComplementArrangement::new(&source); @@ -55,7 +55,7 @@ impl TsArrangement { &mut source, &mut complement, template_switches, - equal_cost_range_mode, + uncertainty_range_mode, ); Ok(Self { @@ -234,6 +234,11 @@ impl TsArrangement { self.source.reference().len() } + /// Returns the first invalid column index right of the arrangement. + pub fn limit_column(&self) -> ArrangementColumn { + self.width().into() + } + pub fn reference_arrangement_char_to_arrangement_column( &self, column: ArrangementCharColumn, @@ -344,6 +349,22 @@ impl TsArrangement { self.inner.inner_last_non_blank_column(inner_identifier) } + pub fn inner_first_non_increasing_column( + &self, + inner_identifier: TsInnerIdentifier, + ) -> ArrangementColumn { + self.inner + .inner_first_non_increasing_column(inner_identifier) + } + + pub fn inner_last_non_increasing_column( + &self, + inner_identifier: TsInnerIdentifier, + ) -> ArrangementColumn { + self.inner + .inner_last_non_increasing_column(inner_identifier) + } + /// Returns the index of the first column that is related to a TSM. pub fn first_interesting_column(&self) -> SourceColumn { self.inners() diff --git a/lib_tsshow/src/ts_arrangement/inner.rs b/lib_tsshow/src/ts_arrangement/inner.rs index 3d93d740..58af0394 100644 --- a/lib_tsshow/src/ts_arrangement/inner.rs +++ b/lib_tsshow/src/ts_arrangement/inner.rs @@ -6,7 +6,7 @@ use lib_tsalign::a_star_aligner::template_switch_distance::{ use log::{trace, warn}; use tagged_vec::TaggedVec; -use crate::{svg::EqualCostRangeMode, ts_arrangement::character::Char}; +use crate::{svg::UncertaintyRangeMode, ts_arrangement::character::Char}; use super::{ complement::TsComplementArrangement, @@ -36,6 +36,8 @@ pub enum InnerChar { OptionalInner { column: SourceColumn, lower_case: bool, + /// True if this character is part of the TSM uncertainty range that increases the size of the inner sequence. + increases_inner: bool, copy_depth: Option, }, Gap { @@ -49,7 +51,7 @@ impl TsInnerArrangement { source_arrangement: &mut TsSourceArrangement, complement_arrangement: &mut TsComplementArrangement, template_switches: Vec, - equal_cost_range_mode: EqualCostRangeMode, + uncertainty_range_mode: UncertaintyRangeMode, ) -> Self { let mut result = Self { inners: Default::default(), @@ -320,12 +322,14 @@ impl TsInnerArrangement { inner.extend(suffix_blanks); if matches!( - equal_cost_range_mode, - EqualCostRangeMode::InnerOnly | EqualCostRangeMode::Full, + uncertainty_range_mode, + UncertaintyRangeMode::InnerOnly | UncertaintyRangeMode::Full, ) { - // Add characters to visualise TSM equal cost range. + // Add characters to visualise TSM uncertainty range. if forward { - warn!("TSM equal cost range visualisation is not implemented for forward TSMs.") + warn!( + "TSM uncertainty range visualisation is not implemented for forward TSMs." + ) } else { // Insert range characters before point 2. @@ -365,13 +369,14 @@ impl TsInnerArrangement { let mut arrangement_column = last_initial_blank.map(|i| i + 1usize).unwrap_or(0.into()); let mut source_column = first_source_column; - for _ in 0..ts.equal_cost_range.max_end { + for _ in 0..ts.uncertainty_range.max_end { arrangement_column -= 1; source_column += 1; inner[arrangement_column] = InnerChar::OptionalInner { column: source_column, lower_case: false, + increases_inner: true, copy_depth: None, }; } @@ -379,38 +384,39 @@ impl TsInnerArrangement { // Add suffix to extend to min_start. let mut arrangement_column = first_final_blank - 1usize; let mut source_column = last_source_column; - for _ in 0..-ts.equal_cost_range.min_start { + for _ in 0..-ts.uncertainty_range.min_start { arrangement_column += 1; source_column -= 1; inner[arrangement_column] = InnerChar::OptionalInner { column: source_column, lower_case: false, + increases_inner: true, copy_depth: None, }; } // Convert prefix to extend to min_end. let mut arrangement_column = first_non_blank; - for _ in 0..-ts.equal_cost_range.min_end { + for _ in 0..-ts.uncertainty_range.min_end { while !inner[arrangement_column].is_source_char() { arrangement_column += 1; } - inner[arrangement_column].to_optional(); + inner[arrangement_column].to_optional(false); arrangement_column += 1; } // Convert suffix to extend to max_start. let mut arrangement_column = first_final_blank; - for _ in 0..ts.equal_cost_range.max_start { + for _ in 0..ts.uncertainty_range.max_start { arrangement_column -= 1; while !inner[arrangement_column].is_source_char() { arrangement_column -= 1; } - inner[arrangement_column].to_optional(); + inner[arrangement_column].to_optional(false); } } } @@ -500,6 +506,33 @@ impl TsInnerArrangement { .map(|(i, _)| i) .unwrap() // If None, would need to return -1 here, but return value is unsigned. } + + pub fn inner_first_non_increasing_column( + &self, + inner_identifier: TsInnerIdentifier, + ) -> ArrangementColumn { + let sequence = &self.inners[inner_identifier].sequence; + + sequence + .iter(..) + .find(|(_, c)| !c.is_blank() && !c.is_increasing()) + .map(|(i, _)| i) + .unwrap_or(sequence.len().into()) + } + + pub fn inner_last_non_increasing_column( + &self, + inner_identifier: TsInnerIdentifier, + ) -> ArrangementColumn { + let sequence = &self.inners[inner_identifier].sequence; + + sequence + .iter(..) + .rev() + .find(|(_, c)| !c.is_blank() && !c.is_increasing()) + .map(|(i, _)| i) + .unwrap() // If None, would need to return -1 here, but return value is unsigned. + } } impl TsInner { @@ -536,27 +569,53 @@ impl InnerChar { } } - pub fn to_optional(&mut self) { + pub fn to_optional(&mut self, increases_inner: bool) { match *self { Self::Inner { column, lower_case, copy_depth, + } => { + *self = Self::OptionalInner { + column, + lower_case, + increases_inner, + copy_depth, + } } - | Self::OptionalInner { + Self::OptionalInner { column, lower_case, copy_depth, + increases_inner: existing_increases_inner, } => { + if increases_inner != existing_increases_inner { + warn!( + "Overwriting TSM uncertainty range membership properties of an InnerChar. This may indicate that the visualisation of the TSM uncertainty range is not correct." + ); + } + *self = Self::OptionalInner { column, lower_case, + increases_inner, copy_depth, } } Self::Gap { .. } | Self::Blank => panic!("Not optionalisable"), } } + + /// Returns true if this character is optional increasing. + pub fn is_increasing(&self) -> bool { + matches!( + self, + Self::OptionalInner { + increases_inner: true, + .. + } + ) + } } impl From for InnerChar { diff --git a/lib_tsshow/src/ts_arrangement/source.rs b/lib_tsshow/src/ts_arrangement/source.rs index e10f3959..15e0284e 100644 --- a/lib_tsshow/src/ts_arrangement/source.rs +++ b/lib_tsshow/src/ts_arrangement/source.rs @@ -3,7 +3,7 @@ use std::{cmp::Ordering, iter}; use lib_tsalign::a_star_aligner::{ alignment_result::alignment::Alignment, template_switch_distance::{ - AlignmentType, EqualCostRange, TemplateSwitchAncestor, TemplateSwitchDescendant, + AlignmentType, TSMUncertaintyRange, TemplateSwitchAncestor, TemplateSwitchDescendant, TemplateSwitchDirection, }, }; @@ -15,7 +15,7 @@ use super::{ index_types::{ArrangementCharColumn, ArrangementColumn, SourceColumn}, template_switch::TemplateSwitch, }; -use crate::{error::Result, svg::EqualCostRangeMode}; +use crate::{error::Result, svg::UncertaintyRangeMode}; pub struct TsSourceArrangement { reference: TaggedVec, @@ -39,6 +39,8 @@ pub enum SourceChar { OptionalSource { column: SourceColumn, lower_case: bool, + /// True if this character is part of the TSM uncertainty range that increases the size of the inner sequence. + increases_inner: bool, copy_depth: Option, }, Hidden { @@ -64,7 +66,7 @@ impl TsSourceArrangement { reference_length: usize, query_length: usize, alignment: impl IntoIterator, - equal_cost_range_mode: EqualCostRangeMode, + uncertainty_range_mode: UncertaintyRangeMode, template_switches_out: &mut impl Extend, ) -> Result { let mut ts_index = 0; @@ -151,7 +153,7 @@ impl TsSourceArrangement { ancestor, direction, first_offset, - equal_cost_range, + uncertainty_range, .. } => { template_switches_out.extend([result.align_ts( @@ -160,8 +162,8 @@ impl TsSourceArrangement { ancestor, direction, first_offset, - equal_cost_range, - equal_cost_range_mode, + uncertainty_range, + uncertainty_range_mode, &mut alignment, &mut current_reference_index, &mut current_query_index, @@ -231,8 +233,8 @@ impl TsSourceArrangement { ts_ancestor: TemplateSwitchAncestor, ts_direction: TemplateSwitchDirection, first_offset: isize, - equal_cost_range: EqualCostRange, - equal_cost_range_mode: EqualCostRangeMode, + uncertainty_range: TSMUncertaintyRange, + uncertainty_range_mode: UncertaintyRangeMode, mut alignment: impl Iterator, current_reference_index: &mut ArrangementColumn, current_query_index: &mut ArrangementColumn, @@ -376,12 +378,12 @@ impl TsSourceArrangement { // Insert spacers. let mut required_spacer_count = 4usize.saturating_sub(anti_descendant_inner_length); - let descendant_minimal_equal_cost_range_start = + let descendant_minimal_uncertainty_range_start = match ts_descendant { TemplateSwitchDescendant::Reference => sp1_reference, TemplateSwitchDescendant::Query => sp1_query, - } + usize::try_from(equal_cost_range.max_start).unwrap(); - let descendant_minimal_equal_cost_range_end = + } + usize::try_from(uncertainty_range.max_start).unwrap(); + let descendant_minimal_uncertainty_range_end = match ts_descendant { TemplateSwitchDescendant::Reference => { self.reference_arrangement_to_arrangement_char_column(*current_descendant_index) @@ -389,23 +391,23 @@ impl TsSourceArrangement { TemplateSwitchDescendant::Query => { self.query_arrangement_to_arrangement_char_column(*current_descendant_index) } - } - usize::try_from(-equal_cost_range.min_end).unwrap(); - let (descendant_minimal_equal_cost_range_start, descendant_minimal_equal_cost_range_end) = + } - usize::try_from(-uncertainty_range.min_end).unwrap(); + let (descendant_minimal_uncertainty_range_start, descendant_minimal_uncertainty_range_end) = match ts_descendant { TemplateSwitchDescendant::Reference => ( self.reference_arrangement_char_to_arrangement_column( - descendant_minimal_equal_cost_range_start, + descendant_minimal_uncertainty_range_start, ), self.reference_arrangement_char_to_arrangement_column( - descendant_minimal_equal_cost_range_end, + descendant_minimal_uncertainty_range_end, ), ), TemplateSwitchDescendant::Query => ( self.query_arrangement_char_to_arrangement_column( - descendant_minimal_equal_cost_range_start, + descendant_minimal_uncertainty_range_start, ), self.query_arrangement_char_to_arrangement_column( - descendant_minimal_equal_cost_range_end, + descendant_minimal_uncertainty_range_end, ), ), }; @@ -421,12 +423,12 @@ impl TsSourceArrangement { .checked_sub(descendant_inner_length) .unwrap(); - if descendant_minimal_equal_cost_range_start - <= descendant_minimal_equal_cost_range_end + if descendant_minimal_uncertainty_range_start + <= descendant_minimal_uncertainty_range_end { descendant.splice( - descendant_minimal_equal_cost_range_end - ..descendant_minimal_equal_cost_range_end, + descendant_minimal_uncertainty_range_end + ..descendant_minimal_uncertainty_range_end, iter::repeat_n(SourceChar::Blank, delta), ); } else { @@ -435,13 +437,13 @@ impl TsSourceArrangement { let end_delta = delta - start_delta; descendant.splice( - descendant_minimal_equal_cost_range_start - ..descendant_minimal_equal_cost_range_start, + descendant_minimal_uncertainty_range_start + ..descendant_minimal_uncertainty_range_start, iter::repeat_n(SourceChar::Blank, start_delta), ); descendant.splice( - descendant_minimal_equal_cost_range_end - ..descendant_minimal_equal_cost_range_end, + descendant_minimal_uncertainty_range_end + ..descendant_minimal_uncertainty_range_end, iter::repeat_n(SourceChar::Blank, end_delta), ); } @@ -484,7 +486,7 @@ impl TsSourceArrangement { } }; - if equal_cost_range_mode == EqualCostRangeMode::Full { + if uncertainty_range_mode == UncertaintyRangeMode::Full { let sp1_descendant = match ts_descendant { TemplateSwitchDescendant::Reference => sp1_reference_arrangement_column, TemplateSwitchDescendant::Query => sp1_query_arrangement_column, @@ -500,24 +502,24 @@ impl TsSourceArrangement { .take(usize::from(sp1_descendant)) .rev() .filter(|c| c.is_char()) - .take(usize::try_from(-equal_cost_range.min_start).unwrap()) - .for_each(|c| c.make_optional()); + .take(usize::try_from(-uncertainty_range.min_start).unwrap()) + .for_each(|c| c.make_optional(true)); // Mark characters right of SP4 as optional. descendant .iter_values_mut() .skip(usize::from(sp4_descendant)) .filter(|c| c.is_char()) - .take(usize::try_from(equal_cost_range.max_end).unwrap()) - .for_each(|c| c.make_optional()); + .take(usize::try_from(uncertainty_range.max_end).unwrap()) + .for_each(|c| c.make_optional(true)); // Mark characters right of SP1 as optional. descendant .iter_values_mut() .skip(usize::from(sp1_descendant)) .filter(|c| c.is_char()) - .take(usize::try_from(equal_cost_range.max_start).unwrap()) - .for_each(|c| c.make_optional()); + .take(usize::try_from(uncertainty_range.max_start).unwrap()) + .for_each(|c| c.make_optional(false)); // Mark characters left of SP4 as optional. descendant @@ -525,8 +527,8 @@ impl TsSourceArrangement { .take(usize::from(sp4_descendant)) .rev() .filter(|c| c.is_char()) - .take(usize::try_from(-equal_cost_range.min_end).unwrap()) - .for_each(|c| c.make_optional()); + .take(usize::try_from(-uncertainty_range.min_end).unwrap()) + .for_each(|c| c.make_optional(false)); } let sp4_reference = @@ -545,7 +547,7 @@ impl TsSourceArrangement { sp3_ancestor, inner, inner_alignment, - equal_cost_range, + uncertainty_range, } } @@ -1004,7 +1006,7 @@ impl SourceChar { } } - pub fn make_optional(&mut self) { + pub fn make_optional(&mut self, increases_inner: bool) { match self { Self::Source { column, copy_depth, .. @@ -1013,6 +1015,7 @@ impl SourceChar { *self = Self::OptionalSource { column: *column, lower_case: false, + increases_inner, copy_depth: *copy_depth, }; } @@ -1034,7 +1037,7 @@ impl SourceChar { }, Self::OptionalSource { column, copy_depth, .. - } => Self::OptionalSource { + } => Self::Source { column: *column, lower_case: false, copy_depth: Some(copy_depth.map(|copy_depth| copy_depth + 1).unwrap_or(0)), @@ -1061,7 +1064,7 @@ impl SourceChar { }, Self::OptionalSource { column, copy_depth, .. - } => Self::OptionalSource { + } => Self::Source { column: *column, lower_case: false, copy_depth: Some(copy_depth.map(|copy_depth| copy_depth + 1).unwrap_or(0)), diff --git a/lib_tsshow/src/ts_arrangement/template_switch.rs b/lib_tsshow/src/ts_arrangement/template_switch.rs index d5b6c719..72daf3f5 100644 --- a/lib_tsshow/src/ts_arrangement/template_switch.rs +++ b/lib_tsshow/src/ts_arrangement/template_switch.rs @@ -5,7 +5,7 @@ use super::{ use lib_tsalign::a_star_aligner::{ alignment_result::alignment::Alignment, template_switch_distance::{ - AlignmentType, EqualCostRange, TemplateSwitchAncestor, TemplateSwitchDescendant, + AlignmentType, TSMUncertaintyRange, TemplateSwitchAncestor, TemplateSwitchDescendant, }, }; @@ -22,7 +22,7 @@ pub struct TemplateSwitch { pub sp3_ancestor: SourceColumn, pub inner: Vec, pub inner_alignment: Alignment, - pub equal_cost_range: EqualCostRange, + pub uncertainty_range: TSMUncertaintyRange, } impl TemplateSwitch { diff --git a/python_bindings/Cargo.toml b/python_bindings/Cargo.toml index 1a32d895..6403c5c9 100644 --- a/python_bindings/Cargo.toml +++ b/python_bindings/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Sebastian Schmidt ", "Jasper Krauter ", ] -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true repository.workspace = true @@ -18,10 +18,10 @@ crate-type = ["cdylib"] [dependencies] pyo3 = "0.26" -lib_tsalign = { version = "5.0.1", path = "../lib_tsalign", features = [ +lib_tsalign = { version = "6.0.1", path = "../lib_tsalign", features = [ "serde", ] } -lib_tsshow = { version = "5.0.1", path = "../lib_tsshow" } +lib_tsshow = { version = "6.0.1", path = "../lib_tsshow" } serde.workspace = true pythonize = "0.26" pyo3-log = "0.13" diff --git a/python_bindings/python/tsalign/_types.py b/python_bindings/python/tsalign/_types.py index 932b8271..a71405c0 100644 --- a/python_bindings/python/tsalign/_types.py +++ b/python_bindings/python/tsalign/_types.py @@ -37,7 +37,7 @@ class TemplateSwitchEntranceOp: descendant: str # "Reference" or "Query" ancestor: str # "Reference" or "Query" direction: str # "Forward" or "Reverse" - equal_cost_range: dict # {min_start, max_start, min_end, max_end} + uncertainty_range: dict # {min_start, max_start, min_end, max_end} @dataclass @@ -63,7 +63,7 @@ def _parse_op(raw: object) -> AlignmentOp: descendant=d["descendant"], ancestor=d["ancestor"], direction=d["direction"], - equal_cost_range=d["equal_cost_range"], + uncertainty_range=d["uncertainty_range"], ) if "TemplateSwitchExit" in raw: return TemplateSwitchExitOp( diff --git a/python_bindings/src/lib.rs b/python_bindings/src/lib.rs index 4bfec2ff..5194b358 100644 --- a/python_bindings/src/lib.rs +++ b/python_bindings/src/lib.rs @@ -6,7 +6,7 @@ use lib_tsalign::{ alignment_geometry::{AlignmentCoordinates, AlignmentRange}, alignment_result::AlignmentResult, configurable_a_star_align::Aligner, - template_switch_distance::AlignmentType, + template_switch_distance::{AlignmentType, TSMUncertaintyRangeExtensionMode}, }, costs::U64Cost, }; @@ -140,6 +140,7 @@ impl TSAligner { cost_limit, memory_limit, true, + TSMUncertaintyRangeExtensionMode::EqualCostIgnoreGeometry, ); match result { diff --git a/seed_chain/Cargo.toml b/seed_chain/Cargo.toml index 79cb0031..c9f2e785 100644 --- a/seed_chain/Cargo.toml +++ b/seed_chain/Cargo.toml @@ -3,7 +3,7 @@ name = "seed_chain" description = "A seeding and generic chaining mechanism for sequence-to-sequence alignment" license.workspace = true authors = ["Sebastian Schmidt "] -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true repository.workspace = true @@ -12,5 +12,5 @@ repository.workspace = true [dependencies] compact-genome.workspace = true log.workspace = true -generic_a_star = { version = "5.0.1", path = "../generic_a_star" } +generic_a_star = { version = "6.0.1", path = "../generic_a_star" } num-traits.workspace = true diff --git a/test_files/twin_equal_cost_ignore_geometry.fa b/test_files/twin_equal_cost_ignore_geometry.fa new file mode 100644 index 00000000..33b497cb --- /dev/null +++ b/test_files/twin_equal_cost_ignore_geometry.fa @@ -0,0 +1,4 @@ +>Reference +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGGGTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +>Query +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCCCTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT \ No newline at end of file diff --git a/test_files/twin_equal_cost_ignore_geometry.sh b/test_files/twin_equal_cost_ignore_geometry.sh new file mode 100755 index 00000000..fbcf418b --- /dev/null +++ b/test_files/twin_equal_cost_ignore_geometry.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +cargo run --release -- align -p test_files/twin_equal_cost_ignore_geometry.fa -o test_files/twin_equal_cost_ignore_geometry.equal_cost.toml -c test_files/config/small --ts-uncertainty-range-extension-mode equal-cost +cargo run --release -- align -p test_files/twin_equal_cost_ignore_geometry.fa -o test_files/twin_equal_cost_ignore_geometry.equal_cost_ignore_geometry.toml -c test_files/config/small --ts-uncertainty-range-extension-mode equal-cost-ignore-geometry +cargo run -- show -i test_files/twin_equal_cost_ignore_geometry.equal_cost.toml -pas test_files/twin_equal_cost_ignore_geometry.equal_cost.inner-only.svg --uncertainty-range-mode inner-only +cargo run -- show -i test_files/twin_equal_cost_ignore_geometry.equal_cost_ignore_geometry.toml -pas test_files/twin_equal_cost_ignore_geometry.equal_cost_ignore_geometry.inner-only.svg --uncertainty-range-mode inner-only +cargo run -- show -i test_files/twin_equal_cost_ignore_geometry.equal_cost.toml -pas test_files/twin_equal_cost_ignore_geometry.equal_cost.full.svg --uncertainty-range-mode full +cargo run -- show -i test_files/twin_equal_cost_ignore_geometry.equal_cost_ignore_geometry.toml -pas test_files/twin_equal_cost_ignore_geometry.equal_cost_ignore_geometry.full.svg --uncertainty-range-mode full \ No newline at end of file diff --git a/tsalign-tests/Cargo.toml b/tsalign-tests/Cargo.toml index bdc0632e..f5abad4d 100644 --- a/tsalign-tests/Cargo.toml +++ b/tsalign-tests/Cargo.toml @@ -2,7 +2,7 @@ name = "tsalign-tests" license.workspace = true authors = ["Sebastian Schmidt ", "Jasper Krauter "] -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true repository.workspace = true diff --git a/tsalign/Cargo.toml b/tsalign/Cargo.toml index df78cf6d..993f13d5 100644 --- a/tsalign/Cargo.toml +++ b/tsalign/Cargo.toml @@ -3,7 +3,7 @@ name = "tsalign" description = "A sequence-to-sequence aligner that accounts for template switches" license.workspace = true authors = ["Sebastian Schmidt "] -version = "5.0.1" +version = "6.0.1" edition.workspace = true rust-version.workspace = true repository.workspace = true @@ -22,12 +22,12 @@ strategy_total_length_maximise = [] default = ["strategy_total_length_maximise", "alphabet_dna", "alphabet_dna_n"] [dependencies] -generic_a_star = { version = "5.0.1", path = "../generic_a_star" } -lib_tsalign = { version = "5.0.1", path = "../lib_tsalign", features = [ +generic_a_star = { version = "6.0.1", path = "../generic_a_star" } +lib_tsalign = { version = "6.0.1", path = "../lib_tsalign", features = [ "serde", ] } -lib_ts_chainalign = { version = "5.0.1", path = "../lib_ts_chainalign" } -lib_tsshow = { version = "5.0.1", path = "../lib_tsshow" } +lib_ts_chainalign = { version = "6.0.1", path = "../lib_ts_chainalign" } +lib_tsshow = { version = "6.0.1", path = "../lib_tsshow" } clap.workspace = true compact-genome = { workspace = true, features = ["io"] } traitsequence.workspace = true diff --git a/tsalign/src/align.rs b/tsalign/src/align.rs index 6c7a3271..74eb7b29 100644 --- a/tsalign/src/align.rs +++ b/tsalign/src/align.rs @@ -57,7 +57,10 @@ use compact_genome::implementation::alphabets::rna_iupac_nucleic_acid_alphabet:: use crate::align::{ a_star_chain_ts::align_a_star_chain_ts, fasta_parser::{parse_pair_fasta_file, parse_single_fasta_file}, - template_switch_distance_type_selectors::TemplateSwitchDescendantStrategySelector, + template_switch_distance_type_selectors::{ + TemplateSwitchDescendantStrategySelector, + TemplateSwitchUncertaintyRangeExtensionModeSelector, + }, }; mod a_star_chain_ts; @@ -147,6 +150,10 @@ pub struct Cli { )] allow_ts_14_out_of_range: Ts14OutOfRangeStrategy, + /// Determines how the TSM uncertainty ranges are extended. + #[clap(long, default_value = "equal-cost-ignore-geometry")] + ts_uncertainty_range_extension_mode: TemplateSwitchUncertaintyRangeExtensionModeSelector, + /// The maximum amount of successors to generate while processing a node during chaining. /// Can be tuned to optimise performance. /// diff --git a/tsalign/src/align/template_switch_distance_type_selectors.rs b/tsalign/src/align/template_switch_distance_type_selectors.rs index 92f3170a..b31ff71e 100644 --- a/tsalign/src/align/template_switch_distance_type_selectors.rs +++ b/tsalign/src/align/template_switch_distance_type_selectors.rs @@ -6,6 +6,7 @@ use lib_tsalign::{ a_star_aligner::{ alignment_geometry::AlignmentRange, template_switch_distance::{ + TSMUncertaintyRangeExtensionMode, context::DynamicStrategies, strategies::{ AlignmentStrategySelection, @@ -85,6 +86,32 @@ pub enum TemplateSwitchDescendantStrategySelector { AllowOnlyAllEqual, } +#[derive(Clone, ValueEnum)] +pub enum TemplateSwitchUncertaintyRangeExtensionModeSelector { + /// Keep the TSM uncertainty range empty. + None, + + /// Extend the TSM uncertainty range as far as possible without increasing cost. + EqualCost, + + /// Extend the TSM uncertainty range as far as possible without increasing cost, but ignore cost increases caused by different TSM geometry. + EqualCostIgnoreGeometry, +} + +impl From + for TSMUncertaintyRangeExtensionMode +{ + fn from(mode: TemplateSwitchUncertaintyRangeExtensionModeSelector) -> Self { + match mode { + TemplateSwitchUncertaintyRangeExtensionModeSelector::None => Self::None, + TemplateSwitchUncertaintyRangeExtensionModeSelector::EqualCost => Self::EqualCost, + TemplateSwitchUncertaintyRangeExtensionModeSelector::EqualCostIgnoreGeometry => { + Self::EqualCostIgnoreGeometry + } + } + } +} + pub fn align_a_star_template_switch_distance< AlphabetType: Alphabet + Debug + Clone + Eq, SubsequenceType: GenomeSequence + ?Sized, @@ -543,6 +570,7 @@ fn align_a_star_template_switch_distance_call< cli.memory_limit, cli.force_label_correcting, !cli.dont_extend_beyond_range, + cli.ts_uncertainty_range_extension_mode.into(), template_switch_count_memory, ); info!("Finished aligning"); diff --git a/tsalign/src/show.rs b/tsalign/src/show.rs index d6176946..8e8c2cc4 100644 --- a/tsalign/src/show.rs +++ b/tsalign/src/show.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Result, bail}; use clap::Parser; use lib_tsshow::{ plain_text::show_template_switches, - svg::{EqualCostRangeMode, SvgConfig, create_error_svg, create_ts_svg}, + svg::{SvgConfig, UncertaintyRangeMode, create_error_svg, create_ts_svg}, svg_to_png, }; use log::{LevelFilter, error, info, warn}; @@ -63,9 +63,9 @@ pub struct Cli { #[clap(long, short = 'z')] restrict_context_to: Option, - /// Add a visualisation of the equal-cost ranges to the SVG output. + /// Add a visualisation of the TSM uncertainty ranges to the SVG output. #[clap(long, short = 'e', default_value = "none", default_missing_value = "full", num_args = 0..=1)] - equal_cost_range_mode: EqualCostRangeMode, + uncertainty_range_mode: UncertaintyRangeMode, } pub fn cli(cli: Cli) -> Result<()> { @@ -115,7 +115,7 @@ pub fn cli(cli: Cli) -> Result<()> { render_arrows: cli.svg_arrows, render_more_complement: cli.more_svg_complement, restrict_context: cli.restrict_context_to, - equal_cost_range_mode: cli.equal_cost_range_mode, + uncertainty_range_mode: cli.uncertainty_range_mode, }, ) { if cli.render_always {