• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jlab / rust-debruijn / 14265292510

04 Apr 2025 12:25PM UTC coverage: 82.582%. First build
14265292510

push

github

web-flow
Merge pull request #7 from jlab/dev

New `Reads` struct and revamped summarizers

- new `reads::Reads` struct for compact reads storage
- new handling of ambiguous bases
- move summarize methods to `SummaryData`
- add new functionality to `SummaryData` (p-values, edge multiplicities)
- `KmerSummarizer` removed

1890 of 2176 new or added lines in 10 files covered. (86.86%)

6078 of 7360 relevant lines covered (82.58%)

3674640.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.56
/src/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

5
use core::f32;
6
use std::fmt::Debug;
7
use std::mem;
8
use std::ops::Range;
9
use std::sync::Arc;
10
use std::sync::Mutex;
11
use std::time::Instant;
12

13
use boomphf::hashmap::BoomHashMap2;
14
use indicatif::MultiProgress;
15
use indicatif::ProgressBar;
16
use indicatif::ProgressIterator;
17
use indicatif::ProgressStyle;
18
use itertools::Itertools;
19
use log::debug;
20
use num_traits::Pow;
21
use rayon::current_num_threads;
22
use rayon::prelude::*;
23

24
use crate::reads::Reads;
25
use crate::summarizer::SummaryConfig;
26
use crate::summarizer::SummaryData;
27
use crate::Dir;
28
use crate::Exts;
29
use crate::Kmer;
30
use crate::Vmer;
31
use crate::BUCKETS;
32

33
// FIXME does not work with k < 4
34
pub fn bucket<K: Kmer>(kmer: K) -> usize {
20,711,950✔
35
    (kmer.get(0) as usize) << 6
20,711,950✔
36
        | (kmer.get(1) as usize) << 4
20,711,950✔
37
        | (kmer.get(2) as usize) << 2
20,711,950✔
38
        | (kmer.get(3) as usize)
20,711,950✔
39
}
20,711,950✔
40

41
fn lin_quant(p: f32, m: f32, b: f32) -> f32 {
3,744✔
42
    ((b * b + 2. * m * p).sqrt() - b) / m
3,744✔
43
}
3,744✔
44

45
fn lin_dist_range(buckets: usize, slices: usize) -> Vec<Range<usize>> {
1,872✔
46
    let b: f32 = 2f32/buckets as f32;
1,872✔
47
    let m: f32 = - 2f32/(buckets as f32).pow(2);
1,872✔
48

49
    let mut bucket_ranges_lin: Vec<std::ops::Range<usize>> = Vec::with_capacity(if slices < buckets {slices} else {buckets});
1,872✔
50

51
    for i in 1..=slices {
1,872✔
52
        // calculate lower and upper bound with Quantile function of linear probability distribution
53
        let lbound = lin_quant((i as f32 - 1.)/slices as f32, m, b) as usize;
1,872✔
54
        let ubound = lin_quant((i as f32)/slices as f32, m, b) as usize;
1,872✔
55

56
        // if upper bound is above no of buckets (256), reduce to no of buckets
57
        let ubound: usize = if ubound > buckets { buckets } else { ubound };
1,872✔
58
        if ubound > lbound && lbound < buckets { bucket_ranges_lin.push(lbound..ubound) };
1,872✔
59
    }
60

61
    bucket_ranges_lin
1,872✔
62
}
1,872✔
63

64

65

