Skip to main content

debruijn/
graph.rs

1// Copyright 2017 10x Genomics
2
3//! Containers for path-compressed De Bruijn graphs
4
5use bimap::BiHashMap;
6use bio::io::fasta;
7use bit_set::BitSet;
8use indicatif::ProgressBar;
9use indicatif::ProgressIterator;
10use indicatif::ProgressStyle;
11use itertools::enumerate;
12use log::warn;
13use log::{debug, trace};
14use rayon::prelude::*;
15use rayon::current_num_threads;
16use serde_derive::{Deserialize, Serialize};
17use smallvec::SmallVec;
18use std::borrow::Borrow;
19
20use std::collections::HashSet;
21use std::collections::VecDeque;
22use std::f32;
23use std::fmt::{self, Debug, Display};
24use std::fs::{remove_file, File};
25use std::hash::Hash;
26use std::io::BufWriter;
27use std::io::{BufReader, Error, Read};
28use std::io::Write;
29use std::iter::FromIterator;
30use std::marker::PhantomData;
31use std::path::Path;
32
33use boomphf::hashmap::BoomHashMap;
34
35use serde_json;
36use serde_json::Value;
37
38type SmallVec4<T> = SmallVec<[T; 4]>;
39type SmallVec8<T> = SmallVec<[T; 8]>;
40
41use crate::BaseQuality;
42use crate::EdgeMap;
43use crate::bits_to_base;
44use crate::colors::ColorMode;
45use crate::colors::Colors;
46use crate::compression::CompressionSpec;
47use crate::dna_string::{DnaString, DnaStringSlice, PackedDnaStringSet};
48use crate::summarizer::SummaryConfig;
49use crate::summarizer::SummaryData;
50use crate::summarizer::Translator;
51use crate::summarizer::ID;
52use crate::BUF;
53use crate::PROGRESS_STYLE;
54use crate::{Dir, Exts, Kmer, Mer, Vmer};
55
56/// A compressed DeBruijn graph carrying auxiliary data on each node of type `D`.
57/// This type does not carry the sorted index arrays the allow the graph
58/// to be walked efficiently. The `DeBruijnGraph` type wraps this type and add those
59/// vectors.
60#[derive(Serialize, Deserialize, Clone, Debug)]
61pub struct BaseGraph<K, D> {
62    pub sequences: PackedDnaStringSet,
63    pub exts: Vec<Exts>,
64    pub data: Vec<D>,
65    pub stranded: bool,
66    phantom: PhantomData<K>,
67}
68
69impl<K, D> BaseGraph<K, D> {
70    pub fn new(stranded: bool) -> Self {
71        BaseGraph {
72            sequences: PackedDnaStringSet::new(),
73            exts: Vec::new(),
74            data: Vec::new(),
75            phantom: PhantomData,
76            stranded,
77        }
78    }
79
80    pub fn len(&self) -> usize {
81        self.sequences.len()
82    }
83
84    pub fn is_empty(&self) -> bool {
85        self.sequences.is_empty()
86    }
87
88    pub fn combine<I: Iterator<Item = BaseGraph<K, D>>>(graphs: I) -> Self {
89        let mut sequences = PackedDnaStringSet::new();
90        let mut exts = Vec::new();
91        let mut data = Vec::new();
92        let mut stranded = Vec::new();
93
94        for g in graphs {
95            for s in 0..g.sequences.len() {
96                sequences.add(&g.sequences.get(s));
97            }
98
99            exts.extend(g.exts);
100            data.extend(g.data);
101            stranded.push(g.stranded);
102        }
103
104        let out_stranded = stranded.iter().all(|x| *x);
105
106        if !out_stranded && !stranded.iter().all(|x| !*x) {
107            panic!("attempted to combine stranded and unstranded graphs");
108        }
109
110        BaseGraph {
111            sequences,
112            stranded: out_stranded,
113            exts,
114            data,
115            phantom: PhantomData,
116        }
117    }
118
119    /// shrink the storage of the `BaseGraph` to fit its contents
120    pub fn shrink_to_fit(&mut self) {
121        self.sequences.shrink_to_fit();
122        self.exts.shrink_to_fit();
123        self.data.shrink_to_fit();
124    }
125}
126
127impl<K: Kmer, D> BaseGraph<K, D> {
128    pub fn add<R: Borrow<u8>, S: IntoIterator<Item = R>>(
129        &mut self,
130        sequence: S,
131        exts: Exts,
132        data: D,
133    ) {
134        self.sequences.add(sequence);
135        self.exts.push(exts);
136        self.data.push(data);
137    }
138}
139
140impl<K: Kmer + Send + Sync, D> BaseGraph<K, D> {
141    pub fn finish(self) -> DebruijnGraph<K, D> {
142        let indices: Vec<u32> = (0..self.len() as u32).collect();
143
144        let left_order = {
145            let mut kmers: Vec<K> = Vec::with_capacity(self.len());
146            for idx in &indices {
147                kmers.push(self.sequences.get(*idx as usize).first_kmer());
148                
149            }
150            
151            BoomHashMap::new_parallel(kmers, indices.clone())
152        };
153
154        let right_order = {
155            let mut kmers: Vec<K> = Vec::with_capacity(self.len());
156            for idx in &indices {
157                kmers.push(self.sequences.get(*idx as usize).last_kmer());
158            }
159            
160            BoomHashMap::new_parallel(kmers, indices)
161        };
162        debug!("finish graph loops: 2x {}", self.len());
163
164        DebruijnGraph {
165            base: self,
166            left_order,
167            right_order,
168        }
169    }
170}
171
172impl<K: Kmer, D> BaseGraph<K, D> {
173    pub fn finish_serial(self) -> DebruijnGraph<K, D> {
174        let indices: Vec<u32> = (0..self.len() as u32).collect();
175
176        let left_order = {
177            let mut kmers: Vec<K> = Vec::with_capacity(self.len());
178            let mut sequences: Vec<String> = Vec::new();
179            for idx in &indices {
180                kmers.push(self.sequences.get(*idx as usize).first_kmer());
181                sequences.push(self.sequences.get(*idx as usize).to_dna_string());
182            }
183
184            BoomHashMap::new(kmers, indices.clone())
185        };
186
187        let right_order = {
188            let mut kmers: Vec<K> = Vec::with_capacity(self.len());
189            let mut sequences: Vec<String> = Vec::new();
190            for idx in &indices {
191                kmers.push(self.sequences.get(*idx as usize).last_kmer());
192                sequences.push(self.sequences.get(*idx as usize).to_dna_string());
193            }
194
195            BoomHashMap::new(kmers, indices)
196        };
197
198        DebruijnGraph {
199            base: self,
200            left_order,
201            right_order,
202        }
203    }
204}
205
206/// A compressed DeBruijn graph carrying auxiliary data on each node of type `D`.
207/// The struct carries sorted index arrays the allow the graph
208/// to be walked efficiently.
209#[derive(Serialize, Deserialize, Debug)]
210pub struct DebruijnGraph<K: Hash, D> {
211    pub base: BaseGraph<K, D>,
212    left_order: BoomHashMap<K, u32>,
213    right_order: BoomHashMap<K, u32>,
214}
215
216impl<K: Kmer, D: Debug> DebruijnGraph<K, D> {
217    /// Total number of nodes in the DeBruijn graph
218    pub fn len(&self) -> usize {
219        self.base.len()
220    }
221
222    pub fn is_empty(&self) -> bool {
223        self.base.is_empty()
224    }
225
226    /// shrink the storage of the `DebruijnGraph` to fit its contents
227    pub fn shrink_to_fit(&mut self)  {
228        self.base.shrink_to_fit();
229    }
230
231    /// Get a node given it's `node_id`
232    pub fn get_node(&'_ self, node_id: usize) -> Node<'_, K, D> {
233        Node {
234            node_id,
235            graph: self,
236        }
237    }
238
239    /// Get a node given it's `node_id`
240    pub fn get_node_kmer(&'_ self, node_id: usize) -> NodeKmer<'_, K, D> {
241        let node = self.get_node(node_id);
242        let node_seq = node.sequence();
243
244        NodeKmer {
245            node_id,
246            node_seq_slice: node_seq,
247            phantom_d: PhantomData,
248            phantom_k: PhantomData,
249        }
250    }
251
252    /// Return an iterator over all nodes in the graph
253    pub fn iter_nodes(&'_ self) -> NodeIter<'_, K, D> {
254        NodeIter {
255            graph: self,
256            node_id: 0,
257        }
258    }
259
260    /// Find the edges leaving node `node_id` in direction `Dir`. Should generally be
261    /// accessed via a Node wrapper object
262    fn find_edges(&self, node_id: usize, dir: Dir) -> SmallVec4<(u8, usize, Dir, bool)> {
263        let exts = self.base.exts[node_id];
264        let sequence = self.base.sequences.get(node_id);
265        let kmer: K = sequence.term_kmer(dir);
266        let mut edges = SmallVec4::new();
267
268        for i in 0..4 {
269            if exts.has_ext(dir, i) {
270                let link = self.find_link(kmer.extend(i, dir), dir).expect("missing link");
271                edges.push((i, link.0, link.1, link.2));
272            }
273        }
274
275        edges
276    }
277
278    /// Find the edges leaving node `node_id` in direction `Dir`. Should generally be
279    /// accessed via a Node wrapper object
280    /// 
281    /// allows missing links
282    fn _find_edges_sharded(&self, node_id: usize, dir: Dir) -> SmallVec4<(u8, usize, Dir, bool)> {
283        let exts = self.base.exts[node_id];
284        let sequence = self.base.sequences.get(node_id);
285        let kmer: K = sequence.term_kmer(dir);
286        let mut edges = SmallVec4::new();
287
288        for i in 0..4 {
289            if exts.has_ext(dir, i) {
290                let link = self.find_link(kmer.extend(i, dir), dir); //.expect("missing link");
291                if let Some(l) = link {
292                    edges.push((i, l.0, l.1, l.2));
293                }
294                // Otherwise, this edge doesn't exist within this shard, so ignore it.
295                // NOTE: this should be allowed in a 'complete' DBG
296            }
297        }
298
299        edges
300    }
301
302    /// Seach for the kmer `kmer`, appearing at the given `side` of a node sequence.
303    fn search_kmer(&self, kmer: K, side: Dir) -> Option<usize> {
304        match side {
305            Dir::Left => self.left_order.get(&kmer).map(|pos| *pos as usize),
306            Dir::Right => self.right_order.get(&kmer).map(|pos| *pos as usize),
307        }
308    }
309
310    /// Find a link in the graph, possibly handling a RC switch.
311    pub fn find_link(&self, kmer: K, dir: Dir) -> Option<(usize, Dir, bool)> {
312        // Only test self-consistent paths through
313        // the edges
314        // Avoids problems due to single kmer edges
315        // (dir, next_side_incoming, rc)
316        // (Dir::Left, Dir::Right, false) => true,
317        // (Dir::Left, Dir::Left,  true) => true,
318        // (Dir::Right, Dir::Left, false) => true,
319        // (Dir::Right, Dir::Right, true) => true,
320
321        let rc = kmer.rc();
322
323        match dir {
324            Dir::Left => {
325                if let Some(idx) = self.search_kmer(kmer, Dir::Right) {
326                    return Some((idx, Dir::Right, false));
327                }
328
329                if !self.base.stranded {
330                    if let Some(idx) = self.search_kmer(rc, Dir::Left) {
331                        return Some((idx, Dir::Left, true));
332                    }
333                }
334            }
335
336            Dir::Right => {
337                if let Some(idx) = self.search_kmer(kmer, Dir::Left) {
338                    return Some((idx, Dir::Left, false));
339                }
340
341                if !self.base.stranded {
342                    if let Some(idx) = self.search_kmer(rc, Dir::Right) {
343                        return Some((idx, Dir::Right, true));
344                    }
345                }
346            }
347        }
348
349        None
350    }
351
352    /// Check whether the graph is fully compressed. Return `None` if it's compressed,
353    /// otherwise return `Some(node1, node2)` representing a pair of node that could
354    /// be collapsed. Probably only useful for testing.
355    pub fn is_compressed<S: CompressionSpec<D>>(&self, spec: &S) -> Option<(usize, usize)> {
356        for i in 0..self.len() {
357            let n = self.get_node(i);
358
359            for dir in &[Dir::Left, Dir::Right] {
360                let dir_edges = n.edges(*dir);
361                if dir_edges.len() == 1 {
362                    let (_, next_id, return_dir, _) = dir_edges[0];
363                    let next = self.get_node(next_id);
364
365                    let ret_edges = next.edges(return_dir);
366                    if ret_edges.len() == 1 {
367                        // Test for us being a palindrome: this makes it OK
368                        if n.len() == K::k() && n.sequence().first_kmer::<K>().is_palindrome() {
369                            continue;
370                        }
371
372                        // Test for a neighbor being a palindrome: this makes it OK
373                        if next.len() == K::k() && next.sequence().first_kmer::<K>().is_palindrome()
374                        {
375                            continue;
376                        }
377
378                        // Test for this edge representing a smooth circle (biting it's own tail):
379                        if n.node_id == next_id {
380                            continue;
381                        }
382
383                        if spec.join_test(n.data(), next.data()) {
384                            // Found a unbranched edge that should have been eliminated
385                            return Some((i, next_id));
386                        }
387                    }
388                }
389            }
390        }
391
392        None
393    }
394
395    /// Remove non-existent extensions that may be created due to filtered kmers
396    /// 
397    /// if `valid_nodes` if `None`, all nodes are valid
398    pub fn fix_exts(&mut self, valid_nodes: Option<&BitSet>) {
399        for i in 0..self.len() {
400            let valid_exts = self.get_valid_exts(i, valid_nodes);
401            self.base.exts[i] = valid_exts;
402        }
403    }
404
405    pub fn get_valid_exts(&self, node_id: usize, valid_nodes: Option<&BitSet>) -> Exts {
406        let mut new_exts = Exts::empty();
407        let node = self.get_node(node_id);
408        let exts = node.exts();
409        let l_kmer: K = node.sequence().first_kmer();
410        let r_kmer: K = node.sequence().last_kmer();
411
412        let check_node = |id| match valid_nodes {
413            Some(bs) => bs.contains(id),
414            None => true,
415        };
416
417        for i in 0..4 {
418            if exts.has_ext(Dir::Left, i) {
419                match self.find_link(l_kmer.extend_left(i), Dir::Left) {
420                    Some((target, _, _)) if check_node(target) => {
421                        new_exts = new_exts.set(Dir::Left, i)
422                    }
423                    _ => (),
424                }
425            }
426
427            if exts.has_ext(Dir::Right, i) {
428                match self.find_link(r_kmer.extend_right(i), Dir::Right) {
429                    Some((target, _, _)) if check_node(target) => {
430                        new_exts = new_exts.set(Dir::Right, i)
431                    }
432                    _ => (),
433                }
434            }
435        }
436
437        new_exts
438    }
439
440    /// mutable reference to the auxiliary data of the node node_id
441    pub fn mut_data(&mut self, node_id: usize) -> &mut D {
442        &mut self.base.data[node_id]
443    }
444
445    /// Find the highest-scoring, unambiguous path in the graph. Each node get a score
446    /// given by `score`. Any node where `solid_path(node) == True` are valid paths -
447    /// paths will be terminated if there are multiple valid paths emanating from a node.
448    pub fn max_path<F, F2>(&self, score: F, solid_path: F2) -> Vec<(usize, Dir)>
449    where
450        F: Fn(&D) -> f32,
451        F2: Fn(&D) -> bool,
452    {
453        if self.is_empty() {
454            return Vec::default();
455        }
456
457        let mut best_node = 0;
458        let mut best_score = f32::MIN;
459        for i in 0..self.len() {
460            let node = self.get_node(i);
461            let node_score = score(node.data());
462
463            if node_score > best_score {
464                best_node = i;
465                best_score = node_score;
466            }
467        }
468
469        let oscore = |state| match state {
470            None => 0.0,
471            Some((id, _)) => score(self.get_node(id).data()),
472        };
473
474        let osolid_path = |state| match state {
475            None => false,
476            Some((id, _)) => solid_path(self.get_node(id).data()),
477        };
478
479        // Now expand in each direction, greedily taking the best path. Stop if we hit a node we've
480        // already put into the path
481        let mut used_nodes = HashSet::new();
482        let mut path = VecDeque::new();
483
484        // Start w/ initial state
485        used_nodes.insert(best_node);
486        path.push_front((best_node, Dir::Left));
487
488        for init in [(best_node, Dir::Left, false), (best_node, Dir::Right, true)].iter() {
489            let &(start_node, dir, do_flip) = init;
490            let mut current = (start_node, dir);
491
492            loop {
493                let mut next = None;
494                let (cur_id, incoming_dir) = current;
495                let node = self.get_node(cur_id);
496                let edges = node.edges(incoming_dir.flip());
497
498                let mut solid_paths = 0;
499                for (_, id, dir, _) in edges {
500                    let cand = Some((id, dir));
501                    if osolid_path(cand) {
502                        solid_paths += 1;
503                    }
504
505                    if oscore(cand) > oscore(next) {
506                        next = cand;
507                    }
508                }
509
510                if solid_paths > 1 {
511                    break;
512                }
513
514                match next {
515                    Some((next_id, next_incoming)) if !used_nodes.contains(&next_id) => {
516                        if do_flip {
517                            path.push_front((next_id, next_incoming.flip()));
518                        } else {
519                            path.push_back((next_id, next_incoming));
520                        }
521
522                        used_nodes.insert(next_id);
523                        current = (next_id, next_incoming);
524                    }
525                    _ => break,
526                }
527            }
528        }
529
530        Vec::from_iter(path)
531    }
532
533
534    /// Find the highest-scoring, unambiguous path in the graph. Each node get a score
535    /// given by `score`. Any node where `solid_path(node) == True` are valid paths -
536    /// paths will be terminated if there are multiple valid paths emanating from a node.
537    /// Returns vec with path for each component
538    pub fn max_path_comp<F, F2>(&self, score: F, solid_path: F2) -> Vec<VecDeque<(usize, Dir)>>
539    where
540        F: Fn(&D) -> f32,
541        F2: Fn(&D) -> bool,
542    {
543        if self.is_empty() {
544            let vec: Vec<VecDeque<(usize, Dir)>> = Vec::new();
545            return vec;
546        }
547
548        let components = self.iter_components();
549        let mut paths: Vec<VecDeque<(usize, Dir)>> = Vec::new();
550
551        for component in components {
552
553            let current_comp = &component;
554            
555
556            let mut best_node = current_comp[0];
557            let mut best_score = f32::MIN;
558            for c in current_comp.iter() {
559                let node = self.get_node(*c);
560                let node_score = score(node.data());
561
562                if node_score > best_score {
563                    best_node = *c;
564                    best_score = node_score;
565                }
566            }
567
568            let oscore = |state| match state {
569                None => 0.0,
570                Some((id, _)) => score(self.get_node(id).data()),
571            };
572
573            let osolid_path = |state| match state {
574                None => false,
575                Some((id, _)) => solid_path(self.get_node(id).data()),
576            };
577
578            // Now expand in each direction, greedily taking the best path. Stop if we hit a node we've
579            // already put into the path
580            let mut used_nodes = HashSet::new();
581            let mut path = VecDeque::new();
582
583            // Start w/ initial state
584            used_nodes.insert(best_node);
585            path.push_front((best_node, Dir::Left));
586
587            for init in [(best_node, Dir::Left, false), (best_node, Dir::Right, true)].iter() {
588                let &(start_node, dir, do_flip) = init;
589                let mut current = (start_node, dir);
590
591                loop {
592                    let mut next = None;
593                    let (cur_id, incoming_dir) = current;
594                    let node = self.get_node(cur_id);
595                    let edges = node.edges(incoming_dir.flip());
596
597                    let mut solid_paths = 0;
598                    for (_, id, dir, _) in edges {
599                        let cand = Some((id, dir));
600                        if osolid_path(cand) {
601                            solid_paths += 1;
602                        }
603
604                        if oscore(cand) > oscore(next) {
605                            next = cand;
606                        }
607                    }
608
609                    if solid_paths > 1 {
610                        break;
611                    }
612
613                    match next {
614                        Some((next_id, next_incoming)) if !used_nodes.contains(&next_id) => {
615                            if do_flip {
616                                path.push_front((next_id, next_incoming.flip()));
617                            } else {
618                                path.push_back((next_id, next_incoming));
619                            }
620
621                            used_nodes.insert(next_id);
622                            current = (next_id, next_incoming);
623                        }
624                        _ => break,
625                    }
626                }
627            }
628            
629            paths.push(path);
630            //paths.push(Vec::from_iter(path));
631        }
632
633        paths
634    
635    }
636
637    pub fn iter_max_path_comp<F, F2>(&'_ self, score: F, solid_path: F2) -> PathCompIter<'_, K, D, F, F2> 
638    where 
639    F: Fn(&D) -> f32,
640    F2: Fn(&D) -> bool
641    {
642        let component_iterator = self.iter_components();
643        PathCompIter { graph: self, component_iterator, graph_pos: 0, score, solid_path }
644    }
645
646    /// write the paths from `iter_max_path_comp` to a fasta file
647    pub fn path_to_fasta<F, F2>(&self, f: &mut dyn std::io::Write, path_iter: PathCompIter<K, D, F, F2>, return_lens: bool) -> (Vec<usize>, Vec<usize>)
648    where 
649    F: Fn(&D) -> f32,
650    F2: Fn(&D) -> bool
651    {
652        // width of fasta lines
653        let columns = 80;
654
655        // sizes of components and of paths
656        let mut comp_sizes = Vec::new();
657        let mut path_lens = Vec::new();
658
659        for (seq_counter, (component, path)) in path_iter.enumerate() {
660            // get dna sequence from path
661            let seq = self.sequence_of_path(path.iter());
662
663            //write header with length & start node
664            writeln!(f, ">path{} len={} start_node={}", seq_counter, seq.len(), path[0].0).unwrap();
665
666            // calculate how sequence has to be split up
667            let slices = (seq.len() / columns) + 1;
668            let mut ranges = Vec::with_capacity(slices);
669
670            let mut start = 0;
671            while start < seq.len() {
672                ranges.push(start..start + columns);
673                start += columns;
674            }
675
676            let last_start = ranges.pop().expect("no kmers in parallel ranges").start;
677            ranges.push(last_start..seq.len());
678
679            // split up sequence and write to file accordingly
680            for range in ranges {
681                writeln!(f, "{:?}", seq.slice(range.start, range.end)).unwrap();
682            }
683
684            if return_lens {
685                comp_sizes.push(component.len());
686                path_lens.push(path.len());
687            }
688        }    
689
690        (comp_sizes, path_lens)
691        
692    }
693
694
695    /// Get the sequence of a path through the graph. The path is given as a sequence of node_id integers
696    pub fn sequence_of_path<'a, I: 'a + Iterator<Item = &'a (usize, Dir)>>(
697        &self,
698        path: I,
699    ) -> DnaString {
700        let mut seq = DnaString::new();
701
702        for (idx, &(node_id, dir)) in path.enumerate() {
703            let start = if idx == 0 { 0 } else { K::k() - 1 };
704
705            let node_seq = match dir {
706                Dir::Left => self.get_node(node_id).sequence(),
707                Dir::Right => self.get_node(node_id).sequence().rc(),
708            };
709
710            for p in start..node_seq.len() {
711                seq.push(node_seq.get(p))
712            }
713        }
714
715        seq
716    }
717
718    /// map sequences from a fasta reference to the nodes of a **completely uncompressed** debruijn graph
719    /// 
720    /// the IDs are stored with the node if the k-mer occured in the reference
721    pub fn map_transcripts<P>(&self, path: P, translator: &mut Translator) -> Result<Vec<Box<[ID]>>, String> 
722    where 
723        P: AsRef<Path>
724    {
725        let reader = fasta::Reader::new(BufReader::new(File::open(path).unwrap()));
726        let mut node_transcript_ids = vec![Vec::new(); self.len()];
727
728        // if the translator has a id translator, use it, else make a new one to use and put it into the translator
729        let id_tr = if let Some(id_tr) = translator.mut_id_translator() {
730            id_tr
731        } else {
732            let new_id_tr = BiHashMap::new();
733            translator.mut_id_translator().replace(new_id_tr);
734            
735            let Some(id_tr) = translator.mut_id_translator() else { panic!("should not happen") };
736
737            id_tr
738        };
739
740        // go through each transcript and map to graph
741        for result in reader.records() {
742            let record = result.expect("error parsing transcripts fasta");
743
744            // get gene id or make new id
745            let gene_id = match id_tr.get_by_left(&record.id().to_string()) {
746                Some(id) => *id,
747                None => {
748                    let new_id = id_tr.len() as ID;
749                    id_tr.insert(record.id().to_string(), new_id);
750                    new_id
751                }
752            };
753
754            // iterate over k-mers in transcript and find each one in the graph
755            let sequence = DnaString::from_acgt_bytes(record.seq());
756            for kmer in sequence.iter_kmers::<K>() {
757                if self.base.stranded {
758                    if let Some(node) = self.search_kmer(kmer, Dir::Right) {
759                        node_transcript_ids[node].push(gene_id);
760                    }
761                } else {
762                    // graph is not stranded, look for both the k-mer ansd its reverse complement
763                    if let Some(node) = self.search_kmer(kmer, Dir::Right) {
764                        node_transcript_ids[node].push(gene_id);
765                    } else if let Some(node) = self.search_kmer(kmer.rc(), Dir::Right) {
766                        node_transcript_ids[node].push(gene_id);
767                    }
768                }
769                
770            }
771        }
772
773        // change to boxed slices to reduce memory
774        let boxed_transcripts = node_transcript_ids.into_iter().map(|vec| vec.into()).collect();
775
776        Ok(boxed_transcripts)
777    }
778
779    /// map sequences from a fasta reference to the edges of a **completely uncompressed** debruijn graph
780    /// 
781    /// the IDs are stored with the edges if the two k-mers occured together in the reference
782    pub fn map_transcripts_to_edges<P>(&self, path: P, translator: &mut Translator) -> Result<Vec<EdgeMap>, String> 
783    where 
784        P: AsRef<Path>
785    {
786        let reader = fasta::Reader::new(BufReader::new(File::open(path).unwrap()));
787        let mut node_edge_transcript_ids = vec![EdgeMap::default(); self.len()];
788
789        // if the translator has a id translator, use it, else make a new one to use and put it into the translator
790        let id_tr = if let Some(id_tr) = translator.mut_id_translator() {
791            id_tr
792        } else {
793            let new_id_tr = BiHashMap::new();
794            translator.mut_id_translator().replace(new_id_tr);
795            
796            let Some(id_tr) = translator.mut_id_translator() else { panic!("should not happen") };
797
798            id_tr
799        };
800
801        // go through each transcript and map to graph
802        for result in reader.records() {
803            let record = result.expect("error parsing transcripts fasta");
804
805            // get gene id or make new id
806            let gene_id = match id_tr.get_by_left(&record.id().to_string()) {
807                Some(id) => *id,
808                None => {
809                    let new_id = id_tr.len() as ID;
810                    id_tr.insert(record.id().to_string(), new_id);
811                    new_id
812                }
813            };
814
815            // iterate over k-mers in transcript and find each one in the graph
816            let sequence = DnaString::from_acgt_bytes(record.seq());
817
818            for (kmer, exts) in sequence.iter_kmer_exts::<K>(Exts::empty()) {
819                if self.base.stranded {
820                    if let Some(node) = self.search_kmer(kmer, Dir::Right) {
821                        node_edge_transcript_ids[node].add_id(exts, gene_id);
822                    }
823                } else {
824                    // graph is not stranded, look for both the k-mer ansd its reverse complement
825                    if let Some(node) = self.search_kmer(kmer, Dir::Right) {
826                        node_edge_transcript_ids[node].add_id(exts, gene_id);
827                    } else if let Some(node) = self.search_kmer(kmer.rc(), Dir::Right) {
828                        node_edge_transcript_ids[node].add_id(exts.rc(), gene_id);
829                    }
830                }
831                
832            }
833        }
834
835        // remove unncecessary memory from vector
836        node_edge_transcript_ids.shrink_to_fit();
837
838        Ok(node_edge_transcript_ids)
839    }
840
841    /// write a node to a dot file
842    /// 
843    /// ### Arguments: 
844    /// 
845    /// * `node`: [`Node<K, D>`] which will be written to a dot file
846    /// * `node_label`: closure taking [`Node<K, D>`] and returning a string containing commands for dot nodes 
847    /// * `edge_label`: closure taking [`Node<K, D>`], the base as a [`u8`], the incoming [`Dir`] of the edge 
848    ///   and if the neighbor is flipped - returns a string containing commands for dot edges, 
849    /// * `f`: writer
850    fn node_to_dot<FN: Fn(&Node<K, D>) -> String, FE: Fn(&Node<K, D>, u8, Dir, bool) -> String>(
851        &self,
852        node: &Node<'_, K, D>,
853        node_label: &FN,
854        edge_label: &FE,
855        f: &mut dyn Write,
856    ) {
857        writeln!(f, "n{} {}", node.node_id, node_label(node)).unwrap();
858        assert_eq!(node.exts().val.count_ones() as usize, node.l_edges().len() + node.r_edges().len());
859
860        for (base, id, incoming_dir, flipped) in node.l_edges() {
861            writeln!(f, "n{} -> n{} {}", id, node.node_id, edge_label(node, base, incoming_dir, flipped)).unwrap();
862        }
863
864        for (base, id, incoming_dir, flipped) in node.r_edges() {
865            writeln!(f, "n{} -> n{} {}", node.node_id, id, edge_label(node, base, incoming_dir, flipped)).unwrap();
866        }
867    }
868
869    /// Write the graph to a dot file
870    /// 
871    /// ### Arguments: 
872    /// 
873    /// * `path`: path to the output file
874    /// * `node_label`: closure taking [`Node<K, D>`] and returning a string containing commands for dot nodes, e.g. [`Node::node_dot_default`]
875    /// * `edge_label`: closure taking [`Node<K, D>`], the base as a [`u8`], the incoming [`Dir`] of the edge, e.g. [`Node::edge_dot_default`]
876    ///   and if the neighbor is flipped - returns a string containing commands for dot edges, 
877    pub fn to_dot<P, FN, FE>(&self, path: P, node_label: &FN, edge_label: &FE) 
878    where 
879    P: AsRef<Path>,
880    FN: Fn(&Node<K, D>) -> String,
881    FE: Fn(&Node<K, D>, u8, Dir, bool) -> String,
882    {
883        let mut f = BufWriter::with_capacity(BUF, File::create(path).expect("error creating dot file"));
884
885        let pb = ProgressBar::new(self.len() as u64);
886        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
887        pb.set_message(format!("{:<32}", "writing graph to DOT file"));
888
889        writeln!(&mut f, "digraph {{\nrankdir=\"LR\"\nmodel=subset").unwrap();
890        for i in (0..self.len()).progress_with(pb) {
891            self.node_to_dot(&self.get_node(i), node_label, edge_label, &mut f);
892        }
893        writeln!(&mut f, "}}").unwrap();
894        
895        f.flush().unwrap();
896        debug!("large to dot loop: {}", self.len());
897    }
898
899    /// Write the graph to a dot file, highlight the nodes which form the 
900    /// "best" path, according to [`PathCompIter`], with the number of occurences 
901    /// as the score and `solid_path` always `true`.
902    /// The nodes are formatted according to [`Node::node_dot_default`].
903    /// 
904    /// ### Arguments: 
905    /// 
906    /// * `path`: path to the output file
907    /// * `edge_label`: closure taking [`Node<K, D>`], the base as a [`u8`], the incoming [`Dir`] of the edge, e.g. [`Node::edge_dot_default`]
908    ///   and if the neighbor is flipped - returns a string containing commands for dot edges, 
909    /// * `colors`: a [`Colors`] with the color settings for the graph
910    /// * `translator`: a [`Translator`] which translates tags or IDs to strings
911    /// * `config`: a [`SummaryConfig`] which contains settings for the graph
912    pub fn to_dot_with_path<P, FE, DI>(&self, path: P, edge_label: &FE, colors: &Colors<'_, D, DI>, translator: &Translator, config: &SummaryConfig, translate_id_groups: bool)
913    where 
914    P: AsRef<Path>,
915    D: SummaryData<DI>,
916    FE: Fn(&Node<K, D>, u8, Dir, bool) -> String,
917    {
918        let mut f = BufWriter::with_capacity(BUF, File::create(path).expect("error creating dot file"));
919
920        writeln!(&mut f, "digraph {{\nrankdir=\"LR\"\nmodel=subset").unwrap();
921
922        // iterate over components
923        for (component, path) in self.iter_max_path_comp(|d| d.sum().unwrap_or(1) as f32, |_| true) {
924            let hashed_path = path.into_iter().map(|(id, _)| id).collect::<HashSet<usize>>();
925            for node_id in component {
926                self.node_to_dot(
927                    &self.get_node(node_id),
928                    &|node| node.node_dot_default(colors, config, translator, hashed_path.contains(&node_id), translate_id_groups), 
929                    edge_label, 
930                    &mut f
931                );
932            }
933        }
934
935        writeln!(&mut f, "}}").unwrap();
936        
937        f.flush().unwrap();
938        debug!("large to dot loop: {}", self.len());
939    }
940
941    /// Write the graph to a dot file in parallel
942    /// Will write in to n_threads files simultaniously,
943    /// then go though the files and add the contents to a larger file, 
944    /// and delete the small files.
945    /// 
946    /// ### Arguments: 
947    /// 
948    /// * `path`: path to the output file
949    /// * `node_label`: closure taking [`Node<K, D>`] and returning a string containing commands for dot nodes 
950    /// * `edge_label`: closure taking [`Node<K, D>`], the base as a [`u8`], the incoming [`Dir`] of the edge 
951    ///   and if the neighbor is flipped - returns a string containing commands for dot edges, 
952    pub fn to_dot_parallel<P, FN, FE>(&self, path: P, node_label: &FN, edge_label: &FE) 
953    where 
954        D: Sync,
955        K: Sync,
956        P: AsRef<Path> + Display + Sync,
957        FN: Fn(&Node<K, D>) -> String + Sync,
958        FE: Fn(&Node<K, D>, u8, Dir, bool) -> String + Sync,
959    {        
960        let slices = current_num_threads();
961        let n_nodes = self.len();
962        let sz = n_nodes / slices + 1;
963
964        debug!("n_nodes: {}", n_nodes);
965        debug!("sz: {}", sz);
966
967        let mut parallel_ranges = Vec::with_capacity(slices);
968        let mut start = 0;
969        while start < n_nodes {
970            parallel_ranges.push(start..start + sz);
971            start += sz;
972        }
973
974        let last_start = parallel_ranges.pop().expect("no kmers in parallel ranges").start;
975        parallel_ranges.push(last_start..n_nodes);
976        debug!("parallel ranges: {:?}", parallel_ranges);
977
978        let mut files = Vec::with_capacity(current_num_threads());
979
980        for i in 0..parallel_ranges.len() {
981            files.push(format!("{}-{}.dot", path, i));
982        } 
983
984        let pb = ProgressBar::new(self.len() as u64);
985        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
986        pb.set_message(format!("{:<32}", "writing graph to DOT files"));
987    
988        parallel_ranges.into_par_iter().enumerate().for_each(|(i, range)| {
989            let mut f = BufWriter::with_capacity(BUF, File::create(&files[i]).expect("error creating parallel dot file"));
990
991            for i in range {
992                self.node_to_dot(&self.get_node(i), node_label, edge_label, &mut f);
993                pb.inc(1);
994            }
995
996            f.flush().unwrap();
997        });
998        pb.finish_and_clear();
999
1000        let mut out_file = BufWriter::with_capacity(BUF, File::create(path).expect("error creating combined dot file"));
1001
1002        writeln!(&mut out_file, "digraph {{\nrankdir=\"LR\"\nmodel=subset").unwrap();
1003
1004        let pb = ProgressBar::new(files.len() as u64);
1005        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1006        pb.set_message(format!("{:<32}", "combining files"));
1007
1008        for file in files.iter().progress_with(pb) {
1009            let open_file = File::open(file).expect("error opening parallel dot file");
1010            let mut reader = BufReader::new(open_file);
1011            let mut buffer = [0; BUF];
1012
1013            loop {
1014                let linecount = reader.read(&mut buffer).unwrap();
1015                if linecount == 0 { break }
1016                out_file.write_all(&buffer[..linecount]).unwrap();
1017            }
1018
1019            remove_file(file).unwrap();
1020        }
1021
1022        writeln!(&mut out_file, "}}").unwrap();
1023
1024        out_file.flush().unwrap();
1025
1026
1027    }
1028
1029
1030    /// Write part of the graph to a dot file
1031    /// 
1032    /// ### Arguments: 
1033    /// 
1034    /// * `path`: path to the output file
1035    /// * `node_label`: closure taking [`Node<K, D>`] and returning a string containing commands for dot nodes 
1036    /// * `edge_label`: closure taking [`Node<K, D>`], the base as a [`u8`], the incoming [`Dir`] of the edge 
1037    ///   and if the neighbor is flipped - returns a string containing commands for dot edges, 
1038    /// * `nodes`: [`Vec<usize>`] listing all IDs of nodes which should be included
1039    pub fn to_dot_partial<P, FN, FE>(&self, path: P, node_label: &FN, edge_label: &FE, nodes: &[usize]) 
1040    where 
1041        P: AsRef<Path>,
1042        FN: Fn(&Node<K, D>) -> String,
1043        FE: Fn(&Node<K, D>, u8, Dir, bool) -> String,
1044    {
1045        let mut f = BufWriter::with_capacity(BUF, File::create(path).expect("error creating dot file"));
1046
1047        let pb = ProgressBar::new(nodes.len() as u64);
1048        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1049        pb.set_message(format!("{:<32}", "writing graph to DOT file"));
1050
1051        writeln!(&mut f, "digraph {{\nrankdir=\"LR\"\nmodel=subset").unwrap();
1052        for i in nodes.iter().progress_with(pb) {
1053            self.node_to_dot(&self.get_node(*i), node_label, edge_label, &mut f);
1054        }
1055        writeln!(&mut f, "}}").unwrap();
1056
1057        f.flush().unwrap();
1058
1059        debug!("large to dot loop: {}", self.len());
1060    }
1061
1062    fn node_to_gfa<F: Fn(&Node<'_, K, D>) -> String>(
1063        &self,
1064        node: &Node<'_, K, D>,
1065        w: &mut dyn Write,
1066        tag_func: Option<&F>,
1067    ) -> Result<(), Error> {
1068        match tag_func {
1069            Some(f) => {
1070                let tags = (f)(node);
1071                writeln!(
1072                    w,
1073                    "S\t{}\t{}\t{}",
1074                    node.node_id,
1075                    node.sequence().to_dna_string(),
1076                    tags
1077                )?;
1078            }
1079            _ => writeln!(
1080                w,
1081                "S\t{}\t{}",
1082                node.node_id,
1083                node.sequence().to_dna_string()
1084            )?,
1085        }
1086
1087        for (_, target, dir, _) in node.l_edges() {
1088            if target >= node.node_id {
1089                let to_dir = match dir {
1090                    Dir::Left => "+",
1091                    Dir::Right => "-",
1092                };
1093                writeln!(
1094                    w,
1095                    "L\t{}\t-\t{}\t{}\t{}M",
1096                    node.node_id,
1097                    target,
1098                    to_dir,
1099                    K::k() - 1
1100                )?;
1101            }
1102        }
1103
1104        for (_, target, dir, _) in node.r_edges() {
1105            if target > node.node_id {
1106                let to_dir = match dir {
1107                    Dir::Left => "+",
1108                    Dir::Right => "-",
1109                };
1110                writeln!(
1111                    w,
1112                    "L\t{}\t+\t{}\t{}\t{}M",
1113                    node.node_id,
1114                    target,
1115                    to_dir,
1116                    K::k() - 1
1117                )?;
1118            }
1119        }
1120
1121        Ok(())
1122    }
1123
1124    /// Write the graph to GFA format
1125    pub fn to_gfa<P: AsRef<Path>>(&self, gfa_out: P) -> Result<(), Error> {
1126        let wtr = BufWriter::with_capacity(BUF, File::create(gfa_out).expect("error creating gfa file"));
1127        self.write_gfa(&mut std::io::BufWriter::new(wtr))
1128    }
1129
1130    pub fn write_gfa(&self, wtr: &mut impl Write) -> Result<(), Error> {
1131        writeln!(wtr, "H\tVN:Z:debruijn-rs")?;
1132
1133        type DummyFn<K, D> = fn(&Node<'_, K, D>) -> String;
1134        let dummy_opt: Option<&DummyFn<K, D>> = None;
1135
1136        let pb = ProgressBar::new(self.len() as u64);
1137        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1138        pb.set_message(format!("{:<32}", "writing graph to GFA file"));
1139
1140        for i in (0..self.len()).progress_with(pb) {
1141            let n = self.get_node(i);
1142            self.node_to_gfa(&n, wtr, dummy_opt)?;
1143        }
1144
1145        wtr.flush().unwrap();
1146
1147        Ok(())
1148    }
1149
1150    /// Write the graph to GFA format
1151    pub fn to_gfa_with_tags<P: AsRef<Path>, F: Fn(&Node<'_, K, D>) -> String>(
1152        &self,
1153        gfa_out: P,
1154        tag_func: F,
1155    ) -> Result<(), Error> {
1156        let mut wtr = BufWriter::with_capacity(BUF, File::create(gfa_out).expect("error creatinf gfa file"));
1157        writeln!(wtr, "H\tVN:Z:debruijn-rs")?;
1158
1159        let pb = ProgressBar::new(self.len() as u64);
1160        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1161        pb.set_message(format!("{:<32}", "writing graph to GFA file"));
1162
1163        for i in (0..self.len()).progress_with(pb) {
1164            let n = self.get_node(i);
1165            self.node_to_gfa(&n, &mut wtr, Some(&tag_func))?;
1166        }
1167
1168        wtr.flush().unwrap();
1169
1170        Ok(())
1171    }
1172
1173    /// Write the graph to GFA format, with multithreading, 
1174    /// pass `tag_func=None` to write without tags
1175    pub fn to_gfa_otags_parallel<P: AsRef<Path> + Display + Sync, F: Fn(&Node<'_, K, D>) -> String + Sync>(
1176        &self,
1177        gfa_out: P,
1178        tag_func: Option<&F>,
1179    ) -> Result<(), Error> 
1180    where 
1181    K: Sync,
1182    D: Sync,
1183    {
1184        // split into ranges according to thread count
1185        let slices = current_num_threads();
1186        let n_nodes = self.len();
1187        let sz = n_nodes / slices + 1;
1188
1189        debug!("n_nodes: {}", n_nodes);
1190        debug!("sz: {}", sz);
1191
1192        let mut parallel_ranges = Vec::with_capacity(slices);
1193        let mut start = 0;
1194        while start < n_nodes {
1195            parallel_ranges.push(start..start + sz);
1196            start += sz;
1197        }
1198
1199        let last_start = parallel_ranges.pop().expect("no kmers in parallel ranges").start;
1200        parallel_ranges.push(last_start..n_nodes);
1201        debug!("parallel ranges: {:?}", parallel_ranges);
1202
1203        let mut files = Vec::with_capacity(current_num_threads());
1204
1205        for i in 0..parallel_ranges.len() {
1206            files.push(format!("{}-{}.gfa", gfa_out, i));
1207        } 
1208
1209        let pb = ProgressBar::new(self.len() as u64);
1210        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1211        pb.set_message(format!("{:<32}", "writing graph to GFA file"));
1212        
1213        
1214        parallel_ranges.into_par_iter().enumerate().for_each(|(i, range)| {
1215            let mut wtr = BufWriter::with_capacity(BUF, File::create(&files[i]).expect("error creating parallel gfa file"));
1216
1217            for i in range {
1218                let n = self.get_node(i);
1219                self.node_to_gfa(&n, &mut wtr, tag_func).unwrap();
1220                pb.inc(1);
1221            }
1222
1223            wtr.flush().unwrap();
1224        });
1225
1226        pb.finish_and_clear();
1227
1228        // combine files
1229        let mut out_file = BufWriter::with_capacity(BUF, File::create(format!("{}.gfa", gfa_out)).expect("error creating combined gfa file"));
1230        writeln!(out_file, "H\tVN:Z:debruijn-rs")?;
1231
1232        let pb = ProgressBar::new(files.len() as u64);
1233        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1234        pb.set_message(format!("{:<32}", "combining files"));
1235
1236        for file in files.iter() {
1237            let open_file = File::open(file).expect("error opening parallel gfa file");
1238            let mut reader = BufReader::new(open_file);
1239            let mut buffer = [0; BUF];
1240
1241            loop {
1242                let linecount = reader.read(&mut buffer).unwrap();
1243                if linecount == 0 { break }
1244                out_file.write_all(&buffer[..linecount]).unwrap();
1245            }
1246
1247            remove_file(file).unwrap();
1248        }
1249
1250        out_file.flush().unwrap();
1251
1252        Ok(())
1253    }
1254
1255    /// Write the graph to GFA format
1256    pub fn to_gfa_partial<P: AsRef<Path>, F: Fn(&Node<'_, K, D>) -> String>(&self, gfa_out: P, tag_func: Option<&F>, nodes: Vec<usize>) -> Result<(), Error> {
1257        let mut wtr = BufWriter::with_capacity(BUF, File::create(gfa_out).expect("error creating gfa file"));
1258        writeln!(wtr, "H\tVN:Z:debruijn-rs")?;
1259
1260        let pb = ProgressBar::new(self.len() as u64);
1261        pb.set_style(ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-"));
1262        pb.set_message(format!("{:<32}", "writing graph to GFA file"));
1263
1264        for i in nodes.into_iter().progress_with(pb) {
1265            let n = self.get_node(i);
1266            self.node_to_gfa(&n, &mut wtr, tag_func)?;
1267        }
1268
1269        wtr.flush().unwrap();
1270
1271        Ok(())    
1272    }
1273
1274    fn node_to_tsv<W: Write, F>(&self, writer: &mut W, node_id: usize, data_format: F) -> Result<(), Box<dyn std::error::Error>> 
1275    where 
1276        F: Fn(&Node<'_, K, D>) -> String,
1277    {
1278
1279        let node = self.get_node(node_id);
1280        let l_e = node.l_edges();
1281        let r_e = node.r_edges();
1282
1283        
1284        if self.base.stranded {
1285            // format: node id    l nb    r nb    seq    data
1286            let l_nb = l_e.iter().map(|(_b, nb, _d, _f)| *nb).collect::<Vec<_>>();
1287            let r_nb = r_e.iter().map(|(_b, nb, _d, _f)| *nb).collect::<Vec<_>>();
1288            writeln!(writer, "{node_id}\t{:?}\t{:?}\t{}\t{}", l_nb, r_nb, node.sequence(), data_format(&node))?
1289        } else {
1290            // format: node id    l nb    l inc dir    r nb    r inc dir    seq    data
1291            let l_nb = l_e.iter().map(|(_b, nb, _d, _f)| *nb).collect::<Vec<_>>();
1292            let r_nb = r_e.iter().map(|(_b, nb, _d, _f)| *nb).collect::<Vec<_>>();
1293            let l_nb_dirs = l_e.iter().map(|(_b, _nb, dir, _f)| *dir).collect::<Vec<_>>();
1294            let r_nb_dirs = r_e.iter().map(|(_b, _nb, dir, _f)| *dir).collect::<Vec<_>>();
1295            writeln!(writer, "{node_id}\t{:?}\t{:?}\t{:?}\t{:?}\t{}\t{}", l_nb, l_nb_dirs, r_nb, r_nb_dirs, node.sequence(), data_format(&node))?
1296        }
1297
1298        Ok(())
1299    }
1300
1301    /// save the graph as a tsv file with custom formatting for the node data
1302    pub fn to_tsv<P, F>(&self, path: P, data_format: F) -> Result<(), Box<dyn std::error::Error>> 
1303    where 
1304        F: Fn(&Node<'_, K, D>) -> String,
1305        P: AsRef<Path> + Display,
1306    { 
1307        let mut writer = BufWriter::new(File::create(path)?);
1308
1309        // different format if stranded vs unstranded
1310        if self.base.stranded {
1311            writeln!(writer, "node id\tleft neighbors\tright neighbors\tsequence\tdata")?;
1312        } else {
1313            writeln!(writer, "node id\tleft neighbors\tleft nb incoming dirs\tright neighbors\tright nb incoming dirs\tsequence\tdata")?;
1314        }
1315            
1316        for i in 0..self.len() {
1317            self.node_to_tsv(&mut writer, i, &data_format)?
1318        }
1319        
1320        Ok(())
1321    }
1322
1323    // iterate over graph or partial node IDs while leaving out the last node
1324    fn iter_optional_partial<'a>(&self, partial_nodes: Option<&'a Vec<usize>>) -> Box<dyn Iterator<Item = usize> + 'a> {
1325        if let Some(partial) = partial_nodes {
1326            Box::new(partial[..(partial.len()-1)].iter().copied())
1327        } else {
1328            Box::new(0..(self.len()-1))
1329        }
1330    }
1331
1332    /// write the graph or parts of the graph to a json file to view in 3d
1333    pub fn to_json_3d<P, FN, FE>(&self, 
1334        path: P, 
1335        node_properties: &FN, 
1336        edge_properties: &FE, 
1337        partial_nodes: Option<&'_ Vec<usize>>
1338    ) -> Result<(), Box<dyn std::error::Error>> 
1339        where 
1340        P: AsRef<Path>,
1341        FN: Fn(&Node<K, D>) -> String,
1342        FE: Fn(&Node<K, D>, usize, u8, Dir, bool) -> String,
1343    {
1344        let mut writer = BufWriter::new(File::create(path)?);
1345
1346        writeln!(writer, "{{")?;
1347        writeln!(writer, "\t\"nodes\": [")?;
1348
1349        // write nodes to json
1350
1351        for node_id in self.iter_optional_partial(partial_nodes) {
1352            let node = self.get_node(node_id);
1353            let node_fmt = node_properties(&node);
1354
1355            writeln!(writer, "\t\t{{ {node_fmt} }},")?;
1356        }
1357
1358        // do last node separately because of comma
1359        let last_node_id = match partial_nodes {
1360            Some(partial) => *partial.last().expect("empty parial nodes vector"),
1361            None => self.len() - 1
1362        };
1363
1364        let last_node = self.get_node(last_node_id);
1365        let last_node_fmt = node_properties(&last_node);
1366
1367        writeln!(writer, "\t\t{{ {last_node_fmt} }}")?;
1368
1369        writeln!(writer, "\t],")?;
1370        writeln!(writer, "\t\"links\": [")?;
1371
1372        // write links to json
1373
1374        for node_id in self.iter_optional_partial(partial_nodes) {
1375            let node = self.get_node(node_id);
1376            // write edges to the right
1377            for (base, target_id, dir, flipped) in node.r_edges() {
1378                let edge_fmt = edge_properties(&node, target_id, base, dir, flipped);
1379                writeln!(writer, "\t\t{{ {edge_fmt} }},")?;
1380            }
1381
1382            // if stranded, continue, else also look at left edges
1383            if self.base.stranded { continue; }
1384
1385            // write edges to the right
1386            for (base, target_id, dir, flipped) in node.l_edges() {
1387                let edge_fmt = edge_properties(&node, target_id, base, dir, flipped);
1388                writeln!(writer, "\t\t{{ {edge_fmt} }},")?;
1389            }
1390        }
1391
1392        // eges for last node without comma
1393        // FIXME only last edge should be without comma, not all edges from last node
1394        // write edges to the right
1395        for (base, target_id, dir, flipped) in last_node.r_edges() {
1396            let edge_fmt = edge_properties(&last_node, target_id, base, dir, flipped);
1397            writeln!(writer, "\t\t{{ {edge_fmt} }}")?;
1398        }
1399
1400        // if not stranded also look at left edges
1401        if !self.base.stranded { 
1402            // write edges to the right
1403            for (base, target_id, dir, flipped) in last_node.l_edges() {
1404                let edge_fmt = edge_properties(&last_node, target_id, base, dir, flipped);
1405                writeln!(writer, "\t\t{{ {edge_fmt} }}")?;
1406            }
1407        }
1408
1409        writeln!(writer, "\t]")?;
1410        writeln!(writer, "}}")?;
1411
1412        Ok(())
1413    }
1414
1415    /// Print a text representation of the graph.
1416    pub fn print(&self) {
1417        println!("DebruijnGraph {{ len: {}, K: {} }} :", self.len(), K::k());
1418        for node in self.iter_nodes() {
1419            println!("{:?}", node);
1420        }
1421    }
1422
1423    pub fn print_with_data(&self) {
1424        println!("DebruijnGraph {{ len: {}, K: {} }} :", self.len(), K::k());
1425        for node in self.iter_nodes() {
1426            println!("{:?} ({:?})", node, node.data());
1427        }
1428    }
1429
1430    pub fn max_path_beam<F, F2>(&self, beam: usize, score: F, _solid_path: F2) -> Vec<(usize, Dir)>
1431    where
1432        F: Fn(&D) -> f32,
1433        F2: Fn(&D) -> bool,
1434    {
1435        if self.is_empty() {
1436            return Vec::default();
1437        }
1438
1439        let mut states = Vec::new();
1440
1441        for i in 0..self.len() {
1442            let node = self.get_node(i);
1443
1444            // Initialize beam search on terminal nodes
1445            if node.exts().num_exts_l() == 0 || node.exts().num_exts_r() == 0 {
1446                let dir = if node.exts().num_exts_l() > 0 {
1447                    Dir::Right
1448                } else {
1449                    Dir::Left
1450                };
1451
1452                let status = if node.exts().num_exts_l() == 0 && node.exts().num_exts_r() == 0 {
1453                    Status::End
1454                } else {
1455                    Status::Active
1456                };
1457
1458                let mut path = SmallVec8::new();
1459                path.push((i as u32, dir));
1460
1461                let s = State {
1462                    path,
1463                    status,
1464                    score: score(node.data()),
1465                };
1466                states.push(s);
1467            }
1468        }
1469
1470        // No end nodes -- just start on the first node
1471        if states.is_empty() {
1472            // Make a start
1473            let node = self.get_node(0);
1474            let mut path = SmallVec8::new();
1475            path.push((0, Dir::Left));
1476            states.push(State {
1477                path,
1478                status: Status::Active,
1479                score: score(node.data()),
1480            });
1481        }
1482
1483        // Beam search until we can't find any more expansions
1484        let mut active = true;
1485        while active {
1486            let mut new_states = Vec::with_capacity(states.len());
1487            active = false;
1488
1489            for s in states {
1490                if s.status == Status::Active {
1491                    active = true;
1492                    let expanded = self.expand_state(&s, &score);
1493                    new_states.extend(expanded);
1494                } else {
1495                    new_states.push(s)
1496                }
1497            }
1498
1499            // workaround to sort by descending score - will panic if there are NaN scores
1500            new_states.sort_by(|a, b| (-(a.score)).partial_cmp(&-(b.score)).unwrap());
1501            new_states.truncate(beam);
1502            states = new_states;
1503        }
1504
1505        for (i, state) in states.iter().take(5).enumerate() {
1506            trace!("i:{}  -- {:?}", i, state);
1507        }
1508
1509        // convert back to using usize for node_id
1510        states[0]
1511            .path
1512            .iter()
1513            .map(|&(node, dir)| (node as usize, dir))
1514            .collect()
1515    }
1516
1517    fn expand_state<F>(&self, state: &State, score: &F) -> SmallVec4<State>
1518    where
1519        F: Fn(&D) -> f32,
1520    {
1521        if state.status != Status::Active {
1522            panic!("only attempt to expand active states")
1523        }
1524
1525        let (node_id, dir) = state.path[state.path.len() - 1];
1526        let node = self.get_node(node_id as usize);
1527        let mut new_states = SmallVec4::new();
1528
1529        for (_, next_node_id, incoming_dir, _) in node.edges(dir.flip()) {
1530            let next_node = self.get_node(next_node_id);
1531            let new_score = state.score + score(next_node.data());
1532
1533            let cycle = state
1534                .path
1535                .iter()
1536                .any(|&(prev_node, _)| prev_node == (next_node_id as u32));
1537
1538            let status = if cycle {
1539                Status::Cycle
1540            } else if next_node.edges(incoming_dir.flip()).is_empty() {
1541                Status::End
1542            } else {
1543                Status::Active
1544            };
1545
1546            let mut new_path = state.path.clone();
1547            new_path.push((next_node_id as u32, incoming_dir));
1548
1549            let next_state = State {
1550                path: new_path,
1551                score: new_score,
1552                status,
1553            };
1554
1555            new_states.push(next_state);
1556        }
1557
1558        new_states
1559    }
1560
1561
1562    pub fn iter_components(&'_ self) -> IterComponents<'_, K, D> {
1563        let mut visited: Vec<bool> = Vec::with_capacity(self.len());
1564        let pos = 0;
1565
1566        for _i in 0..self.len() {
1567            visited.push(false);
1568        }
1569
1570        IterComponents { 
1571            graph: self, 
1572            visited, 
1573            pos }
1574    }
1575
1576
1577    /// iteratively returns 2D Vec with node_ids grouped according to the connected components they form
1578    pub fn components_i(&self) -> Vec<Vec<usize>> {
1579        let mut components: Vec<Vec<usize>> = Vec::with_capacity(self.len());
1580        let mut visited: Vec<bool> = Vec::with_capacity(self.len());
1581
1582        for _i in 0..self.len() {
1583            visited.push(false);
1584        }
1585
1586        for i in 0..self.len() {
1587            if !visited[i] {
1588                let comp = self.component_i(&mut visited, i);
1589                components.push(comp);
1590            }
1591        }
1592
1593        components
1594    }
1595
1596    /// recursively detects which nodes form separate graph components
1597    /// returns 2D vector with node ids per component
1598    /// (may lead to stack overflow)
1599    pub fn components_r(&self) -> Vec<Vec<usize>> {
1600        let mut components: Vec<Vec<usize>> = Vec::with_capacity(self.len());
1601        let mut visited: Vec<bool> = Vec::with_capacity(self.len());
1602
1603        for _i in 0..self.len() {
1604            visited.push(false);
1605        }
1606
1607        for i in 0..self.len() {
1608            if !visited[i] {
1609                let mut comp: Vec<usize> = Vec::new();
1610                self.component_r(&mut visited, i, &mut comp);
1611                components.push(comp);
1612            }
1613        }
1614
1615        components
1616
1617    }
1618
1619    fn component_r<'a>(&'a self, visited: &'a mut Vec<bool>, i: usize, comp: &'a mut Vec<usize>) {
1620        
1621        visited[i] = true;
1622        comp.push(i);
1623        let mut edges = self.find_edges(i, Dir::Left);
1624        let mut r_edges = self.find_edges(i, Dir::Right);
1625
1626        edges.append(&mut r_edges);
1627
1628        for (_, edge, _, _) in edges.iter() {
1629            if !visited[*edge] {
1630                self.component_r(visited, *edge, comp);
1631            }
1632        }
1633    }
1634
1635    fn component_i<'a>(&'a self, visited: &'a mut [bool], i: usize) -> Vec<usize> {
1636        let mut edges: Vec<usize> = Vec::new();
1637        let mut comp: Vec<usize> = Vec::new();
1638
1639        edges.push(i);
1640
1641        while let Some(current_edge) = edges.pop() {
1642            if !visited[current_edge] { 
1643                comp.push(current_edge);
1644                visited[current_edge] = true;
1645
1646                let mut l_edges = self.find_edges(current_edge, Dir::Left);
1647                let mut r_edges = self.find_edges(current_edge, Dir::Right);
1648
1649                l_edges.append(&mut r_edges);
1650
1651                for (_, new_edge, _, _) in l_edges.into_iter() {
1652                    if !visited[new_edge] {
1653                        edges.push(new_edge);
1654                    }
1655                }
1656            }
1657        }
1658        comp
1659    }
1660
1661    /// iterate over all edges of the graph, item: (node, ext base, ext dir, target node)
1662    pub fn iter_edges(&self) -> EdgeIter<'_, K, D> {
1663        EdgeIter::new(self)
1664    }
1665
1666    pub fn find_bad_nodes<F: Fn(&Node<'_, K, D>) -> bool>(&self, valid: F) -> Vec<usize> {
1667        let mut bad_nodes = Vec::new();
1668
1669        for (i, node) in enumerate(self.iter_nodes()) {
1670            if !valid(&node) { bad_nodes.push(i); }
1671        }
1672        
1673        bad_nodes
1674    }
1675}
1676
1677impl<K: Kmer, SD: Debug> DebruijnGraph<K, SD> {
1678    pub fn create_colors<'a, 'b: 'a, DI>(&'a self, config: &SummaryConfig, color_mode: ColorMode<'b>) -> Colors<'b, SD, DI> 
1679    where 
1680    SD: SummaryData<DI>,
1681    {
1682        Colors::new(self, config, color_mode)
1683    }
1684    
1685    /// [`crate::EdgeMult`] and [] will contain hanging edges if the nodes were filtered
1686    pub fn fix_edge_data<DI>(&mut self) 
1687    where 
1688        SD: SummaryData<DI>
1689    {
1690        if self.get_node(0).data().edge_mults().is_some() {
1691            for i in 0..self.len() {
1692                self.base.data[i].fix_edge_data(self.base.exts[i]);
1693            }
1694        }
1695    }
1696
1697    /// if there are [`crate::EdgeMult`]s in the data, prune the graph by removing edges that have a low coverage
1698    pub fn filter_edges<DI>(&mut self, min: u32) -> Result<(), String>
1699    where 
1700        SD: SummaryData<DI>
1701    {
1702        // return if there is no edge coverage available
1703        if self.get_node(0).data().edge_mults().is_none() { return Err(String::from("no edge mults available")) };
1704
1705        for i in 0..self.len() {
1706            let em = self.get_node(i).data().edge_mults().expect("shold have em").clone();
1707            let edges = [(Dir::Left, 0), (Dir::Left, 1), (Dir::Left, 2), (Dir::Left, 3), 
1708                (Dir::Right, 0), (Dir::Right, 1), (Dir::Right, 2), (Dir::Right, 3)];
1709            
1710            for (dir, base) in edges {
1711                if min > em.edge_mult(base, dir) {
1712                    // remove invalid ext from node
1713                    let ext = self.base.exts[i].remove(dir, base);
1714                    self.base.exts[i] = ext;
1715                }
1716            }
1717        }
1718
1719        Ok(())
1720    }
1721
1722    /// if a node has a connection to a high quality node and low quality nodes 
1723    /// in the same direction, remove the connections to the low quality ndoes
1724    pub fn remove_lq_splits<DI>(&mut self, min_quality: BaseQuality) -> Result<(), String>
1725    where 
1726        SD: SummaryData<DI>
1727    {
1728        // check we do indeed have quality and graph is stranded
1729        if self.get_node(0).data().quality().is_none() { return Err(String::from("no quality scores available")); }
1730
1731        // iterate over all nodes and look in both directions if there is a low quality node splitting off
1732        for (node_id, out_dir) in (0..self.len()).flat_map(|id| [(id, Dir::Right), (id, Dir::Left)]) {
1733            let out_edges = self.get_node(node_id).edges(out_dir);
1734
1735            // check for split
1736            if out_edges.len() < 2 {
1737                // no split, move on
1738                continue;
1739            }
1740
1741            // get qualities 
1742            let nb_qualities = out_edges
1743                .iter()
1744                .map(|(_, nb_id, nb_in_dir, _)| (*nb_id, *nb_in_dir, self
1745                    .get_node(*nb_id)
1746                    .data()
1747                    .quality()
1748                    .unwrap()
1749                )).collect::<Vec<_>>();
1750
1751            // check for good neighbor
1752            let has_good_nb = nb_qualities.iter()
1753                .any(|(_, _, quality)| *quality >= min_quality);
1754
1755            if !has_good_nb {
1756                // split but no good path, move on
1757                continue;
1758            }
1759
1760            // remove split with low quality
1761            for (nb_id, nb_in_dir, _) in nb_qualities.iter().filter(|(_, _, quality)| *quality < min_quality ) {
1762                let path = vec![(node_id, out_dir.flip()), (*nb_id, *nb_in_dir)];
1763                if self.remove_path(path).is_err() {
1764                    warn!("lq tip path could not be removed")
1765                }
1766            }
1767        }
1768
1769        Ok(())
1770    }
1771
1772    /// remove bubbles/ladders and tips in which one path has a quality lower than the given `min_quality`. 
1773    /// The method continues searching on a path for a maximum of (`max_path_fac`` * k - 1).
1774    pub fn remove_lq_paths<DI>(&mut self, min_quality: BaseQuality, max_path_fac: usize) -> Result<(), String>
1775    where
1776        SD: SummaryData<DI>
1777    {
1778        // check we do indeed have quality and graph is stranded
1779        if self.get_node(0).data().quality().is_none() { return Err(String::from("no quality scores available")); }
1780
1781        let min_path = 2 * K::k() - 1;
1782        let max_path = max_path_fac * K::k() - 1;
1783
1784        // iterate over nodes
1785        for (node_id, out_dir) in (0..self.len()).flat_map(|id| [(id, Dir::Right), (id, Dir::Left)]) {
1786            // check if node has multile outs, at least one with bad quality and one with good quality
1787            let node_out_edges = self.get_node(node_id).edges(out_dir);
1788
1789            let good_neighbors = node_out_edges.iter()
1790                .map(|(_, target_id, target_in_dir, _)| (*target_id, target_in_dir))
1791                .filter(|(target_id, _)| self.get_node(*target_id)
1792                    .data()
1793                    .quality()
1794                    .unwrap() >= min_quality
1795                ).collect::<Vec<_>>();
1796
1797            let bad_neighbors = node_out_edges.iter()
1798                .map(|(_, target_id, target_in_dir, _)| (*target_id, target_in_dir))
1799                .filter(|(target_id, _)| self.get_node(*target_id)
1800                    .data()
1801                    .quality()
1802                    .unwrap() < min_quality
1803                ).collect::<Vec<_>>();
1804
1805            if good_neighbors.is_empty() | bad_neighbors.is_empty() { continue; }
1806
1807            // follow the bad quality paths until we reach nodes with high quality again (or max search radius)
1808            let mut possible_paths = Vec::new();
1809            let mut tips = Vec::new();
1810
1811            for (bn, bn_in_dir) in bad_neighbors {
1812                let mut current_in_dir = *bn_in_dir; // in an unstranded graph, the out dir can change
1813                let mut current_node_id = bn;
1814                let mut path_groups = vec![vec![(node_id, out_dir.flip())]];
1815                let mut path_length = K::k() - 1;
1816
1817                let mut state = LadderState::Singular;
1818                let mut q_state_high = false;
1819
1820                loop {
1821                    let current_node = self.get_node(current_node_id);
1822                    let out_edges = current_node.edges(current_in_dir.flip());
1823
1824                    // add current node length to path
1825                    path_length += current_node.len() - K::k() + 1;
1826
1827                    // check if path has reached max length -> interrupt
1828                    if path_length > max_path {
1829                        break;
1830                    }
1831                    
1832                    // check state and add current node to path
1833                    let path_index = path_groups.len() - 1;
1834
1835                    // if in singular state now or before, add node to path
1836                    if matches!(state, LadderState::Singular) { 
1837                        path_groups[path_index].push((current_node_id, current_in_dir));
1838                    }
1839
1840                    // check if we increase ladder state
1841                    let q_increase = (current_node.data().quality().unwrap() >= min_quality) & !q_state_high;
1842                    let mult_increase = current_node.edges(current_in_dir).len() > 1;
1843                    
1844                    if q_increase {
1845                        q_state_high = true
1846                    }
1847
1848                    if q_increase | mult_increase {
1849                        match state {
1850                            LadderState::Singular => {
1851                                state = LadderState::Double;
1852                            }
1853                            LadderState::Double => () // ignore
1854                        };
1855                    }
1856
1857                    // check if we decrease ladder state
1858                    let q_decrease = (current_node.data().quality().unwrap() < min_quality) & q_state_high;
1859                    let mult_decrease = out_edges.len() > 1;
1860
1861                    if q_decrease {
1862                        q_state_high = false;
1863                    }
1864
1865                    if q_decrease | mult_decrease {
1866                        match state {
1867                            LadderState::Singular => (), // ignore
1868                            LadderState::Double => {
1869                                state = LadderState::Singular;
1870                                path_groups.push(vec![(current_node_id, current_in_dir)]); // start new path group
1871                            }
1872                        }
1873                    }
1874
1875                    // check if we have met end criterium -> save path
1876                    let quality_req =  current_node.data().quality().unwrap() >= min_quality;
1877                    let len_req = path_length >= min_path;
1878                    // path is a simple tip -> save as tip (does not have to meet length requirement)
1879                    let is_tip = out_edges.is_empty() & (path_groups.len() == 1); // TODO maybe remove req 2 in future
1880
1881                    if quality_req & len_req {
1882                        possible_paths.push(path_groups);
1883                        break;
1884                    } else if is_tip {
1885                        tips.push(path_groups);
1886                        break;
1887                    }
1888
1889                    // we have not met the conditions and keep moving
1890
1891                    // find next node
1892                    let (next_node_id, next_in_dir)  = if out_edges.len() == 1 {
1893                        let (_, next_node_id, next_in_dir, _) = out_edges[0];
1894                        (next_node_id, next_in_dir)
1895                    } else if out_edges.is_empty() {
1896                        // dead end which has not qualified as tip
1897                        break;
1898                    } else {
1899                        // choose the lowest quality path
1900
1901                        let worst_neighbor = out_edges.iter()
1902                            .map(|(_, target_id, target_in_dir, _)| (*target_id, *target_in_dir, self.get_node(*target_id)
1903                                .data()
1904                                .quality()
1905                                .unwrap())
1906                            )
1907                            .min_by(|(_, _, q_a), (_, _, q_b)| q_a.cmp(q_b));
1908
1909                        if let Some((worst_nb_id, worst_nb_inc_dir, _)) = worst_neighbor {
1910                            (worst_nb_id, worst_nb_inc_dir)
1911                        } else {
1912                            break;
1913                        }
1914                    };
1915
1916                    current_node_id = next_node_id;
1917                    current_in_dir = next_in_dir;
1918                }
1919            }
1920
1921            let possible_targets = possible_paths.iter().map(|p| p.last().unwrap().last().unwrap().0).collect::<Vec<_>>();
1922            let mut confirmed_targets = Vec::new();
1923            // follow the good quality paths until we reach a possible target node (or max search radius)
1924            // if we reached a target node, send bad path to be removed from graph
1925            for (gn, gn_in_dir) in good_neighbors {
1926                let mut path_length = K::k() - 1;
1927                let mut current_node_id = gn;
1928                let mut current_in_dir = *gn_in_dir;
1929
1930                loop {
1931                    // check if we have exceeded the search radius
1932                    if path_length > max_path {
1933                        break;
1934                    }
1935
1936                    // add current node length to path length
1937                    let current_node = self.get_node(current_node_id);
1938                    path_length += current_node.len() - K::k() + 1;
1939
1940                    // check if we have found a target
1941                    if possible_targets.contains(&current_node_id) {
1942                        confirmed_targets.push(current_node_id);
1943                        break;
1944                    }
1945
1946                    // look for next node
1947                    let out_edges = current_node.edges(current_in_dir.flip());
1948
1949                    let (next_node_id, next_in_dir) = if out_edges.len() == 1 {
1950                        let (_, next_node_id, next_in_dir, _) = out_edges[0];
1951                        (next_node_id, next_in_dir)
1952                    } else if out_edges.is_empty() {
1953                        // dead end, break
1954                        break;
1955                    } else {
1956                        // use node with highest quality
1957                        // TODO future: use read mapping?
1958                        let good_neighbor = out_edges.iter()
1959                            .map(|(_, target_id, target_in_dir, _)| (*target_id, target_in_dir, self.get_node(*target_id)
1960                                .data()
1961                                .quality()
1962                                .unwrap()))
1963                            .max_by(|(_, _, q_a), (_, _, q_b)| q_a.cmp(q_b));
1964                        if let Some((best_nb_id, best_nb_in_dir, _)) = good_neighbor {
1965                            (best_nb_id, *best_nb_in_dir)
1966                        } else {
1967                            break;
1968                        }
1969                    };
1970
1971                    current_node_id = next_node_id;
1972                    current_in_dir = next_in_dir;
1973                }
1974            }
1975
1976            // check if we have found end nodes of possible paths by following good quality paths
1977            // if so, remove path
1978            for path_group in possible_paths {
1979                let target = path_group.last().unwrap().last().unwrap().0;
1980
1981                if confirmed_targets.contains(&target) {
1982                    for path in path_group {
1983                        if let Err(_err) = self.remove_path(path.clone()) {
1984                            warn!("lq ladder partial path could not be removed, likely cause: loop, edges were already removed. parital path: {:?}", path)
1985                        }
1986                    }
1987                }
1988            }
1989
1990            // remove tip paths
1991            for path_group in tips {
1992                let path = path_group.into_iter().next().expect("empty tip path found");
1993                if let Err(_err) = self.remove_path(path.clone()) {
1994                    warn!("lq tip partial path could not be removed, likely cause: loop, edges were already removed. parital path: {:?}", path)
1995                }
1996            }
1997
1998        }
1999
2000        Ok(())
2001    }
2002
2003    /// remove bubbles/ladders and tips in which one path has a lower coverage than the alternative path. 
2004    /// The method continues searching on a path for a maximum of (`max_path_fac`` * k - 1).
2005    /// The path is only removed if the average coverage of the lower path is below `max_avg_low_cov` and the 
2006    /// average coverage is at least `min_diff_factor` times higher.
2007    pub fn remove_lc_paths<DI>(&mut self, max_path_fac: usize, min_diff_factor: u32, max_avg_low_cov: f32) -> Result<(), String>
2008    where
2009        SD: SummaryData<DI>
2010    {
2011        // state is switched if coverage rises by 20% + 2 (so it's at least 2 more)
2012        // nodes with higher state are kept connected together
2013        // tests have shown that increase in coverage does not necessarily indicate 
2014        // a multiplicity change
2015        // however, we are trying to avoid false positives - false negatives will 
2016        // most likely be cut off from the component or would be removed by remove_tips
2017        // as a next step
2018        // increasing these values will increase the number of removed edges
2019        const COV_STATE_FACTOR: f32 = 0.2; 
2020        const COV_STATE_ADD: f32 = 2.;
2021
2022        // check we do indeed have the edge coverage
2023        if self.get_node(0).data().edge_mults().is_none() { return Err(String::from("no quality scores available")); }
2024
2025        let min_path = 2 * K::k() - 1;
2026        let max_path = max_path_fac * K::k() - 1;
2027
2028        // iterate over nodes
2029        for (node_id, start_out_dir) in (0..self.len()).flat_map(|id| [(id, Dir::Right), (id, Dir::Left)]) {
2030
2031            let node = self.get_node(node_id);
2032
2033            // check if node has multile outs, at least one with bad quality and one with good quality
2034            let node_out_coverages = node.data().edge_mults().expect("should have em").single_dir(start_out_dir).edge_mults;
2035
2036            let Some((out_max_base, &out_max_cov)) = node_out_coverages.iter().rev().enumerate().filter(|&(_, &c)| c  > 0).max_by(|&(_b1, &c1), &(_b2, c2)| c1.cmp(c2)) else { continue };
2037            let smaller_outs = node_out_coverages.iter().copied().rev().enumerate().filter(|&(_b, c)| (c > 0) & (out_max_cov > c)).collect::<Vec<_>>();
2038
2039            if smaller_outs.is_empty() { continue; }
2040
2041            // follow the bad quality paths until we reach nodes with high quality again (or max search radius)
2042            let mut possible_paths = Vec::new();
2043            let mut tips = Vec::new();
2044
2045            let node_term_kmer = node.sequence().term_kmer::<K>(start_out_dir);
2046
2047            for (start_out_base, start_out_cov) in smaller_outs {
2048                // get nb id
2049                let next_kmer = node_term_kmer.extend(start_out_base as u8, start_out_dir);
2050                let (mut current_node_id, mut current_in_dir, _) = self.find_link(next_kmer, start_out_dir).expect("link should exist"); 
2051
2052                let mut current_cov = start_out_cov as f32;
2053
2054                let mut path_groups = vec![vec![(node_id, start_out_dir.flip())]];
2055                let mut path_length = K::k() - 1;
2056
2057                let mut state = LadderState::Singular;
2058                let mut c_state_high = false;
2059
2060                let mut cov_sum = start_out_cov;
2061                let mut cov_count = 1;
2062
2063                loop {
2064                    let current_node = self.get_node(current_node_id);
2065                    let out_edges = current_node.edges(current_in_dir.flip());
2066
2067                    // add current node length to path
2068                    path_length += current_node.len() - K::k() + 1;
2069
2070                    // check if path has reached max length -> interrupt
2071                    if path_length > max_path {
2072                        break;
2073                    }
2074                    
2075                    // check state and add current node to path
2076                    let path_index = path_groups.len() - 1;
2077
2078                    // if in singular state now or before, add node to path
2079                    if matches!(state, LadderState::Singular) { 
2080                        path_groups[path_index].push((current_node_id, current_in_dir));
2081                    }
2082
2083                    // choose next edge by choosing coverage closest to current coverage
2084
2085                    // get coverage
2086                    // edge cov must be available
2087                    let edge_coverages = current_node.data().edge_mults().expect("must have edge mults");
2088                    let out_edge_coverages = edge_coverages.single_dir(current_in_dir.flip());
2089
2090                    let mut closest_out_cov = None;
2091                    let mut next_node_id = None;
2092                    let mut next_in_dir = None;
2093                    let mut cov_diff = i32::MAX;
2094                    for (base, id, in_dir, _) in out_edges.iter() {
2095                        let cov = out_edge_coverages.edge_mult(*base) as i32;
2096                        let new_cov_diff = (current_cov as i32 - cov).abs();
2097                        if new_cov_diff < cov_diff {
2098                            (closest_out_cov, next_node_id, next_in_dir, cov_diff) = (Some(cov as f32), Some(*id), Some(*in_dir), new_cov_diff);
2099                        }
2100                    }
2101
2102                    // check if coverage increases on high path, similar to c_increase check but with highest outgoing coverage and start cov
2103                    // TODO check if better with min_diff_factor
2104                    // TODO check if we should replace consts with min diff factor
2105                    let highest_cov = out_edge_coverages.edge_mults.iter().max().unwrap_or(&0);
2106                    let coverage_req =  (*highest_cov as f32 > out_max_cov as f32 - out_max_cov as f32 * COV_STATE_FACTOR + COV_STATE_ADD) & !c_state_high; // higer coverage than last edge
2107
2108                    // check if we have met end criterium -> save path
2109                    let len_req = path_length >= min_path; // path long enough
2110                    // path is a simple tip -> save as tip
2111                    let is_tip = out_edges.is_empty() & (path_groups.len() == 1); // TODO maybe remove req 2 in future
2112
2113                    if coverage_req & len_req {
2114                        possible_paths.push((path_groups, cov_sum as f32 / cov_count as f32));
2115                        break;
2116                    } else if is_tip {
2117                        tips.push((path_groups, cov_sum as f32 / cov_count as f32));
2118                        break;
2119                    }
2120
2121                    // check if we have a next node: 
2122                    let (Some(closest_out_cov), Some(next_node_id), Some(next_in_dir)) = (closest_out_cov, next_node_id, next_in_dir) else { 
2123                        break; 
2124                    };
2125
2126                    // check if we increase ladder state
2127                    let c_increase = (closest_out_cov > current_cov + current_cov * COV_STATE_FACTOR + COV_STATE_ADD) & !c_state_high;
2128                    let mult_increase = current_node.edges(current_in_dir).len() > 1;
2129                    
2130                    if c_increase {
2131                        c_state_high = true
2132                    }
2133
2134                    if c_increase | mult_increase {
2135                        match state {
2136                            LadderState::Singular => {
2137                                state = LadderState::Double;
2138                            }
2139                            LadderState::Double => () // ignore
2140                        };
2141                    }
2142
2143                    // check if we decrease ladder state
2144                    // do not check if coverage was already high bc state could be increased for just one node by inc edge
2145                    let c_decrease = closest_out_cov < current_cov + current_cov * COV_STATE_FACTOR + COV_STATE_ADD; //& c_state_high;
2146                    let mult_decrease = out_edges.len() > 1;
2147
2148                    if c_decrease {
2149                        c_state_high = false;
2150                    }
2151
2152                    if c_decrease | mult_decrease {
2153                        match state {
2154                            LadderState::Singular => (), // ignore
2155                            LadderState::Double => {
2156                                state = LadderState::Singular;
2157                                path_groups.push(vec![(current_node_id, current_in_dir)]); // start new path group
2158                            }
2159                        }
2160                    }
2161
2162                    // we have not met the conditions and keep moving
2163                    current_node_id = next_node_id;
2164                    current_in_dir = next_in_dir;
2165
2166                    // if the ladder state is singular, use coverage for avg cov
2167                    if matches!(state, LadderState::Singular) {
2168                        current_cov = closest_out_cov;
2169                        cov_sum += current_cov as u32;
2170                        cov_count += 1;
2171                    }
2172                }
2173            }
2174
2175            let possible_targets = possible_paths.iter().map(|(p, _c)| p.last().unwrap().last().unwrap().0).collect::<Vec<_>>();
2176            let mut confirmed_targets = Vec::new();
2177
2178            // follow the high coverage until we reach a possible target node (or max search radius)
2179            // if we reached a target node, send bad path to be removed from graph
2180            
2181            let next_kmer = node_term_kmer.extend(out_max_base as u8, start_out_dir);
2182            let (mut current_node_id, mut current_in_dir, _) = self.find_link(next_kmer, start_out_dir).expect("link should exist"); 
2183
2184            let mut path_length = K::k() - 1;
2185            let mut current_cov = out_max_cov;
2186
2187            // track coverage 
2188            let mut cov_sum = current_cov;
2189            let mut cov_count = 1;
2190
2191            loop {
2192                // check if we have exceeded the search radius
2193                if path_length > max_path {
2194                    break;
2195                }
2196
2197                // add current node length to path length
2198                let current_node = self.get_node(current_node_id);
2199                path_length += current_node.len() - K::k() + 1;
2200
2201                // check if we have found a target
2202                if possible_targets.contains(&current_node_id) {
2203                    confirmed_targets.push(current_node_id);
2204                    // keep going in case we find another one
2205                }
2206
2207                // look for next node
2208                let out_coverages = current_node.data().edge_mults().expect("must have edge mults").single_dir(current_in_dir.flip()).edge_mults;
2209
2210                // use node with highest coverage
2211                // find highest coverage
2212                let current_term_kmer = current_node.sequence().term_kmer::<K>(current_in_dir.flip());
2213                let Some((next_out_base, next_out_cov)) = out_coverages.iter().rev().enumerate().filter(|&(_, &c)| c > 0).max_by(|&(_b1, &c1), &(_b2, c2)| c1.cmp(c2)) else { break };
2214                let next_kmer = current_term_kmer.extend(next_out_base as u8, current_in_dir.flip());
2215                let (next_node_id, next_in_dir, _) = self.find_link(next_kmer, current_in_dir.flip()).expect("link should exist"); 
2216
2217
2218                current_node_id = next_node_id;
2219                current_in_dir = next_in_dir;
2220                current_cov = *next_out_cov;
2221                cov_sum += current_cov;
2222                cov_count += 1;
2223
2224            }
2225
2226            let avg_cov_high_path = (cov_sum as f32 / cov_count as f32) as u32;
2227
2228            // check if we have found end nodes of possible paths by following high coverage paths
2229            // if so, remove path
2230            for (path_group, avg_low_cov) in possible_paths {
2231                let target = path_group.last().unwrap().last().unwrap().0;
2232
2233                let cov_valid = (avg_low_cov.round() as u32 * min_diff_factor <= avg_cov_high_path) & (avg_low_cov <= max_avg_low_cov);
2234
2235                if confirmed_targets.contains(&target) & cov_valid {
2236                    for path in path_group {
2237                        if let Err(_err) = self.remove_path(path.clone()) {
2238                            warn!("lq ladder partial path could not be removed, likely cause: loop, edges were already removed. parital path: {:?}", path)
2239                        }
2240                    }
2241                }
2242            }
2243
2244            // remove tip paths
2245            for (path_group, avg_tip_cov) in tips {
2246                let path = path_group.into_iter().next().expect("empty tip path found");
2247                let cov_valid = (avg_tip_cov.round() as u32 * min_diff_factor <= avg_cov_high_path) & (avg_tip_cov <= max_avg_low_cov);
2248
2249                if cov_valid {
2250                    if let Err(_err) = self.remove_path(path.clone()) {
2251                        warn!("lq tip partial path could not be removed, likely cause: loop, edges were already removed. parital path: {:?}", path)
2252                    }
2253                }
2254                
2255            }
2256
2257        }
2258
2259        Ok(())
2260    }
2261
2262    /// remove simple ladder structures (bubbles) caused by 1-base sequencing errors from the graph
2263    /// 
2264    /// graph must contain edge mults and be stranded
2265    /// this function will likely leave tips on the graph, so it is recommended to run
2266    /// [`DebruijnGraph::remove_tips`] afterwards
2267    /// 
2268    /// ladder structure refers to bubbles where one side has been compressed into one node
2269    /// but the other has low compression, due to differences in coverage and thus data variance,
2270    /// leading to a ladder-like appearance
2271    pub fn remove_ladders<DI, P>(&mut self, min_diff_factor: u32, max_avg_low_cov: f32, out_path: Option<P>) -> Result<(), String> 
2272    where 
2273        SD: SummaryData<DI>,
2274        P: AsRef<Path>
2275    {
2276        // interrupt if we dont have edge mults
2277        if self.get_node(0).data().edge_mults().is_none() { return Err(String::from("no edge mults available")) };
2278
2279        let mut writer = out_path.map(|path| BufWriter::new(File::create(path).expect("error creating ladder stats file")));
2280        if let Some(wtr) = writer.as_mut() {
2281            writeln!(wtr, "avg high cov,avg low cov,high truth ratio,low truth ratio").unwrap();
2282        }
2283
2284        // iterate over nodes, check in both directions
2285        for (node_id, out_dir) in (0..self.len()).flat_map(|id| [(id, Dir::Right), (id, Dir::Left)]) {
2286            // check if node has outgoing edge(s) with both high and low coverage
2287            let outs = self.get_node(node_id).data().edge_mults().expect("should have em").single_dir(out_dir).edge_mults;
2288
2289            let Some((out_max_base, &out_max_cov)) = outs.iter().rev().enumerate().filter(|&(_, &c)| c  > 0).max_by(|&(_b1, &c1), &(_b2, c2)| c1.cmp(c2)) else { continue };
2290            let smaller_outs = outs.iter().copied().rev().enumerate().filter(|&(_b, c)| (c > 0) & (out_max_cov > c)).collect::<Vec<_>>();
2291
2292            // TODO add factor back in, remove writer
2293
2294            if smaller_outs.is_empty() { continue; }
2295
2296            // follow path with highest coverage until target length is reached
2297            let Some((target_node, avg_high_cov, high_cc)) = self.follow_ladder_path_high(node_id, out_max_base as u8, out_max_cov, out_dir) else { continue; };
2298            
2299            // check all small outs
2300            for (s_base, s_cov) in smaller_outs {
2301                let Some((target_paths, avg_low_cov, low_cc)) = self.follow_ladder_path_low(node_id, s_base as u8, s_cov, out_dir) else { continue; };
2302                let other_target_node = target_paths.last().expect("should have at least one element").last().expect("should have at least two elements").0;
2303
2304                if target_node == other_target_node {
2305                    // the two paths landed on the same node -> remove all edges in the low coverage path
2306                    if (s_cov * min_diff_factor <= out_max_cov) & (avg_low_cov <= max_avg_low_cov) {
2307                        for path in target_paths.iter() {
2308                            if self.remove_path(path.clone()).is_err() {
2309                                warn!("removing ladders: partial path could not be removed, likely cause: loop, edges were already removed. parital path: {:?}", path)
2310                            }
2311                        } 
2312                    }
2313                    
2314                    if let Some(wtr) = writer.as_mut() {
2315                        writeln!(wtr, "{},{},{},{}", avg_high_cov, avg_low_cov, high_cc, low_cc).unwrap();
2316                    }
2317                }
2318            }
2319        }
2320
2321        Ok(())
2322    }
2323
2324    /// follow the presumably erroneous ladder path with low coverage
2325    fn follow_ladder_path_low<DI>(&self, start_node_id: usize, start_ext: u8, start_cov: u32, start_out_dir: Dir) -> Option<(Vec<Vec<(usize, Dir)>>, f32, f32)> 
2326    where SD: SummaryData<DI>
2327    {
2328        // state is switched if coverage rises by 20% + 2 (so it's at least 2 more)
2329        // nodes with higher state are kept connected together
2330        // tests have shown that increase in coverage does not necessarily indicate 
2331        // a multiplicity change
2332        // however, we are trying to avoid false positives - false negatives will 
2333        // most likely be cut off from the component or would be removed by remove_tips
2334        // as a next step
2335        // increasing these values will increase the number of removed edges
2336        const COV_STATE_FACTOR: f32 = 0.2; 
2337        const COV_STATE_ADD: f32 = 2.;
2338
2339        // path, including start and target node
2340        let mut paths = Vec::new();
2341        paths.push(Vec::new());
2342        // add start node and start in dir as first element in path
2343        paths[0].push((start_node_id, start_out_dir.flip()));
2344
2345        let target_length = K::k();
2346        let mut path_length = 0;
2347        let mut n_correct_edges = 0;
2348
2349        // get fist next node
2350        let sequence = self.base.sequences.get(start_node_id);
2351        let term_kmer: K = sequence.term_kmer(start_out_dir);
2352        let next_kmer = term_kmer.extend(start_ext, start_out_dir);
2353        let (mut current_node_id, mut current_in_dir, _) = self.find_link(next_kmer, start_out_dir).expect("link should exist"); 
2354        paths[0].push((current_node_id, current_in_dir));
2355
2356        if self.check_edge_truth(start_node_id, current_node_id) {
2357            n_correct_edges += 1;
2358        }
2359
2360        let mut current_cov = start_cov as f32;
2361        let mut sum_path_cov = start_cov as f32;
2362        let mut coverage_counter = 1; 
2363
2364        // state to track if nodes should be removed or we're suspecting there's another path here
2365        // only track edges for removal while state is Singular
2366        // only track one additional path, else too complicated, return None
2367        // increase state if there is another incoming edge or if the coverage suddenly increases drastically
2368        // decrease state if there is another outgoing edge ot if the coverage suddenly decreases drastically
2369        // if state cannot be increased or decreased further, return None
2370        let mut state = LadderState::Singular;
2371
2372        loop {
2373            // check if current node is "target node", i.e., the path has reached the desired length
2374            match path_length {
2375                len if len == target_length => return Some((paths, (sum_path_cov / coverage_counter as f32), (n_correct_edges as f32 / (path_length + 1) as f32))), // path has target length
2376                len if len > target_length => return None, // path has surpassed desired length => invalid
2377                _ => () // path has not yet reached desired length, continue
2378            }
2379
2380            // since current node is not target node, add length to path
2381            let len = self.get_node(current_node_id).len() - (K::k() - 1);
2382            path_length += len;
2383
2384            // get current node
2385            let current_node = self.get_node(current_node_id);
2386            
2387            // get edges
2388            let in_edges = current_node.edges(current_in_dir);
2389            let out_edges = current_node.edges(current_in_dir.flip());
2390
2391            // get coverage
2392            // edge cov must be available
2393            let out_edge_coverages = current_node.data().edge_mults().expect("must have edge mults").single_dir(current_in_dir.flip());
2394
2395            // choose next edge by choosing coverage closest to current coverage
2396            let mut out_ext = None;
2397            let mut next_node_id = None;
2398            let mut next_in_dir = None;
2399            let mut cov_diff = i32::MAX;
2400            for (e, id, in_dir, _) in out_edges.iter() {
2401                let cov = out_edge_coverages.edge_mult(*e) as i32;
2402                let new_cov_diff = (current_cov as i32 - cov).abs();
2403                if new_cov_diff < cov_diff {
2404                    (out_ext, next_node_id, next_in_dir, cov_diff) = (Some(*e), Some(*id), Some(in_dir), new_cov_diff);
2405                }
2406            }
2407            let (Some(out_ext), Some(next_node_id), Some(next_in_dir)) = (out_ext, next_node_id, next_in_dir) else { return None; };
2408
2409            // ideally, low path nodes should have one incoming and one outgoing edge
2410            // if two incoming edges, increase state from singular to double if possible
2411            match in_edges.len() {
2412                1 => (),
2413                2 => match state {
2414                    LadderState::Singular => state = LadderState::Double,
2415                    LadderState::Double => return None // getting too complicated
2416                }
2417                _ => return None
2418            }
2419
2420            // if two outgoing edges, decrease state from double to singular if possible
2421            // if already singular, 
2422            match out_edges.len() {
2423                1 => (),
2424                2 => match state {
2425                    LadderState::Singular => (), // could return None, but would kill if overlap is only one node long
2426                    LadderState::Double => {
2427                        state = LadderState::Singular;
2428                        paths.push(vec![(current_node_id, current_in_dir)]); // start new path
2429                    }
2430                }
2431                _ => return None
2432            }
2433
2434            // check coverage, in theoretical ladder coverage should be uniform
2435            let coverage = out_edge_coverages.edge_mult(out_ext) as f32;
2436            match state {
2437                LadderState::Singular => {
2438                    // single state: check if next coverage is similar enough to current coverage
2439                    // if way bigger, increase state, else adapt current coverage
2440                    if  coverage > current_cov + current_cov * COV_STATE_FACTOR + COV_STATE_ADD {
2441                        // higher by too much, change state
2442                        state = LadderState::Double;
2443                    } else {
2444                        // in acceptable frame
2445                        current_cov = coverage;
2446                    }
2447                }
2448                LadderState::Double => {
2449                    // double state: if way smaller (back in acceptable frame), decrease state, else dont treat as current coverage
2450                    if coverage < current_cov + current_cov * COV_STATE_FACTOR + COV_STATE_ADD {
2451                        // back in acceptable range
2452                        state = LadderState::Singular;
2453                        paths.push(vec![(current_node_id, current_in_dir)]); // start new path
2454                        current_cov = coverage;
2455                    } // else continue on 
2456                    // TODO check if better to also interrupt if coverage increases further
2457                }
2458            }
2459
2460            // if state singular try to check if edge is "correct", add extra length of current node to it to account for compression
2461            match state {
2462                LadderState::Singular => {
2463                    if self.check_edge_truth(current_node_id, next_node_id) {
2464                        n_correct_edges += len;
2465                    }
2466                }
2467                LadderState::Double => ()
2468            }
2469
2470            // set current node id to next node to be visited
2471            current_node_id = next_node_id;
2472            current_in_dir = *next_in_dir;
2473
2474            // add current node to path, unless state is double
2475            match state {
2476                LadderState::Double => (),
2477                LadderState::Singular => {
2478                    sum_path_cov += current_cov;
2479                    coverage_counter += 1;
2480                    let last_path = paths.len() - 1;
2481                    paths[last_path].push((current_node_id, current_in_dir)); // add node to latest path
2482                }
2483            }
2484        }
2485    }
2486
2487    /// follow a path of the length 2*k - 1 by choosing the edges with the hightest coverage
2488    /// requres the graph to have edge mults
2489    fn follow_ladder_path_high<DI>(&self, start_node_id: usize, start_ext: u8, start_cov: u32, start_out_dir: Dir) -> Option<(usize, f32, f32)> 
2490    where SD: SummaryData<DI>
2491    {
2492
2493        let target_length = K::k();
2494        let mut path_length = 0;
2495        let mut sum_path_cov = start_cov; 
2496        let mut coverage_counter = 1;
2497        let mut n_correct_edges = 0; 
2498
2499        // get fist next node
2500        let sequence = self.base.sequences.get(start_node_id);
2501        let term_kmer: K = sequence.term_kmer(start_out_dir);
2502        let next_kmer = term_kmer.extend(start_ext, start_out_dir);
2503        let (mut current_node_id, mut current_in_dir, _) = self.find_link(next_kmer, start_out_dir).expect("link should exist"); 
2504
2505        if self.check_edge_truth(start_node_id, current_node_id) {
2506            n_correct_edges += 1;
2507        }
2508
2509        loop {
2510            // check if current node is "target node", i.e., the path has reached the desired length
2511            match path_length {
2512                pl if pl == target_length => return Some((current_node_id, (sum_path_cov as f32 / coverage_counter as f32), (n_correct_edges as f32 / (path_length + 1) as f32))), // path has target length
2513                pl if pl > target_length => return None, // path has surpassed desired length => invalid
2514                _ => () // path has not yet reached desired length, continue
2515            }
2516
2517            // since current node is not target node, add length to path
2518            let len = self.get_node(current_node_id).len() - (K::k() - 1);
2519            path_length += len;
2520
2521            // get current node
2522            let current_node = self.get_node(current_node_id);
2523        
2524            // find outgoing edge with highest coverage
2525            let em = current_node.data().edge_mults().expect("must have edge mults").single_dir(current_in_dir.flip()).edge_mults;
2526            let (max_cov_base, &max_cov) = em.iter().rev().enumerate().filter(|&(_, &c)| c > 0).max_by(|&(_b1, &c1), &(_b2, c2)| c1.cmp(c2))?;
2527
2528            // get next node id
2529            let sequence = self.base.sequences.get(current_node_id);
2530            let term_kmer: K = sequence.term_kmer(current_in_dir.flip());
2531            let next_kmer = term_kmer.extend(max_cov_base as u8, current_in_dir.flip());
2532            let (next_node_id, next_in_dir, _) = self.find_link(next_kmer, current_in_dir.flip()).expect("link should exist"); 
2533
2534            // try to check if edge is "correct", add extra length of current node to it to account for compression
2535            if self.check_edge_truth(current_node_id, next_node_id) {
2536                n_correct_edges += len;
2537            }
2538
2539            // set current node id to next node to be visited
2540            current_node_id = next_node_id;
2541            current_in_dir = next_in_dir;
2542            sum_path_cov += max_cov;
2543            coverage_counter += 1;
2544        }
2545    }
2546
2547    /// remove tips from the graph, reqires the graoh to have edge mults and be stranded
2548    /// it is recommended to use this function after [`DebruijnGraph::remove_ladders`], since 
2549    /// the latter will likely leave tips in the graph
2550    pub fn remove_tips<DI, P>(&mut self, min_diff_factor: u32, max_avg_tip_cov: f32, out_path: Option<P>) -> Result<(), String> 
2551    where 
2552        SD: SummaryData<DI>,
2553        P: AsRef<Path>
2554    {
2555        // interrupt if we dont have edge mults
2556        if self.get_node(0).data().edge_mults().is_none() { return Err(String::from("no edge mults available")) };
2557
2558        let mut writer = out_path.map(|path| BufWriter::new(File::create(path).expect("error creating ladder stats file")));
2559        if let Some(wtr) = writer.as_mut() {
2560            writeln!(wtr, "high cov,avg low cov,max true,tip truth ratio,tip len,dir").unwrap();
2561        }
2562
2563        let max_len = K::k();
2564
2565        // iterate over nodes
2566        for node_id in 0..self.len() {
2567            // do for both left and right as "outgoing" direction 
2568            // right for regular outgoing and tips from the read-end
2569            // left for the reverse -> tips from the read-start
2570            for dir in [Dir::Left, Dir::Right] {
2571                let current_node = self.get_node(node_id);
2572                // check if node has outgoing edge(s) with both high and low coverage
2573                let outs = current_node.data().edge_mults().expect("should have em").single_dir(dir).edge_mults;
2574
2575                let Some((out_max_base, &out_max_cov)) = outs.iter().rev().enumerate().filter(|&(_, &c)| c  > 0).max_by(|&(_b1, &c1), &(_b2, c2)| c1.cmp(c2)) else { continue };
2576                let smaller_outs = outs.iter().copied().rev().enumerate().filter(|&(_b, c)| (c > 0) & (out_max_cov > c)).collect::<Vec<_>>();
2577                
2578                if smaller_outs.is_empty() { continue; }
2579
2580                // try and check if max cov edge is correct
2581                let max_connection_correct = {
2582                    let sequence = self.base.sequences.get(node_id);
2583                    let term_kmer: K = sequence.term_kmer(dir);
2584                    let next_kmer = term_kmer.extend(out_max_base as u8, dir);
2585                    let (next_node_id, _, _) = self.find_link(next_kmer, dir).expect("missing link");
2586
2587                    self.check_edge_truth(node_id, next_node_id)
2588                }; // if current node is incorrect, connection is incorrect in any case
2589                
2590                // check all small outs
2591                for (s_base, s_cov) in smaller_outs {
2592                    // path has to be short + unambiguous + consistently low coverage
2593                    let Some((tip_path, avg_tip_coverage, truth_ratio, tip_len)) = self.follow_tip_path(node_id, s_base as u8, s_cov, dir) else { continue; };
2594
2595                    // remove path if coverage below threshold
2596                    if (s_cov * min_diff_factor <= out_max_cov) & (avg_tip_coverage <= max_avg_tip_cov) & (tip_len <= max_len) {
2597                        self.remove_path(tip_path)?;
2598                    }
2599                        
2600                    if let Some(wtr) = writer.as_mut() {
2601                        writeln!(wtr, "{},{},{},{},{},{:?}", out_max_cov, avg_tip_coverage, max_connection_correct, truth_ratio, tip_len, dir).unwrap();
2602                    }
2603                }
2604            }
2605        }
2606        
2607        Ok(())
2608    }
2609
2610    /// follow a tip path, returns path, average coverage and ration of correct to incorrect connections
2611    fn follow_tip_path<DI>(&self, start_node_id: usize, start_ext: u8, start_cov: u32, start_out_dir: Dir) -> Option<(Vec<(usize, Dir)>, f32, f32, usize)> 
2612    where 
2613        SD: SummaryData<DI>
2614    {
2615        const COV_MARGIN: f32 = 0.3;
2616        const COV_ADD_MARGIN: f32 = 4.;
2617
2618        // path, including start and target node
2619        let mut path = Vec::new();
2620        path.push((start_node_id, start_out_dir.flip()));
2621
2622        let mut path_length = 0;
2623        let mut n_correct_edges = 0;
2624
2625        // get fist next node
2626        let sequence = self.base.sequences.get(start_node_id);
2627        let term_kmer: K = sequence.term_kmer(start_out_dir);
2628        let next_kmer = term_kmer.extend(start_ext, start_out_dir);
2629        let (mut current_node_id, mut current_in_dir, _) = self.find_link(next_kmer, start_out_dir).expect("link should exist"); 
2630        path.push((current_node_id, current_in_dir));
2631
2632        if self.check_edge_truth(start_node_id, current_node_id) {
2633            n_correct_edges += 1;
2634        }
2635
2636        let mut current_cov = start_cov as f32;
2637        let mut sum_path_cov = start_cov as f32;
2638        let mut coverage_counter = 1;
2639
2640        loop {     
2641            // get current node
2642            let current_node = self.get_node(current_node_id);
2643
2644            // add length to path
2645            let len = current_node.len() - (K::k() - 1);
2646            path_length += len;
2647
2648            // low path nodes should have only one incoming and one outgoing edge
2649            let in_edges = current_node.edges(current_in_dir);
2650            if in_edges.len() != 1 { return None; }
2651
2652            let out_edges = current_node.edges(current_in_dir.flip());
2653            match out_edges.len() {
2654                oe if oe > 1 => return None,
2655                0 => return Some((path, (sum_path_cov / coverage_counter as f32), (n_correct_edges as f32 / path_length as f32), path_length)),
2656                _ => ()
2657            }
2658
2659            // if edge cov avalable, check for similarity
2660            let (out_ext, next_node_id, next_in_dir, _) = out_edges[0];
2661            if let Some(em) = current_node.data().edge_mults() {
2662                let coverage = em.edge_mult(out_ext, current_in_dir.flip()) as f32;
2663                if coverage < current_cov - current_cov * COV_MARGIN - COV_ADD_MARGIN // extra two for small values
2664                    || coverage > current_cov + current_cov * COV_MARGIN + COV_ADD_MARGIN {
2665                    return None;
2666                } else {
2667                    current_cov = coverage;
2668                }
2669            }
2670
2671
2672
2673            // try to check if edge is "correct", add extra length of current node to it to account for compression
2674            if self.check_edge_truth(current_node_id, next_node_id) {
2675                n_correct_edges += len;
2676            }
2677
2678            // set current node id to next node to be visited
2679            current_node_id = next_node_id;
2680            current_in_dir = next_in_dir;
2681            sum_path_cov += current_cov;
2682            coverage_counter += 1;
2683            // add current node to path
2684            path.push((current_node_id, current_in_dir));
2685        }
2686    }
2687
2688    /// remove the edges (not the nodes) of a path in the graph, the path has to contain 
2689    /// the node ID and the direction the path is coming from moving through the node
2690    fn remove_path<DI>(&mut self, path: Vec<(usize, Dir)>) -> Result<(), String> 
2691    where SD: SummaryData<DI>
2692    {
2693        let mut path_iter = path.clone().into_iter();
2694        let Some((mut current_node_id, mut current_in_dir)) = path_iter.next() else { return Ok(()); };
2695
2696        loop {
2697            // get next node id
2698            let Some((next_node_id, next_in_dir)) = path_iter.next() else { return Ok(()); }; // whole path has been covered
2699            // find base to remove ext leaving the current node
2700            let Some((out_base, _, _, _)) = self.get_node(current_node_id).edges(current_in_dir.flip()).iter().find(|&(_, id, _, _)| *id == next_node_id).copied()
2701                else {
2702                    return Err( format!("no edge to remove (other dir), node 1:  {:?}, node 2: {:?}",
2703                        self.get_node(current_node_id),
2704                        self.get_node(next_node_id)
2705                    ))
2706                };
2707
2708            // remove ext 
2709            self.base.exts[current_node_id] = self.base.exts[current_node_id].remove(current_in_dir.flip(), out_base);
2710        
2711
2712            // find base to remove ext entering the next node
2713            let Some((in_base, _, _, _)) = self.get_node(next_node_id).edges(next_in_dir).iter().find(|&(_, id, _, _)| *id == current_node_id).copied() 
2714                else { 
2715                    return Err( format!("no edge to remove (other dir), node 1:  {:?}, node 2: {:?}",
2716                    self.get_node(current_node_id),
2717                    self.get_node(next_node_id)
2718                ))
2719                };
2720
2721            // remove ext
2722            self.base.exts[next_node_id] = self.base.exts[next_node_id].remove(next_in_dir, in_base); 
2723
2724            // use new exts to fix edge mults and edge maps
2725            self.base.data[current_node_id].fix_edge_data(self.base.exts[current_node_id]);
2726            self.base.data[next_node_id].fix_edge_data(self.base.exts[next_node_id]);
2727
2728
2729            current_node_id = next_node_id;
2730            current_in_dir = next_in_dir;
2731        }
2732    }
2733
2734    /// use ids mapped to nodes to check if the nodes of an edge were mapped to the same id
2735    /// returns false if mapped ids are not available
2736    pub fn check_edge_truth<DI>(&self, node_id_1: usize, node_id_2: usize) -> bool
2737    where 
2738        SD: SummaryData<DI>
2739    {
2740        let mapped_ids_1 = self.get_node(node_id_1).data().mapped_ids();
2741        let mapped_ids_2 = self.get_node(node_id_2).data().mapped_ids();
2742
2743        if let (Some(mids1), Some(mids2)) = (mapped_ids_1, mapped_ids_2) {
2744            let hashed_mi2 = mids2.iter().copied().collect::<HashSet<_>>();
2745            for mid in mids1 {
2746                if hashed_mi2.contains(mid) {
2747                    return true;
2748                }
2749            }
2750        }
2751
2752        false
2753    }
2754
2755    /// use ids mapped to edges to check if the edge is a true edge
2756    /// returns false if mapped ids are not available or if the nodes are not connected
2757    pub fn check_edge_truth_emap<DI>(&self, node_id_1: usize, node_id_2: usize) -> bool
2758    where 
2759        SD: SummaryData<DI>
2760    {
2761        let mapped_ids_1 = self.get_node(node_id_1).data().mapped_edge_ids();
2762
2763        if let Some(emap) = mapped_ids_1 {
2764            let node_1 = self.get_node(node_id_1);
2765
2766            // find node 2 in edges of node 1, check in both dirs
2767            if let Some((out_base, _nb_id, _nb_inc_dir, _flip)) = node_1.l_edges().iter().find(|(_b, nb, _d, _f)| *nb == node_id_2) {
2768                let edge_map = emap.edge_map(*out_base, Dir::Left);
2769                if !edge_map.is_empty() {
2770                    return true
2771                }
2772            } else if let Some((out_base, _nb_id, _nb_inc_dir, _flip)) = node_1.r_edges().iter().find(|(_b, nb, _d, _f)| *nb == node_id_2) {
2773                let edge_map = emap.edge_map(*out_base, Dir::Right);
2774                if !edge_map.is_empty() {
2775                    return true
2776                }
2777            }
2778        }
2779
2780        false
2781    }
2782}
2783
2784
2785#[derive(Debug, Eq, PartialEq)]
2786enum Status {
2787    Active,
2788    End,
2789    Cycle,
2790}
2791
2792#[derive(Debug)]
2793struct State {
2794    path: SmallVec8<(u32, Dir)>,
2795    score: f32,
2796    status: Status,
2797}
2798
2799impl State {}
2800
2801#[derive(Debug, PartialEq, Eq)]
2802pub enum LadderState {
2803    Singular,
2804    Double
2805}
2806
2807/// Iterator over nodes in a `DeBruijnGraph`
2808pub struct NodeIter<'a, K: Kmer + 'a, D: Debug + 'a> {
2809    graph: &'a DebruijnGraph<K, D>,
2810    node_id: usize,
2811}
2812
2813impl<'a, K: Kmer + 'a, D: Debug + 'a> Iterator for NodeIter<'a, K, D> {
2814    type Item = Node<'a, K, D>;
2815
2816    fn next(&mut self) -> Option<Node<'a, K, D>> {
2817        if self.node_id < self.graph.len() {
2818            let node = self.graph.get_node(self.node_id);
2819            self.node_id += 1;
2820            Some(node)
2821        } else {
2822            None
2823        }
2824    }
2825}
2826
2827impl<'a, K: Kmer + 'a, D: Debug + 'a> IntoIterator for &'a DebruijnGraph<K, D> {
2828    type Item = NodeKmer<'a, K, D>;
2829    type IntoIter = NodeIntoIter<'a, K, D>;
2830
2831    fn into_iter(self) -> Self::IntoIter {
2832        NodeIntoIter {
2833            graph: self,
2834            node_id: 0,
2835        }
2836    }
2837}
2838
2839/// Iterator over nodes in a `DeBruijnGraph`
2840pub struct NodeIntoIter<'a, K: Kmer + 'a, D: Debug + 'a> {
2841    graph: &'a DebruijnGraph<K, D>,
2842    node_id: usize,
2843}
2844
2845impl<'a, K: Kmer + 'a, D: Debug + 'a> Iterator for NodeIntoIter<'a, K, D> {
2846    type Item = NodeKmer<'a, K, D>;
2847
2848    fn next(&mut self) -> Option<Self::Item> {
2849        if self.node_id < self.graph.len() {
2850            let node_id = self.node_id;
2851            let node = self.graph.get_node(node_id);
2852            let node_seq = node.sequence();
2853
2854            self.node_id += 1;
2855            Some(NodeKmer {
2856                node_id,
2857                node_seq_slice: node_seq,
2858                phantom_d: PhantomData,
2859                phantom_k: PhantomData,
2860            })
2861        } else {
2862            None
2863        }
2864    }
2865}
2866
2867/// A `DebruijnGraph` node with a reference to the sequence of the node.
2868#[derive(Clone)]
2869pub struct NodeKmer<'a, K: Kmer + 'a, D: Debug + 'a> {
2870    pub node_id: usize,
2871    node_seq_slice: DnaStringSlice<'a>,
2872    phantom_k: PhantomData<K>,
2873    phantom_d: PhantomData<D>,
2874}
2875
2876/// An iterator over the kmers in a `DeBruijn graph node`
2877pub struct NodeKmerIter<'a, K: Kmer + 'a, D: Debug + 'a> {
2878    kmer_id: usize,
2879    kmer: K,
2880    num_kmers: usize,
2881    node_seq_slice: DnaStringSlice<'a>,
2882    phantom_k: PhantomData<K>,
2883    phantom_d: PhantomData<D>,
2884}
2885
2886impl<'a, K: Kmer + 'a, D: Debug + 'a> IntoIterator for NodeKmer<'a, K, D> {
2887    type Item = K;
2888    type IntoIter = NodeKmerIter<'a, K, D>;
2889
2890    fn into_iter(self) -> Self::IntoIter {
2891        let num_kmers = self.node_seq_slice.len() - K::k() + 1;
2892
2893        let kmer = if num_kmers > 0 {
2894            self.node_seq_slice.get_kmer::<K>(0)
2895        } else {
2896            K::empty()
2897        };
2898
2899        NodeKmerIter {
2900            kmer_id: 0,
2901            kmer,
2902            num_kmers,
2903            node_seq_slice: self.node_seq_slice,
2904            phantom_k: PhantomData,
2905            phantom_d: PhantomData,
2906        }
2907    }
2908}
2909
2910impl<'a, K: Kmer + 'a, D: Debug + 'a> Iterator for NodeKmerIter<'a, K, D> {
2911    type Item = K;
2912
2913    fn next(&mut self) -> Option<Self::Item> {
2914        if self.num_kmers == self.kmer_id {
2915            None
2916        } else {
2917            let current_kmer = self.kmer;
2918
2919            self.kmer_id += 1;
2920            if self.kmer_id < self.num_kmers {
2921                let next_base = self.node_seq_slice.get(self.kmer_id + K::k() - 1);
2922                let new_kmer = self.kmer.extend_right(next_base);
2923                self.kmer = new_kmer;
2924            }
2925
2926            Some(current_kmer)
2927        }
2928    }
2929
2930    fn size_hint(&self) -> (usize, Option<usize>) {
2931        (self.num_kmers, Some(self.num_kmers))
2932    }
2933
2934    /// Provide a 'fast-forward' capability for this iterator
2935    /// MPHF will use this to reduce the number of kmers that
2936    /// need to be produced.
2937    fn nth(&mut self, n: usize) -> Option<Self::Item> {
2938        if n <= 4 {
2939            // for small skips forward, shift one base at a time
2940            for _ in 0..n {
2941                self.next();
2942            }
2943        } else {
2944            self.kmer_id += n;
2945            self.kmer = self.node_seq_slice.get_kmer::<K>(self.kmer_id);
2946        }
2947
2948        self.next()
2949    }
2950}
2951
2952/// Marker signifying that NodeKmerIter has a known size.
2953impl<'a, K: Kmer + 'a, D: Debug + 'a> ExactSizeIterator for NodeKmerIter<'a, K, D> {}
2954
2955/// Unbranched sequence in the DeBruijn graph
2956pub struct Node<'a, K: Kmer + 'a, D: 'a> {
2957    pub node_id: usize,
2958    pub graph: &'a DebruijnGraph<K, D>,
2959}
2960
2961impl<'a, K: Kmer, D: Debug> Node<'a, K, D> {
2962    /// Length of the sequence of this node
2963    pub fn len(&self) -> usize {
2964        self.graph.base.sequences.get(self.node_id).len()
2965    }
2966
2967    pub fn is_empty(&self) -> bool {
2968        self.graph.base.sequences.get(self.node_id).is_empty()
2969    }
2970
2971    /// Sequence of the node
2972    pub fn sequence(&self) -> DnaStringSlice<'a> {
2973        self.graph.base.sequences.get(self.node_id)
2974    }
2975
2976    /// Reference to auxiliarly data associated with the node
2977    pub fn data(&self) -> &'a D {
2978        &self.graph.base.data[self.node_id]
2979    }
2980
2981    /// Extension bases from this node
2982    pub fn exts(&self) -> Exts {
2983        self.graph.base.exts[self.node_id]
2984    }
2985
2986    /// Edges leaving the left side of the node in the format
2987    /// (base, target_node id, incoming side of target node, whether target node is flipped)
2988    pub fn l_edges(&self) -> SmallVec4<(u8, usize, Dir, bool)> {
2989        self.graph.find_edges(self.node_id, Dir::Left)
2990    }
2991
2992    /// Edges leaving the right side of the node in the format
2993    /// (base, target_node id, incoming side of target node, whether target node is flipped)
2994    pub fn r_edges(&self) -> SmallVec4<(u8, usize, Dir, bool)> {
2995        self.graph.find_edges(self.node_id, Dir::Right)
2996    }
2997
2998    /// Edges leaving the 'dir' side of the node in the format
2999    /// (base, target_node id, incoming side of target node, whether target node is flipped)
3000    pub fn edges(&self, dir: Dir) -> SmallVec4<(u8, usize, Dir, bool)> {
3001        self.graph.find_edges(self.node_id, dir)
3002    }
3003}
3004
3005// TODO make generic instead of u8 (u8 is sufficient for dbg)
3006impl<K: Kmer, SD: Debug> Node<'_, K, SD>  {
3007    /// get default format for dot edges based on node data
3008    pub fn edge_dot_default<DI>(&self, colors: &Colors<SD, DI>, base: u8, incoming_dir: Dir, flipped: bool) -> String 
3009    where SD: SummaryData<DI>
3010    {
3011        // set color based on dir
3012        let color = match incoming_dir {
3013            Dir::Left => "blue",
3014            Dir::Right => "red"
3015        };
3016        
3017        if let Some(em) = self.data().edge_mults() {
3018            
3019            let dir = if flipped { 
3020                incoming_dir 
3021            } else {
3022                incoming_dir.flip()
3023            };
3024
3025            // set penwidth based on count
3026            let count = em.edge_mult(base, dir);
3027            let penwidth = colors.edge_width(count);
3028
3029            if let Some(emap) = self.data().mapped_edge_ids() {
3030                // if available, also print IDs mapped to edges
3031                let mapped_ids = emap.edge_map(base, dir);
3032
3033                // change color according to edge truth
3034                let color = match incoming_dir {
3035                    Dir::Left => if mapped_ids.is_empty() {"deepskyblue"} else {"blue"},
3036                    Dir::Right => if mapped_ids.is_empty() {"salmon"} else {"red"},
3037                };
3038
3039                format!("[color={color}, penwidth={penwidth}, label=\"{}: {count}, {:?}\", weight={count}]", bits_to_base(base), mapped_ids)
3040            } else {
3041                format!("[color={color}, penwidth={penwidth}, label=\"{}: {count}\", weight={count}]", bits_to_base(base))
3042            }
3043            
3044        } else {
3045            format!("[color={color}, penwidth={}]", colors.edge_width(1)) // since there should be no edge mults, this will return default value
3046        }
3047    }
3048
3049    /// get default format for dot nodes, based on node data
3050    pub fn node_dot_default<DI>(&self, colors: &Colors<SD, DI>, config: &SummaryConfig, translator: &Translator, outline: bool, translate_id_groups: bool) -> String
3051    where SD: SummaryData<DI>
3052    {
3053        // set color based on labels/fold change/p-value
3054        let color = colors.node_color_dot(self.data(), config, outline);
3055        let translate_id_groups = if translate_id_groups { colors.id_group_ids() } else { None };
3056
3057        let data_info = self.data().print(translator, config, translate_id_groups);
3058        const MIN_TEXT_WIDTH: usize = 40;
3059        let wrap = if self.len() > MIN_TEXT_WIDTH { self.len() } else { MIN_TEXT_WIDTH };
3060
3061        let label = textwrap::fill(&format!("id: {}, len: {}, exts: {:?}, seq: {}\n{}", 
3062            self.node_id,
3063            self.len(),
3064            self.exts(),
3065            self.sequence(),
3066            data_info
3067        ), wrap);
3068
3069        format!("[{color}, label=\"{label}\"]")
3070    }
3071
3072    // get the default properties for json edges
3073    pub fn edge_json_default<DI>(&self, target_node_id: usize, base: u8, incoming_dir: Dir, flipped: bool) -> String 
3074    where SD: SummaryData<DI>
3075    {
3076        // set color based on dir
3077        let dir = match incoming_dir {
3078            Dir::Right => 0,
3079            Dir::Left => 1
3080        };
3081
3082        // get base in other dir
3083        let target_node = self.graph.get_node(target_node_id);
3084        let nb_base = if self.graph.base.stranded {
3085            // only look at left edges of target node
3086            let Some(nb_base) = target_node.l_edges().iter().filter_map(|(b, id, _in_dir, _flip)|
3087                if *id == self.node_id {
3088                    Some(*b)
3089                } else { None }
3090            ).next() else { panic!("missing neighbor") };
3091            nb_base
3092        } else {
3093            // look at edges in either direction
3094            let Some(nb_base) = target_node.r_edges().iter().chain(target_node.l_edges().iter()).filter_map(|(b, id, _in_dir, _flip)|
3095                if *id == self.node_id {
3096                    Some(*b)
3097                } else { None }
3098            ).next() else { panic!("missing neighbor") };
3099            nb_base
3100        };
3101        
3102        let value = if let Some(em) = self.data().edge_mults() {
3103            
3104            let dir = if flipped { 
3105                incoming_dir 
3106            } else {
3107                incoming_dir.flip()
3108            };
3109
3110            let count = em.edge_mult(base, dir);
3111
3112            format!(", \"strength\": {count}")
3113        } else {
3114            String::from("")
3115        };
3116
3117        format!("\"source\": {}, \"target\": {target_node_id}, \"source_b\": \"{}\", \"target_b\": \"{}\", \"dir\": {dir}{value}",
3118            self.node_id,
3119            bits_to_base(base),
3120            bits_to_base(nb_base),
3121        )
3122    }
3123
3124    /// get default properties for json nodes, based on node data
3125    pub fn node_json_default<DI>(&self, colors: &Colors<SD, DI>, config: &SummaryConfig, translator: &Translator, translate_id_groups: bool) -> String
3126    where SD: SummaryData<DI>
3127    {
3128        // set hue based on node data
3129        let hue = colors.hue_json(self.data(), config);
3130        let translate_id_groups = if translate_id_groups { colors.id_group_ids() } else { None };
3131
3132        let data_info = self.data().print_json(translator, config, translate_id_groups);
3133
3134        format!("\"id\": {}, \"len\": {}, \"seq\": \"{}\", \"hue\": {hue}, {data_info}",
3135            self.node_id,
3136            self.len(),
3137            self.sequence(),
3138        )
3139    }
3140}
3141
3142impl<K: Kmer, D> fmt::Debug for Node<'_, K, D>
3143where
3144    D: Debug,
3145{
3146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3147        write!(
3148            f,
3149            "Node {{ id:{}, Exts: {:?}, L:{:?} R:{:?}, Seq: {:?}, Data: {:?} }}",
3150            self.node_id,
3151            self.exts(),
3152            self.l_edges(),
3153            self.r_edges(),
3154            self.sequence().len(),
3155            self.data()
3156        )
3157    }
3158}
3159
3160pub struct IterComponents<'a, K: Kmer, D> {
3161    graph: &'a DebruijnGraph<K, D>,
3162    visited: Vec<bool>,
3163    pos: usize,
3164}
3165
3166impl<K: Kmer, D: Debug> Iterator for IterComponents<'_, K, D> {
3167    type Item = Vec<usize>;
3168    fn next(&mut self) -> Option<Self::Item> {
3169        while self.pos < self.graph.len() {
3170            if !self.visited[self.pos] {
3171                let comp = self.graph.component_i(&mut self.visited, self.pos);
3172                self.pos += 1;
3173                return Some(comp)
3174            } else {
3175                self.pos += 1;
3176            }
3177        }
3178        assert!(self.visited.iter().map(|x| *x as usize).sum::<usize>() == self.graph.len());
3179        None
3180    }
3181    
3182}
3183
3184pub struct PathCompIter<'a, K: Kmer, D: Debug, F, F2> 
3185where 
3186F: Fn(&D) -> f32,
3187F2: Fn(&D) -> bool
3188{
3189    graph: &'a DebruijnGraph<K, D>,
3190    component_iterator: IterComponents<'a, K, D>,
3191    graph_pos: usize,
3192    score: F,
3193    solid_path: F2,
3194}
3195
3196/// returns the component and the "best" path in the component
3197impl<K: Kmer, D: Debug, F, F2> Iterator for PathCompIter<'_, K, D, F, F2> 
3198where 
3199F: Fn(&D) -> f32,
3200F2: Fn(&D) -> bool
3201{
3202    type Item = (Vec<usize>, VecDeque<(usize, Dir)>,);
3203    fn next(&mut self) -> Option<Self::Item> {
3204        match self.component_iterator.next() {
3205            Some(component) => {
3206                let current_comp = component;
3207                
3208    
3209                let mut best_node = current_comp[0];
3210                let mut best_score = f32::MIN;
3211                for c in current_comp.iter() {
3212                    let node = self.graph.get_node(*c);
3213                    let node_score = (self.score)(node.data());
3214    
3215                    if node_score > best_score {
3216                        best_node = *c;
3217                        best_score = node_score;
3218                    }
3219                }
3220    
3221                let oscore = |state| match state {
3222                    None => 0.0,
3223                    Some((id, _)) => (self.score)(self.graph.get_node(id).data()),
3224                };
3225    
3226                let osolid_path = |state| match state {
3227                    None => false,
3228                    Some((id, _)) => (self.solid_path)(self.graph.get_node(id).data()),
3229                };
3230    
3231                // Now expand in each direction, greedily taking the best path. Stop if we hit a node we've
3232                // already put into the path
3233                let mut used_nodes = HashSet::new();
3234                let mut path = VecDeque::new();
3235    
3236                // Start w/ initial state
3237                used_nodes.insert(best_node);
3238                path.push_front((best_node, Dir::Left));
3239    
3240                for init in [(best_node, Dir::Left, false), (best_node, Dir::Right, true)].iter() {
3241                    let &(start_node, dir, do_flip) = init;
3242                    let mut current = (start_node, dir);
3243    
3244                    loop {
3245                        let mut next = None;
3246                        let (cur_id, incoming_dir) = current;
3247                        let node = self.graph.get_node(cur_id);
3248                        let edges = node.edges(incoming_dir.flip());
3249    
3250                        let mut solid_paths = 0;
3251                        for (_, id, dir, _) in edges {
3252                            let cand = Some((id, dir));
3253                            if osolid_path(cand) {
3254                                solid_paths += 1;
3255
3256                                // second if clause is outside of first in original code (see max_path) 
3257                                // but would basically ignore path validity.
3258                                if oscore(cand) > oscore(next) {
3259                                    next = cand;
3260                                }
3261                            }
3262                        }
3263                        
3264                        // break if multiple solid paths are available
3265                        /* if solid_paths > 1 {
3266                            break;
3267                        } */
3268    
3269                        match next {
3270                            Some((next_id, next_incoming)) if !used_nodes.contains(&next_id) => {
3271                                if do_flip {
3272                                    path.push_front((next_id, next_incoming.flip()));
3273                                } else {
3274                                    path.push_back((next_id, next_incoming));
3275                                }
3276    
3277                                used_nodes.insert(next_id);
3278                                current = (next_id, next_incoming);
3279                            }
3280                            _ => break,
3281                        }
3282                    }
3283                }
3284                
3285                
3286                Some((current_comp, path))
3287            }, 
3288            None => {
3289                // should technically not need graph_pos after this 
3290                self.graph_pos += 1;
3291                None
3292            }
3293        }
3294    }
3295}
3296
3297
3298/// iterator over the edges of the de bruijn graph
3299pub struct EdgeIter<'a, K: Kmer, D: Debug> {
3300    graph: &'a DebruijnGraph<K, D>,
3301    visited_edges: HashSet<(usize, usize)>,
3302    current_node: usize,
3303    current_dir: Dir,
3304    node_edge_iter: smallvec::IntoIter<[(u8, usize, Dir, bool); 4]>
3305}
3306
3307impl<K: Kmer, D: Debug> EdgeIter<'_, K, D> {
3308    pub fn new(graph: &DebruijnGraph<K, D>) -> EdgeIter<'_, K, D>{
3309        let node_edge_iter = graph.get_node(0).l_edges().into_iter();
3310
3311        EdgeIter { 
3312            graph, 
3313            visited_edges: HashSet::new(), 
3314            current_node: 0, 
3315            current_dir: Dir::Left, 
3316            node_edge_iter
3317        }
3318    }
3319}
3320
3321impl<K: Kmer, D: Debug> Iterator for EdgeIter<'_, K, D> {
3322    type Item = (usize, Dir, u8, usize); // node, direction leaving node, base, target node
3323
3324    fn next(&mut self) -> Option<Self::Item> {
3325        loop {
3326            if let Some((base, nb_node_id, _, _)) = self.node_edge_iter.next() {
3327                let edge = if self.current_node > nb_node_id { (nb_node_id, self.current_node) } else { (self.current_node, nb_node_id) };
3328
3329                if self.visited_edges.insert(edge) { return Some((self.current_node, self.current_dir, base, nb_node_id)); } // else simply skip and move on
3330
3331            } else {
3332                match self.current_dir {
3333                    Dir::Left => {
3334                        // no left edges, switch to right edges
3335                        self.current_dir = Dir::Right;
3336                        self.node_edge_iter = self.graph.get_node(self.current_node).r_edges().into_iter();
3337                        
3338                    }
3339                    Dir::Right => {
3340                        // no right edges, switch to next node left edges
3341                        self.current_node += 1;
3342
3343                        // quit if end of graph is reached
3344                        if self.current_node == self.graph.len() { return None }
3345
3346                        self.current_dir = Dir::Left;
3347                        self.node_edge_iter = self.graph.get_node(self.current_node).l_edges().into_iter();
3348                    }
3349                }
3350            }
3351            
3352        }
3353    }
3354}
3355
3356#[cfg(test)]
3357mod test {
3358    use std::fs::remove_file;
3359
3360    use crate::{BaseQuality, EdgeMap, Exts, build_test_graph, colors::Colors, compression::{CheckCompress, ScmapCompress, compress_kmers_with_hash, uncompressed_graph}, dna_string::DnaString, filter::filter_kmers, kmer::{Kmer6, Kmer16, Kmer22}, reads::{Reads, ReadsPaired, Strandedness}, summarizer::{IDMapEMData, IDMapEMQualityData, IDTag, MapEMEmapQualityData, SampleInfo, SummaryConfig, TagsCountsData, TagsCountsSumData, Translator}, test::random_dna};
3361
3362    use crate::{summarizer::SummaryData, Dir};
3363
3364
3365    #[test]
3366    #[cfg(not(feature = "sample128"))]
3367    fn test_components() {
3368        use crate::{kmer::Kmer16, test::build_test_graph};
3369
3370        let (_, _, ser_graph) = build_test_graph::<Kmer16, TagsCountsSumData, _>();
3371        let graph = ser_graph.graph();
3372
3373        let components = graph.iter_components();
3374
3375        let check_components = [
3376            vec![0, 7, 43, 24, 47, 22, 37, 89, 25, 79, 63, 95, 64, 9, 96, 13, 11, 86, 74, 71, 92, 51, 94, 45, 12, 76, 21],
3377            vec![1, 54, 44, 5, 57, 65, 84, 10, 58, 35, 42, 73, 30, 83, 77, 15, 80, 72, 81, 78, 67, 49, 69, 91, 2, 90, 33, 87, 55, 8, 17, 88, 31, 56, 52, 27, 4, 6, 99, 40, 93, 28, 26, 62, 59, 97, 82, 46],
3378            vec![3, 41, 36, 34, 38, 85, 75, 19, 48, 16, 61, 66, 23, 20, 14, 18, 39, 29, 70, 32, 50, 53, 68, 60, 98],
3379        ];
3380
3381        let mut counter = 0;
3382
3383        for component in components {
3384            if component.len() > 1 { 
3385                println!("component: {:?}", component);
3386                assert_eq!(component, check_components[counter]);
3387                counter += 1;
3388            }
3389        }
3390
3391        assert_eq!(
3392            vec![(88, Dir::Left), (17, Dir::Left), (8, Dir::Left), (55, Dir::Left), (87, Dir::Left), (33, Dir::Left), (90, Dir::Left), (2, Dir::Left), (91, Dir::Left), (69, Dir::Left), (49, Dir::Left), (78, Dir::Left), (81, Dir::Left), (72, Dir::Left), (1, Dir::Left), (54, Dir::Left), (44, Dir::Left), (5, Dir::Left), (57, Dir::Left)], 
3393            graph.max_path(|data| data.sum().unwrap_or(1) as f32, |_| true)
3394        );
3395    }
3396
3397    #[test]
3398    fn test_iter_edges() {
3399        use crate::{compression::uncompressed_graph, filter::filter_kmers, reads::{Reads, ReadsPaired}, summarizer::{SampleInfo, SummaryConfig, TagsData}};
3400
3401        let read1 = "CAGCATCGATGCGACGAGCGCTCGCATCGA".as_bytes();
3402        let read2 = "ACGATCGTACGTAGCTAGCTGACTGAGC".as_bytes();
3403
3404        let mut reads = Reads::new(crate::reads::Strandedness::Forward);
3405        reads.add_from_bytes(read1, None, 0u8);
3406        reads.add_from_bytes(read2, None, 1);
3407
3408        let reads_paired = ReadsPaired::Unpaired { reads };
3409
3410        let sample_info = SampleInfo::new(0b1, 0b10, vec![12, 12]);
3411        let summary_config = SummaryConfig::new(sample_info);
3412        let (kmers, _) = filter_kmers::<TagsData, Kmer16, _>(&reads_paired, &summary_config, false, 1., false);
3413
3414        let graph = uncompressed_graph(kmers, true).finish();
3415
3416        let check_edges: Vec<(usize, Dir, u8, usize)> = vec![(0, Dir::Left, 2, 16), (0, Dir::Right, 1, 2), (1, Dir::Left, 0, 11), 
3417        (1, Dir::Right, 0, 16), (3, Dir::Left, 0, 13), (3, Dir::Right, 3, 10), (4, Dir::Left, 2, 21), (4, Dir::Right, 1, 17), (5, Dir::Left, 1, 27), 
3418        (5, Dir::Right, 2, 19), (6, Dir::Left, 1, 24), (6, Dir::Right, 2, 12), (7, Dir::Left, 2, 23), (7, Dir::Right, 3, 11), (8, Dir::Left, 0, 12), 
3419        (9, Dir::Left, 3, 17), (9, Dir::Right, 3, 24), (10, Dir::Right, 1, 21), (13, Dir::Left, 1, 18), (14, Dir::Right, 0, 26), 
3420        (15, Dir::Left, 2, 20), (15, Dir::Right, 3, 22), (18, Dir::Left, 2, 19), (20, Dir::Left, 1, 26), (22, Dir::Right, 2, 25), (23, Dir::Left, 1, 25)];
3421
3422        let edges = graph.iter_edges().collect::<Vec<_>>();
3423
3424        assert_eq!(check_edges, edges);
3425    }
3426
3427    #[test]
3428    fn test_map_transcripts() {
3429        let t_ref_path = "test_data/test_transcriptome_reference.fasta";
3430        let (_, ser_kmers, _) = build_test_graph::<Kmer22, u32, _>();
3431        let (kmers, mut translator, _) = ser_kmers.dissolve();
3432
3433        let unc_graph = uncompressed_graph(kmers, true).finish();
3434
3435        let mut id_strings = translator.id_translator().clone().unwrap().into_iter().map(|(name, _id)| name).collect::<Vec<_>>();
3436        id_strings.sort();
3437
3438        let t_map = unc_graph.map_transcripts(t_ref_path, &mut translator).unwrap();
3439        assert_eq!(t_map.len(), unc_graph.len());
3440        assert_eq!(t_map.iter().filter(|&ids| !ids.is_empty()).collect::<Vec<_>>().len(), 439);
3441
3442        // repeat the same without a previous existing translator
3443        let mut new_translator = Translator::empty();
3444        let t_map = unc_graph.map_transcripts(t_ref_path, &mut new_translator).unwrap();
3445        assert_eq!(t_map.len(), unc_graph.len());
3446        assert_eq!(t_map.iter().filter(|&ids| !ids.is_empty()).collect::<Vec<_>>().len(), 439);
3447        let mut new_id_strings = new_translator.id_translator().clone().unwrap().into_iter().map(|(name, _id)| name).collect::<Vec<_>>();
3448        new_id_strings.sort();
3449
3450        assert_eq!(id_strings, new_id_strings);
3451    }
3452
3453
3454    #[test]
3455    fn test_map_transcripts_to_edges() {
3456        let t_ref_path = "test_data/test_transcriptome_reference.fasta";
3457        let (_, ser_kmers, _) = build_test_graph::<Kmer22, u32, _>();
3458        let (kmers, mut translator, _) = ser_kmers.dissolve();
3459
3460        let unc_graph = uncompressed_graph(kmers, true).finish();
3461
3462        let mut id_strings = translator.id_translator().clone().unwrap().into_iter().map(|(name, _id)| name).collect::<Vec<_>>();
3463        id_strings.sort();
3464
3465        let t_map = unc_graph.map_transcripts_to_edges(t_ref_path, &mut translator).unwrap();
3466        assert_eq!(t_map.len(), unc_graph.len());
3467        assert_eq!(t_map.iter().filter(|&emap| !emap.is_empty()).collect::<Vec<_>>().len(), 439);
3468
3469        // repeat the same without a previous existing translator
3470        let mut new_translator = Translator::empty();
3471        let t_map = unc_graph.map_transcripts(t_ref_path, &mut new_translator).unwrap();
3472        assert_eq!(t_map.len(), unc_graph.len());
3473        assert_eq!(t_map.iter().filter(|&ids| !ids.is_empty()).collect::<Vec<_>>().len(), 439);
3474        let mut new_id_strings = new_translator.id_translator().clone().unwrap().into_iter().map(|(name, _id)| name).collect::<Vec<_>>();
3475        new_id_strings.sort();
3476
3477        assert_eq!(id_strings, new_id_strings);
3478    }
3479
3480    fn build_reads_quality_test(strand: Strandedness) -> ReadsPaired<IDTag> {
3481        let correct1 = "CGATGCTGCTGATGCTGAGTCTGACGTATGCGATCGATCGACGATCGTACTAGCTGACTGTGCAGCTAGCTGACTGATCGTAGCTAGCTACGTGCTAGCTACTAGCACTGATGC";
3482        let qu_corr1 = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
3483        let incorrect1 =                                     "CGACGATCGTACTAGCTGACTGTGCAGCTAGCTGACTGATCGTGGCTAGCTACGTGCTAGCTA";
3484        let qu_incorr1 =                                     "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC-CCCCCCCCCCCCCCCCCCC";
3485        let correct2 =         "GCATCGATCGACGACGTTACGTACGATCTACGTAGCTAGCTAGCTGACATGCTAGCTAGCTCTGACTGATCGTGGCTAGCTGACTGACTGTAGCT";
3486        let qu_corr2 =         "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
3487        let incorrect2 = "ATGCTGCTGATGCTGAGTCTGACGTAGGCGATCGATCGACGATCGTACTAGCTGACT";
3488        let qu_incorr2 = "CCCCCCCCCCCCCCCCCCCCCCCCCC-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
3489        let incorrect3 = "ATGCTGCTGATGCTGACTCTGACGTAGGCGATCGATCGATGATCGTACTAGCTGACT";
3490        let qu_incorr3 = "CCCCCCCCCCCCCCCC-CCCCCCCCC-CCCCCCCCCCCC-CCCCCCCCCCCCCCCCC";
3491
3492        let incorrect4_ins =                 "GACGTATGCGATCGATCGACGATCGTACTAGCTGACTTGTGCAGCTAGCTGACTGAT";
3493        let qu_incorr4_ins =                 "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC-CCCCCCCCCCCCCCCCCCC";
3494
3495        let incorrect5_tip =                                                             "AGCTGACTGATCGTAGCTAGCTACGTGCTAGCTACTATCACTGATGC";
3496        let qu_incorr5_tip =                                                             "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC-CCCCCCCCC";
3497
3498
3499        let mut reads = Reads::new_with_quality(strand);
3500
3501        for _i in 0..20 {
3502            reads.add_read(DnaString::from_acgt_bytes(correct1.as_bytes()), None, IDTag::new(1, 0), Some(qu_corr1.as_bytes()));
3503        }
3504
3505        for _i in 0..40 {
3506            reads.add_read(DnaString::from_acgt_bytes(correct2.as_bytes()), None, IDTag::new(2, 0), Some(qu_corr2.as_bytes()));
3507        }
3508
3509        reads.add_read(DnaString::from_acgt_bytes(incorrect1.as_bytes()), None, IDTag::new(3, 1), Some(qu_incorr1.as_bytes()));
3510        reads.add_read(DnaString::from_acgt_bytes(incorrect2.as_bytes()), None, IDTag::new(4, 1), Some(qu_incorr2.as_bytes()));
3511        reads.add_read(DnaString::from_acgt_bytes(incorrect3.as_bytes()), None, IDTag::new(5, 1), Some(qu_incorr3.as_bytes()));
3512        reads.add_read(DnaString::from_acgt_bytes(incorrect4_ins.as_bytes()), None, IDTag::new(6, 1), Some(qu_incorr4_ins.as_bytes()));
3513        reads.add_read(DnaString::from_acgt_bytes(incorrect5_tip.as_bytes()), None, IDTag::new(7, 1), Some(qu_incorr5_tip.as_bytes()));
3514
3515
3516        ReadsPaired::Unpaired { reads }
3517    }
3518
3519    #[test]
3520    fn test_remove_lq_splits_s() {
3521        test_remove_lq_splits(true);
3522    }
3523
3524    #[test]
3525    fn test_remove_lq_splits_us() {
3526        test_remove_lq_splits(false);
3527    }
3528
3529    fn test_remove_lq_splits(stranded: bool) {
3530        let print = false;
3531        let strandedness = if stranded { Strandedness::Forward } else { Strandedness::Unstranded };
3532
3533
3534        type K = Kmer16;
3535
3536        let seqs = build_reads_quality_test(strandedness);
3537        let sample_info = SampleInfo::new(1, 0b111110, vec![1000, 10, 20, 20, 20, 20]);
3538        let summary_config = SummaryConfig::new(sample_info);
3539        let (kmers, _) = filter_kmers::<IDMapEMQualityData, K, IDTag>(&seqs, &summary_config, false, 1., false);
3540
3541
3542        // make uncompressed graph
3543        let mut unc_graph = uncompressed_graph(kmers.clone(), stranded).finish();
3544
3545        // add "mapped" ids to graph
3546        for i in 0..unc_graph.len() {
3547            let data = unc_graph.mut_data(i);
3548            if let Some(ids) = data.ids() {
3549                if ids.contains(&1) {
3550                    data.set_mapped_ids(vec![1].into());
3551                }
3552                else if ids.contains(&2) {
3553                    data.set_mapped_ids(vec![2].into());
3554                }
3555            }
3556        }
3557
3558        let colors = Colors::new(&unc_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 7 });
3559
3560        if print { unc_graph.to_dot("uncompressed_bf-lq-splits.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3561        let n_edges = unc_graph.iter_edges().count();
3562        unc_graph.remove_lq_splits(BaseQuality::Medium).unwrap();
3563        if print { unc_graph.to_dot("uncompressed_af-lq-splits.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3564    
3565        let n_edges_af = unc_graph.iter_edges().count();
3566        assert_eq!(n_edges, n_edges_af + 11);
3567
3568        // make compressed graph
3569        let spec = CheckCompress::new(|d: IDMapEMQualityData, _| d, |d, d1| d.join_test(d1));
3570        let mut c_graph = compress_kmers_with_hash(stranded, &spec, kmers, false, false).finish();
3571    
3572         // add "mapped" ids to graph
3573        for i in 0..c_graph.len() {
3574            let data = c_graph.mut_data(i);
3575            if let Some(ids) = data.ids() {
3576                if ids.contains(&1) {
3577                    data.set_mapped_ids(vec![1].into());
3578                }
3579                else if ids.contains(&2) {
3580                    data.set_mapped_ids(vec![2].into());
3581                }
3582            }
3583        }
3584
3585        let colors = Colors::new(&c_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 7 });
3586
3587        if print { c_graph.to_dot("compressed_bf-lq-splits.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3588        let n_edges = c_graph.iter_edges().count();
3589        c_graph.remove_lq_splits(BaseQuality::Medium).unwrap();
3590        if print { c_graph.to_dot("compressed_af-lq-splits.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3591    
3592        let n_edges_af = c_graph.iter_edges().count();
3593        assert_eq!(n_edges, n_edges_af + 11);
3594    }
3595
3596    #[test]
3597    fn test_remove_lq_paths_s() {
3598        test_remove_lq_paths(true);
3599    }
3600
3601    #[test]
3602    fn test_remove_lq_paths_us() {
3603        test_remove_lq_paths(false);
3604    }
3605
3606    fn test_remove_lq_paths(stranded: bool) {
3607        let print = false;
3608        let strandedness = if stranded { Strandedness::Forward } else { Strandedness::Unstranded };
3609
3610        type K = Kmer16;
3611
3612        let seqs = build_reads_quality_test(strandedness);
3613        let sample_info = SampleInfo::new(1, 0b111110, vec![1000, 10, 20, 20, 20, 20]);
3614        let summary_config = SummaryConfig::new(sample_info);
3615        let (kmers, _) = filter_kmers::<IDMapEMQualityData, K, IDTag>(&seqs, &summary_config, false, 1., false);
3616
3617
3618        // make uncompressed graph
3619        let mut unc_graph = uncompressed_graph(kmers.clone(), stranded).finish();
3620
3621        // add "mapped" ids to graph
3622        for i in 0..unc_graph.len() {
3623            let data = unc_graph.mut_data(i);
3624            if let Some(ids) = data.ids() {
3625                if ids.contains(&1) {
3626                    data.set_mapped_ids(vec![1].into());
3627                }
3628                else if ids.contains(&2) {
3629                    data.set_mapped_ids(vec![2].into());
3630                }
3631            }
3632        }
3633
3634        let colors = Colors::new(&unc_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 7 });
3635
3636        if print { unc_graph.to_dot("uncompressed_bf-lq.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3637        let n_edges = unc_graph.iter_edges().count();
3638        unc_graph.remove_lq_paths(BaseQuality::Medium, 4).unwrap();
3639        if print { unc_graph.to_dot("uncompressed_af-lq.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3640    
3641        let n_edges_af = unc_graph.iter_edges().count();
3642        assert_eq!(n_edges, n_edges_af + 90);
3643
3644        // make compressed graph
3645        let spec = CheckCompress::new(|d: IDMapEMQualityData, _| d, |d, d1| d.join_test(d1));
3646        let mut c_graph = compress_kmers_with_hash(stranded, &spec, kmers, false, false).finish();
3647    
3648         // add "mapped" ids to graph
3649        for i in 0..c_graph.len() {
3650            let data = c_graph.mut_data(i);
3651            if let Some(ids) = data.ids() {
3652                if ids.contains(&1) {
3653                    data.set_mapped_ids(vec![1].into());
3654                }
3655                else if ids.contains(&2) {
3656                    data.set_mapped_ids(vec![2].into());
3657                }
3658            }
3659        }
3660
3661        let colors = Colors::new(&c_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 7 });
3662
3663        if print { c_graph.to_dot("compressed_bf-lq.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3664        let n_edges = c_graph.iter_edges().count();
3665        c_graph.remove_lq_paths(BaseQuality::Medium, 4).unwrap();
3666        if print { c_graph.to_dot("compressed_af-lq.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3667    
3668        let n_edges_af = c_graph.iter_edges().count();
3669        assert_eq!(n_edges, n_edges_af + 15);
3670    }
3671
3672    #[test]
3673    fn test_remove_lc_paths_s() {
3674        test_remove_lc_paths(true);
3675    }
3676
3677    #[test]
3678    fn test_remove_lc_paths_us() {
3679        test_remove_lc_paths(false);
3680    }
3681    
3682    fn test_remove_lc_paths(stranded: bool) {
3683        let print = true; 
3684        let strandedness = if stranded { Strandedness::Forward } else { Strandedness::Unstranded };
3685
3686        let (n_diff_uc, n_diff_c) = if stranded { (27, 8) } else { (27, 10) };
3687
3688        let   correct = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3689        let incorrect = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGGCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3690        let incorrec2 = "TGACAGCTGACGGCTGACTACTACGTCACTGACGATGCTGACAC".as_bytes();
3691        let incorrec3 = "AAAAAAAAAGCTGACTGCTGACGGCTG".as_bytes();
3692        let incorrec4 = "ACGGCTGACTACTGACTGAAAAAAAAAAA".as_bytes();
3693
3694        let insertion = "ACGATCGATCGCGATCGATAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3695
3696        let mut reads = Reads::new(strandedness);
3697        for _i in 0..1000 {
3698            reads.add_from_bytes(correct, None, IDTag::new(0, 0));
3699        }
3700
3701        for _i in 0..2 {
3702            reads.add_from_bytes(incorrect, None, IDTag::new(1, 1)); // should be removed
3703        }
3704
3705        for _i in 0..15 {
3706            reads.add_from_bytes(incorrec2, None, IDTag::new(2, 2)); // should be removed
3707        }
3708
3709        for _i in 0..25 {
3710            reads.add_from_bytes(incorrec3, None, IDTag::new(3, 3)); // should be removed
3711        }
3712
3713        for _i in 0..30 {
3714            reads.add_from_bytes(incorrec4, None, IDTag::new(4, 4)); // should be removed
3715        }
3716
3717
3718        for _i in 0..1 {
3719            reads.add_from_bytes(insertion, None, IDTag::new(1, 3)); // should not be removed
3720        }
3721
3722        let seqs = ReadsPaired::Unpaired { reads };
3723        let sample_info = SampleInfo::new(1, 0b111110, vec![1000, 10, 20, 20, 20, 20]);
3724        let summary_config = SummaryConfig::new(sample_info);
3725        let (kmers, _) = filter_kmers::<IDMapEMData, Kmer16, IDTag>(&seqs, &summary_config, false, 1., false);
3726
3727
3728        // test with uncompressed graph
3729        let mut unc_graph = uncompressed_graph(kmers.clone(), stranded).finish();
3730        // add ids to graph
3731        for i in 0..unc_graph.len() {
3732            let data = unc_graph.mut_data(i);
3733            if let Some(ids) = data.ids() {
3734                if ids.contains(&0) {
3735                    data.set_mapped_ids(vec![0].into());
3736                }
3737            }
3738        }
3739        let colors = Colors::new(&unc_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 5 });
3740        if print { unc_graph.to_dot("uncompressed_bf-lcp.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3741        let n_edges = unc_graph.iter_edges().count();
3742        unc_graph.remove_lc_paths(10, 10, 10.).unwrap();
3743        if print { unc_graph.to_dot("uncompressed_af-lcp.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3744        assert_eq!(n_edges - n_diff_uc, unc_graph.iter_edges().count());
3745
3746        // test with compressed graph
3747        let spec = CheckCompress::new(|d: IDMapEMData, _| d, |d, d1| d.join_test(d1));
3748        let mut c_graph = compress_kmers_with_hash(stranded, &spec, kmers, false, false).finish();
3749        // add ids to graph
3750        for i in 0..c_graph.len() {
3751            let data = c_graph.mut_data(i);
3752            if let Some(ids) = data.ids() {
3753                if ids.contains(&0) {
3754                    data.set_mapped_ids(vec![0].into());
3755                }
3756            }
3757        }
3758        let colors = Colors::new(&c_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 5 });
3759        if print { c_graph.to_dot("compressed_bf-lcp.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3760        let n_edges = c_graph.iter_edges().count();
3761        c_graph.remove_lc_paths(10, 10, 10.).unwrap();
3762        if print { c_graph.to_dot("compressed_af-lcp.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3763        assert_eq!(n_edges - n_diff_c, c_graph.iter_edges().count());
3764    }
3765
3766    #[test]
3767    fn test_remove_ladders_s() {
3768        test_remove_ladders(true);
3769    }
3770
3771    #[test]
3772    fn test_remove_ladders_us() {
3773        test_remove_ladders(false);
3774    }
3775
3776    fn test_remove_ladders(stranded: bool) {
3777        let print = true; 
3778        let strandedness = if stranded { Strandedness::Forward } else { Strandedness::Unstranded };
3779
3780        let c_csv = if print { Some("c_ladders.csv") } else { None };
3781        let uc_csv = if print { Some("uc_ladders.csv") } else { None };
3782
3783        let   correct = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3784        let incorrect = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGGCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3785        let incorrec2 = "TGACAGCTGACGGCTGACTACTACGTCACTGACGATGCTGACAC".as_bytes();
3786        let incorrec3 = "AAAAAAAAAGCTGACTGCTGACGGCTG".as_bytes();
3787        let incorrec4 = "ACGGCTGACTACTGACTGAAAAAAAAAAA".as_bytes();
3788
3789        let insertion = "ACGATCGATCGCGATCGATAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3790
3791        let mut reads = Reads::new(strandedness);
3792        for _i in 0..1000 {
3793            reads.add_from_bytes(correct, None, IDTag::new(0, 0));
3794        }
3795
3796        for _i in 0..2 {
3797            reads.add_from_bytes(incorrect, None, IDTag::new(1, 1)); // should be removed
3798        }
3799
3800        for _i in 0..15 {
3801            reads.add_from_bytes(incorrec2, None, IDTag::new(2, 2)); // should be removed
3802        }
3803
3804        for _i in 0..25 {
3805            reads.add_from_bytes(incorrec3, None, IDTag::new(3, 3)); // should be removed
3806        }
3807
3808        for _i in 0..30 {
3809            reads.add_from_bytes(incorrec4, None, IDTag::new(4, 4)); // should be removed
3810        }
3811
3812
3813        for _i in 0..1 {
3814            reads.add_from_bytes(insertion, None, IDTag::new(1, 3)); // should not be removed
3815        }
3816
3817        let seqs = ReadsPaired::Unpaired { reads };
3818        let sample_info = SampleInfo::new(1, 0b111110, vec![1000, 10, 20, 20, 20, 20]);
3819        let summary_config = SummaryConfig::new(sample_info);
3820        let (kmers, _) = filter_kmers::<IDMapEMData, Kmer16, IDTag>(&seqs, &summary_config, false, 1., false);
3821
3822
3823        // test with uncompressed graph
3824        let mut unc_graph = uncompressed_graph(kmers.clone(), stranded).finish();
3825        // add ids to graph
3826        for i in 0..unc_graph.len() {
3827            let data = unc_graph.mut_data(i);
3828            if let Some(ids) = data.ids() {
3829                if ids.contains(&0) {
3830                    data.set_mapped_ids(vec![0].into());
3831                }
3832            }
3833        }
3834        let colors = Colors::new(&unc_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 5 });
3835        if print { unc_graph.to_dot("uncompressed_bf-lad.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3836        let n_edges = unc_graph.iter_edges().count();
3837        unc_graph.remove_ladders(10, 10., uc_csv).unwrap();
3838        if print { unc_graph.to_dot("uncompressed_af-lad.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3839        assert_eq!(n_edges - 10, unc_graph.iter_edges().count());
3840
3841        // test with compressed graph
3842        let spec = CheckCompress::new(|d: IDMapEMData, _| d, |d, d1| d.join_test(d1));
3843        let mut c_graph = compress_kmers_with_hash(stranded, &spec, kmers, false, false).finish();
3844        // add ids to graph
3845        for i in 0..c_graph.len() {
3846            let data = c_graph.mut_data(i);
3847            if let Some(ids) = data.ids() {
3848                if ids.contains(&0) {
3849                    data.set_mapped_ids(vec![0].into());
3850                }
3851            }
3852        }
3853        let colors = Colors::new(&c_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 5 });
3854        if print { c_graph.to_dot("compressed_bf-lad.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3855        let n_edges = c_graph.iter_edges().count();
3856        c_graph.remove_ladders(10, 10., c_csv).unwrap();
3857        if print { c_graph.to_dot("compressed_af-lad.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3858        assert_eq!(n_edges - 6, c_graph.iter_edges().count());
3859    }
3860
3861
3862    #[test]
3863    fn test_remove_tips_s() {
3864        test_remove_tips(true);
3865    }
3866
3867    #[test]
3868    fn test_remove_tips_us() {
3869        test_remove_tips(false);
3870    }
3871
3872    fn test_remove_tips(stranded: bool) {
3873        let print = false;
3874        let strandedness = if stranded { Strandedness::Forward } else { Strandedness::Unstranded };
3875
3876        let c_csv = if print { Some("c_tips.csv") } else { None };
3877        let uc_csv = if print { Some("uc_tips.csv") } else { None };
3878
3879        let     correct = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3880        let incorrect_r = "ACGATCGATCGCGATCGTAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTAACGTGAC".as_bytes();
3881        let incorrect_l = "ACGTTCGATCGCGATCGTAGCTGACTGCTGACGTCTGACTACTGACTGATGCTAGCTATCGTGAC".as_bytes();
3882
3883
3884
3885        let mut reads = Reads::new(strandedness);
3886        for _i in 0..1000 {
3887            reads.add_from_bytes(correct, None, IDTag::new(0, 0));
3888        }
3889
3890        for _i in 0..10 {
3891            reads.add_from_bytes(incorrect_r, None, IDTag::new(1, 1)); // should be removed
3892        }
3893
3894        for _i in 0..10 {
3895            reads.add_from_bytes(incorrect_l, None, IDTag::new(2, 2)); // should be removed
3896        }
3897
3898        let seqs = ReadsPaired::Unpaired { reads };
3899        let sample_info = SampleInfo::new(1, 6, vec![1000, 10, 20]);
3900        let summary_config = SummaryConfig::new(sample_info);
3901        let (kmers, _) = filter_kmers::<IDMapEMData, Kmer16, IDTag>(&seqs, &summary_config, false, 1., false);
3902
3903
3904        // test with uncompressed graph
3905        let mut unc_graph = uncompressed_graph(kmers.clone(), stranded).finish();
3906        // add ids to graph
3907        for i in 0..unc_graph.len() {
3908            let data = unc_graph.mut_data(i);
3909            if let Some(ids) = data.ids() {
3910                if ids.contains(&0) {
3911                    data.set_mapped_ids(vec![0].into());
3912                }
3913            }
3914        }
3915
3916        let colors = Colors::new(&unc_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 3 });
3917        if print { unc_graph.to_dot("uncompressed_bf-tips.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3918        let n_edges = unc_graph.iter_edges().count();
3919        
3920        unc_graph.remove_tips(10, 10., uc_csv).unwrap();
3921        if print { unc_graph.to_dot("uncompressed_af-tips.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3922        assert_eq!(n_edges - 11, unc_graph.iter_edges().count());
3923
3924        // test with compressed graph
3925        let spec = CheckCompress::new(|d: IDMapEMData, _| d, |d, d1| d.join_test(d1));
3926        let mut c_graph = compress_kmers_with_hash(stranded, &spec, kmers, false, false).finish();
3927        // add ids to graph
3928        for i in 0..c_graph.len() {
3929            let data = c_graph.mut_data(i);
3930            if let Some(ids) = data.ids() {
3931                if ids.contains(&0) {
3932                    data.set_mapped_ids(vec![0].into());
3933                }
3934            }
3935        }
3936
3937        let colors = Colors::new(&c_graph, &summary_config, crate::colors::ColorMode::IDS { n_ids: 3 });
3938        if print { c_graph.to_dot("compressed_bf-tips.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3939        let n_edges = c_graph.iter_edges().count();
3940        
3941        c_graph.remove_tips(10, 10., c_csv).unwrap();
3942        if print { c_graph.to_dot("compressed_af-tips.dot", &|node| node.node_dot_default(&colors, &summary_config, &Translator::empty(), false, false), &|node, base, dir, flip| node.edge_dot_default(&colors, base, dir, flip)); }
3943        assert_eq!(n_edges - 2, c_graph.iter_edges().count());
3944
3945    }
3946
3947    #[test]
3948    fn test_check_edge_truth() {
3949        let (_, _, ser_graph) = build_test_graph::<Kmer16, MapEMEmapQualityData, _>(); 
3950        let (mut graph, _translator, _config) = ser_graph.dissolve();
3951
3952        // add map Ids to some nodes
3953        graph.mut_data(0).set_mapped_ids(vec![0, 1].into());
3954        graph.mut_data(11).set_mapped_ids(vec![0].into());
3955        graph.mut_data(16).set_mapped_ids(vec![1, 2].into());
3956
3957        assert!(graph.check_edge_truth(0, 11)); // have correct edge
3958        assert!(graph.check_edge_truth(0, 16)); // have correct edge
3959        assert!(!graph.check_edge_truth(0, 2)); // not connected
3960        assert!(!graph.check_edge_truth(1, 2)); // connected but no correct edge
3961
3962        // add map ID 0 at G left to node 0 
3963        let mut emap = EdgeMap::default();
3964        emap.add_id_to_edge_map_at_index(0, 5);
3965        graph.mut_data(0).set_mapped_edge_ids(Some(emap));
3966
3967        // add map ID 0 at T right to node 11 
3968        let mut emap = EdgeMap::default();
3969        emap.add_id_to_edge_map_at_index(0, 0);
3970        graph.mut_data(11).set_mapped_edge_ids(Some(emap));
3971
3972        assert!(graph.check_edge_truth_emap(0, 11)); // nodes are connected and should habe correct edge
3973        assert!(!graph.check_edge_truth_emap(0, 16)); // nodes are connected but should not have a correct edge (only node IDs)
3974        assert!(!graph.check_edge_truth_emap(0, 2)); // nodes are not connected
3975    }
3976
3977    #[test]
3978    fn test_to_tsv() {
3979        let reads_us = Reads::from_vmer_vec(
3980            (0..10).map(|i| (DnaString::from_bytes(&random_dna(100)), Exts::empty(), i as u8)).collect::<Vec<_>>(), 
3981            crate::reads::Strandedness::Unstranded
3982        );
3983
3984        let reads_paired = ReadsPaired::Unpaired { reads: reads_us };
3985
3986        let sample_info = SampleInfo::new(0b1111100000, 0b0000011111, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
3987        let summary_config = SummaryConfig::new(sample_info);
3988        let (kmers, _) = filter_kmers::<TagsCountsData, Kmer6, _>(&reads_paired, &summary_config, false, 1., false);
3989
3990        let graph = compress_kmers_with_hash(false, &ScmapCompress::new(), kmers, false, false).finish();
3991
3992        graph.to_tsv("test_graph_unstranded.tsv", |node| node.data().print_ol(&Translator::empty(), &summary_config, None)).unwrap();
3993
3994
3995        let reads_us = Reads::from_vmer_vec(
3996            (0..10).map(|i| (DnaString::from_bytes(&random_dna(100)), Exts::empty(), i as u8)).collect::<Vec<_>>(), 
3997            crate::reads::Strandedness::Forward
3998        );
3999
4000        let reads_paired = ReadsPaired::Unpaired { reads: reads_us };
4001
4002        let sample_info = SampleInfo::new(0b1111100000, 0b0000011111, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4003        let summary_config = SummaryConfig::new(sample_info);
4004        let (kmers, _) = filter_kmers::<TagsCountsData, Kmer6, _>(&reads_paired, &summary_config, false, 1., false);
4005
4006        let graph = compress_kmers_with_hash(true, &ScmapCompress::new(), kmers, false, false).finish();
4007
4008        graph.to_tsv("test_graph_stranded.tsv", |node| node.data().print_ol(&Translator::empty(), &summary_config, None)).unwrap();
4009    
4010        remove_file("test_graph_unstranded.tsv").unwrap();
4011        remove_file("test_graph_stranded.tsv").unwrap();
4012    }
4013}
4014