ciri_toolkit/index_compare.rs
1//! Splice-signal lookup used by Scan1.
2//!
3//! The functions in this module search short flanking windows for canonical and
4//! semi-canonical splice motifs and return Java-compatible payload strings.
5
6use std::collections::BTreeMap;
7
8/// Splice-signal comparator for BSJ flanking sequences.
9///
10/// A `BTreeMap` is used deliberately so shift offsets are iterated in ascending
11/// order, matching the ordering assumptions in later validator logic.
12pub struct IndexCompare;
13
14impl IndexCompare {
15 /// Compares flanking sequences for nuclear-chromosome splice motifs.
16 ///
17 /// - `end_string1`: Sequence flanking the predicted upstream site (acceptor side).
18 /// - `end_string2`: Sequence flanking the predicted downstream site (donor side).
19 ///
20 /// Returns a map of shift offsets to a formatted string containing:
21 /// `offset, strand, donor_signal, acceptor_signal`.
22 pub fn index_compare(end_string1: &str, end_string2: &str) -> BTreeMap<i32, String> {
23 let mut index_strand_map = BTreeMap::new();
24 let up_s1 = end_string1.to_uppercase();
25 let up_s2 = end_string2.to_uppercase();
26
27 // Canonical splicing motifs:
28 // For '+' strand: Donor is GT, Acceptor is AG.
29 // For '-' strand: Donor (complement) is CT, Acceptor (complement) is AC.
30 // bibases[0] contains upstream motifs, bibases[1] contains downstream motifs.
31 let bibases = [
32 ["AC", "AG"], // motifs for '-' and '+' strands respectively
33 ["CT", "GT"],
34 ];
35 let strand_index = ["-", "+"];
36
37 for i in 0..=1 {
38 let mut pre_index: i32 = -1;
39 loop {
40 let target = bibases[0][i];
41 let search_start = (pre_index + 1) as usize;
42 if search_start >= up_s1.len() {
43 break;
44 }
45
46 // Search for the first motif in the upstream flanking sequence.
47 if let Some(index) = up_s1[search_start..].find(target) {
48 let actual_index = (search_start + index) as i32;
49 // Verify if the matching second motif exists in the downstream sequence at the same relative position.
50 if up_s2.len() >= (actual_index + 2) as usize {
51 let s2_part = &up_s2[actual_index as usize..(actual_index + 2) as usize];
52 if s2_part.eq_ignore_ascii_case(bibases[1][i]) {
53 // Motif pair found! Store it with the relative shift (offset).
54 index_strand_map.insert(
55 actual_index,
56 format!(
57 "{}\t{}\t{}\t{}",
58 actual_index, strand_index[i], target, bibases[1][i]
59 ),
60 );
61 }
62 } else {
63 break;
64 }
65 pre_index = actual_index;
66 } else {
67 break;
68 }
69 }
70 }
71 index_strand_map
72 }
73
74 /// Compares flanking sequences for mitochondrial or otherwise relaxed motif sets.
75 ///
76 /// Mitochondrial handling intentionally accepts a wider motif palette to match
77 /// the original CIRI3 logic.
78 pub fn index_compare_chrm(end_string1: &str, end_string2: &str) -> BTreeMap<i32, String> {
79 let mut index_strand_map = BTreeMap::new();
80 let up_s1 = end_string1.to_uppercase();
81 let up_s2 = end_string2.to_uppercase();
82
83 // Expanded set of splicing motifs for mitochondrial DNA or specialized cases.
84 // Includes non-canonical pairs like GC/AG, AT/AC, etc.
85 let bibases_mut = [
86 ["AC", "AG", "GC", "AG", "AT", "AC", "AT", "AG"],
87 ["CT", "GT", "CT", "GC", "GT", "AT", "CT", "AT"],
88 ];
89 let strand_index = ["-", "+"];
90
91 for i in 0..=7 {
92 let mut pre_index: i32 = -1;
93 let strand = i % 2; // Alternates between '-' (even i) and '+' (odd i) strands in this array.
94 loop {
95 let target = bibases_mut[0][i];
96 let search_start = (pre_index + 1) as usize;
97 if search_start >= up_s1.len() {
98 break;
99 }
100
101 if let Some(index) = up_s1[search_start..].find(target) {
102 let actual_index = (search_start + index) as i32;
103 if up_s2.len() >= (actual_index + 2) as usize {
104 let s2_part = &up_s2[actual_index as usize..(actual_index + 2) as usize];
105 if s2_part.eq_ignore_ascii_case(bibases_mut[1][i]) {
106 index_strand_map.insert(
107 actual_index,
108 format!(
109 "{}\t{}\t{}\t{}",
110 actual_index, strand_index[strand], target, bibases_mut[1][i]
111 ),
112 );
113 }
114 } else {
115 break;
116 }
117 pre_index = actual_index;
118 } else {
119 break;
120 }
121 }
122 }
123 index_strand_map
124 }
125}