66
/// Process DNA sequences into kmers and determine the set of valid kmers,
67
/// their extensions, and summarize associated label/'color' data. The input
68
/// sequences are converted to kmers of type `K`, and like kmers are grouped together.
69
/// All instances of each kmer, along with their label data are then proccessed with
70
/// [`SummaryData::summarize`], which generates an implementation of [`SummaryData`],
71
/// which is specified with the generic `SD`, decides if the k-mer is 'valid' 
72
/// based on the parameters given in `summary_config`, and
73
/// summarizes the the individual label into a single label data structure
74
/// for the kmer. Care is taken to keep the memory consumption small.
75
/// 
76
/// Be aware that the configuration in `summary_config` only applies if the required
77
/// informaiton can be supplied by the chosen implementation of `SummaryData`.
78
/// E.g., the k-mers will not be filtered according to p-value when the `SummaryData`
79
/// only contains the number of observations.
80
///
81
/// # Arguments
82
///
83
/// * `seqs` are the reads wrapped in a `Reads<u8>`. See [`Reads<D>`]
84
/// * `summary_config` is a [`SummaryConfig`], which contains prameters and 
85
///    information necessary for the filtering
86
/// * `stranded`: if true, preserve the strandedness of the input sequences, effectively
87
///   assuming they are all in the positive strand. If false, the kmers will be canonicalized
88
///   to the lexicographic minimum of the kmer and it's reverse complement.
89
/// * `report_all_kmers`: if true returns the vector of all the observed kmers and performs the
90
///   kmer based filtering
91
/// * `memory_size`: gives the size bound on the memory in GB to use and automatically determines
92
///   the number of passes needed
93
/// * `time`: print information about the time needed for each step
94
/// # Returns
95
/// BoomHashMap2 Object, check rust-boomphf for details
96
/// 
97
/// /// # Returns
98
/// BoomHashMap2 Object, check rust-boomphf for details
99
/// 
100
/// # Examples:
101
/// 
102
/// ```
103
/// use debruijn::summarizer::{SampleInfo, SummaryConfig, TagsCountsData, StatTest, GroupFrac};
104
/// use debruijn::reads::Reads;
105
/// use debruijn::filter::filter_kmers_parallel;
106
/// use debruijn::kmer::Kmer16;
107
/// use debruijn::Exts;
108
/// 
109
/// let mut seqs = Reads::new();
110
/// seqs.add_from_bytes("ACCGATCATATATTTTCGGGGCTAGGCGAAGCGATCTTATCGAGC".as_bytes(), Exts::empty(), 1u8);
111
/// seqs.add_from_bytes("GCGATCGAGCATGCTCAGCTGACGTGACTGACGTAGCTATCTTTTCGTAGCTAC".as_bytes(), Exts::empty(), 1u8);
112
/// seqs.add_from_bytes("GCGAGTTTGCGACTCGAGGCTATCTAGCTAGCTASGCTCTCGACTAGCTGACTTACGACGACTACG".as_bytes(), Exts::empty(), 2u8);
113
/// seqs.add_from_bytes("CGATTAGCTACGTAGCTAGCTGACGTACTGGGGGGTATTTCGGATCTGCGGAGCGATCT".as_bytes(), Exts::empty(), 2u8);
114
///       
115
/// let sample_info = SampleInfo::new(
116
///     0b000011,
117
///     0b111100,
118
///     2,
119
///     4,
120
///     vec![23423, 3463454, 2242234, 2233243, 234322434, 2323234],
121
/// );
122
///     
123
/// let summary_config = SummaryConfig::new(
124
///     3,
125
///     None,
126
///     GroupFrac::One,
127
///     0.33333,
128
///     sample_info,
129
///     None,
130
///     StatTest::StudentsTTest,
131
/// );
132
///    
133
/// let (hashed_kmers, _) = filter_kmers_parallel::<Kmer16, TagsCountsData>(
134
///     &seqs,
135
///     &summary_config,
136
///     false,
137
///     false,
138
///     10,
139
///     false,
140
/// );
141
/// ```
142
#[inline(never)]
143
//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>(
144
pub fn filter_kmers_parallel<K: Kmer + Sync + Send, SD: Clone + std::fmt::Debug + Send + SummaryData<u8>>(
4✔
145
    seqs: &Reads<u8>,
4✔
146
    summariy_config: &SummaryConfig,
4✔
147
    stranded: bool,
4✔
148
    report_all_kmers: bool,
4✔
149
    memory_size: usize,
4✔
150
    time: bool,
4✔
151
) -> (BoomHashMap2<K, Exts, SD>, Vec<K>)
4✔
152
{
4✔
153
    // take timestamp before all processes
4✔
154
    let before_all = Instant::now();
4✔
155

4✔
156
    let rc_norm = !stranded;
4✔
157

4✔
158
    // Estimate 6 consumed by Kmer vectors, and set iteration count appropriately
4✔
159
    let input_kmers: usize = seqs
4✔
160
        .iter()
4✔
161
        .map(|(ref read, _, _)| read.len().saturating_sub(K::k() - 1))
10,042✔
162
        .sum();
4✔
163

4✔
164
    if time { println!("time counting kmers (s): {}", before_all.elapsed().as_secs_f32()) }
4✔
165

166
    let kmer_mem = input_kmers * mem::size_of::<(K, Exts, u8)>();
4✔
167
    let max_mem = memory_size * 10_usize.pow(9);
4✔
168
    let slices_seq = kmer_mem / max_mem + 1;
4✔
169
    let slices = slices_seq;
4✔
170
    //let sz = buckets / slices + 1;
4✔
171

4✔
172
    debug!("kmers: {}, mem per kmer: {}, kmer_mem: {} Bytes, slices: {}", input_kmers, mem::size_of::<(K, Exts, u8)>(), kmer_mem, slices);
4✔
173
    
174
    // split ranges into slices according to constant probrbiliy dist. if stranded, else according to linear probability distribition
175
    let bucket_ranges: Vec<std::ops::Range<usize>> = if stranded {
4✔
NEW
176
        let mut bucket_ranges = Vec::with_capacity(slices);
×
NEW
177
        let mut start = 0;
×
NEW
178
        let sz = BUCKETS / slices + 1;
×
NEW
179
        while start < BUCKETS {
×
NEW
180
            bucket_ranges.push(start..start + sz);
×
NEW
181
            start += sz;
×
NEW
182
        }
×
NEW
183
        bucket_ranges
×
184
    } else {
185
        lin_dist_range(BUCKETS, slices)
4✔
186
    };
187
        
188

189
    debug!("bucket_ranges: {:?}, len br: {}", bucket_ranges, bucket_ranges.len());
4✔
190
    assert!(bucket_ranges[bucket_ranges.len() - 1].end >= BUCKETS);
4✔
191
    let n_buckets = bucket_ranges.len();
4✔
192

4✔
193
    if bucket_ranges.len() > 1 {
4✔
194
        debug!(
×
195
            "{} sequences, {} kmers, {} passes",
×
NEW
196
            seqs.n_reads(),
×
197
            input_kmers,
×
198
            bucket_ranges.len()
×
199
        );
200
    }
4✔
201

202
    debug!("n of seqs: {}", seqs.n_reads());
4✔
203

204
    let mut time_picking_par = 0.;
4✔
205
    let mut time_picking = 0.;
4✔
206
    let mut time_summarizing = 0.;
4✔
207

4✔
208
    let shared_target_vecs = Arc::new(Mutex::new((Vec::new(), Vec::new(), Vec::new(), Vec::new())));
4✔
209

4✔
210
    if time { println!("time all prepariations before sliced in filter_kmers (s): {}", before_all.elapsed().as_secs_f32()) }
4✔
211

212
    // progress bars
213
    let multi_pb = MultiProgress::new();
4✔
214
    let style = ProgressStyle::with_template("{msg} [{elapsed_precise}] {bar:60.cyan/blue} ({pos}/{len})").unwrap().progress_chars("#/-");
4✔
215

4✔
216
    let pb_bucket_ranges = multi_pb.add(ProgressBar::new(bucket_ranges.len() as u64));
4✔
217
    pb_bucket_ranges.set_style(style.clone());
4✔
218
    pb_bucket_ranges.set_message(format!("{:<32}", "filtering kmers"));
4✔
219

220
    for (i, bucket_range) in bucket_ranges.into_iter().enumerate() {
4✔
221

222
        debug!("Processing bucket {} of {}", i+1, n_buckets);
4✔
223

224
        let before_kmer_picking = Instant::now();
4✔
225
        // first step: picking kmers with their exts & data from the reads
4✔
226
        // go through all kmers and sort into bucket according to first four bases
4✔
227
        // all kmers starting with "AAAA" go in kmer_buckets[0], all starting with AAAC go in kmer_buckets[1] and so on
4✔
228
        // when using the first four bases, this needs 256 buckets
4✔
229
        // the buckets are split in to the bucket_ranges to save memory
4✔
230

4✔
231
        // split all reads into ranges to be processed in parallel for counting capacities and picking kmers
4✔
232

4✔
233
        let n_threads = current_num_threads();
4✔
234
        let n_reads = seqs.n_reads();
4✔
235
        let sz = n_reads / n_threads + 1;
4✔
236

4✔
237
        debug!("n_reads: {}", n_reads);
4✔
238
        debug!("sz: {}", sz);
4✔
239

240
        let mut parallel_ranges = Vec::with_capacity(slices);
4✔
241
        let mut start = 0;
4✔
242
        while start < n_reads {
20✔
243
            parallel_ranges.push(start..start + sz);
16✔
244
            start += sz;
16✔
245
        }
16✔
246

247
        let last_start = parallel_ranges.pop().expect("no kmers in parallel ranges").start;
4✔
248
        parallel_ranges.push(last_start..n_reads);
4✔
249
        debug!("parallel ranges: {:?}", parallel_ranges);
4✔
250

251

252
        let kmer_buckets = Arc::new(Mutex::new(vec![Vec::new(); n_threads]));
4✔
253

4✔
254
        let before_picking_parallel = Instant::now();
4✔
255

4✔
256
        let pb_size_buckets = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
4✔
257
        pb_size_buckets.set_style(style.clone());
4✔
258
        pb_size_buckets.set_message(format!("{:<32}", "finding bucket sizes"));
4✔
259
        
4✔
260

4✔
261
        let pb_fill_buckets = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
4✔
262
        pb_fill_buckets.set_style(style.clone());
4✔
263
        pb_fill_buckets.set_message(format!("{:<32}", "filling buckets with k-mers"));
4✔
264

4✔
265
        let pb_sum_buckets = multi_pb.add(ProgressBar::new(BUCKETS as u64));
4✔
266
        pb_sum_buckets.set_style(style.clone());
4✔
267
        pb_sum_buckets.set_message(format!("{:<32}", "summarizing k-mers in buckets"));
4✔
268

4✔
269
        parallel_ranges.clone().into_par_iter().enumerate().for_each(|(i, range)| {
16✔
270

16✔
271
            // first go trough all kmers to find the length of all buckets (to reserve capacity)
16✔
272
            let mut capacities = [0usize; BUCKETS];
16✔
273
            for (ref seq, _, _) in seqs.partial_iter(range.clone()) { 
10,042✔
274
                // iterate through all kmers in seq
275
                for kmer in seq.iter_kmers::<K>() {
1,452,358✔
276
                    // if not stranded choose lexiographically lesser of kmer and rc of kmer
277
                    let min_kmer = if rc_norm {
1,452,358✔
278
                        let (min_kmer, _) = kmer.min_rc_flip();
1,452,358✔
279
                        min_kmer
1,452,358✔
280
                    } else {
281
                        kmer
×
282
                    };
283

284
                    // calculate which bucket this kmer belongs to
285
                    let bucket = if K::k() > 3 { bucket(min_kmer) } else { min_kmer.to_u64() as usize };
1,452,358✔
286
                    //let bucket = bucket(min_kmer);
287
                    // check if bucket is in current range and if so, add one to needed capacity
288
                    let in_range = bucket >= bucket_range.start && bucket < bucket_range.end;
1,452,358✔
289
                    if in_range { 
1,452,358✔
290
                        capacities[bucket] += 1;
1,452,358✔
291
                    }
1,452,358✔
292
                }
293
                pb_size_buckets.inc(1);
10,042✔
294
            }
295

296
            let mut kmer_buckets1d = Vec::with_capacity(BUCKETS); 
16✔
297
            
298
            // reserve capacities needed for current range in each bucket
299
            for capacity in capacities.into_iter() {
4,096✔
300
                kmer_buckets1d.push(Vec::with_capacity(capacity));
4,096✔
301
            }
4,096✔
302

303
            // fill buckets with kmers
304
            for (ref seq, seq_exts, ref d) in seqs.partial_iter(range) {
10,042✔
305
                for (kmer, exts) in seq.iter_kmer_exts::<K>(seq_exts) {
1,452,358✔
306
                    let (min_kmer, flip_exts) = if rc_norm {
1,452,358✔
307
                        let (min_kmer, flip) = kmer.min_rc_flip();
1,452,358✔
308
                        let flip_exts = if flip { exts.rc() } else { exts };
1,452,358✔
309
                        (min_kmer, flip_exts)
1,452,358✔
310
                    } else {
311
                        (kmer, exts)
×
312
                    };
313

314
                    // calculate which bucket this kmer belongs to
315
                    let bucket = if K::k() > 3 { bucket(min_kmer) } else { min_kmer.to_u64() as usize };
1,452,358✔
316
                    //let bucket = bucket(min_kmer);
317
                    // check if bucket is in current range and if so, push kmer to bucket
318
                    let in_range = bucket >= bucket_range.start && bucket < bucket_range.end;
1,452,358✔
319
                    if in_range {
1,452,358✔
320
                        kmer_buckets1d[bucket].push((min_kmer, flip_exts, *d));
1,452,358✔
321
                    }
1,452,358✔
322
                }
323

324
                pb_fill_buckets.inc(1);
10,042✔
325
            }
326

327
            // clone and lock kmer_buckets to safely share across threads
328
            let _kb_clone = Arc::clone(&kmer_buckets);
16✔
329
            let mut kb2d = kmer_buckets.lock().expect("lock kmer buckets 2d");
16✔
330
            // replace empty vec too keep order
16✔
331
            kb2d[i] = kmer_buckets1d;
16✔
332

16✔
333
        });
16✔
334

4✔
335
        time_picking_par += before_picking_parallel.elapsed().as_secs_f32();
4✔
336

4✔
337
        // unlock kmer buckets and move out of guard so they can be turned into iterator
4✔
338
        let mut kmer_buckets = kmer_buckets.lock().expect("unlock kmer_buckets final");
4✔
339
        let kmer_buckets = mem::take(&mut *kmer_buckets);
4✔
340

4✔
341
        // all combined buckets go into this vector
4✔
342
        let mut new_buckets = vec![Vec::new(); BUCKETS];
4✔
343
        // flatten kmer buckets
344
        for thread_vec in kmer_buckets.into_iter() {
16✔
345
            for (i, mut bucket) in thread_vec.into_iter().enumerate() {
4,096✔
346
                new_buckets[i].reserve_exact(bucket.len());
4,096✔
347
                new_buckets[i].append(&mut bucket);
4,096✔
348
            }
4,096✔
349
        }
350

351
        time_picking += before_kmer_picking.elapsed().as_secs_f32();
4✔
352
        
4✔
353
        let before_parallel = Instant::now();
4✔
354
        
4✔
355
        // parallel start
4✔
356
        // summarize kmers in buckets      
4✔
357
        new_buckets.into_par_iter().enumerate().for_each(|(j, mut kmer_vec)| {
1,024✔
358
            //debug!("kmers in bucket #{}: {}", j, kmer_vec.len());
1,024✔
359
            debug!("starting bucket {} with {} kmers, capacity of {}", j, kmer_vec.len(), kmer_vec.capacity());
1,024✔
360
            kmer_vec.sort_by_key(|elt| elt.0);
2,907,894✔
361

1,024✔
362
            let size = kmer_vec.iter().chunk_by(|elt| elt.0).into_iter().count();
1,452,358✔
363

1,024✔
364
            let mut all_kmers = Vec::with_capacity(size);
1,024✔
365
            let mut valid_kmers = Vec::with_capacity(size);
1,024✔
366
            let mut valid_exts = Vec::with_capacity(size);
1,024✔
367
            let mut valid_data = Vec::with_capacity(size);
1,024✔
368

369

370
            for (kmer, kmer_obs_iter) in kmer_vec.into_iter().chunk_by(|elt| elt.0).into_iter() {
1,452,358✔
371
                let (is_valid, exts, summary_data) = SD::summarize(kmer_obs_iter, summariy_config);
442✔
372
                if report_all_kmers {
442✔
373
                    all_kmers.push(kmer);
×
374
                }
442✔
375
                if is_valid {
442✔
376
                    valid_kmers.push(kmer);
442✔
377
                    valid_exts.push(exts);
442✔
378
                    valid_data.push(summary_data);
442✔
379
                }
442✔
380
            }
381

382
            // if there are valid k-mers in this bucket, append them to the shared target vectors
383
            // important that this is done in one step so each kmer has the same index with its exts and data
384
            if !valid_kmers.is_empty() {
1,024✔
385

313✔
386
                let _stv_clone = Arc::clone(&shared_target_vecs);
313✔
387
                let mut stv = shared_target_vecs.lock().expect("lock target vectors");
313✔
388
                // valid kmers
313✔
389
                stv.0.reserve_exact(valid_kmers.len());
313✔
390
                stv.0.append(&mut valid_kmers); 
313✔
391
                // valid exts
313✔
392
                stv.1.reserve_exact(valid_exts.len());
313✔
393
                stv.1.append(&mut valid_exts); 
313✔
394
                // valid data
313✔
395
                stv.2.reserve_exact(valid_data.len());
313✔
396
                stv.2.append(&mut valid_data); 
313✔
397
            }
711✔
398

399
            // if kmers were collected into all_kmers, append them to shared target vector
400
            if !all_kmers.is_empty() {
1,024✔
401
                let _stv_clone = Arc::clone(&shared_target_vecs);
×
402
                let mut stv = shared_target_vecs.lock().expect("lock target vectors");
×
403
                // all kmers
×
404
                stv.3.reserve_exact(all_kmers.len());
×
405
                stv.3.append(&mut all_kmers);
×
406
            }
1,024✔
407

408
            pb_sum_buckets.inc(1);
1,024✔
409
        });
1,024✔
410
        // parallel end
4✔
411

4✔
412
        pb_bucket_ranges.inc(1);
4✔
413

4✔
414
        time_summarizing += before_parallel.elapsed().as_secs_f32();
4✔
415

4✔
416
        debug!("processed bucket {i}");
4✔
417
    }
418
    pb_bucket_ranges.finish_and_clear();
4✔
419

4✔
420
    if time { 
4✔
421
        println!("time counting + collecting par (s): {}", time_picking_par);
3✔
422
        println!("time counting + collecting (s): {}", time_picking);
3✔
423
        println!("time summarizing (s): {}", time_summarizing);
3✔
424
    }
3✔
425

426
    let stv = shared_target_vecs.lock().expect("final lock target vectors");
4✔
427

4✔
428
    debug!("valid kmers - capacity: {}, size: {}, mem: {}", stv.0.capacity(), stv.0.len(), mem::size_of_val(&*stv.0));
4✔
429
    debug!("valid exts - capacity: {}, size: {}, mem: {}", stv.1.capacity(), stv.1.len(), mem::size_of_val(&*stv.1));
4✔
430
    debug!("valid data - capacity: {}, size: {}, struct mem: {}, real mem: {}", stv.2.capacity(), stv.2.len(), mem::size_of_val(&*stv.2), {
4✔
431
        let mut data_size = 0;
×
432
        for data in &stv.2 {
×
433
            data_size += data.mem();
×
434
        }
×
435
        data_size
×
436
    });
437
    debug!("all kmers - capacity: {}, size: {}, mem: {}", stv.3.capacity(), stv.3.len(), mem::size_of_val(&*stv.3));
4✔
438
    
439

440
    let before_hash = Instant::now();
4✔
441
    let hm = BoomHashMap2::new_parallel(stv.0.to_vec(), stv.1.to_vec(), stv.2.to_vec());
4✔
442
    let after_hash = before_hash.elapsed().as_secs_f32();
4✔
443
    let all_kmers = stv.3.to_vec();
4✔
444

4✔
445
    let filter_kmers_inner = before_all.elapsed().as_secs_f32();
4✔
446
    if time { 
4✔
447
        println!("time filter_kmers inner (s): {}", filter_kmers_inner);
3✔
448
        println!("time build filtered hash map (s): {}", after_hash);
3✔
449
    }
3✔
450

451
    (
4✔
452
        hm,
4✔
453
        all_kmers,
4✔
454
    )
4✔
455
}
4✔
456

