1use crate::index_compare::IndexCompare;
7use crate::runtime::{
8 emit_perf_line, emit_trace_line, scan1_profile_enabled, scan2_profile_enabled,
9 trace_hg2_enabled,
10};
11use std::collections::HashMap;
12use std::fmt::Write as FmtWrite;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::Instant;
15
16#[derive(Default)]
21struct HgProfile {
22 hg1_total_ns: AtomicU64,
23 hg1_calls: AtomicU64,
24 index_compare_ns: AtomicU64,
25 index_compare_calls: AtomicU64,
26 exon_lookup_ns: AtomicU64,
27 exon_lookup_calls: AtomicU64,
28 shift_prepare_ns: AtomicU64,
29 shift_prepare_calls: AtomicU64,
30 shift_entries: AtomicU64,
31 linear_check_ns: AtomicU64,
32 linear_check_calls: AtomicU64,
33 circ2_ns: AtomicU64,
34 circ2_calls: AtomicU64,
35 circ3_ns: AtomicU64,
36 circ3_calls: AtomicU64,
37 hg1_hits: AtomicU64,
38 sw_ns: AtomicU64,
39 sw_calls: AtomicU64,
40}
41
42static HG_PROFILE: HgProfile = HgProfile {
43 hg1_total_ns: AtomicU64::new(0),
44 hg1_calls: AtomicU64::new(0),
45 index_compare_ns: AtomicU64::new(0),
46 index_compare_calls: AtomicU64::new(0),
47 exon_lookup_ns: AtomicU64::new(0),
48 exon_lookup_calls: AtomicU64::new(0),
49 shift_prepare_ns: AtomicU64::new(0),
50 shift_prepare_calls: AtomicU64::new(0),
51 shift_entries: AtomicU64::new(0),
52 linear_check_ns: AtomicU64::new(0),
53 linear_check_calls: AtomicU64::new(0),
54 circ2_ns: AtomicU64::new(0),
55 circ2_calls: AtomicU64::new(0),
56 circ3_ns: AtomicU64::new(0),
57 circ3_calls: AtomicU64::new(0),
58 hg1_hits: AtomicU64::new(0),
59 sw_ns: AtomicU64::new(0),
60 sw_calls: AtomicU64::new(0),
61};
62
63#[derive(Default)]
65struct Hg2Profile {
66 hg2_total_ns: AtomicU64,
67 hg2_calls: AtomicU64,
68 sw_ns: AtomicU64,
69 sw_calls: AtomicU64,
70 linear11_ns: AtomicU64,
71 linear11_calls: AtomicU64,
72 linear12_ns: AtomicU64,
73 linear12_calls: AtomicU64,
74 circ2_ns: AtomicU64,
75 circ2_calls: AtomicU64,
76 circ3_ns: AtomicU64,
77 circ3_calls: AtomicU64,
78}
79
80static HG2_PROFILE: Hg2Profile = Hg2Profile {
81 hg2_total_ns: AtomicU64::new(0),
82 hg2_calls: AtomicU64::new(0),
83 sw_ns: AtomicU64::new(0),
84 sw_calls: AtomicU64::new(0),
85 linear11_ns: AtomicU64::new(0),
86 linear11_calls: AtomicU64::new(0),
87 linear12_ns: AtomicU64::new(0),
88 linear12_calls: AtomicU64::new(0),
89 circ2_ns: AtomicU64::new(0),
90 circ2_calls: AtomicU64::new(0),
91 circ3_ns: AtomicU64::new(0),
92 circ3_calls: AtomicU64::new(0),
93};
94
95fn trace_scan2_hg2_enabled() -> bool {
102 trace_hg2_enabled()
103}
104
105fn trace_scan2_hg2(stage: &str, circ_line_arr: &[String], extra: &str) {
112 if !trace_scan2_hg2_enabled() {
113 return;
114 }
115 emit_trace_line(&format!(
116 "[TRACE_SCAN2_HG2] stage={} type={} strand={} chr={} site1={} site2={} mapq={} s2_ok={} str_len={} pair_len={} str3_len={} {}",
117 stage,
118 circ_line_arr[2],
119 circ_line_arr[0],
120 circ_line_arr[1],
121 circ_line_arr[3],
122 circ_line_arr[4],
123 circ_line_arr[12],
124 circ_line_arr[8],
125 circ_line_arr[5].len(),
126 circ_line_arr[6].len(),
127 circ_line_arr[7].len(),
128 extra,
129 ));
130}
131
132#[inline]
134fn add_ns(counter: &AtomicU64, started: Instant) {
135 counter.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed);
136}
137
138#[inline]
140fn finish_scan2_hg2(result: String, started: Option<Instant>) -> String {
141 if let Some(started) = started {
142 add_ns(&HG2_PROFILE.hg2_total_ns, started);
143 }
144 result
145}
146
147pub fn report_scan1_hg_profile() {
152 if !scan1_profile_enabled() {
153 return;
154 }
155 let hg1_total_ns = HG_PROFILE.hg1_total_ns.load(Ordering::Relaxed);
156 let hg1_calls = HG_PROFILE.hg1_calls.load(Ordering::Relaxed);
157 let index_compare_ns = HG_PROFILE.index_compare_ns.load(Ordering::Relaxed);
158 let index_compare_calls = HG_PROFILE.index_compare_calls.load(Ordering::Relaxed);
159 let exon_lookup_ns = HG_PROFILE.exon_lookup_ns.load(Ordering::Relaxed);
160 let exon_lookup_calls = HG_PROFILE.exon_lookup_calls.load(Ordering::Relaxed);
161 let shift_prepare_ns = HG_PROFILE.shift_prepare_ns.load(Ordering::Relaxed);
162 let shift_prepare_calls = HG_PROFILE.shift_prepare_calls.load(Ordering::Relaxed);
163 let shift_entries = HG_PROFILE.shift_entries.load(Ordering::Relaxed);
164 let linear_check_ns = HG_PROFILE.linear_check_ns.load(Ordering::Relaxed);
165 let linear_check_calls = HG_PROFILE.linear_check_calls.load(Ordering::Relaxed);
166 let circ2_ns = HG_PROFILE.circ2_ns.load(Ordering::Relaxed);
167 let circ2_calls = HG_PROFILE.circ2_calls.load(Ordering::Relaxed);
168 let circ3_ns = HG_PROFILE.circ3_ns.load(Ordering::Relaxed);
169 let circ3_calls = HG_PROFILE.circ3_calls.load(Ordering::Relaxed);
170 let hg1_hits = HG_PROFILE.hg1_hits.load(Ordering::Relaxed);
171 let sw_ns = HG_PROFILE.sw_ns.load(Ordering::Relaxed);
172 let sw_calls = HG_PROFILE.sw_calls.load(Ordering::Relaxed);
173 let known_ns = index_compare_ns
174 .saturating_add(exon_lookup_ns)
175 .saturating_add(shift_prepare_ns)
176 .saturating_add(linear_check_ns)
177 .saturating_add(circ2_ns)
178 .saturating_add(circ3_ns);
179 let other_ns = hg1_total_ns.saturating_sub(known_ns);
180 let pct = |part: u64, whole: u64| -> f64 {
181 if whole == 0 {
182 0.0
183 } else {
184 part as f64 * 100.0 / whole as f64
185 }
186 };
187 emit_perf_line(&format!(
188 "[PROFILE_SCAN1_HG1] calls={} hits={} shift_entries={} sw_calls={}",
189 hg1_calls, hg1_hits, shift_entries, sw_calls
190 ));
191 emit_perf_line(&format!(
192 "[PROFILE_SCAN1_HG1] total_ms={:.3} index_compare_ms={:.3} ({:.1}%, calls={}) exon_lookup_ms={:.3} ({:.1}%, calls={}) shift_prepare_ms={:.3} ({:.1}%, calls={}) linear_check_ms={:.3} ({:.1}%, calls={}) circ2_ms={:.3} ({:.1}%, calls={}) circ3_ms={:.3} ({:.1}%, calls={}) other_ms={:.3} ({:.1}%) sw_ms={:.3}",
193 hg1_total_ns as f64 / 1_000_000.0,
194 index_compare_ns as f64 / 1_000_000.0,
195 pct(index_compare_ns, hg1_total_ns),
196 index_compare_calls,
197 exon_lookup_ns as f64 / 1_000_000.0,
198 pct(exon_lookup_ns, hg1_total_ns),
199 exon_lookup_calls,
200 shift_prepare_ns as f64 / 1_000_000.0,
201 pct(shift_prepare_ns, hg1_total_ns),
202 shift_prepare_calls,
203 linear_check_ns as f64 / 1_000_000.0,
204 pct(linear_check_ns, hg1_total_ns),
205 linear_check_calls,
206 circ2_ns as f64 / 1_000_000.0,
207 pct(circ2_ns, hg1_total_ns),
208 circ2_calls,
209 circ3_ns as f64 / 1_000_000.0,
210 pct(circ3_ns, hg1_total_ns),
211 circ3_calls,
212 other_ns as f64 / 1_000_000.0,
213 pct(other_ns, hg1_total_ns),
214 sw_ns as f64 / 1_000_000.0,
215 ));
216}
217
218pub fn report_scan2_hg_profile() {
220 if !scan2_profile_enabled() {
221 return;
222 }
223 let hg2_total_ns = HG2_PROFILE.hg2_total_ns.load(Ordering::Relaxed);
224 let hg2_calls = HG2_PROFILE.hg2_calls.load(Ordering::Relaxed);
225 let sw_ns = HG2_PROFILE.sw_ns.load(Ordering::Relaxed);
226 let sw_calls = HG2_PROFILE.sw_calls.load(Ordering::Relaxed);
227 let linear11_ns = HG2_PROFILE.linear11_ns.load(Ordering::Relaxed);
228 let linear11_calls = HG2_PROFILE.linear11_calls.load(Ordering::Relaxed);
229 let linear12_ns = HG2_PROFILE.linear12_ns.load(Ordering::Relaxed);
230 let linear12_calls = HG2_PROFILE.linear12_calls.load(Ordering::Relaxed);
231 let circ2_ns = HG2_PROFILE.circ2_ns.load(Ordering::Relaxed);
232 let circ2_calls = HG2_PROFILE.circ2_calls.load(Ordering::Relaxed);
233 let circ3_ns = HG2_PROFILE.circ3_ns.load(Ordering::Relaxed);
234 let circ3_calls = HG2_PROFILE.circ3_calls.load(Ordering::Relaxed);
235 let known_ns = sw_ns
236 .saturating_add(linear11_ns)
237 .saturating_add(linear12_ns)
238 .saturating_add(circ2_ns)
239 .saturating_add(circ3_ns);
240 let other_ns = hg2_total_ns.saturating_sub(known_ns);
241 let pct = |part: u64, whole: u64| -> f64 {
242 if whole == 0 {
243 0.0
244 } else {
245 part as f64 * 100.0 / whole as f64
246 }
247 };
248 emit_perf_line(&format!(
249 "[PROFILE_SCAN2_HG2] calls={} sw_calls={} linear11_calls={} linear12_calls={} circ2_calls={} circ3_calls={}",
250 hg2_calls, sw_calls, linear11_calls, linear12_calls, circ2_calls, circ3_calls
251 ));
252 emit_perf_line(&format!(
253 "[PROFILE_SCAN2_HG2] total_ms={:.3} sw_ms={:.3} ({:.1}%) linear11_ms={:.3} ({:.1}%) linear12_ms={:.3} ({:.1}%) circ2_ms={:.3} ({:.1}%) circ3_ms={:.3} ({:.1}%) other_ms={:.3} ({:.1}%)",
254 hg2_total_ns as f64 / 1_000_000.0,
255 sw_ns as f64 / 1_000_000.0,
256 pct(sw_ns, hg2_total_ns),
257 linear11_ns as f64 / 1_000_000.0,
258 pct(linear11_ns, hg2_total_ns),
259 linear12_ns as f64 / 1_000_000.0,
260 pct(linear12_ns, hg2_total_ns),
261 circ2_ns as f64 / 1_000_000.0,
262 pct(circ2_ns, hg2_total_ns),
263 circ3_ns as f64 / 1_000_000.0,
264 pct(circ3_ns, hg2_total_ns),
265 other_ns as f64 / 1_000_000.0,
266 pct(other_ns, hg2_total_ns),
267 ));
268}
269
270pub struct SmithWaterman {
276 pub match_score: i32,
277 pub mismatch_penalty: i32,
278 pub gap_penalty: i32,
279 pub score: i32,
280 pub aligned_len: i32,
282 seq1: String,
283 seq2: String,
284}
285
286impl SmithWaterman {
287 pub fn new(m: i32, mis: i32, gap: i32) -> Self {
289 Self {
290 match_score: m,
291 mismatch_penalty: mis,
292 gap_penalty: gap,
293 score: 0,
294 aligned_len: 0,
295 seq1: String::new(),
296 seq2: String::new(),
297 }
298 }
299
300 pub fn set_seq(&mut self, s1: &str, s2: &str) {
305 self.seq1 = s1.to_uppercase();
306 self.seq2 = s2.to_uppercase();
307 }
308
309 pub fn align(&mut self) {
316 let sw_started = scan1_profile_enabled().then(Instant::now);
317 let cols = self.seq1.len();
319 let rows = self.seq2.len();
320 if cols == 0 || rows == 0 {
321 self.score = 0;
322 self.aligned_len = 0;
323 if let Some(sw_started) = sw_started {
324 HG_PROFILE.sw_calls.fetch_add(1, Ordering::Relaxed);
325 add_ns(&HG_PROFILE.sw_ns, sw_started);
326 }
327 return;
328 }
329
330 let s1 = self.seq1.as_bytes();
331 let s2 = self.seq2.as_bytes();
332 let mut score_table = vec![vec![0i32; cols + 1]; rows + 1];
333 let mut prev: Vec<Vec<Option<(usize, usize)>>> = vec![vec![None; cols + 1]; rows + 1];
334 let mut high_row = 0usize;
335 let mut high_col = 0usize;
336
337 for row in 1..=rows {
338 for col in 1..=cols {
339 let row_space_score = score_table[row - 1][col] + self.gap_penalty;
340 let col_space_score = score_table[row][col - 1] + self.gap_penalty;
341 let mut match_or_mismatch_score = score_table[row - 1][col - 1];
342 if s2[row - 1] == s1[col - 1] {
343 match_or_mismatch_score += self.match_score;
344 } else {
345 match_or_mismatch_score += self.mismatch_penalty;
346 }
347
348 let mut cell_score = 0i32;
349 let mut cell_prev: Option<(usize, usize)> = None;
350 if row_space_score >= col_space_score {
351 if match_or_mismatch_score >= row_space_score {
352 if match_or_mismatch_score > 0 {
353 cell_score = match_or_mismatch_score;
354 cell_prev = Some((row - 1, col - 1));
355 }
356 } else if row_space_score > 0 {
357 cell_score = row_space_score;
358 cell_prev = Some((row - 1, col));
359 }
360 } else if match_or_mismatch_score >= col_space_score {
361 if match_or_mismatch_score > 0 {
362 cell_score = match_or_mismatch_score;
363 cell_prev = Some((row - 1, col - 1));
364 }
365 } else if col_space_score > 0 {
366 cell_score = col_space_score;
367 cell_prev = Some((row, col - 1));
368 }
369
370 score_table[row][col] = cell_score;
371 prev[row][col] = cell_prev;
372 if cell_score > score_table[high_row][high_col] {
373 high_row = row;
374 high_col = col;
375 }
376 }
377 }
378
379 let mut row = high_row;
381 let mut col = high_col;
382 let mut align1: Vec<u8> = Vec::new();
383 let mut align2: Vec<u8> = Vec::new();
384 while score_table[row][col] != 0 {
385 let (pr, pc) = match prev[row][col] {
386 Some(p) => p,
387 None => break,
388 };
389 if row - pr == 1 {
390 align2.push(s2[row - 1]);
391 } else {
392 align2.push(b'-');
393 }
394 if col - pc == 1 {
395 align1.push(s1[col - 1]);
396 } else {
397 align1.push(b'-');
398 }
399 row = pr;
400 col = pc;
401 }
402 self.aligned_len = align2.len() as i32;
403
404 let mut total = 0i32;
406 for i in 0..align1.len() {
407 let c1 = align1[i];
408 let c2 = align2[i];
409 if c1 == b'-' || c2 == b'-' {
410 total += self.gap_penalty;
411 } else if c1 == c2 {
412 total += self.match_score;
413 } else {
414 total += self.mismatch_penalty;
415 }
416 }
417 self.score = total;
418 if let Some(sw_started) = sw_started {
419 HG_PROFILE.sw_calls.fetch_add(1, Ordering::Relaxed);
420 add_ns(&HG_PROFILE.sw_ns, sw_started);
421 }
422 }
423}
424
425pub struct IsBSJHg2 {
431 pub linear_range_size_min: i32,
432 pub min_mapq_uni: i32,
433 pub initial_size1: i32,
434 pub aligner: SmithWaterman,
435 window_unit: [i32; 5],
436 query_code_buf: Vec<u64>,
437 circ_pos_buf: Vec<i32>,
438 pem_pos_buf: Vec<i32>,
439}
440
441pub fn java_substring(s: &str, start: i32, end: i32) -> &str {
446 let len = s.len() as i32;
447 assert!(
448 start >= 0,
449 "java_substring start<0: start={}, end={}, len={}",
450 start,
451 end,
452 len
453 );
454 assert!(
455 end >= 0,
456 "java_substring end<0: start={}, end={}, len={}",
457 start,
458 end,
459 len
460 );
461 assert!(
462 start <= end,
463 "java_substring start>end: start={}, end={}, len={}",
464 start,
465 end,
466 len
467 );
468 assert!(
469 end <= len,
470 "java_substring end>len: start={}, end={}, len={}",
471 start,
472 end,
473 len
474 );
475 &s[start as usize..end as usize]
476}
477
478impl IsBSJHg2 {
479 pub fn new(linear_range_size_min: i32, min_mapq_uni: i32) -> Self {
481 Self {
482 linear_range_size_min,
483 min_mapq_uni,
484 initial_size1: 7,
485 aligner: SmithWaterman::new(1, -1, -1),
486 window_unit: [9, 7, 5, 4, 3],
487 query_code_buf: Vec::with_capacity(64),
488 circ_pos_buf: Vec::with_capacity(64),
489 pem_pos_buf: Vec::with_capacity(64),
490 }
491 }
492
493 fn distance_loci_stats(
495 &self,
496 locus_count: usize,
497 locus_sum: i32,
498 locus2_count: usize,
499 locus2_sum: i32,
500 window_step: i32,
501 ) -> i32 {
502 if locus_count < 2 && locus2_count < 2 {
503 return 0;
504 }
505 if locus_sum <= window_step * locus_count as i32 && locus_sum * 20 < locus2_sum {
506 1
507 } else {
508 0
509 }
510 }
511
512 #[inline]
518 fn linear_range_left<'a>(&self, chr_taga: &'a str, site1_new: i32, site2_new: i32) -> &'a str {
519 if site2_new - site1_new + 5 >= self.linear_range_size_min {
520 if 2 * site1_new >= site2_new + 6 {
521 java_substring(chr_taga, 2 * site1_new - site2_new - 6, site1_new - 1)
522 } else {
523 java_substring(chr_taga, 0, site1_new - 1)
524 }
525 } else if site1_new >= self.linear_range_size_min + 1 {
526 java_substring(
527 chr_taga,
528 site1_new - self.linear_range_size_min - 1,
529 site1_new - 1,
530 )
531 } else {
532 java_substring(chr_taga, 0, site1_new - 1)
533 }
534 }
535
536 #[inline]
538 fn linear_range_right<'a>(
539 &self,
540 chr_taga: &'a str,
541 site1_new: i32,
542 site2_new: i32,
543 chr_taga_len: i32,
544 ) -> &'a str {
545 if site2_new - site1_new + 5 >= self.linear_range_size_min {
546 if 2 * site2_new - site1_new + 5 > chr_taga_len {
547 java_substring(chr_taga, site2_new, chr_taga_len)
548 } else {
549 java_substring(chr_taga, site2_new, 2 * site2_new - site1_new + 5)
550 }
551 } else if site2_new + self.linear_range_size_min > chr_taga_len {
552 java_substring(chr_taga, site2_new, chr_taga_len)
553 } else {
554 java_substring(chr_taga, site2_new, site2_new + self.linear_range_size_min)
555 }
556 }
557
558 pub fn is_in_circ_rna_1_1(
561 &mut self,
562 len_str: i32,
563 str_val: &str,
564 circ_range_seq: &str,
565 linear_range: &str,
566 ) -> i32 {
567 for &step in &self.window_unit {
568 if len_str < step * 2 {
569 continue;
570 }
571 let window_size = step * 2;
572 let mut trial = (len_str - window_size) / step;
573 let mut window_count = trial.max(0) as usize + 1;
574 if len_str % step != 0 {
575 window_count += 1;
576 }
577 self.query_code_buf.clear();
578 self.query_code_buf
579 .reserve(window_count.saturating_sub(self.query_code_buf.capacity()));
580 for j in 0..=trial {
581 let s_idx = len_str - j * step - window_size;
582 let e_idx = len_str - j * step;
583 self.query_code_buf.push(Self::encode_window(
584 &str_val.as_bytes()[s_idx as usize..e_idx as usize],
585 ));
586 }
587 if len_str % step != 0 {
588 self.query_code_buf.push(Self::encode_window(
589 &str_val.as_bytes()[0..window_size as usize],
590 ));
591 }
592 let mut hash_keys = [u64::MAX; 128];
593 let mut hash_masks = [0u64; 128];
594 let use_hash_fast_path =
595 Self::build_query_hash(&self.query_code_buf, &mut hash_keys, &mut hash_masks);
596 self.circ_pos_buf.clear();
597 self.circ_pos_buf.resize(window_count, -1);
598 Self::scan_window_positions(
599 &self.query_code_buf,
600 circ_range_seq.as_bytes(),
601 &mut self.circ_pos_buf,
602 window_size as usize,
603 true,
604 use_hash_fast_path,
605 &hash_keys,
606 &hash_masks,
607 );
608 self.pem_pos_buf.clear();
609 self.pem_pos_buf.resize(window_count, -1);
610 Self::scan_window_positions(
611 &self.query_code_buf,
612 linear_range.as_bytes(),
613 &mut self.pem_pos_buf,
614 window_size as usize,
615 true,
616 use_hash_fast_path,
617 &hash_keys,
618 &hash_masks,
619 );
620 let mut locus_count = 0usize;
621 let mut locus2_count = 0usize;
622 let mut locus_sum = 0i32;
623 let mut locus2_sum = 0i32;
624 let mut prev_locus: Option<i32> = None;
625 let mut prev_locus2: Option<i32> = None;
626 let mut miss_count = [0, 0, 0];
627 let mut miss_count2_total = 0;
628 if len_str % step != 0 {
629 trial += 1;
630 }
631 for (&pos, &pos2) in self.circ_pos_buf.iter().zip(self.pem_pos_buf.iter()) {
632 if pos >= 0 {
633 if let Some(prev) = prev_locus {
634 locus_sum += (pos - prev).abs();
635 }
636 prev_locus = Some(pos);
637 locus_count += 1;
638 miss_count[0] = 0;
639 } else {
640 miss_count[1] += 1;
641 miss_count[0] += 1;
642 if miss_count[0] > miss_count[2] {
643 miss_count[2] = miss_count[0];
644 }
645 }
646 if pos2 >= 0 {
647 if let Some(prev) = prev_locus2 {
648 locus2_sum += (pos2 - prev).abs();
649 }
650 prev_locus2 = Some(pos2);
651 locus2_count += 1;
652 } else {
653 miss_count2_total += 1;
654 }
655 }
656 if miss_count2_total == 0 && miss_count[1] == 0 {
657 if self.distance_loci_stats(locus_count, locus_sum, locus2_count, locus2_sum, step)
658 == 1
659 {
660 return 1;
661 } else {
662 return 0;
663 }
664 } else if miss_count2_total <= miss_count[1] {
665 if locus2_count != 0 {
666 return 0;
667 }
668 } else if miss_count[2] > 5 || miss_count[1] * 2 > trial {
669 continue;
670 } else {
671 return 1;
672 }
673 }
674 0
675 }
676
677 pub fn is_in_circ_rna_1_2(
680 &mut self,
681 len_str: i32,
682 str_val: &str,
683 circ_range_seq: &str,
684 linear_range: &str,
685 ) -> i32 {
686 for &step in &self.window_unit {
687 if len_str < step * 2 {
688 continue;
689 }
690 let window_size = step * 2;
691 let mut trial = (len_str - window_size) / step;
692 let mut window_count = trial.max(0) as usize + 1;
693 if len_str % step != 0 {
694 window_count += 1;
695 }
696 self.query_code_buf.clear();
697 self.query_code_buf
698 .reserve(window_count.saturating_sub(self.query_code_buf.capacity()));
699 for j in 0..=trial {
700 let s_idx = j * step;
701 let e_idx = j * step + window_size;
702 self.query_code_buf.push(Self::encode_window(
703 &str_val.as_bytes()[s_idx as usize..e_idx as usize],
704 ));
705 }
706 if len_str % step != 0 {
707 self.query_code_buf.push(Self::encode_window(
708 &str_val.as_bytes()[(len_str - window_size) as usize..len_str as usize],
709 ));
710 }
711 let mut hash_keys = [u64::MAX; 128];
712 let mut hash_masks = [0u64; 128];
713 let use_hash_fast_path =
714 Self::build_query_hash(&self.query_code_buf, &mut hash_keys, &mut hash_masks);
715 self.circ_pos_buf.clear();
716 self.circ_pos_buf.resize(window_count, -1);
717 Self::scan_window_positions(
718 &self.query_code_buf,
719 circ_range_seq.as_bytes(),
720 &mut self.circ_pos_buf,
721 window_size as usize,
722 false,
723 use_hash_fast_path,
724 &hash_keys,
725 &hash_masks,
726 );
727 self.pem_pos_buf.clear();
728 self.pem_pos_buf.resize(window_count, -1);
729 Self::scan_window_positions(
730 &self.query_code_buf,
731 linear_range.as_bytes(),
732 &mut self.pem_pos_buf,
733 window_size as usize,
734 false,
735 use_hash_fast_path,
736 &hash_keys,
737 &hash_masks,
738 );
739 let mut locus_count = 0usize;
740 let mut locus2_count = 0usize;
741 let mut locus_sum = 0i32;
742 let mut locus2_sum = 0i32;
743 let mut prev_locus: Option<i32> = None;
744 let mut prev_locus2: Option<i32> = None;
745 let mut miss_count = [0, 0, 0];
746 let mut miss_count2_total = 0;
747 if len_str % step != 0 {
748 trial += 1;
749 }
750 for (&pos, &pos2) in self.circ_pos_buf.iter().zip(self.pem_pos_buf.iter()) {
751 if pos >= 0 {
752 if let Some(prev) = prev_locus {
753 locus_sum += (pos - prev).abs();
754 }
755 prev_locus = Some(pos);
756 locus_count += 1;
757 miss_count[0] = 0;
758 } else {
759 miss_count[1] += 1;
760 miss_count[0] += 1;
761 if miss_count[0] > miss_count[2] {
762 miss_count[2] = miss_count[0];
763 }
764 }
765 if pos2 >= 0 {
766 if let Some(prev) = prev_locus2 {
767 locus2_sum += (pos2 - prev).abs();
768 }
769 prev_locus2 = Some(pos2);
770 locus2_count += 1;
771 } else {
772 miss_count2_total += 1;
773 }
774 }
775 if miss_count2_total == 0 && miss_count[1] == 0 {
776 if self.distance_loci_stats(locus_count, locus_sum, locus2_count, locus2_sum, step)
777 == 1
778 {
779 return 1;
780 } else {
781 return 0;
782 }
783 } else if miss_count2_total <= miss_count[1] {
784 if locus2_count != 0 {
785 return 0;
786 }
787 } else if miss_count[2] > 5 || miss_count[1] * 2 > trial {
788 continue;
789 } else {
790 return 1;
791 }
792 }
793 0
794 }
795
796 pub fn is_in_circ_rna_2(&self, unmap_seq: &str, circ_range_seq: &str) -> i32 {
799 let seq_len = unmap_seq.len() as i32;
800 let window_step = 5;
801 let window_size = 10;
802 let mut miss_count3 = [0, 0, 0];
803 let trial = (seq_len - window_size) / window_step;
804 if trial < 0 {
805 return 1;
806 }
807 for j in 0..=trial {
808 let seq = if seq_len <= 10 {
809 unmap_seq
810 } else {
811 &unmap_seq[(j * window_step) as usize..(j * window_step + window_size) as usize]
812 };
813 if circ_range_seq.contains(seq) {
814 miss_count3[0] = 0;
815 } else {
816 miss_count3[1] += 1;
817 miss_count3[0] += 1;
818 if miss_count3[0] > miss_count3[2] {
819 miss_count3[2] = miss_count3[0];
820 }
821 }
822 }
823 if miss_count3[2] > 5 || (miss_count3[1] - 1) * 2 > trial {
824 return 0;
825 }
826 1
827 }
828
829 #[inline]
832 fn encode_window_base(base: u8) -> u64 {
833 match base {
834 b'A' | b'a' => 0,
835 b'C' | b'c' => 1,
836 b'G' | b'g' => 2,
837 b'T' | b't' => 3,
838 b'N' | b'n' => 4,
839 _ => 7,
840 }
841 }
842
843 #[inline]
845 fn encode_window(seq: &[u8]) -> u64 {
846 let mut code = 0u64;
847 for &base in seq {
848 code = (code << 3) | Self::encode_window_base(base);
849 }
850 code
851 }
852
853 fn build_query_hash(
858 query_codes: &[u64],
859 hash_keys: &mut [u64; 128],
860 hash_masks: &mut [u64; 128],
861 ) -> bool {
862 const EMPTY_CODE: u64 = u64::MAX;
863
864 if query_codes.len() > 64 {
865 return false;
866 }
867
868 hash_keys.fill(EMPTY_CODE);
869 hash_masks.fill(0);
870 for (idx, &code) in query_codes.iter().enumerate() {
871 let mut slot = (code as usize).wrapping_mul(0x9E37_79B1usize) & (128 - 1);
872 loop {
873 if hash_keys[slot] == EMPTY_CODE {
874 hash_keys[slot] = code;
875 hash_masks[slot] = 1u64 << idx;
876 break;
877 }
878 if hash_keys[slot] == code {
879 hash_masks[slot] |= 1u64 << idx;
880 break;
881 }
882 slot = (slot + 1) & (128 - 1);
883 }
884 }
885 true
886 }
887
888 fn scan_window_positions(
890 query_codes: &[u64],
891 haystack: &[u8],
892 out: &mut [i32],
893 window_size: usize,
894 keep_last: bool,
895 use_hash_fast_path: bool,
896 hash_keys: &[u64; 128],
897 hash_masks: &[u64; 128],
898 ) {
899 const EMPTY_CODE: u64 = u64::MAX;
900
901 if haystack.len() < window_size || query_codes.is_empty() {
902 return;
903 }
904
905 let mut remaining = out.len();
906 let keep_last_bases_mask = (1u64 << ((window_size as u32 - 1) * 3)) - 1;
907 let mut rolling_code = Self::encode_window(&haystack[..window_size]);
908 let last_start = haystack.len() - window_size;
909
910 for start in 0..=last_start {
911 if start != 0 {
912 rolling_code = ((rolling_code & keep_last_bases_mask) << 3)
913 | Self::encode_window_base(haystack[start + window_size - 1]);
914 }
915
916 if use_hash_fast_path {
917 let mut slot = (rolling_code as usize).wrapping_mul(0x9E37_79B1usize) & (128 - 1);
918 let mut mask = 0u64;
919 loop {
920 let key = hash_keys[slot];
921 if key == EMPTY_CODE {
922 break;
923 }
924 if key == rolling_code {
925 mask = hash_masks[slot];
926 break;
927 }
928 slot = (slot + 1) & (128 - 1);
929 }
930 while mask != 0 {
931 let idx = mask.trailing_zeros() as usize;
932 if keep_last {
933 out[idx] = start as i32;
934 } else if out[idx] == -1 {
935 out[idx] = start as i32;
936 remaining -= 1;
937 }
938 mask &= mask - 1;
939 }
940 } else {
941 for (idx, &query_code) in query_codes.iter().enumerate() {
942 if query_code == rolling_code {
943 if keep_last {
944 out[idx] = start as i32;
945 } else if out[idx] == -1 {
946 out[idx] = start as i32;
947 remaining -= 1;
948 }
949 }
950 }
951 }
952
953 if !keep_last && remaining == 0 {
954 break;
955 }
956 }
957 }
958
959 pub fn is_in_circ_rna_3(
966 &mut self,
967 ano_read: &str,
968 pre_judge: &str,
969 circ_range_seq: &str,
970 pem_null_range_seq: &str,
971 ) -> i32 {
972 const WINDOW_STEP: usize = 5;
973 const WINDOW_SIZE: usize = 10;
974 let ano_read_len = ano_read.len() as i32;
975 let trial = (ano_read_len - WINDOW_SIZE as i32) / WINDOW_STEP as i32;
976 if trial < 0 {
977 if !pem_null_range_seq.is_empty() {
978 return -2;
979 }
980 return 1;
981 }
982 let mut miss_count = [0, 0, 0];
983 let mut miss_count2_total = 0;
984 let mut locus_count = 0usize;
985 let mut locus2_count = 0usize;
986 let mut locus_sum = 0i32;
987 let mut locus2_sum = 0i32;
988 let mut prev_locus: Option<i32> = None;
989 let mut prev_locus2: Option<i32> = None;
990 let ano_read_bytes = ano_read.as_bytes();
991 let window_count = trial.max(0) as usize + 1;
992 self.query_code_buf.clear();
993 self.query_code_buf
994 .reserve(window_count.saturating_sub(self.query_code_buf.capacity()));
995 for j in 0..window_count {
996 let start = j * WINDOW_STEP;
997 let end = start + WINDOW_SIZE;
998 self.query_code_buf
999 .push(Self::encode_window(&ano_read_bytes[start..end]));
1000 }
1001
1002 let mut hash_keys = [u64::MAX; 128];
1003 let mut hash_masks = [0u64; 128];
1004 let use_hash_fast_path =
1005 Self::build_query_hash(&self.query_code_buf, &mut hash_keys, &mut hash_masks);
1006
1007 self.circ_pos_buf.clear();
1008 self.circ_pos_buf.resize(window_count, -1);
1009 Self::scan_window_positions(
1010 &self.query_code_buf,
1011 circ_range_seq.as_bytes(),
1012 &mut self.circ_pos_buf,
1013 WINDOW_SIZE,
1014 false,
1015 use_hash_fast_path,
1016 &hash_keys,
1017 &hash_masks,
1018 );
1019
1020 let has_pem_positions = !pem_null_range_seq.is_empty();
1021 if has_pem_positions {
1022 self.pem_pos_buf.clear();
1023 self.pem_pos_buf.resize(window_count, -1);
1024 Self::scan_window_positions(
1025 &self.query_code_buf,
1026 pem_null_range_seq.as_bytes(),
1027 &mut self.pem_pos_buf,
1028 WINDOW_SIZE,
1029 false,
1030 use_hash_fast_path,
1031 &hash_keys,
1032 &hash_masks,
1033 );
1034 }
1035
1036 for j in 0..window_count {
1037 let pos = self.circ_pos_buf[j];
1038 if pos >= 0 {
1039 if let Some(prev) = prev_locus {
1040 locus_sum += (pos - prev).abs();
1041 }
1042 prev_locus = Some(pos);
1043 locus_count += 1;
1044 miss_count[0] = 0;
1045 } else {
1046 miss_count[1] += 1;
1047 miss_count[0] += 1;
1048 if miss_count[0] > miss_count[2] {
1049 miss_count[2] = miss_count[0];
1050 }
1051 }
1052
1053 if has_pem_positions {
1054 let pos2 = self.pem_pos_buf[j];
1055 if pos2 >= 0 {
1056 if let Some(prev) = prev_locus2 {
1057 locus2_sum += (pos2 - prev).abs();
1058 }
1059 prev_locus2 = Some(pos2);
1060 locus2_count += 1;
1061 } else {
1062 miss_count2_total += 1;
1063 }
1064 }
1065 }
1066 if !pem_null_range_seq.is_empty() {
1067 if miss_count2_total == 0 && miss_count[1] == 0 {
1068 if self.distance_loci_stats(
1069 locus_count,
1070 locus_sum,
1071 locus2_count,
1072 locus2_sum,
1073 WINDOW_STEP as i32,
1074 ) == 1
1075 {
1076 return 1;
1077 } else {
1078 return -2;
1079 }
1080 } else if miss_count2_total <= miss_count[1] {
1081 if locus2_count != 0 {
1082 return -2;
1083 } else {
1084 return -1;
1085 }
1086 } else if miss_count[1] * 4 > trial * 3 && pre_judge == "0" {
1087 return -2;
1088 } else if miss_count[2] > 5 || miss_count[1] * 2 > trial {
1089 return -1;
1090 } else {
1091 return 1;
1092 }
1093 } else {
1094 if miss_count[1] * 4 > trial * 3 && pre_judge == "0" {
1095 return -2;
1096 } else if miss_count[2] > 5 || miss_count[1] * 2 > trial {
1097 return -1;
1098 }
1099 }
1100 1
1101 }
1102
1103 pub fn is_bsj_hg1(
1110 &mut self,
1111 circ_line_arr: &mut [String],
1112 chr_taga: &str,
1113 sum_q: i32,
1114 mitochondrion: &str,
1115 sp_label: bool,
1116 chr_exon_start_map: &HashMap<String, String>,
1117 chr_exon_end_map: &HashMap<String, String>,
1118 ) -> Option<String> {
1119 let hg1_started = scan1_profile_enabled().then(Instant::now);
1120 HG_PROFILE.hg1_calls.fetch_add(1, Ordering::Relaxed);
1121 let site1 = circ_line_arr[9].parse::<i32>().unwrap_or(0);
1122 let site2 = circ_line_arr[10].parse::<i32>().unwrap_or(0);
1123 let end_adjt1 = circ_line_arr[11].parse::<i32>().unwrap_or(0);
1124 let end_adjt2 = circ_line_arr[12].parse::<i32>().unwrap_or(0);
1125 let total_adjustment = end_adjt1 + end_adjt2;
1126 let chr_taga_len = chr_taga.len() as i32;
1127
1128 let (end_string1, end_string2, tmp_site1, tmp_site2, adjt_bp);
1129 if end_adjt2 >= 0 {
1130 tmp_site1 = site1 - end_adjt1 - 1;
1131 tmp_site2 = site2 - end_adjt1 - 1;
1132 adjt_bp = 2 + total_adjustment;
1133 if site1 - end_adjt1 - 4 >= 0 {
1134 end_string1 =
1135 java_substring(chr_taga, site1 - end_adjt1 - 4, end_adjt2 + site1).to_string();
1136 } else {
1137 let n_pad = (0 - (site1 - end_adjt1 - 4)) as usize;
1138 end_string1 = format!(
1139 "{}{}",
1140 "N".repeat(n_pad),
1141 java_substring(chr_taga, 0, end_adjt2 + site1)
1142 );
1143 }
1144 if 3 + end_adjt2 + site2 > chr_taga_len {
1145 end_string2 =
1146 java_substring(chr_taga, site2 - end_adjt1 - 1, chr_taga_len).to_string();
1147 } else {
1148 end_string2 =
1149 java_substring(chr_taga, site2 - end_adjt1 - 1, 3 + end_adjt2 + site2)
1150 .to_string();
1151 }
1152 } else {
1153 tmp_site1 = site1 + end_adjt2 - 1;
1154 tmp_site2 = site2 + end_adjt2 - 1;
1155 adjt_bp = 2 - total_adjustment;
1156 if site1 + end_adjt2 - 4 >= 0 {
1157 end_string1 =
1158 java_substring(chr_taga, site1 + end_adjt2 - 4, site1 - end_adjt1).to_string();
1159 } else {
1160 let n_pad = (0 - (site1 + end_adjt2 - 4)) as usize;
1161 end_string1 = format!(
1162 "{}{}",
1163 "N".repeat(n_pad),
1164 java_substring(chr_taga, 0, site1 - end_adjt1)
1165 );
1166 }
1167 if 3 + site2 - end_adjt1 > chr_taga_len {
1168 end_string2 =
1169 java_substring(chr_taga, site2 + end_adjt2 - 1, chr_taga_len).to_string();
1170 } else {
1171 end_string2 =
1172 java_substring(chr_taga, site2 + end_adjt2 - 1, 3 + site2 - end_adjt1)
1173 .to_string();
1174 }
1175 }
1176
1177 let index_compare_started = scan1_profile_enabled().then(Instant::now);
1178 let mut index_strand_map = if circ_line_arr[1] == mitochondrion || sp_label {
1179 IndexCompare::index_compare_chrm(&end_string1, &end_string2)
1180 } else {
1181 IndexCompare::index_compare(&end_string1, &end_string2)
1182 };
1183 if let Some(index_compare_started) = index_compare_started {
1184 HG_PROFILE
1185 .index_compare_calls
1186 .fetch_add(1, Ordering::Relaxed);
1187 add_ns(&HG_PROFILE.index_compare_ns, index_compare_started);
1188 }
1189 let chr = circ_line_arr[1].as_str();
1190 let mut start_key = String::with_capacity(chr.len() + 16);
1191 let mut end_key = String::with_capacity(chr.len() + 16);
1192 let exon_lookup_started = scan1_profile_enabled().then(Instant::now);
1193 for i in 0..=adjt_bp {
1194 start_key.clear();
1195 start_key.push_str(chr);
1196 start_key.push('\t');
1197 let _ = write!(&mut start_key, "{}", tmp_site1 + i);
1198 end_key.clear();
1199 end_key.push_str(chr);
1200 end_key.push('\t');
1201 let _ = write!(&mut end_key, "{}", tmp_site2 + i);
1202 if !index_strand_map.contains_key(&i) {
1203 if let (Some(gene_start), Some(gene_end)) = (
1204 chr_exon_start_map.get(&start_key),
1205 chr_exon_end_map.get(&end_key),
1206 ) {
1207 if gene_start == gene_end {
1208 let mut parts = gene_start.split('\t');
1209 let _ = parts.next();
1210 let strand = parts.next().unwrap_or("");
1211 index_strand_map.insert(
1212 i,
1213 format!(
1214 "{}\t{}\t{}\t{}",
1215 i,
1216 strand,
1217 java_substring(chr_taga, tmp_site1 + i - 3, tmp_site1 + i - 1),
1218 java_substring(chr_taga, tmp_site2 + i, tmp_site2 + i + 2)
1219 ),
1220 );
1221 }
1222 }
1223 }
1224 }
1225 if let Some(exon_lookup_started) = exon_lookup_started {
1226 HG_PROFILE.exon_lookup_calls.fetch_add(1, Ordering::Relaxed);
1227 add_ns(&HG_PROFILE.exon_lookup_ns, exon_lookup_started);
1228 }
1229
1230 if !index_strand_map.is_empty() {
1231 for (&shift, sig) in &index_strand_map {
1232 HG_PROFILE.shift_entries.fetch_add(1, Ordering::Relaxed);
1233 let mut shift_parts = sig.split('\t');
1234 let _shift_idx = shift_parts.next().unwrap_or("");
1235 let shift_strand = shift_parts.next().unwrap_or("");
1236 let shift_left = shift_parts.next().unwrap_or("");
1237 let shift_right = shift_parts.next().unwrap_or("");
1238 let diff_adjt = if end_adjt2 >= 0 {
1239 shift - 1 - end_adjt1
1240 } else {
1241 shift - 1 + total_adjustment - end_adjt1
1242 };
1243 let site1_new = site1 + diff_adjt;
1244 let site2_new = site2 + diff_adjt;
1245 let shift_prepare_started = scan1_profile_enabled().then(Instant::now);
1246 let mut str_new = ["".to_string(), "".to_string()];
1247 if diff_adjt >= 0 {
1248 let str_adj = java_substring(&circ_line_arr[2], 0, diff_adjt);
1249 str_new[1] = String::with_capacity(circ_line_arr[3].len() + str_adj.len());
1250 str_new[1].push_str(&circ_line_arr[3]);
1251 str_new[1].push_str(str_adj);
1252 str_new[0] =
1253 java_substring(&circ_line_arr[2], diff_adjt, circ_line_arr[2].len() as i32)
1254 .to_string();
1255 } else {
1256 let str_adj = java_substring(
1257 &circ_line_arr[3],
1258 circ_line_arr[3].len() as i32 + diff_adjt,
1259 circ_line_arr[3].len() as i32,
1260 );
1261 str_new[0] = String::with_capacity(str_adj.len() + circ_line_arr[2].len());
1262 str_new[0].push_str(str_adj);
1263 str_new[0].push_str(&circ_line_arr[2]);
1264 str_new[1] = java_substring(
1265 &circ_line_arr[3],
1266 0,
1267 circ_line_arr[3].len() as i32 + diff_adjt,
1268 )
1269 .to_string();
1270 }
1271 {
1272 let mut merged = String::with_capacity(shift_left.len() + str_new[0].len());
1273 merged.push_str(shift_left);
1274 merged.push_str(&str_new[0]);
1275 str_new[0] = merged;
1276 }
1277 str_new[1].push_str(shift_right);
1278 let initial_seq1 = java_substring(&str_new[0], 0, self.initial_size1);
1279 let initial_seq2 = java_substring(
1280 &str_new[1],
1281 str_new[1].len() as i32 - self.initial_size1,
1282 str_new[1].len() as i32,
1283 );
1284 let circ_range_seq = if site1_new - 3 < 0 && site2_new + 2 > chr_taga_len {
1285 &chr_taga[..]
1286 } else if site1_new - 3 < 0 {
1287 java_substring(chr_taga, 0, site2_new + 2)
1288 } else if site2_new + 2 > chr_taga_len {
1289 java_substring(chr_taga, site1_new - 3, chr_taga_len)
1290 } else {
1291 java_substring(chr_taga, site1_new - 3, site2_new + 2)
1292 };
1293 if let Some(shift_prepare_started) = shift_prepare_started {
1294 HG_PROFILE
1295 .shift_prepare_calls
1296 .fetch_add(1, Ordering::Relaxed);
1297 add_ns(&HG_PROFILE.shift_prepare_ns, shift_prepare_started);
1298 }
1299 if circ_range_seq.starts_with(initial_seq1)
1300 && circ_range_seq.ends_with(initial_seq2)
1301 {
1302 let right_linear_range =
1303 self.linear_range_right(chr_taga, site1_new, site2_new, chr_taga_len);
1304 let left_linear_range = self.linear_range_left(chr_taga, site1_new, site2_new);
1305 if circ_line_arr[6] != "1" {
1306 let linear_started = scan1_profile_enabled().then(Instant::now);
1307 circ_line_arr[6] = self
1308 .is_in_circ_rna_1_2(
1309 str_new[0].len() as i32,
1310 &str_new[0],
1311 circ_range_seq,
1312 right_linear_range,
1313 )
1314 .to_string();
1315 if let Some(linear_started) = linear_started {
1316 HG_PROFILE
1317 .linear_check_calls
1318 .fetch_add(1, Ordering::Relaxed);
1319 add_ns(&HG_PROFILE.linear_check_ns, linear_started);
1320 }
1321 }
1322 if circ_line_arr[7] != "1" {
1323 let linear_started = scan1_profile_enabled().then(Instant::now);
1324 circ_line_arr[7] = self
1325 .is_in_circ_rna_1_2(
1326 str_new[1].len() as i32,
1327 &str_new[1],
1328 circ_range_seq,
1329 left_linear_range,
1330 )
1331 .to_string();
1332 if let Some(linear_started) = linear_started {
1333 HG_PROFILE
1334 .linear_check_calls
1335 .fetch_add(1, Ordering::Relaxed);
1336 add_ns(&HG_PROFILE.linear_check_ns, linear_started);
1337 }
1338 }
1339 if circ_line_arr[6] == "1" && circ_line_arr[7] == "1" {
1340 if circ_line_arr[4] != "*" {
1341 let circ2_started = scan1_profile_enabled().then(Instant::now);
1342 let circ2_ok = self.is_in_circ_rna_2(&circ_line_arr[4], circ_range_seq);
1343 if let Some(circ2_started) = circ2_started {
1344 HG_PROFILE.circ2_calls.fetch_add(1, Ordering::Relaxed);
1345 add_ns(&HG_PROFILE.circ2_ns, circ2_started);
1346 }
1347 if circ2_ok == 0 {
1348 if let Some(hg1_started) = hg1_started {
1349 add_ns(&HG_PROFILE.hg1_total_ns, hg1_started);
1350 }
1351 return None;
1352 }
1353 }
1354 let mut tag = 1;
1355 if circ_line_arr[5].len() > 5 {
1356 let pem_null = if circ_line_arr[0] == "1"
1358 && site1_new - site1_new + 5 >= self.linear_range_size_min
1359 {
1360 if 2 * site1_new >= site2_new + 6 {
1361 java_substring(
1362 chr_taga,
1363 2 * site1_new - site2_new - 6,
1364 site1_new - 1,
1365 )
1366 } else {
1367 java_substring(chr_taga, 0, site1_new - 1)
1368 }
1369 } else if circ_line_arr[0] == "1" {
1370 if site1_new >= self.linear_range_size_min + 1 {
1371 java_substring(
1372 chr_taga,
1373 site1_new - self.linear_range_size_min - 1,
1374 site1_new - 1,
1375 )
1376 } else {
1377 java_substring(chr_taga, 0, site1_new - 1)
1378 }
1379 } else if circ_line_arr[0] == "0"
1380 && site2_new - site1_new + 5 > self.linear_range_size_min
1381 {
1382 if 2 * site2_new - site1_new + 5 > chr_taga_len {
1383 java_substring(chr_taga, site2_new, chr_taga_len)
1384 } else {
1385 java_substring(
1386 chr_taga,
1387 site2_new,
1388 2 * site2_new - site1_new + 5,
1389 )
1390 }
1391 } else {
1392 if site2_new + self.linear_range_size_min > chr_taga_len {
1393 java_substring(chr_taga, site2_new, chr_taga_len)
1394 } else {
1395 java_substring(
1396 chr_taga,
1397 site2_new,
1398 site2_new + self.linear_range_size_min,
1399 )
1400 }
1401 };
1402 let circ3_started = scan1_profile_enabled().then(Instant::now);
1403 tag = self.is_in_circ_rna_3(
1404 &circ_line_arr[5],
1405 &circ_line_arr[8],
1406 circ_range_seq,
1407 pem_null,
1408 );
1409 if let Some(circ3_started) = circ3_started {
1410 HG_PROFILE.circ3_calls.fetch_add(1, Ordering::Relaxed);
1411 add_ns(&HG_PROFILE.circ3_ns, circ3_started);
1412 }
1413 }
1414 HG_PROFILE.hg1_hits.fetch_add(1, Ordering::Relaxed);
1415 if let Some(hg1_started) = hg1_started {
1416 add_ns(&HG_PROFILE.hg1_total_ns, hg1_started);
1417 }
1418 return Some(format!(
1419 "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
1420 tag,
1421 circ_line_arr[1],
1422 site1_new,
1423 site2_new,
1424 shift_strand,
1425 shift_left,
1426 shift_right,
1427 sum_q
1428 ));
1429 }
1430 }
1431 }
1432 }
1433 if let Some(hg1_started) = hg1_started {
1434 add_ns(&HG_PROFILE.hg1_total_ns, hg1_started);
1435 }
1436 None
1437 }
1438
1439 pub fn is_bsj_hg2(&mut self, circ_line_arr: &[String], chr_taga: &str) -> String {
1446 let hg2_started = scan2_profile_enabled().then(Instant::now);
1447 if hg2_started.is_some() {
1448 HG2_PROFILE.hg2_calls.fetch_add(1, Ordering::Relaxed);
1449 }
1450 trace_scan2_hg2("enter", circ_line_arr, "");
1451 let mut judge_tag = "3".to_string();
1452 let start_site = circ_line_arr[3].parse::<i32>().unwrap_or(0);
1453 let end_site = circ_line_arr[4].parse::<i32>().unwrap_or(0);
1454 let quant = circ_line_arr[12].parse::<i32>().unwrap_or(0);
1455 let chr_taga_len = chr_taga.len() as i32;
1456 let circ_range_seq = if start_site - 3 < 0 && end_site + 2 > chr_taga_len {
1457 &chr_taga[..]
1458 } else if start_site - 3 < 0 {
1459 java_substring(chr_taga, 0, end_site + 2)
1460 } else if end_site + 2 > chr_taga_len {
1461 java_substring(chr_taga, start_site - 3, chr_taga_len)
1462 } else {
1463 java_substring(chr_taga, start_site - 3, end_site + 2)
1464 };
1465 let circ_range_len = circ_range_seq.len() as i32;
1466 let str_full;
1467 let mut pem_null_range_seq = "";
1468 if circ_line_arr[2] == "sm" {
1469 str_full = format!("{}{}", circ_line_arr[5], circ_line_arr[11]);
1470 let len_str = str_full.len() as i32;
1471 if len_str < 7 {
1472 if java_substring(circ_range_seq, circ_range_len - len_str, circ_range_len)
1473 != str_full
1474 {
1475 trace_scan2_hg2("sm_short_mismatch", circ_line_arr, "");
1476 return finish_scan2_hg2("0".to_string(), hg2_started);
1477 } else {
1478 trace_scan2_hg2("sm_short_exact", circ_line_arr, "");
1479 return finish_scan2_hg2("2".to_string(), hg2_started);
1480 }
1481 } else {
1482 let s1_part1 =
1483 java_substring(circ_range_seq, circ_range_len - 4, circ_range_len - 2);
1484 let s1_part2 = java_substring(&str_full, len_str - 4, len_str - 2);
1485 let s2_part1 =
1486 java_substring(circ_range_seq, circ_range_len - 7, circ_range_len - 4);
1487 let s2_part2 = java_substring(&str_full, len_str - 7, len_str - 4);
1488 if s1_part1 == s1_part2 || s2_part1 == s2_part2 {
1489 let mut label = true;
1490 let linear_range;
1491 if end_site - start_site + 5 >= self.linear_range_size_min {
1492 if 2 * start_site >= end_site + 6 {
1493 linear_range = java_substring(
1494 chr_taga,
1495 2 * start_site - end_site - 6,
1496 start_site - 1,
1497 );
1498 } else {
1499 linear_range = java_substring(chr_taga, 0, start_site - 1);
1500 }
1501 } else if start_site - 1 >= self.linear_range_size_min {
1502 linear_range = java_substring(
1503 chr_taga,
1504 start_site - self.linear_range_size_min - 1,
1505 start_site - 1,
1506 );
1507 } else {
1508 linear_range = java_substring(chr_taga, 0, start_site - 1);
1509 }
1510 if circ_range_len < len_str {
1511 return finish_scan2_hg2("2".to_string(), hg2_started);
1512 }
1513 let sw_started = scan2_profile_enabled().then(Instant::now);
1514 self.aligner.set_seq(
1515 java_substring(circ_range_seq, circ_range_len - len_str, circ_range_len),
1516 &str_full,
1517 );
1518 self.aligner.align();
1519 if let Some(sw_started) = sw_started {
1520 HG2_PROFILE.sw_calls.fetch_add(1, Ordering::Relaxed);
1521 add_ns(&HG2_PROFILE.sw_ns, sw_started);
1522 }
1523 if self.aligner.score >= (len_str - (len_str - 2) / 10 * 2) {
1524 if quant >= self.min_mapq_uni {
1525 let initial_seq =
1526 java_substring(&str_full, len_str - self.initial_size1, len_str);
1527 if java_substring(
1528 circ_range_seq,
1529 circ_range_len - self.initial_size1,
1530 circ_range_len,
1531 ) == initial_seq
1532 {
1533 let linear11_started = scan2_profile_enabled().then(Instant::now);
1534 let linear11_ok = self.is_in_circ_rna_1_1(
1535 len_str,
1536 &str_full,
1537 circ_range_seq,
1538 linear_range,
1539 ) == 1;
1540 if let Some(linear11_started) = linear11_started {
1541 HG2_PROFILE.linear11_calls.fetch_add(1, Ordering::Relaxed);
1542 add_ns(&HG2_PROFILE.linear11_ns, linear11_started);
1543 }
1544 if linear11_ok {
1545 judge_tag = "2".to_string();
1546 } else {
1547 judge_tag = "3".to_string();
1548 }
1549 }
1550 }
1551 if circ_line_arr[0] == "1" {
1552 pem_null_range_seq = linear_range;
1553 }
1554 label = false;
1555 }
1556 if label {
1557 let initial_seq =
1558 java_substring(&str_full, len_str - self.initial_size1, len_str);
1559 if java_substring(
1560 circ_range_seq,
1561 circ_range_len - self.initial_size1,
1562 circ_range_len,
1563 ) == initial_seq
1564 {
1565 let linear11_started = scan2_profile_enabled().then(Instant::now);
1566 let linear11_ok = self.is_in_circ_rna_1_1(
1567 len_str,
1568 &str_full,
1569 circ_range_seq,
1570 linear_range,
1571 ) == 1;
1572 if let Some(linear11_started) = linear11_started {
1573 HG2_PROFILE.linear11_calls.fetch_add(1, Ordering::Relaxed);
1574 add_ns(&HG2_PROFILE.linear11_ns, linear11_started);
1575 }
1576 if linear11_ok {
1577 if quant >= self.min_mapq_uni {
1578 judge_tag = "2".to_string();
1579 if circ_line_arr[0] == "1" {
1580 pem_null_range_seq = linear_range;
1581 }
1582 } else {
1583 trace_scan2_hg2("sm_linear11_low_mapq", circ_line_arr, "");
1584 return finish_scan2_hg2("2".to_string(), hg2_started);
1585 }
1586 } else {
1587 trace_scan2_hg2("sm_linear11_fail", circ_line_arr, "");
1588 return finish_scan2_hg2("0".to_string(), hg2_started);
1589 }
1590 } else {
1591 trace_scan2_hg2("sm_initial_fail", circ_line_arr, "");
1592 return finish_scan2_hg2("0".to_string(), hg2_started);
1593 }
1594 }
1595 } else {
1596 trace_scan2_hg2("sm_seed_fail", circ_line_arr, "");
1597 return finish_scan2_hg2("0".to_string(), hg2_started);
1598 }
1599 }
1600 } else {
1601 str_full = format!("{}{}", circ_line_arr[10], circ_line_arr[5]);
1602 let len_str = str_full.len() as i32;
1603 if len_str < 7 {
1604 if java_substring(circ_range_seq, 0, len_str) != str_full {
1605 trace_scan2_hg2("ms_short_mismatch", circ_line_arr, "");
1606 return finish_scan2_hg2("0".to_string(), hg2_started);
1607 } else {
1608 trace_scan2_hg2("ms_short_exact", circ_line_arr, "");
1609 return finish_scan2_hg2("2".to_string(), hg2_started);
1610 }
1611 } else {
1612 let s1_part1 = java_substring(circ_range_seq, 2, 4);
1613 let s1_part2 = java_substring(&str_full, 2, 4);
1614 let s2_part1 = java_substring(circ_range_seq, 4, 7);
1615 let s2_part2 = java_substring(&str_full, 4, 7);
1616 if s1_part1 == s1_part2 || s2_part1 == s2_part2 {
1617 let mut label = true;
1618 let linear_range;
1619 if end_site - start_site + 5 >= self.linear_range_size_min {
1620 if 2 * end_site - start_site + 5 > chr_taga_len {
1621 linear_range = java_substring(chr_taga, end_site, chr_taga_len);
1622 } else {
1623 linear_range =
1624 java_substring(chr_taga, end_site, 2 * end_site - start_site + 5);
1625 }
1626 } else if end_site + self.linear_range_size_min > chr_taga_len {
1627 linear_range = java_substring(chr_taga, end_site, chr_taga_len);
1628 } else {
1629 linear_range = java_substring(
1630 chr_taga,
1631 end_site,
1632 end_site + self.linear_range_size_min,
1633 );
1634 }
1635 if circ_range_len < len_str {
1636 return finish_scan2_hg2("2".to_string(), hg2_started);
1637 }
1638 let sw_started = scan2_profile_enabled().then(Instant::now);
1639 self.aligner
1640 .set_seq(java_substring(circ_range_seq, 0, len_str), &str_full);
1641 self.aligner.align();
1642 if let Some(sw_started) = sw_started {
1643 HG2_PROFILE.sw_calls.fetch_add(1, Ordering::Relaxed);
1644 add_ns(&HG2_PROFILE.sw_ns, sw_started);
1645 }
1646 if self.aligner.score >= (len_str - (len_str - 2) / 10 * 2) {
1647 let initial_seq = java_substring(&str_full, 0, self.initial_size1);
1648 if java_substring(circ_range_seq, 0, self.initial_size1) == initial_seq {
1649 let linear12_started = scan2_profile_enabled().then(Instant::now);
1650 let linear12_ok = self.is_in_circ_rna_1_2(
1651 len_str,
1652 &str_full,
1653 circ_range_seq,
1654 linear_range,
1655 ) == 1;
1656 if let Some(linear12_started) = linear12_started {
1657 HG2_PROFILE.linear12_calls.fetch_add(1, Ordering::Relaxed);
1658 add_ns(&HG2_PROFILE.linear12_ns, linear12_started);
1659 }
1660 if linear12_ok {
1661 if quant >= self.min_mapq_uni {
1662 judge_tag = "2".to_string();
1663 }
1664 } else {
1665 judge_tag = "3".to_string();
1666 }
1667 }
1668 if circ_line_arr[0] == "0" {
1669 pem_null_range_seq = linear_range;
1670 }
1671 label = false;
1672 }
1673 if label {
1674 let initial_seq = java_substring(&str_full, 0, self.initial_size1);
1675 if java_substring(circ_range_seq, 0, self.initial_size1) == initial_seq {
1676 let linear12_started = scan2_profile_enabled().then(Instant::now);
1677 let linear12_ok = self.is_in_circ_rna_1_2(
1678 len_str,
1679 &str_full,
1680 circ_range_seq,
1681 linear_range,
1682 ) == 1;
1683 if let Some(linear12_started) = linear12_started {
1684 HG2_PROFILE.linear12_calls.fetch_add(1, Ordering::Relaxed);
1685 add_ns(&HG2_PROFILE.linear12_ns, linear12_started);
1686 }
1687 if linear12_ok {
1688 if quant >= self.min_mapq_uni {
1689 judge_tag = "2".to_string();
1690 if circ_line_arr[0] == "0" {
1691 pem_null_range_seq = linear_range;
1692 }
1693 } else {
1694 trace_scan2_hg2("ms_linear12_low_mapq", circ_line_arr, "");
1695 return finish_scan2_hg2("2".to_string(), hg2_started);
1696 }
1697 } else {
1698 trace_scan2_hg2("ms_linear12_fail", circ_line_arr, "");
1699 return finish_scan2_hg2("0".to_string(), hg2_started);
1700 }
1701 } else {
1702 trace_scan2_hg2("ms_initial_fail", circ_line_arr, "");
1703 return finish_scan2_hg2("0".to_string(), hg2_started);
1704 }
1705 }
1706 } else {
1707 trace_scan2_hg2("ms_seed_fail", circ_line_arr, "");
1708 return finish_scan2_hg2("0".to_string(), hg2_started);
1709 }
1710 }
1711 }
1712 if circ_line_arr[7] != "*" {
1713 let circ2_started = scan2_profile_enabled().then(Instant::now);
1714 let circ2_ok = self.is_in_circ_rna_2(&circ_line_arr[7], circ_range_seq) != 0;
1715 if let Some(circ2_started) = circ2_started {
1716 HG2_PROFILE.circ2_calls.fetch_add(1, Ordering::Relaxed);
1717 add_ns(&HG2_PROFILE.circ2_ns, circ2_started);
1718 }
1719 if !circ2_ok {
1720 trace_scan2_hg2("circ2_fail", circ_line_arr, "");
1721 return finish_scan2_hg2("0".to_string(), hg2_started);
1722 }
1723 }
1724 if circ_line_arr[6].len() > 5 {
1725 let circ3_started = scan2_profile_enabled().then(Instant::now);
1726 let res = self.is_in_circ_rna_3(
1727 &circ_line_arr[6],
1728 &circ_line_arr[8],
1729 circ_range_seq,
1730 pem_null_range_seq,
1731 );
1732 if let Some(circ3_started) = circ3_started {
1733 HG2_PROFILE.circ3_calls.fetch_add(1, Ordering::Relaxed);
1734 add_ns(&HG2_PROFILE.circ3_ns, circ3_started);
1735 }
1736 trace_scan2_hg2(
1737 "final",
1738 circ_line_arr,
1739 &format!("judge_tag={} circ3_res={}", judge_tag, res),
1740 );
1741 return finish_scan2_hg2(format!("{}{}", res, judge_tag), hg2_started);
1742 }
1743 trace_scan2_hg2(
1744 "final_short_pair",
1745 circ_line_arr,
1746 &format!("judge_tag={}", judge_tag),
1747 );
1748 finish_scan2_hg2(format!("1{}", judge_tag), hg2_started)
1749 }
1750}
1751
1752#[cfg(test)]
1753mod tests {
1754 use super::*;
1755
1756 #[test]
1757 fn test_java_substring() {
1758 assert_eq!(java_substring("ABCDE", 1, 3), "BC");
1759 assert_eq!(java_substring("ABCDE", 0, 5), "ABCDE");
1760 }
1761
1762 #[test]
1763 fn test_smith_waterman_exact() {
1764 let mut sw = SmithWaterman::new(1, -1, -1);
1765 sw.set_seq("ATGC", "ATGC");
1766 sw.align();
1767 assert_eq!(sw.score, 4);
1768 assert_eq!(sw.aligned_len, 4);
1769 }
1770
1771 #[test]
1772 fn test_smith_waterman_mismatch() {
1773 let mut sw = SmithWaterman::new(1, -1, -1);
1774 sw.set_seq("ATGC", "ATGG");
1775 sw.align();
1776 assert_eq!(sw.score, 3);
1777 }
1778
1779 #[test]
1780 fn test_smith_waterman_short() {
1781 let mut sw = SmithWaterman::new(1, -1, -1);
1782 sw.set_seq("GCAT", "GC");
1783 sw.align();
1784 assert_eq!(sw.score, 2);
1785 }
1786}