1
//! Contrahomozygosity
2
//!
3
//! This tool checks for genome wide runs of homozygosity
4
//!
5
//! To run the command for a $filename using 8 threads use:
6
//! ```bash
7
//! hatk homozygosity --file $filename -t 8
8
//! ```
9
//! To see the source code of the program, click on the upper right.
10
//!
11
use std::{collections::BTreeMap, path::PathBuf};
12

            
13
use color_eyre::{eyre::eyre, Result};
14
use crossbeam_channel::{unbounded, Sender};
15
use rayon::prelude::*;
16
use rust_htslib::bcf::Read;
17
use crate::core::{read_tabix, spawn_csv_collector};
18

            
19
#[doc(hidden)]
20
12
#[tracing::instrument]
21
6
pub fn run(
22
    file: PathBuf,
23
    samples_path: Option<PathBuf>,
24
    mut output: PathBuf,
25
    info_limit: Option<f32>,
26
    sort: bool,
27
    min_marker_len: usize,
28
    prefix: Option<String>,
29
) -> Result<()> {
30
    let tabix = read_tabix(&file)?;
31
    let contigs: Vec<String> = tabix.keys().cloned().collect();
32

            
33
    let (tx, rx) = unbounded();
34

            
35
    if let Some(prefix) = &prefix {
36
        output.push(format!("{prefix}_genome_wide_rwc.csv"));
37
    } else {
38
        output.push("genome_wide_rwc.csv");
39
    }
40

            
41
    let header = ["chr", "pos", "snp_count", "bp_length"]
42
        .iter()
43
24
        .map(|&s| s.to_string())
44
        .collect();
45

            
46
    let handle = spawn_csv_collector(rx, header, Some(output), sort);
47

            
48
    contigs
49
        .par_iter()
50
        .map_with((&file, tx), |(path, tx), contig| {
51
            // Fetch readers
52
6
            let mut reader = crate::read_vcf::get_reader(path, contig, None)?;
53

            
54
6
            let samples = crate::read_vcf::get_samples(reader.header())?;
55
6
            let wanted = crate::io::read_sample_ids(&samples_path)?;
56
6
            let sample_indexes = crate::utils::filter_samples(&samples, wanted);
57
6

            
58
6
            let records = reader.records();
59

            
60
            // Program logic
61
6
            let (merged_gt_vec, pos_vec) =
62
6
                read_vcf_to_rwc_vec(records, &sample_indexes, info_limit)?;
63

            
64
6
            let map = rwc_vec_to_btreemap(merged_gt_vec);
65
6
            write_out_btreemap(map, pos_vec, min_marker_len, contig, tx)?;
66

            
67
6
            Ok(())
68
6
        })
69
        .collect::<Result<()>>()?;
70

            
71
    let join = handle.join().unwrap();
72
    join?;
73

            
74
    Ok(())
75
}
76

            
77
#[doc(hidden)]
78
624
pub fn read_vcf_to_rwc_vec<R: Read>(
79
624
    records: rust_htslib::bcf::Records<R>,
80
624
    sample_indexes: &[usize],
81
624
    info_limit: Option<f32>,
82
624
) -> Result<(Vec<u8>, Vec<i64>)> {
83
624
    let mut haplotypes_vec = vec![];
84
624
    let mut pos_vec = vec![];
85
39912
    for record in records {
86
39288
        let record = record?;
87

            
88
39288
        if record.alleles().len() > 2 {
89
            return Err(eyre!("VCF has multiallelics. Use bcftools norm."));
90
39288
        }
91

            
92
39288
        let passed_check = if let Some(info_limit) = info_limit {
93
132
            match record.info(b"INFO").float()? {
94
                Some(buffer) => buffer[0] >= info_limit,
95
                None => return Err(eyre!("No INFO score on a variant at: {}", record.pos())),
96
            }
97
        } else {
98
39156
            true
99
        };
100

            
101
39156
        if passed_check {
102
39156
            let gts = record.genotypes()?;
103
39156
            let found_contrahomozygote = sample_indexes
104
39156
                .iter()
105
170670
                .map(|x| {
106
170670
                    let gt_vec: Vec<Option<u32>> =
107
341244
                        gts.get(*x).iter().map(|gt| gt.index()).collect();
108
170670
                    match (gt_vec[0], gt_vec[1]) {
109
158238
                        (Some(0), Some(0)) => 0,
110
36
                        (Some(1), Some(1)) => 2,
111
13638
                        _ => 1,
112
                    }
113
171912
                })
114
171066
                .scan((false, false), |acc, gt| {
115
171066
                    if gt == 0 {
116
157662
                        acc.0 = true
117
13404
                    } else if gt == 2 {
118
36
                        acc.1 = true
119
13632
                    }
120
171330
                    Some(*acc)
121
171330
                })
122
158532
                .find(|acc| acc == &(true, true));
123
39156

            
124
39156
            match found_contrahomozygote {
125
36
                Some(_) => haplotypes_vec.push(1),
126
39252
                None => haplotypes_vec.push(0),
127
            }
128

            
129
39288
            pos_vec.push(record.pos() + 1);
130
        }
131
    }
132

            
133
624
    Ok((haplotypes_vec, pos_vec))
134
624
}
135

            
136
#[doc(hidden)]
137
12
pub fn rwc_vec_to_btreemap(merged_gt_vec: Vec<u8>) -> BTreeMap<usize, (usize, usize)> {
138
12
    let mut counter = 1;
139
12
    let mut map = BTreeMap::<usize, (usize, usize)>::new();
140
12
    merged_gt_vec
141
12
        .iter()
142
12
        .enumerate()
143
12
        .fold(1, |last, (index, curr)| {
144
            // The last and current element are 0, no contrahomozygotes are present
145
756
            if last == 0 && *curr == 0 {
146
696
                counter += 1;
147
696

            
148
696
            // If the last element is 0 and the current is not, a shared sequence has ended
149
696
            } else if last == 0 && *curr == 1 {
150
                // check no duplicate with longer sequence exists
151
24
                if let Some((old_counter, _)) = map.insert(index - counter, (counter, index)) {
152
                    if old_counter > counter {
153
                        map.insert(index - old_counter, (old_counter, index));
154
                    }
155
24
                };
156
24
                counter = 1;
157
36
            }
158

            
159
756
            *curr
160
756
        });
161
12
    counter -= 1;
162
12
    if counter > 0 {
163
12
        let index =  merged_gt_vec.len() - 1;
164
12
        if let Some((old_counter, _)) = map.insert(index - counter, (counter, index)) {
165
            if old_counter > counter {
166
                map.insert(index - old_counter, (old_counter, index));
167
            }
168
12
        };
169
    }
170
12
    map
171
12
}
172

            
173
#[doc(hidden)]
174
6
pub fn write_out_btreemap(
175
6
    map: BTreeMap<usize, (usize, usize)>,
176
6
    pos_vec: Vec<i64>,
177
6
    min_run_length: usize,
178
6
    contig: &str,
179
6
    tx: &mut Sender<Vec<String>>,
180
6
) -> Result<()> {
181
24
    for (start, (count, stop)) in map {
182
18
        if count >= min_run_length {
183
6
            let start = &pos_vec[start];
184
6
            let stop = &pos_vec[stop];
185
6
            tx.send(vec![
186
6
                contig.to_string(),
187
6
                start.to_string(),
188
6
                count.to_string(),
189
6
                (stop - start).to_string(),
190
6
            ])
191
6
            .unwrap();
192
12
        }
193
    }
194
6
    Ok(())
195
6
}