457
// TODO add conditional filters to all SummaryDatas
458

459
/// Process DNA sequences into kmers and determine the set of valid kmers,
460
/// their extensions, and summarize associated label/'color' data. The input
461
/// sequences are converted to kmers of type `K`, and like kmers are grouped together.
462
/// All instances of each kmer, along with their label data are then proccessed with
463
/// [`SummaryData::summarize`], which generates an implementation of [`SummaryData`],
464
/// which is specified with the generic `SD`, decides if the k-mer is 'valid' 
465
/// based on the parameters given in `summary_config`, and
466
/// summarizes the the individual label into a single label data structure
467
/// for the kmer. Care is taken to keep the memory consumption small.
468
/// 
469
/// Be aware that the configuration in `summary_config` only applies if the required
470
/// informaiton can be supplied by the chosen implementation of `SummaryData`.
471
/// E.g., the k-mers will not be filtered according to p-value when the `SummaryData`
472
/// only contains the number of observations.
473
///
474
/// # Arguments
475
///
476
/// * `seqs` are the reads wrapped in a `Reads<u8>`. See [`Reads<D>`]
477
/// * `summary_config` is a [`SummaryConfig`], which contains prameters and 
478
///   information necessary for the filtering
479
/// * `stranded`: if true, preserve the strandedness of the input sequences, effectively
480
///   assuming they are all in the positive strand. If false, the kmers will be canonicalized
481
///   to the lexicographic minimum of the kmer and it's reverse complement.
482
/// * `report_all_kmers`: if true returns the vector of all the observed kmers and performs the
483
///   kmer based filtering
484
/// * `memory_size`: gives the size bound on the memory in GB to use and automatically determines
485
///   the number of passes needed.
486
/// 
487
/// # Returns
488
/// BoomHashMap2 Object, check rust-boomphf for details
489
/// 
490
/// # Examples:
491
/// 
492
/// ```
493
/// use debruijn::summarizer::{SampleInfo, SummaryConfig, TagsCountsData, StatTest, GroupFrac};
494
/// use debruijn::reads::Reads;
495
/// use debruijn::filter::filter_kmers;
496
/// use debruijn::kmer::Kmer16;
497
/// use debruijn::Exts;
498
/// 
499
/// let mut seqs = Reads::new();
500
/// seqs.add_from_bytes("ACCGATCATATATTTTCGGGGCTAGGCGAAGCGATCTTATCGAGC".as_bytes(), Exts::empty(), 1u8);
501
/// seqs.add_from_bytes("GCGATCGAGCATGCTCAGCTGACGTGACTGACGTAGCTATCTTTTCGTAGCTAC".as_bytes(), Exts::empty(), 1u8);
502
/// seqs.add_from_bytes("GCGAGTTTGCGACTCGAGGCTATCTAGCTAGCTASGCTCTCGACTAGCTGACTTACGACGACTACG".as_bytes(), Exts::empty(), 2u8);
503
/// seqs.add_from_bytes("CGATTAGCTACGTAGCTAGCTGACGTACTGGGGGGTATTTCGGATCTGCGGAGCGATCT".as_bytes(), Exts::empty(), 2u8);
504
///       
505
/// let sample_info = SampleInfo::new(
506
///     0b000011,
507
///     0b111100,
508
///     2,
509
///     4,
510
///     vec![23423, 3463454, 2242234, 2233243, 234322434, 2323234],
511
/// );
512
///     
513
/// let summary_config = SummaryConfig::new(
514
///     3,
515
///     None,
516
///     GroupFrac::One,
517
///     0.33333,
518
///     sample_info,
519
///     None,
520
///     StatTest::StudentsTTest,
521
/// );
522
///    
523
/// let (hashed_kmers, _) = filter_kmers::<TagsCountsData, Kmer16, _>(
524
///     &seqs,
525
///     &summary_config,
526
///     false,
527
///     false,
528
///     10,
529
///    false,
530
/// );
531
/// ```
532
#[inline(never)]
533
pub fn filter_kmers<SD, K: Kmer, D1: Copy + Clone + Debug>(
1,868✔
534
    seqs: &Reads<D1>,
1,868✔
535
    summary_config: &SummaryConfig,
1,868✔
536
    stranded: bool,
1,868✔
537
    report_all_kmers: bool,
1,868✔
538
    memory_size: usize,
1,868✔
539
    time: bool,
1,868✔
540
) -> (BoomHashMap2<K, Exts, SD>, Vec<K>)
1,868✔
541
where
1,868✔
542
    SD: Debug + SummaryData<D1>,
