Skip to main content

debruijn/
filter.rs

1// Copyright 2017 10x Genomics
2
3//! Methods for converting sequences into kmers, filtering observed kmers before De Bruijn graph construction, and summarizing 'color' annotations.
4
5use std::collections::HashSet;
6use std::fmt::Debug;
7use std::hash::Hash;
8use std::mem;
9use std::ops::Range;
10use std::sync::Arc;
11use std::sync::Mutex;
12use std::time::Instant;
13
14use boomphf::hashmap::BoomHashMap2;
15use indicatif::MultiProgress;
16use indicatif::ProgressBar;
17use indicatif::ProgressIterator;
18use indicatif::ProgressStyle;
19use itertools::Itertools;
20use log::debug;
21use log::warn;
22use rayon::current_num_threads;
23use rayon::prelude::*;
24
25use crate::KmerDataItem;
26use crate::reads::Read;
27use crate::reads::ReadsPaired;
28use crate::reads::Strandedness;
29use crate::summarizer::SummaryConfig;
30use crate::summarizer::SummaryData;
31use crate::Dir;
32use crate::Exts;
33use crate::Kmer;
34use crate::Vmer;
35use crate::BUCKETS;
36use crate::PROGRESS_STYLE;
37
38const HEAP_APPROX: f32 = 1.5;
39
40/// check which bucket a k-mer has to be sorted into according to first four bases
41pub fn bucket<K: Kmer>(kmer: K) -> usize {
42    if K::k() > 3 {
43        (kmer.get(0) as usize) << 6
44            | (kmer.get(1) as usize) << 4
45            | (kmer.get(2) as usize) << 2
46            | (kmer.get(3) as usize)
47    } else {
48        kmer.to_u64() as usize
49    }
50}
51
52fn bucket_flip<K: Kmer>(kmer: K, stranded: Strandedness) -> (usize, K) {
53    // if not stranded choose lexiographically lesser of kmer and rc of kmer
54    // if forward, use original kmer
55    // if reverse, use rc of kmer
56    let min_kmer = match stranded {
57        Strandedness::Unstranded => {
58            let (min_kmer, _) = kmer.min_rc_flip();
59            min_kmer
60        },
61        Strandedness::Forward => kmer,
62        Strandedness::Reverse => kmer.rc(),
63    };
64
65    // calculate which bucket this kmer belongs to
66    (bucket(min_kmer), min_kmer)
67}
68
69fn bucket_ext_flip<K: Kmer>(kmer: K, exts: Exts, stranded: Strandedness, bucket_range: Range<usize>) ->Option<(K, Exts, usize)> {
70    // if not stranded choose lexiographically lesser of kmer and rc of kmer
71    // if forward, use original kmer
72    // if reverse, use rc of kmer
73    let (min_kmer, flip_exts) = match stranded {
74        Strandedness::Unstranded => {
75            let (min_kmer, flip) = kmer.min_rc_flip();
76            let flip_exts = if flip { exts.rc() } else { exts };
77            (min_kmer, flip_exts)
78        },
79        Strandedness::Forward => (kmer, exts),
80        Strandedness::Reverse => (kmer.rc(), exts.rc()),
81    };
82
83    // calculate which bucket this kmer belongs to
84    let bucket = if K::k() > 3 { bucket(min_kmer) } else { min_kmer.to_u64() as usize };
85
86    // check if bucket is in current range
87    let in_range = bucket >= bucket_range.start && bucket < bucket_range.end;
88
89    if in_range {
90        Some((min_kmer, flip_exts, bucket))
91    } else {
92        None
93    }
94}
95
96/// increase the capacities for each bucket for the k-mers in one read
97fn add_seq_bucket_capacities<K: Kmer, DI: Clone + Copy>(read: &Read<DI>, capacities: &mut [usize; BUCKETS], unique_kmers: &mut HashSet<K>) {
98    // iterate through all kmers in seq
99    for kmer in read.seq().iter_kmers::<K>() {
100        // calculate which bucket this kmer belongs to
101        let (bucket, min_kmer) = bucket_flip(kmer, read.stranded());
102        capacities[bucket] += 1;
103
104        // count k-mer coverages
105        unique_kmers.insert(min_kmer);
106    }
107}
108
109/// caclulate the ranges (slices) in which the buckets are split
110fn bucket_ranges<K, SD, DI, I>(n_nodes: usize, input_kmers: usize, memory_size: f32, iter_capacities: I) -> Vec<Range<usize>> 
111where I: Iterator<Item = (usize, usize)>
112{
113    // calculate expected size per node
114    // some SDs will have additional content in heap, we approximate this by adding 50%
115    let exp_node_mem = mem::size_of::<K>() + mem::size_of::<Exts>() + (mem::size_of::<SD>() as f32 * HEAP_APPROX) as usize;
116
117    let graph_mem = n_nodes * exp_node_mem;
118    debug!("n final nodes: {n_nodes}");
119    debug!("expected graph memory: {graph_mem}");
120
121    // calculate numnber of slices for memory limit
122    let mem_per_kmer = mem::size_of::<(K, Exts, DI)>();
123    debug!("size used for calculation: {} bytes", mem_per_kmer);
124    debug!("size of kmer, E, D: {} bytes", mem::size_of::<(K, Exts, DI)>());
125    debug!("size of K: {} bytes, size of Exts: {} bytes, size of DI: {} bytes", mem::size_of::<K>(), mem::size_of::<Exts>(), mem::size_of::<DI>());
126    debug!("type DI: {}", std::any::type_name::<DI>());
127
128    let memory_limit = (memory_size * 10f32.powf(9.)) as usize;
129    let max_mem = memory_limit.saturating_sub(graph_mem);
130    let required_slices = if max_mem == 0 {
131        BUCKETS
132    } else {
133        mem_per_kmer * input_kmers / max_mem
134    }; 
135
136    let bucket_ranges = if required_slices >= BUCKETS {
137        warn!("supplied memory limit might not be sufficient, will construct graph with lowest possible memory usage");
138        // run each bucket in a separate slice
139        (0..BUCKETS).map(|i| i..(i+1)).collect::<Vec<_>>()
140    } else {
141        // if max_mem > 0, split k-mers into however many slices are needed
142        debug!("splitting k-mers into {required_slices} slices");
143
144        let mut start_bucket = 0;
145        let mut size = 0;
146
147        // maximum number of k-mers in a slice
148        let max_size = max_mem / mem_per_kmer;
149
150        let mut bucket_ranges = Vec::with_capacity(required_slices);
151
152        for (i, capacity) in iter_capacities {
153            size += capacity;
154            if size > max_size {
155                bucket_ranges.push(start_bucket..i);
156                start_bucket = i;
157                size = capacity;
158            }
159        }
160        bucket_ranges.push(start_bucket..BUCKETS);
161
162        bucket_ranges
163    };
164
165    debug!("bucket ranges: {:?}", bucket_ranges);
166    debug!("kmer_mem: {} bytes, max_mem: {} bytes, slices: {}", mem_per_kmer * input_kmers, max_mem, bucket_ranges.len());
167    debug!("bucket_ranges: {:?}", bucket_ranges);
168    assert!(bucket_ranges[bucket_ranges.len() - 1].end >= BUCKETS);
169
170    bucket_ranges
171}
172
173/// Process DNA sequences into kmers and determine the set of valid kmers,
174/// their extensions, and summarize associated label/'color' data. The input
175/// sequences are converted to kmers of type `K`, and like kmers are grouped together.
176/// All instances of each kmer, along with their label data are then proccessed with
177/// [`SummaryData::summarize`], which generates an implementation of [`SummaryData`],
178/// which is specified with the generic `SD`, decides if the k-mer is 'valid' 
179/// based on the parameters given in `summary_config`, and
180/// summarizes the the individual label into a single label data structure
181/// for the kmer. Care is taken to keep the memory consumption small.
182/// 
183/// Be aware that the configuration in `summary_config` only applies if the required
184/// informaiton can be supplied by the chosen implementation of `SummaryData`.
185/// E.g., the k-mers will not be filtered according to p-value when the `SummaryData`
186/// only contains the number of observations.
187///
188/// # Arguments
189///
190/// * `seqs` are the reads wrapped in a `Reads<u8>`. See [`Reads<D>`]
191/// * `summary_config` is a [`SummaryConfig`], which contains prameters and 
192///   information necessary for the filtering
193/// * `stranded`: if true, preserve the strandedness of the input sequences, effectively
194///   assuming they are all in the positive strand. If false, the kmers will be canonicalized
195///   to the lexicographic minimum of the kmer and it's reverse complement.
196/// * `report_all_kmers`: if true returns the vector of all the observed kmers and performs the
197///   kmer based filtering
198/// * `memory_size`: gives the size bound on the memory in GB to use and automatically determines
199///   the number of passes needed
200/// * `time`: print information about the time needed for each step
201/// # Returns
202/// BoomHashMap2 Object, check rust-boomphf for details
203/// 
204/// /// # Returns
205/// BoomHashMap2 Object, check rust-boomphf for details
206/// 
207/// # Examples:
208/// 
209/// ```
210/// use debruijn::summarizer::{SampleInfo, SummaryConfig, TagsCountsData, StatTest, GroupFrac};
211/// use debruijn::reads::{Reads, ReadsPaired, Strandedness};
212/// use debruijn::filter::filter_kmers_parallel;
213/// use debruijn::kmer::Kmer16;
214/// use debruijn::Exts;
215/// 
216/// let mut seqs = Reads::new(Strandedness::Unstranded);
217/// seqs.add_from_bytes("ACCGATCATATATTTTCGGGGCTAGGCGAAGCGATCTTATCGAGC".as_bytes(), None, 1u8);
218/// seqs.add_from_bytes("GCGATCGAGCATGCTCAGCTGACGTGACTGACGTAGCTATCTTTTCGTAGCTAC".as_bytes(), None, 1u8);
219/// seqs.add_from_bytes("GCGAGTTTGCGACTCGAGGCTATCTAGCTAGCTASGCTCTCGACTAGCTGACTTACGACGACTACG".as_bytes(), None, 2u8);
220/// seqs.add_from_bytes("CGATTAGCTACGTAGCTAGCTGACGTACTGGGGGGTATTTCGGATCTGCGGAGCGATCT".as_bytes(), None, 2u8);
221///       
222/// let sample_info = SampleInfo::new(
223///     0b000011,
224///     0b111100,
225///     vec![23423, 3463454, 2242234, 2233243, 234322434, 2323234],
226/// );
227///     
228/// let summary_config = SummaryConfig::new(sample_info)
229///     .with_min_kmer_obs(3)
230///     .with_group_frac(GroupFrac::One, 0.333)
231///     .with_stat_test(StatTest::StudentsTTest);
232///    
233/// let (hashed_kmers, _) = filter_kmers_parallel::<Kmer16, TagsCountsData, _>(
234///     &ReadsPaired::Unpaired { reads: seqs },
235///     &summary_config,
236///     false,
237///     10.,
238///     false,
239/// );
240/// ```
241#[inline(never)]
242//pub fn filter_kmers_parallel<K: Kmer + Sync + Send, V: Vmer + Sync, D1: Clone + Debug + Sync, DS: Clone + Sync + Send, S: KmerSummarizer<D1, DS, (usize, usize)> +  Send>(
243pub fn filter_kmers_parallel<K, SD, DI>(
244    seqs: &ReadsPaired<DI>,
245    summariy_config: &SummaryConfig,
246    report_all_kmers: bool,
247    memory_size: f32,
248    time: bool,
249) -> (BoomHashMap2<K, Exts, SD>, Vec<K>)
250where 
251K: Kmer + Sync + Send,
252SD: Clone + std::fmt::Debug + Send + SummaryData<DI>,
253DI: Clone + Copy + Send + Sync
254{
255    // take timestamp before all processes
256    let before_all = Instant::now();
257
258    // progress bars
259    let multi_pb = MultiProgress::new();
260    let style = ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-");
261
262    // split all reads into ranges to be processed in parallel for counting capacities and picking kmers
263    let n_threads = current_num_threads();
264    let n_reads = seqs.n_reads();
265    let sz = n_reads / n_threads + 1;
266
267    debug!("n_reads: {}", n_reads);
268    debug!("sz: {}", sz);
269
270    let mut parallel_ranges = Vec::with_capacity(n_threads);
271    let mut start = 0;
272    while start < n_reads {
273        parallel_ranges.push(start..start + sz);
274        start += sz;
275    }
276
277    let last_start = parallel_ranges.pop().expect("no kmers in parallel ranges").start;
278    parallel_ranges.push(last_start..n_reads);
279    debug!("parallel ranges: {:?}", parallel_ranges);
280
281    let capacities = Arc::new(Mutex::new(vec![[0; BUCKETS]; n_threads]));
282    let unique_kmers = Arc::new(Mutex::new(vec![HashSet::<K>::new(); n_threads]));
283    let too_short_reads = Arc::new(Mutex::new(0));
284
285    let pb_size_buckets = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
286    pb_size_buckets.set_style(style.clone());
287    pb_size_buckets.set_message(format!("{:<32}", "finding bucket sizes"));
288
289    parallel_ranges.clone().into_par_iter().enumerate().for_each(|(i, range)| {
290
291        // first go trough all kmers to find the length of all buckets (to reserve capacity)
292        let mut thread_capacities = [0usize; BUCKETS];
293        let mut thread_kmers = HashSet::new();
294        let mut threads_too_short_reads = 0;
295        for ref read in seqs.iter_partial(range.clone())
296        { 
297            // add the required capacities to the respective buckets
298            add_seq_bucket_capacities(read, &mut thread_capacities, &mut thread_kmers);
299            if read.seq().len() < K::k() { threads_too_short_reads += 1 };
300            pb_size_buckets.inc(1);
301        }
302
303        let mut cap = capacities.lock().expect("error locking capacity mutex");
304        cap[i] = thread_capacities;
305
306        let mut u_kmers = unique_kmers.lock().expect("error locking coverages mutex");
307        u_kmers[i] = thread_kmers;
308
309        let mut tsr = too_short_reads.lock().expect("error locking n short reads");
310        *tsr += threads_too_short_reads;
311    });
312
313    let capacities = capacities.lock().expect("error in final lock capacities");
314    let input_kmers = capacities.iter().flatten().sum::<usize>();
315    let too_short_reads = *too_short_reads.lock().expect("error in last lock n short reads");
316
317    if too_short_reads > 0 {
318        warn!("{too_short_reads} out of {} reads will be skipped in graph construction because they are shorter than k", seqs.n_reads());
319    }
320    if too_short_reads >= seqs.n_reads() {
321        panic!("none of the reads were longer than k, aborting")
322    }
323
324    let mut unique_kmers = unique_kmers.lock().expect("error final lock coverages");
325
326    // combine unique k-mers found by separate threads
327    let mut combined_kmers = unique_kmers.pop().expect("no k-mers, empty graph");
328
329    while let Some(k) = unique_kmers.pop() {
330        combined_kmers.extend(k);
331    }
332
333    let iter_capacities = (0..BUCKETS).map(|i| (i, capacities.iter().map(|c_bucket| c_bucket[i]).sum::<usize>()));
334    let bucket_ranges = bucket_ranges::<K, SD, DI, _>(combined_kmers.len(), input_kmers, memory_size, iter_capacities);
335
336    let n_slices = bucket_ranges.len();
337
338    debug!("n of seqs: {}", seqs.n_reads());
339
340    let mut time_picking_par = 0.;
341    let mut time_picking = 0.;
342    let mut time_summarizing = 0.;
343
344    let shared_target_vecs = Arc::new(Mutex::new((Vec::new(), Vec::new(), Vec::new(), Vec::new())));
345
346    if time { println!("time all prepariations before sliced in filter_kmers (s): {}", before_all.elapsed().as_secs_f32()) }
347
348    let pb_bucket_ranges = multi_pb.add(ProgressBar::new(bucket_ranges.len() as u64));
349    pb_bucket_ranges.set_style(style.clone());
350    pb_bucket_ranges.set_message(format!("{:<32}", "filtering kmers"));
351
352    for (i, bucket_range) in bucket_ranges.into_iter().enumerate() {
353
354        debug!("Processing slice {} of {}", i+1, n_slices);
355
356        let before_kmer_picking = Instant::now();
357        // first step: picking kmers with their exts & data from the reads
358        // go through all kmers and sort into bucket according to first four bases
359        // all kmers starting with "AAAA" go in kmer_buckets[0], all starting with AAAC go in kmer_buckets[1] and so on
360        // when using the first four bases, this needs 256 buckets
361        // the buckets are split in to the bucket_ranges to save memory
362
363
364        let kmer_buckets = Arc::new(Mutex::new(vec![Vec::new(); n_threads]));
365
366        let before_picking_parallel = Instant::now();
367
368        let pb_fill_buckets = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
369        pb_fill_buckets.set_style(style.clone());
370        pb_fill_buckets.set_message(format!("{:<32}", "filling buckets with k-mers"));
371
372        let pb_sum_buckets = multi_pb.add(ProgressBar::new(BUCKETS as u64));
373        pb_sum_buckets.set_style(style.clone());
374        pb_sum_buckets.set_message(format!("{:<32}", "summarizing k-mers in buckets"));
375
376        parallel_ranges.clone().into_par_iter().enumerate().for_each(|(i, range)| {
377            let mut kmer_buckets1d = Vec::with_capacity(BUCKETS); 
378            
379            // reserve capacities needed for current range in each bucket
380            for (i, capacity) in capacities[i].into_iter().enumerate() {
381                if bucket_range.contains(&i) {
382                    // capacity is in bucket range, allocate bucket with capacity
383                    kmer_buckets1d.push(Vec::with_capacity(capacity));
384                } else {
385                    // not in current range, add empty placeholder vector
386                    kmer_buckets1d.push(Vec::new());
387                }
388                
389            }
390
391            // fill buckets with kmers
392            for ref read in seqs.iter_partial(range.clone())
393            {
394                for (kmer, exts, quality) in read.iter_kmer_exts_quality::<K>() {
395                    // if needed, flip kmer and exts
396                    // check if bucket is in current range and if so, push kmer to bucket
397                    if let Some((min_kmer, flip_exts, bucket)) = bucket_ext_flip(kmer, exts, read.stranded(), bucket_range.clone()) {
398                        kmer_buckets1d[bucket].push(KmerDataItem::new(min_kmer, flip_exts, read.data(), quality));
399                    }
400                }
401
402                pb_fill_buckets.inc(1);
403            }
404
405            // clone and lock kmer_buckets to safely share across threads
406            let _kb_clone = Arc::clone(&kmer_buckets);
407            let mut kb2d = kmer_buckets.lock().expect("lock kmer buckets 2d");
408            // replace empty vec too keep order
409            kb2d[i] = kmer_buckets1d;
410
411        });
412
413        time_picking_par += before_picking_parallel.elapsed().as_secs_f32();
414
415        // unlock kmer buckets and move out of guard so they can be turned into iterator
416        let mut kmer_buckets = kmer_buckets.lock().expect("unlock kmer_buckets final");
417        let kmer_buckets = mem::take(&mut *kmer_buckets);
418
419        // all combined buckets go into this vector
420        let mut new_buckets = vec![Vec::new(); BUCKETS];
421        // flatten kmer buckets
422        for thread_vec in kmer_buckets.into_iter() {
423            for (i, mut bucket) in thread_vec.into_iter().enumerate() {
424                new_buckets[i].reserve_exact(bucket.len());
425                new_buckets[i].append(&mut bucket);
426            }
427        }
428
429        time_picking += before_kmer_picking.elapsed().as_secs_f32();
430        
431        let before_parallel = Instant::now();
432        
433        // parallel start
434        // summarize kmers in buckets      
435        new_buckets.into_par_iter().for_each(|mut kmer_vec| {
436            kmer_vec.sort_by_key(|elt| elt.kmer);
437
438            let size = kmer_vec.iter().chunk_by(|elt| elt.kmer).into_iter().count();
439
440            let mut all_kmers = Vec::with_capacity(size);
441            let mut valid_kmers = Vec::with_capacity(size);
442            let mut valid_exts = Vec::with_capacity(size);
443            let mut valid_data = Vec::with_capacity(size);
444
445
446            for (kmer, kmer_obs_iter) in kmer_vec.into_iter().chunk_by(|elt| elt.kmer).into_iter() {
447                let (is_valid, exts, summary_data) = SD::summarize(kmer_obs_iter, summariy_config);
448                if report_all_kmers {
449                    all_kmers.push(kmer);
450                }
451                if is_valid {
452                    valid_kmers.push(kmer);
453                    valid_exts.push(exts);
454                    valid_data.push(summary_data);
455                }
456            }
457
458            // if there are valid k-mers in this bucket, append them to the shared target vectors
459            // important that this is done in one step so each kmer has the same index with its exts and data
460            if !valid_kmers.is_empty() {
461
462                let _stv_clone = Arc::clone(&shared_target_vecs);
463                let mut stv = shared_target_vecs.lock().expect("lock target vectors");
464                // valid kmers
465                stv.0.reserve_exact(valid_kmers.len());
466                stv.0.append(&mut valid_kmers); 
467                // valid exts
468                stv.1.reserve_exact(valid_exts.len());
469                stv.1.append(&mut valid_exts); 
470                // valid data
471                stv.2.reserve_exact(valid_data.len());
472                stv.2.append(&mut valid_data); 
473            }
474
475            // if kmers were collected into all_kmers, append them to shared target vector
476            if !all_kmers.is_empty() {
477                let _stv_clone = Arc::clone(&shared_target_vecs);
478                let mut stv = shared_target_vecs.lock().expect("lock target vectors");
479                // all kmers
480                stv.3.reserve_exact(all_kmers.len());
481                stv.3.append(&mut all_kmers);
482            }
483
484            pb_sum_buckets.inc(1);
485        });
486        // parallel end
487
488        pb_bucket_ranges.inc(1);
489
490        time_summarizing += before_parallel.elapsed().as_secs_f32();
491
492        debug!("processed slice {}", i+1);
493    }
494    pb_bucket_ranges.finish_and_clear();
495
496    if time { 
497        println!("time collecting par (s): {}", time_picking_par);
498        println!("time collecting (s): {}", time_picking);
499        println!("time summarizing (s): {}", time_summarizing);
500    }
501
502    let stv = shared_target_vecs.lock().expect("final lock target vectors");
503
504    debug!("valid kmers - capacity: {}, size: {}, mem: {}", stv.0.capacity(), stv.0.len(), mem::size_of_val(&*stv.0));
505    debug!("valid exts - capacity: {}, size: {}, mem: {}", stv.1.capacity(), stv.1.len(), mem::size_of_val(&*stv.1));
506    debug!("valid data - capacity: {}, size: {}, struct mem: {}, real mem: {}", stv.2.capacity(), stv.2.len(), mem::size_of_val(&*stv.2), {
507        let mut data_size = 0;
508        for data in &stv.2 {
509            data_size += data.mem();
510        }
511        data_size
512    });
513    debug!("all kmers - capacity: {}, size: {}, mem: {}", stv.3.capacity(), stv.3.len(), mem::size_of_val(&*stv.3));
514    
515
516    let before_hash = Instant::now();
517    let hm = BoomHashMap2::new_parallel(stv.0.to_vec(), stv.1.to_vec(), stv.2.to_vec());
518    let after_hash = before_hash.elapsed().as_secs_f32();
519    let all_kmers = stv.3.to_vec();
520
521    let filter_kmers_inner = before_all.elapsed().as_secs_f32();
522    if time { 
523        println!("time filter_kmers inner (s): {}", filter_kmers_inner);
524        println!("time build filtered hash map (s): {}", after_hash);
525    }
526
527    (
528        hm,
529        all_kmers,
530    )
531}
532
533// TODO add conditional filters to all SummaryDatas
534
535/// Process DNA sequences into kmers and determine the set of valid kmers,
536/// their extensions, and summarize associated label/'color' data. The input
537/// sequences are converted to kmers of type `K`, and like kmers are grouped together.
538/// All instances of each kmer, along with their label data are then proccessed with
539/// [`SummaryData::summarize`], which generates an implementation of [`SummaryData`],
540/// which is specified with the generic `SD`, decides if the k-mer is 'valid' 
541/// based on the parameters given in `summary_config`, and
542/// summarizes the the individual label into a single label data structure
543/// for the kmer. Care is taken to keep the memory consumption small.
544/// 
545/// Be aware that the configuration in `summary_config` only applies if the required
546/// informaiton can be supplied by the chosen implementation of `SummaryData`.
547/// E.g., the k-mers will not be filtered according to p-value when the `SummaryData`
548/// only contains the number of observations.
549///
550/// # Arguments
551///
552/// * `seqs` are the reads wrapped in a `Reads<u8>`. See [`Reads<DI>`]
553/// * `summary_config` is a [`SummaryConfig`], which contains prameters and 
554///   information necessary for the filtering
555/// * `stranded`: if true, preserve the strandedness of the input sequences, effectively
556///   assuming they are all in the positive strand. If false, the kmers will be canonicalized
557///   to the lexicographic minimum of the kmer and it's reverse complement.
558/// * `report_all_kmers`: if true returns the vector of all the observed kmers and performs the
559///   kmer based filtering
560/// * `memory_size`: gives the size bound on the memory in GB to use and automatically determines
561///   the number of passes needed.
562/// 
563/// # Returns
564/// BoomHashMap2 Object, check rust-boomphf for details
565/// 
566/// # Examples:
567/// 
568/// ```
569/// use debruijn::summarizer::{SampleInfo, SummaryConfig, TagsCountsData, StatTest, GroupFrac};
570/// use debruijn::reads::{Reads, ReadsPaired, Strandedness};
571/// use debruijn::filter::filter_kmers;
572/// use debruijn::kmer::Kmer16;
573/// use debruijn::Exts;
574/// 
575/// let mut seqs = Reads::new(Strandedness::Unstranded);
576/// seqs.add_from_bytes("ACCGATCATATATTTTCGGGGCTAGGCGAAGCGATCTTATCGAGC".as_bytes(), None, 1u8);
577/// seqs.add_from_bytes("GCGATCGAGCATGCTCAGCTGACGTGACTGACGTAGCTATCTTTTCGTAGCTAC".as_bytes(), None, 1u8);
578/// seqs.add_from_bytes("GCGAGTTTGCGACTCGAGGCTATCTAGCTAGCTASGCTCTCGACTAGCTGACTTACGACGACTACG".as_bytes(), None, 2u8);
579/// seqs.add_from_bytes("CGATTAGCTACGTAGCTAGCTGACGTACTGGGGGGTATTTCGGATCTGCGGAGCGATCT".as_bytes(), None, 2u8);
580///       
581/// let sample_info = SampleInfo::new(
582///     0b000011,
583///     0b111100,
584///     vec![23423, 3463454, 2242234, 2233243, 234322434, 2323234],
585/// );
586///     
587/// let summary_config = SummaryConfig::new(sample_info)
588///     .with_min_kmer_obs(3)
589///     .with_group_frac(GroupFrac::One, 0.333)
590///     .with_stat_test(StatTest::StudentsTTest);
591///
592///    
593/// let (hashed_kmers, _) = filter_kmers::<TagsCountsData, Kmer16, _>(
594///     &ReadsPaired::Unpaired { reads: seqs },
595///     &summary_config,
596///     false,
597///     10.,
598///    false,
599/// );
600/// ```
601#[inline(never)]
602pub fn filter_kmers<SD, K, DI>(
603    seqs: &ReadsPaired<DI>,
604    summary_config: &SummaryConfig,
605    report_all_kmers: bool,
606    memory_size: f32,
607    time: bool,
608) -> (BoomHashMap2<K, Exts, SD>, Vec<K>)
609where
610    SD: Debug + SummaryData<DI>, 
611    K: Kmer, 
612    DI: Copy + Clone + Debug + Hash + Eq
613{
614    let before_all = Instant::now();
615
616    // progress bars
617    let multi_pb = MultiProgress::new();
618    let style = ProgressStyle::with_template(PROGRESS_STYLE).unwrap().progress_chars("#/-");
619
620    let pb = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
621    pb.set_style(style.clone());
622    pb.set_message(format!("{:<32}", "finding bucket lengths"));
623
624    // go trough all kmers to find the length of all buckets (to reserve capacity)
625    let mut capacities = [0; BUCKETS];
626
627    // also track coverage to predict final graph size
628    let mut unique_kmers = HashSet::<K>::new();
629    let mut too_short_reads = 0;
630
631    for ref read in seqs.iter().progress_with(pb) {
632        // add the required capacities to the respective buckets
633        add_seq_bucket_capacities(read, &mut capacities, &mut unique_kmers);
634        if read.seq().len() < K::k()  { too_short_reads += 1}
635    }
636
637    if too_short_reads > 0 {
638        warn!("{too_short_reads} out of {} reads will be skipped in graph construction because they are shorter than k", seqs.n_reads());
639    }
640    if too_short_reads >= seqs.n_reads() {
641        panic!("none of the reads were longer than k, aborting")
642    }
643    
644    debug!("kmer capacities: {:?}, times {}", capacities, mem::size_of::<(K, Exts, DI)>());
645    let input_kmers = capacities.iter().sum::<usize>();
646
647    if time { println!("time counting kmers (s): {}", before_all.elapsed().as_secs_f32()) }
648
649    let iter_capacities = capacities.iter().enumerate().map(|(i, &c)| (i, c));
650    let bucket_ranges = bucket_ranges::<K, SD, DI, _>(unique_kmers.len(), input_kmers, memory_size, iter_capacities);
651
652    let n_slices = bucket_ranges.len();
653
654    debug!("n of seqs: {}", seqs.n_reads());
655
656    let mut all_kmers = Vec::new();
657    let mut valid_kmers = Vec::new();
658    let mut valid_exts = Vec::new();
659    let mut valid_data = Vec::new();
660
661    let mut time_collecting = 0.;
662    let mut time_summarizing = 0.;
663
664    if time { println!("time all prepariations before sliced in filter_kmers (s): {}", before_all.elapsed().as_secs_f32()) }
665
666    let pb_bucket_ranges = multi_pb.add(ProgressBar::new(bucket_ranges.len() as u64));
667    pb_bucket_ranges.set_style(style.clone());
668    pb_bucket_ranges.set_message(format!("{:<32}", "filtering k-mers"));
669
670
671    // iterate over the bucket ranges
672    for (i, bucket_range) in bucket_ranges.into_iter().enumerate() {
673        debug!("Processing slice {} of {}", i+1, n_slices);
674
675        let before_kmer_picking = Instant::now();
676        // first step: picking kmers with their exts & data from the reads
677        // go through all kmers and sort into bucket according to first four bases
678        // all kmers starting with "AAAA" go in kmer_buckets[0], all starting with AAAC go in kmer_buckets[1] and so on
679        // when using the first four bases, this needs 256 buckets
680        // the buckets are split in to the bucket_ranges to save memory
681        
682        let mut kmer_buckets = Vec::with_capacity(BUCKETS);
683        // reserve needed capacity in each bucket
684        for (i, capacity) in capacities.iter().enumerate() {
685            if bucket_range.contains(&i) {
686                // bucket is in range, add with prepared capacity
687                kmer_buckets.push(Vec::with_capacity(*capacity));
688            } else {
689                // bucket is not in range, add empty placeholder
690                kmer_buckets.push(Vec::new());
691            }
692            
693        }
694
695        // then go through all kmers and add to bucket according to first four bases and current bucket_range
696        let pb = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
697        pb.set_style(style.clone());
698        pb.set_message(format!("{:<32}", "filling buckets with kmers"));
699
700        for ref read in seqs.iter().progress_with(pb) {
701            // iterate trough all kmers in seq
702            for (kmer, exts, quality) in read.iter_kmer_exts_quality::<K>() {
703                // if needed, flip kmer and exts
704                // check if bucket is in current range and if so, push kmer to bucket
705                if let Some((min_kmer, flip_exts, bucket)) = bucket_ext_flip(kmer, exts, read.stranded(), bucket_range.clone()) {
706                    kmer_buckets[bucket].push(KmerDataItem::new(min_kmer, flip_exts, read.data(), quality));
707                }
708            }
709        }
710
711        time_collecting += before_kmer_picking.elapsed().as_secs_f32();
712
713        let before_summarizing = Instant::now();
714
715        // go trough all buckets and summarize the contents
716        let pb = multi_pb.add(ProgressBar::new(kmer_buckets.len() as u64));
717        pb.set_style(style.clone());
718        pb.set_message(format!("{:<32}", "summarizing k-mers in buckets"));
719
720        for mut kmer_vec in kmer_buckets.into_iter().progress_with(pb) {
721            //debug!("kmers in this bucket: {}", kmer_vec.len());
722            kmer_vec.sort_by_key(|elt| elt.kmer);
723
724            
725            // predict amount of unique k-mers found in this bucket
726            let size = kmer_vec.iter().chunk_by(|elt| elt.kmer).into_iter().count();
727
728            // only works perfectly if min k-mer count is 1, else this might reserve too much 
729            // still better than doubling the vector
730            // also reserve_exact considers pre-existing free capacity -> this might mostly add to runtime
731            valid_kmers.reserve_exact(size);
732            valid_exts.reserve_exact(size);
733            valid_data.reserve_exact(size);
734
735            // if all k-mers should be reported, also grow all_kmers by exact amount -> this should always be the perfect capacity
736            if report_all_kmers {
737                all_kmers.reserve_exact(size);
738            }
739
740            // group the tuples by the k-mers and iterate over the groups
741            for (kmer, kmer_obs_iter) in kmer_vec.into_iter().chunk_by(|elt| elt.kmer).into_iter() {
742                // summarize group with chosen summarizer and add result to vectors
743                let (is_valid, exts, summary_data) = SD::summarize(kmer_obs_iter, summary_config);
744                if report_all_kmers {
745                    all_kmers.push(kmer);
746                }
747                if is_valid {
748                    valid_kmers.push(kmer);
749                    valid_exts.push(exts);
750                    valid_data.push(summary_data); 
751                }
752            }
753        }
754
755        pb_bucket_ranges.inc(1);
756
757        time_summarizing += before_summarizing.elapsed().as_secs_f32();
758
759        debug!("valid kmers - capacity: {}, size: {}, mem: {} Bytes", valid_kmers.capacity(), valid_kmers.len(), mem::size_of_val(&*valid_kmers));
760        debug!("valid exts - capacity: {}, size: {}, mem: {} Bytes", valid_exts.capacity(), valid_exts.len(), mem::size_of_val(&*valid_exts));
761        debug!("valid data - capacity: {}, size: {}, mem: {} Bytes", valid_data.capacity(), valid_data.len(), mem::size_of_val(&*valid_data));
762    }
763
764    pb_bucket_ranges.finish_and_clear();
765
766    if time { 
767        println!("time collecting (s): {}", time_collecting);
768        println!("time summarizing (s): {}", time_summarizing);
769    }
770
771
772    debug!(
773        "Unique kmers: {}, All kmers (if returned): {}",
774        valid_kmers.len(),
775        all_kmers.len(),
776    );
777
778    debug!("size of valid kmers: {} Bytes
779        size of valid exts: {} Bytes
780        size of valid data: {} Bytes", mem::size_of_val(&*valid_kmers), mem::size_of_val(&*valid_exts), mem::size_of_val(&*valid_data));
781
782    let before_hash = Instant::now();
783    let hm = BoomHashMap2::new(valid_kmers, valid_exts, valid_data);
784    let after_hash = before_hash.elapsed().as_secs_f32();
785
786    let filter_kmers_inner = before_all.elapsed().as_secs_f32();
787    if time { 
788        println!("time filter_kmers inner (s): {}", filter_kmers_inner);
789        println!("time build filter hash map (s): {}", after_hash);
790    }
791    (
792        hm,
793        all_kmers,
794    )
795}
796
797/// Remove extensions in valid_kmers that point to censored kmers. A censored kmer
798/// exists in `all_kmers` but not `valid_kmers`. Since the kmer exists in this partition,
799/// but was censored, we know that we can delete extensions to it.
800/// In sharded kmer processing, we will have extensions to kmers in other shards. We don't
801/// know whether these are censored until later, so we retain these extension.
802pub fn remove_censored_exts_sharded<K: Kmer, D>(
803    stranded: bool,
804    valid_kmers: &mut [(K, (Exts, D))],
805    all_kmers: &[K],
806) {
807    for idx in 0..valid_kmers.len() {
808        let mut new_exts = Exts::empty();
809        let kmer = valid_kmers[idx].0;
810        let exts = (valid_kmers[idx].1).0;
811
812        for dir in [Dir::Left, Dir::Right].iter() {
813            for i in 0..4 {
814                if exts.has_ext(*dir, i) {
815                    let _ext_kmer = kmer.extend(i, *dir);
816
817                    let ext_kmer = if stranded {
818                        _ext_kmer
819                    } else {
820                        _ext_kmer.min_rc()
821                    };
822
823                    let censored = if valid_kmers.binary_search_by_key(&ext_kmer, |d| d.0).is_ok() {
824                        // ext_kmer is valid. not censored
825                        false
826                    } else {
827                        // ext_kmer is not valid. if it was in this shard, then we censor it
828                        all_kmers.binary_search(&ext_kmer).is_ok()
829                    };
830
831                    if !censored {
832                        new_exts = new_exts.set(*dir, i);
833                    }
834                }
835            }
836        }
837
838        (valid_kmers[idx].1).0 = new_exts;
839    }
840}
841
842/// Remove extensions in valid_kmers that point to censored kmers. Use this method in a non-partitioned
843/// context when valid_kmers includes _all_ kmers that will ultimately be included in the graph.
844pub fn remove_censored_exts<K: Kmer, D>(stranded: bool, valid_kmers: &mut [(K, (Exts, D))]) {
845    for idx in 0..valid_kmers.len() {
846        let mut new_exts = Exts::empty();
847        let kmer = valid_kmers[idx].0;
848        let exts = (valid_kmers[idx].1).0;
849
850        for dir in [Dir::Left, Dir::Right].iter() {
851            for i in 0..4 {
852                if exts.has_ext(*dir, i) {
853                    let ext_kmer = if stranded {
854                        kmer.extend(i, *dir)
855                    } else {
856                        kmer.extend(i, *dir).min_rc()
857                    };
858
859                    let kmer_valid = valid_kmers.binary_search_by_key(&ext_kmer, |d| d.0).is_ok();
860
861                    if kmer_valid {
862                        new_exts = new_exts.set(*dir, i);
863                    }
864                }
865            }
866        }
867
868        (valid_kmers[idx].1).0 = new_exts;
869    }
870}
871
872#[cfg(test)]
873mod tests {
874    use boomphf::hashmap::BoomHashMap2;
875    use crate::{Exts, dna_string::DnaString, filter::*, kmer::{Kmer2, Kmer6, Kmer80}, reads::Reads, summarizer::{SampleInfo, TagsSumData}, test::{random_dna, random_kmer}};
876
877    #[test]
878    fn test_filter_kmers() {
879        let fastq = [
880            (DnaString::from_dna_string("AAAAATTT"), Exts::empty(), 6u8),
881            (DnaString::from_dna_string("TTTTTTTTTTAAAAAA"), Exts::empty(), 6u8),
882            (DnaString::from_dna_string("AAAAAAAAAAAAA"), Exts::empty(), 7u8),
883        ];
884
885        let reads = Reads::from_vmer_vec(fastq, crate::reads::Strandedness::Unstranded);
886
887        let sample_info = SampleInfo::new(0, 0, Vec::new());
888
889        let config = SummaryConfig::new(sample_info).with_stat_test(crate::summarizer::StatTest::StudentsTTest);
890
891
892        let (hm, _): (BoomHashMap2<Kmer6, Exts, TagsSumData>, Vec<_>) = filter_kmers(
893            &ReadsPaired::Unpaired { reads }, 
894            &config,
895            false, 
896            1.,
897            false,
898         );
899
900         println!("{:?}", hm);
901
902    }
903
904    #[test]
905    fn test_filter_kmers_parallel() {
906        /* let fastq = [
907            (DnaString::from_dna_string("AAAAATTT"), Exts::empty(), 6u8),
908            (DnaString::from_dna_string("TTTTTTTTTTAAAAAA"), Exts::empty(), 6u8),
909            (DnaString::from_dna_string("AAAAAAAAAAAAA"), Exts::empty(), 7u8),
910        ];
911
912        let mut reads = Reads::new();
913
914        for (read, exts, data) in fastq {
915            reads.add_read(read, exts, data);
916
917        } */
918
919        let mut reads = Reads::new(crate::reads::Strandedness::Unstranded);
920
921        for _i in 0..10000 {
922            let dna = random_dna(150);
923            reads.add_from_bytes(&dna, None, 0u8);
924        }
925
926        let sample_info = SampleInfo::new(0, 0, Vec::new());
927        let config = SummaryConfig::new(sample_info.clone()).with_stat_test(crate::summarizer::StatTest::StudentsTTest);
928
929
930        let (hm, _): (BoomHashMap2<Kmer6, Exts, TagsSumData>, Vec<_>) = filter_kmers_parallel(
931            &ReadsPaired::Unpaired { reads }, 
932            &config,
933            false, 
934            1.,
935            false,         
936        );
937
938        println!("{:?}", hm);
939
940    }
941
942    #[test]
943    fn test_filter_kmers_parallel_k80() {
944        /* let fastq = [
945            (DnaString::from_dna_string("AAAAATTT"), Exts::empty(), 6u8),
946            (DnaString::from_dna_string("TTTTTTTTTTAAAAAA"), Exts::empty(), 6u8),
947            (DnaString::from_dna_string("AAAAAAAAAAAAA"), Exts::empty(), 7u8),
948        ];
949
950        let mut reads = Reads::new();
951
952        for (read, exts, data) in fastq {
953            reads.add_read(read, exts, data);
954
955        } */
956
957        let mut reads = Reads::new(crate::reads::Strandedness::Unstranded);
958
959        for _i in 0..10000 {
960            let dna = random_dna(150);
961            reads.add_from_bytes(&dna, None, 0u8);
962        }
963
964        let sample_info = SampleInfo::new(0, 0, Vec::new());
965        let config = SummaryConfig::new(sample_info.clone()).with_stat_test(crate::summarizer::StatTest::StudentsTTest);
966
967
968        let (_hm, _): (BoomHashMap2<Kmer80, Exts, TagsSumData>, Vec<_>) = filter_kmers_parallel(
969            &ReadsPaired::Unpaired { reads }, 
970            &config,
971            false, 
972            1.,
973            false,         
974        );
975    }
976
977
978    #[test]
979    fn test_par_hmap_combination() {
980        let n_threads = rayon::current_num_threads();
981        let kmers = Arc::new(Mutex::new(vec![HashSet::new(); n_threads]));
982        
983        (0..n_threads).into_par_iter().for_each(|i| {
984            let mut ks = HashSet::new();
985            for _j in 0..100 {
986                let kmer = random_kmer::<Kmer2>();
987                ks.insert(kmer);
988            }
989            let mut c = kmers.lock().expect("cov lock");
990            c[i] = ks;
991        });
992
993        println!("k-mers uncombinded: {:?}", kmers);
994
995        let mut kmers = kmers.lock().expect("final lock coverages");
996
997        let mut combined_kmers = kmers.pop().expect("no kmers");
998        while  let Some(kmap) = kmers.pop() {
999                combined_kmers.extend(kmap);
1000        }
1001
1002        assert_eq!(combined_kmers.len(), 16);
1003
1004        println!("combined k-mers: {:?}", combined_kmers)
1005    }
1006}
1007