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