1,868✔
543
{
1,868✔
544
    let before_all = Instant::now();
1,868✔
545
    let rc_norm = !stranded;
1,868✔
546

1,868✔
547
    // Estimate memory consumed by Kmer vectors, and set iteration count appropriately
1,868✔
548
    let input_kmers: usize = seqs
1,868✔
549
        .iter()
1,868✔
550
        .map(|(ref read, _, _)| read.len().saturating_sub(K::k() - 1))
152,390✔
551
        .sum();
1,868✔
552

1,868✔
553
    if time { println!("time counting kmers (s): {}", before_all.elapsed().as_secs_f32()) }
1,868✔
554

555
    let kmer_mem = input_kmers * mem::size_of::<(K, D1)>();
1,868✔
556
    debug!("size used for calculation: {}B", mem::size_of::<(K, D1)>());
1,868✔
557
    debug!("size of kmer, E, D: {} B", mem::size_of::<(K, Exts, D1)>());
1,868✔
558
    debug!("size of K: {} B, size of Exts: {} B, size of D1: {}", mem::size_of::<K>(), mem::size_of::<Exts>(), mem::size_of::<D1>());
1,868✔
559
    debug!("type D1: {}", std::any::type_name::<D1>());
1,868✔
560

561
    let max_mem: usize = memory_size * 10_usize.pow(9);
1,868✔
562
    let slices: usize = kmer_mem / max_mem + 1;
1,868✔
563
  
564
    // split ranges into slices according to constant probrbiliy dist. if stranded, else according to linear probability distribition
565
    let bucket_ranges: Vec<std::ops::Range<usize>> = if stranded {
1,868✔
NEW
566
        let mut bucket_ranges = Vec::with_capacity(slices);
×
NEW
567
        let mut start = 0;
×
NEW
568
        let sz = BUCKETS / slices + 1;
×
NEW
569
        while start < BUCKETS {
×
NEW
570
            bucket_ranges.push(start..start + sz);
×
NEW
571
            start += sz;
×
NEW
572
        }
×
NEW
573
        bucket_ranges
×
574
    } else {
575
        lin_dist_range(BUCKETS, slices)
1,868✔
576
    };
577

578
    debug!("bucket ranges: {:?}", bucket_ranges);
1,868✔
579

580
    debug!("kmer_mem: {} B, max_mem: {}B, slices: {}", kmer_mem, max_mem, slices);
1,868✔
581

582
    debug!("bucket_ranges: {:?}, len br: {}", bucket_ranges, bucket_ranges.len());
1,868✔
583
    assert!(bucket_ranges[bucket_ranges.len() - 1].end >= BUCKETS);
1,868✔
584
    let n_buckets = bucket_ranges.len();
1,868✔
585

1,868✔
586
    if bucket_ranges.len() > 1 {
1,868✔
587
        debug!(
×
588
            "{} sequences, {} kmers, {} passes",
×
NEW
589
            seqs.n_reads(),
×
590
            input_kmers,
×
591
            bucket_ranges.len()
×
592
        );
593
    }
1,868✔
594

595
    debug!("n of seqs: {}", seqs.n_reads());
1,868✔
596

597

598
    let mut all_kmers = Vec::new();
1,868✔
599
    let mut valid_kmers = Vec::new();
1,868✔
600
    let mut valid_exts = Vec::new();
1,868✔
601
    let mut valid_data = Vec::new();
1,868✔
602

1,868✔
603
    let mut time_picking = 0.;
1,868✔
604
    let mut time_summarizing = 0.;
1,868✔
605
    let mut time_picking_par = 0.;
1,868✔
606

1,868✔
607
    if time { println!("time all prepariations before sliced in filter_kmers (s): {}", before_all.elapsed().as_secs_f32()) }
1,868✔
608

609
    // progress bars
610
    let multi_pb = MultiProgress::new();
1,868✔
611
    let style = ProgressStyle::with_template("{msg} [{elapsed_precise}] {bar:60.cyan/blue} ({pos}/{len})").unwrap().progress_chars("#/-");
1,868✔
612

1,868✔
613
    let pb_bucket_ranges = multi_pb.add(ProgressBar::new(bucket_ranges.len() as u64));
1,868✔
614
    pb_bucket_ranges.set_style(style.clone());
1,868✔
615
    pb_bucket_ranges.set_message(format!("{:<32}", "filtering k-mers"));
1,868✔
616

617
    // iterate over the bucket ranges
618
    for (i, bucket_range) in bucket_ranges.into_iter().enumerate() {
1,868✔
619
        debug!("Processing slice {} of {}", i+1, n_buckets);
1,868✔
620

621
        let before_kmer_picking = Instant::now();
1,868✔
622
        // first step: picking kmers with their exts & data from the reads
1,868✔
623
        // go through all kmers and sort into bucket according to first four bases
1,868✔
624
        // all kmers starting with "AAAA" go in kmer_buckets[0], all starting with AAAC go in kmer_buckets[1] and so on
1,868✔
625
        // when using the first four bases, this needs 256 buckets
1,868✔
626
        // the buckets are split in to the bucket_ranges to save memory
1,868✔
627

1,868✔
628
        let pb = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
1,868✔
629
        pb.set_style(style.clone());
1,868✔
630
        pb.set_message(format!("{:<32}", "finding bucket lengths"));
1,868✔
631

1,868✔
632
        // first go trough all kmers to find the length of all buckets (to reserve capacity)
1,868✔
633
        let mut capacities = [0; BUCKETS];
1,868✔
634

635
        for (ref seq, _, _) in seqs.iter().progress_with(pb) {
152,390✔
636
            // iterate through all kmers in seq
637
            for kmer in seq.iter_kmers::<K>() {
8,901,569✔
638
                // if not stranded choose lexiographically lesser of kmer and rc of kmer
639
                let min_kmer = if rc_norm {
8,901,569✔
640
                    let (min_kmer, _) = kmer.min_rc_flip();
8,901,569✔
641
                    min_kmer
8,901,569✔
642
                } else {
643
                    kmer
×
644
                };
645

646
                // calculate which bucket this kmer belongs to
647
                let bucket = if K::k() > 3 { bucket(min_kmer) } else { min_kmer.to_u64() as usize };
8,901,569✔
648
                //let bucket = bucket(min_kmer);                // check if bucket is in current range and if so, add one to needed capacity
649
                let in_range = bucket >= bucket_range.start && bucket < bucket_range.end;
8,901,569✔
650
                if in_range { capacities[bucket] += 1 }
8,901,569✔
651
            }
652
        }
653

654
        debug!("kmer capacities: {:?}, times {}", capacities, mem::size_of::<(K, Exts, D1)>());
1,868✔
655
        
656
        let mut kmer_buckets = Vec::new();
1,868✔
657
        // reserve needed capacity in each bucket
658
        for capacity in capacities {
480,076✔
659
            kmer_buckets.push(Vec::with_capacity(capacity));
478,208✔
660
        }
478,208✔
661

662
        // then go through all kmers and add to bucket according to first four bases and current bucket_range
663
        let pb = multi_pb.add(ProgressBar::new(seqs.n_reads() as u64));
1,868✔
664
        pb.set_style(style.clone());
1,868✔
665
        pb.set_message(format!("{:<32}", "filling buckets with kmers"));
1,868✔
666

667
        for (ref seq, seq_exts, ref d) in seqs.iter().progress_with(pb) {
152,390✔
668
            // iterate trough all kmers in seq
669
            for (kmer, exts) in seq.iter_kmer_exts::<K>(seq_exts) {
8,901,569✔
670
                // // if not stranded choose lexiographically lesser of kmer and rc of kmer, flip exts if needed
671
                let (min_kmer, flip_exts) = if rc_norm {
8,901,569✔
672
                    let (min_kmer, flip) = kmer.min_rc_flip();
8,901,569✔
673
                    let flip_exts = if flip { exts.rc() } else { exts };
8,901,569✔
674
                    (min_kmer, flip_exts)
8,901,569✔
675
                } else {
676
                    (kmer, exts)
×
677
                };
678

679
                // calculate which bucket this kmer belongs to
680
                let bucket = if K::k() > 3 { bucket(min_kmer) } else { min_kmer.to_u64() as usize };
8,901,569✔
681
                //let bucket = bucket(min_kmer);
682
                // check if bucket is in current range and if so, push kmer to bucket
683
                let in_range = bucket >= bucket_range.start && bucket < bucket_range.end;
8,901,569✔
684
                if in_range {
8,901,569✔
685
                    kmer_buckets[bucket].push((min_kmer, flip_exts, *d));
8,901,569✔
686
                }
8,901,569✔
687
            }
688
        }
689

690
        time_picking_par += before_kmer_picking.elapsed().as_secs_f32();
1,868✔
691

1,868✔
692
        debug!("size of the slice: {} B", mem::size_of_val(&*kmer_buckets));
1,868✔
693
        let mut slice_elements: usize = 0;
1,868✔
694
        for bucket in kmer_buckets.iter() {
478,208✔
695
            slice_elements += bucket.len();
478,208✔
696
        }
478,208✔
697
        debug!("overall elements in this bucket: {slice_elements}");
1,868✔
698
        debug!("slice size guess (advanced version):
1,868✔
699
            {} (len) * 24B (ref vec) + {} B (elements size) * {} (elements)
×
700
            = {} B", kmer_buckets.len(), mem::size_of::<(K, Exts, D1)>(), slice_elements, kmer_buckets.len() * 24 + (mem::size_of::<(K, Exts, D1)>() * slice_elements));
×
701
        
702
        debug!("no of kmer buckets: {}", kmer_buckets.len());
1,868✔
703

704
        time_picking += before_kmer_picking.elapsed().as_secs_f32();
1,868✔
705

1,868✔
706
        let before_summarizing = Instant::now();
1,868✔
707

1,868✔
708
        let mut progress_counter = 0;
1,868✔
709

1,868✔
710
        // go trough all buckets and summarize the contents
1,868✔
711
        let pb = multi_pb.add(ProgressBar::new(kmer_buckets.len() as u64));
1,868✔
712
        pb.set_style(style.clone());
1,868✔
713
        pb.set_message(format!("{:<32}", "summarizing k-mers in buckets"));
1,868✔
714

715
        for mut kmer_vec in kmer_buckets.into_iter().progress_with(pb) {
478,208✔
716
            debug!("bucket {} with {} kmers, capacity of {}", progress_counter, kmer_vec.len(), kmer_vec.capacity());
478,208✔
717
            progress_counter += 1;
478,208✔
718
            //debug!("kmers in this bucket: {}", kmer_vec.len());
478,208✔
719
            kmer_vec.sort_by_key(|elt| elt.0);
106,633,812✔
720

478,208✔
721
            
478,208✔
722
            // predict amount of unique k-mers found in this bucket
478,208✔
723
            let size = kmer_vec.iter().chunk_by(|elt| elt.0).into_iter().count();
8,901,569✔
724

478,208✔
725
            // only works perfectly if min k-mer count is 1, else this might reserve too much 
478,208✔
726
            // still better than doubling the vector
478,208✔
727
            // also reserve_exact considers pre-existing free capacity -> this might mostly add to runtime
478,208✔
728
            valid_kmers.reserve_exact(size);
478,208✔
729
            valid_exts.reserve_exact(size);
478,208✔
730
            valid_data.reserve_exact(size);
478,208✔
731

478,208✔
732
            // if all k-mers should be reported, also grow all_kmers by exact amount -> this should always be the perfect capacity
478,208✔
733
            if report_all_kmers {
478,208✔
734
                all_kmers.reserve_exact(size);
×
735
            }
478,208✔
736

737

738
            // group the tuples by the k-mers and iterate over the groups
739
            for (kmer, kmer_obs_iter) in kmer_vec.into_iter().chunk_by(|elt: &(K, Exts, D1)| elt.0).into_iter() {
8,901,569✔
740
                // summarize group with chosen summarizer and add result to vectors
741
                let (is_valid, exts, summary_data) = SD::summarize(kmer_obs_iter, summary_config);
757,407✔
742
                if report_all_kmers {
757,407✔
743
                    all_kmers.push(kmer);
×
744
                }
757,407✔
745
                if is_valid {
757,407✔
746
                    valid_kmers.push(kmer);
757,407✔
747
                    valid_exts.push(exts);
757,407✔
748
                    valid_data.push(summary_data); 
757,407✔
749
                }
757,407✔
750
            }
751
            debug!("finished bucket {}, current mems: valid_kmers {} Bytes, valid_exts {} Bytes, valid_data {} Bytes", 
478,208✔
752
                progress_counter, mem::size_of_val(&*valid_kmers), mem::size_of_val(&*valid_exts), mem::size_of_val(&*valid_data))
×
753
        }
754

755
        pb_bucket_ranges.inc(1);
1,868✔
756

1,868✔
757
        time_summarizing += before_summarizing.elapsed().as_secs_f32();
1,868✔
758

1,868✔
759
        debug!("valid kmers - capacity: {}, size: {}, mem: {} Bytes", valid_kmers.capacity(), valid_kmers.len(), mem::size_of_val(&*valid_kmers));
1,868✔
760
        debug!("valid exts - capacity: {}, size: {}, mem: {} Bytes", valid_exts.capacity(), valid_exts.len(), mem::size_of_val(&*valid_exts));
1,868✔
761
        debug!("valid data - capacity: {}, size: {}, mem: {} Bytes", valid_data.capacity(), valid_data.len(), mem::size_of_val(&*valid_data));
1,868✔
762
    }
763

764
    pb_bucket_ranges.finish_and_clear();
1,868✔
765

1,868✔
766
    if time { 
1,868✔
767
        println!("time picking par (s): {}", time_picking_par);
1,866✔
768
        println!("time picking (s): {}", time_picking);
1,866✔
769
        println!("time summarizing (s): {}", time_summarizing);
1,866✔
770
    }
1,866✔
771

772

773
    debug!(
1,868✔
774
        "Unique kmers: {}, All kmers (if returned): {}",
×
775
        valid_kmers.len(),
×
776
        all_kmers.len(),
×
777
    );
778

779
    debug!("size of valid kmers: {} Bytes
1,868✔
780
        size of valid exts: {} Bytes
×
781
        size of valid data: {} Bytes", mem::size_of_val(&*valid_kmers), mem::size_of_val(&*valid_exts), mem::size_of_val(&*valid_data));
×
782

783
    let before_hash = Instant::now();
1,868✔
784
    let hm = BoomHashMap2::new(valid_kmers, valid_exts, valid_data);
1,868✔
785
    let after_hash = before_hash.elapsed().as_secs_f32();
1,868✔
786

1,868✔
787
    let filter_kmers_inner = before_all.elapsed().as_secs_f32();
1,868✔
788
    if time { 
1,868✔
789
        println!("time filter_kmers inner (s): {}", filter_kmers_inner);
1,866✔
790
        println!("time build filter hash map (s): {}", after_hash);
1,866✔
791
    }
1,866✔
792
    (
1,868✔
793
        hm,
1,868✔
794
        all_kmers,
1,868✔
795
    )
1,868✔
796
}
1,868✔
797

798
/// Remove extensions in valid_kmers that point to censored kmers. A censored kmer
799
/// exists in `all_kmers` but not `valid_kmers`. Since the kmer exists in this partition,
800
/// but was censored, we know that we can delete extensions to it.
801
/// In sharded kmer processing, we will have extensions to kmers in other shards. We don't
802
/// know whether these are censored until later, so we retain these extension.
803
pub fn remove_censored_exts_sharded<K: Kmer, D>(
×
804
    stranded: bool,
×
805
    valid_kmers: &mut [(K, (Exts, D))],
×
806
    all_kmers: &[K],
×
807
) {
×
808
    for idx in 0..valid_kmers.len() {
×
809
        let mut new_exts = Exts::empty();
×
810
        let kmer = valid_kmers[idx].0;
×
811
        let exts = (valid_kmers[idx].1).0;
×
812

813
        for dir in [Dir::Left, Dir::Right].iter() {
×
814
            for i in 0..4 {
×
815
                if exts.has_ext(*dir, i) {
×
816
                    let _ext_kmer = kmer.extend(i, *dir);
×
817

818
                    let ext_kmer = if stranded {
×
819
                        _ext_kmer
×
820
                    } else {
821
                        _ext_kmer.min_rc()
×
822
                    };
823

824
                    let censored = if valid_kmers.binary_search_by_key(&ext_kmer, |d| d.0).is_ok() {
×
825
                        // ext_kmer is valid. not censored
826
                        false
×
827
                    } else {
828
                        // ext_kmer is not valid. if it was in this shard, then we censor it
829
                        all_kmers.binary_search(&ext_kmer).is_ok()
×
830
                    };
831

832
                    if !censored {
×
833
                        new_exts = new_exts.set(*dir, i);
×
834
                    }
×
835
                }
×
836
            }
837
        }
838

839
        (valid_kmers[idx].1).0 = new_exts;
×
840
    }
841
}
×
842

843
/// Remove extensions in valid_kmers that point to censored kmers. Use this method in a non-partitioned
844
/// context when valid_kmers includes _all_ kmers that will ultimately be included in the graph.
845
pub fn remove_censored_exts<K: Kmer, D>(stranded: bool, valid_kmers: &mut [(K, (Exts, D))]) {
×
846
    for idx in 0..valid_kmers.len() {
×
847
        let mut new_exts = Exts::empty();
×
848
        let kmer = valid_kmers[idx].0;
×
849
        let exts = (valid_kmers[idx].1).0;
×
850

851
        for dir in [Dir::Left, Dir::Right].iter() {
×
852
            for i in 0..4 {
×
853
                if exts.has_ext(*dir, i) {
×
854
                    let ext_kmer = if stranded {
×
855
                        kmer.extend(i, *dir)
×
856
                    } else {
857
                        kmer.extend(i, *dir).min_rc()
×
858
                    };
859

860
                    let kmer_valid = valid_kmers.binary_search_by_key(&ext_kmer, |d| d.0).is_ok();
×
861

×
862
                    if kmer_valid {
×
863
                        new_exts = new_exts.set(*dir, i);
×
864
                    }
×
865
                }
×
866
            }
867
        }
868

869
        (valid_kmers[idx].1).0 = new_exts;
×
870
    }
871
}
×
872

873
#[cfg(test)]
874
mod tests {
875
    use boomphf::hashmap::BoomHashMap2;
876
    use crate::{dna_string::DnaString, filter::*, kmer::Kmer6, reads::Reads, summarizer::{GroupFrac, SampleInfo, TagsSumData}, test::random_dna, Exts};
877

878
    #[test]
879
    fn test_filter_kmers() {
1✔
880
        let fastq = [
1✔
881
            (DnaString::from_dna_string("AAAAATTT"), Exts::empty(), 6u8),
1✔
882
            (DnaString::from_dna_string("TTTTTTTTTTAAAAAA"), Exts::empty(), 6u8),
1✔
883
            (DnaString::from_dna_string("AAAAAAAAAAAAA"), Exts::empty(), 7u8),
1✔
884
        ];
1✔
885

1✔
886
        let mut reads = Reads::new();
1✔
887

888
        for (read, exts, data) in fastq {
4✔
889
            reads.add_read(read, exts, data);
3✔
890
        }
3✔
891

892
        let sample_info = SampleInfo::new(0, 0, 0, 0, Vec::new());
1✔
893

1✔
894
        let config = SummaryConfig::new(1, None, GroupFrac::None, 0.33, sample_info, None, crate::summarizer::StatTest::StudentsTTest);
1✔
895

1✔
896

1✔
897
        let (hm, _): (BoomHashMap2<Kmer6, Exts, TagsSumData>, Vec<_>) = filter_kmers(
1✔
898
            &reads, 
1✔
899
            &config,
1✔
900
            false, 
1✔
901
            false, 
1✔
902
            1,
1✔
903
            false,
1✔
904
         );
1✔
905

1✔
906
         println!("{:?}", hm);
1✔
907

1✔
908
    }
1✔
909

910
    #[test]
911
    fn test_filter_kmers_parallel() {
1✔
912
        /* let fastq = [
1✔
913
            (DnaString::from_dna_string("AAAAATTT"), Exts::empty(), 6u8),
1✔
914
            (DnaString::from_dna_string("TTTTTTTTTTAAAAAA"), Exts::empty(), 6u8),
1✔
915
            (DnaString::from_dna_string("AAAAAAAAAAAAA"), Exts::empty(), 7u8),
1✔
916
        ];
1✔
917

1✔
918
        let mut reads = Reads::new();
1✔
919

1✔
920
        for (read, exts, data) in fastq {
1✔
921
            reads.add_read(read, exts, data);
1✔
922

1✔
923
        } */
1✔
924

1✔
925
        let mut reads = Reads::new();
1✔
926

927
        for _i in 0..10000 {
10,001✔
928
            let dna = random_dna(150);
10,000✔
929
            reads.add_from_bytes(&dna, Exts::empty(), 0u8);
10,000✔
930
        }
10,000✔
931

932
        let sample_info = SampleInfo::new(0, 0, 0, 0, Vec::new());
1✔
933
        let config = SummaryConfig::new(1, None, GroupFrac::None, 0.33, sample_info.clone(), None, crate::summarizer::StatTest::StudentsTTest);
1✔
934

1✔
935

1✔
936
        let (hm, _): (BoomHashMap2<Kmer6, Exts, TagsSumData>, Vec<_>) = filter_kmers_parallel(
1✔
937
            &reads, 
1✔
938
            &config,
1✔
939
            false, 
1✔
940
            false, 
1✔
941
            1,
1✔
942
            false,         
1✔
943
        );
1✔
944

1✔
945
        println!("{:?}", hm);
1✔
946

1✔
947
    }
1✔
948
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc