1
use std::path::PathBuf;
2

            
3
use color_eyre::{
4
    eyre::{eyre, WrapErr},
5
    Result,
6
};
7
use ndarray::parallel::prelude::*;
8
use ndarray::{s, Axis};
9

            
10
use crate::{args::Selection, graphs::matrix_graph::matrix_graph_png};
11
use crate::args::{GraphArgs, StandardArgs};
12
use crate::graphs::MatrixGraph;
13
use crate::io::{read_haplotype_file, read_sample_ids};
14
use crate::read_vcf::read_vcf_to_matrix;
15
use crate::structs::{HapVariant, PhasedMatrix};
16
use crate::utils::{
17
    push_to_output, select_carrier_haplotypes, select_only_longest_alleles,
18
    select_only_longest_haplotypes, shared_lengths_by_majority,
19
};
20
use crate::core::{open_csv_writer, parse_snp_coord};
21

            
22
#[doc(hidden)]
23
36
pub fn run(
24
36
    args: StandardArgs,
25
36
    haplotype_path: PathBuf,
26
36
    decoy_samples: Option<PathBuf>,
27
36
    mark_shorter_alleles: bool,
28
36
    want_png: bool,
29
36
    graph_args: GraphArgs,
30
36
) -> Result<()> {
31
36
    if args.selection == Selection::Unphased {
32
6
        return Err(eyre!("Running with unphased data is not supported."))
33
30
    }
34

            
35
30
    let decoy_samples = read_sample_ids(&decoy_samples)?;
36

            
37
30
    let ht = read_haplotype_file(haplotype_path)?;
38
30
    let start = ht.first().unwrap();
39
30
    let end = ht.last().unwrap();
40

            
41
30
    let (contig, variant_pos) = parse_snp_coord(&args.coords)?;
42

            
43

            
44
    // Test svg writing before expensive computation
45
30
    let img_type = match want_png {
46
        true => "png",
47
30
        false => "svg",
48
    };
49

            
50
30
    let mut img_output = args.output.clone();
51
30
    let name = match mark_shorter_alleles {
52
6
        true => "differences_shorter_alleles_marked",
53
24
        false => "differences"
54
    };
55
30
    push_to_output(&args, &mut img_output, name, img_type);
56
30

            
57
30
    svg::save(&img_output, &svg::Document::new())
58
30
        .wrap_err(eyre!("Failed writing to {:?}", img_output))?;
59

            
60
    // CSV output
61
30
    let mut output = args.output.clone();
62
30
    push_to_output(&args, &mut output, "differences", "csv");
63

            
64
30
    let mut writer = open_csv_writer(output)?;
65

            
66
30
    let mut only_longest: Option<Vec<usize>> = None;
67
30

            
68
30
    tracing::debug!("Reading into a sample-variant matrix");
69
30
    let vcf = match args.selection {
70
        Selection::OnlyAlts | Selection::OnlyRefs => {
71
12
            let vcf = read_vcf_to_matrix(
72
12
                &args,
73
12
                contig,
74
12
                variant_pos,
75
12
                Some((start.pos as u64, end.pos as u64)),
76
12
            )?;
77
12
            select_carrier_haplotypes(vcf, variant_pos, &args.coords, &args.selection)
78
        }
79
        Selection::OnlyLongest => {
80
6
            let vcf = read_vcf_to_matrix(&args, contig, variant_pos, None)?;
81
6
            let shared_lengths = shared_lengths_by_majority(&vcf, vcf.variant_idx());
82
6

            
83
6
            let only_longest_lengths = select_only_longest_alleles(&shared_lengths);
84
84
            only_longest = Some(only_longest_lengths.iter().map(|s| s.index).collect());
85
6

            
86
6
            let mut vcf = select_only_longest_haplotypes(&shared_lengths, vcf);
87
6

            
88
6
            // Use start and end from the haplotype to select columns from the matrix by range
89
6
            vcf.select_columns_by_range(
90
6
                vcf.idx_by_hapvariant(start).unwrap()..vcf.idx_by_hapvariant(end).unwrap() + 1,
91
6
            );
92
6
            vcf
93
        }
94
12
        Selection::All => read_vcf_to_matrix(
95
12
            &args,
96
12
            contig,
97
12
            variant_pos,
98
12
            Some((start.pos as u64, end.pos as u64)),
99
12
        )?,
100
        Selection::Unphased => panic!("impossible panic")
101
    };
102

            
103
30
    tracing::debug!("Finished reading the matrix");
104

            
105
30
    let vcf = transform_gt_matrix_to_match_matrix(vcf, &ht);
106
30
    tracing::debug!("Finished transforming to match matrix");
107

            
108
30
    let mut npy_output = args.output.clone();
109
30
    push_to_output(&args, &mut npy_output, "differences", "npy");
110
30

            
111
30
    ndarray_npy::write_npy(npy_output, &vcf.matrix)?;
112

            
113
30
    let index_order = sort_indexes_for_diff_graph(&vcf, &decoy_samples, mark_shorter_alleles);
114
30

            
115
30
    tracing::debug!("Finished sorting");
116

            
117
30
    let shared_ranges = find_shared_haplotype_ranges(&vcf);
118
30
    print_ranges_to_csv(
119
30
        &vcf,
120
30
        &shared_ranges,
121
30
        &decoy_samples,
122
30
        &only_longest,
123
30
        &mut writer,
124
30
    )?;
125

            
126
30
    tracing::debug!(
127
        "Sample haplotypes: {}, average length: {}, median length: {}",
128
        shared_ranges.len(),
129
        range_length_avg(&shared_ranges),
130
        range_length_median(&shared_ranges)
131
    );
132

            
133
30
    if want_png {
134
        let imgbuf = matrix_graph_png(&vcf, graph_args, mark_shorter_alleles, decoy_samples, &index_order);
135
        imgbuf.save(img_output.clone()).wrap_err(eyre!("failed writing to: {img_output:?}"))?;
136
    } else {
137
30
        let mut dg = MatrixGraph::new(&vcf, graph_args, decoy_samples, mark_shorter_alleles);
138
30
        dg.draw_graph(&index_order);
139
30
        svg::save(img_output, &dg.document)?;
140
    }
141
30
    tracing::debug!("Finished drawing graph");
142

            
143
30
    Ok(())
144
36
}
145

            
146
30
fn sort_indexes_for_diff_graph(
147
30
    vcf: &PhasedMatrix,
148
30
    decoy_samples: &Option<Vec<String>>,
149
30
    mark_shorter_alleles: bool,
150
30
) -> Vec<usize> {
151
30
    // Take all from start to variant index, reverse and calculate 1 count in parallel
152
30
    // to get the amount of markers shared to the left
153
30
    let mut values: Vec<(usize, i32)> = vcf
154
30
        .matrix
155
30
        .slice(s![.., 0..vcf.variant_idx()])
156
30
        .axis_iter(Axis(0))
157
30
        .into_par_iter()
158
30
        .enumerate()
159
588
        .map(|(y, row)| {
160
588
            let mut count = 0;
161
7182
            for i in row.iter().rev() {
162
7182
                match i {
163
252
                    0 => break,
164
6930
                    1 => count += 1,
165
                    _ => panic!(),
166
                }
167
            }
168
588
            (y, count)
169
588
        })
170
30
        .collect();
171
30

            
172
30
    // Sort shortest alleles to the top
173
30
    if mark_shorter_alleles {
174
6
        let shared_lengths = shared_lengths_by_majority(vcf, vcf.variant_idx());
175
6
        let only_longest = select_only_longest_alleles(&shared_lengths);
176
84
        let only_longest: Vec<usize> = only_longest.iter().map(|s| s.index).collect();
177
162
        values.sort_by(|a, b| {
178
162
            only_longest
179
162
                .contains(&a.0)
180
162
                .cmp(&only_longest.contains(&b.0))
181
162
        });
182
24
    }
183

            
184
    // // Sort shortest alleles to the top
185
    // if let Some(only_longest) = only_longest {
186
    //     values.sort_by(|a, b| only_longest.contains(&a.0).cmp(&only_longest.contains(&b.0)));
187
    // }
188
    //
189
    // Sort decoy alleles to the top
190
30
    if let Some(samples) = decoy_samples {
191
        values.sort_by(|b, a| {
192
            samples
193
                .contains(&vcf.get_sample_name(a.0))
194
                .cmp(&samples.contains(&vcf.get_sample_name(b.0)))
195
        });
196
30
    }
197
    // Sort by left side length
198
558
    values.sort_by(|a, b| a.1.cmp(&b.1));
199
30

            
200
588
    values.iter().map(|v| v.0).collect::<Vec<usize>>()
201
30
}
202

            
203
42
pub fn transform_gt_matrix_to_match_matrix(
204
42
    mut vcf: PhasedMatrix,
205
42
    ht: &Vec<HapVariant>,
206
42
) -> PhasedMatrix {
207
42
    // Info to users
208
42
    match ht.len().cmp(&vcf.matrix().shape()[1]) {
209
        std::cmp::Ordering::Greater => {
210
            tracing::warn!(
211
                "Haplotype has more variants than the given genotypes {} vs {}",
212
                ht.len(),
213
                vcf.matrix().shape()[1]
214
            );
215

            
216
            ht.iter()
217
                .filter(|&ht| !vcf.coords().iter().any(|c| c == ht))
218
                .for_each(|ht| tracing::warn!("Coord {ht} is not contained in the haplotype"));
219
        }
220
        std::cmp::Ordering::Less => {
221
            tracing::warn!(
222
                "Haplotype has less variants than the given genotypes {} vs {}",
223
                ht.len(),
224
                vcf.matrix().shape()[1]
225
            );
226

            
227
            vcf.coords()
228
                .iter()
229
                .filter(|&c| !ht.iter().any(|ht| ht == c))
230
                .for_each(|c| tracing::warn!("Coord {c} is not contained in the haplotype"));
231
        }
232
42
        std::cmp::Ordering::Equal => (),
233
    }
234

            
235
    // Swap coords to allow simultaneous iter_mut on the matrix
236
42
    let coords = std::mem::take(vcf.coords_mut());
237
42

            
238
42
    vcf.matrix
239
42
        .axis_iter_mut(Axis(0))
240
42
        .into_par_iter()
241
840
        .for_each_with(&coords, |coords, mut row| {
242
25782
            for (i, gt) in row.iter_mut().enumerate() {
243
25782
                let coord = coords.get(i).unwrap();
244
244722
                if let Some(variant_idx) = ht.iter().position(|c| c == coord) {
245
25782
                    *gt = match &ht[variant_idx].gt == gt {
246
24942
                        true => 1,
247
840
                        false => 0,
248
                    }
249
                } else {
250
                    *gt = 1;
251
                }
252
            }
253
840
        });
254
42
    // Swap back
255
42
    vcf.set_coords(coords);
256
42
    vcf
257
42
}
258

            
259
30
pub fn find_shared_haplotype_ranges(vcf: &PhasedMatrix) -> Vec<(usize, usize, usize)> {
260
30
    vcf.matrix
261
30
        .axis_iter(Axis(0))
262
30
        .into_par_iter()
263
30
        .enumerate()
264
588
        .map(|(idx, row)| {
265
588
            let (mut last_start, mut last_end) = (0, 0);
266
16590
            for (i, gt) in row.iter().enumerate() {
267
16590
                match gt {
268
15834
                    1 => last_end = i,
269
                    0 => {
270
756
                        if vcf.variant_idx() <= last_end && vcf.variant_idx() >= last_start {
271
252
                            return (idx, last_start, last_end);
272
504
                        }
273
504
                        last_start = i;
274
504
                        last_end = i;
275
                    }
276
                    _ => panic!(),
277
                }
278
            }
279
            //
280
336
            (idx, 0, vcf.ncoords() - 1)
281
            // panic!("No haplotye found in check-diff algo")
282
588
        })
283
30
        .collect()
284
30
}
285

            
286
pub fn range_length_avg(ranges: &[(usize, usize, usize)]) -> f32 {
287
    let sum: usize = ranges.iter().map(|n| n.2 - n.1).sum();
288
    sum as f32 / ranges.len() as f32
289
}
290

            
291
pub fn range_length_median(ranges: &[(usize, usize, usize)]) -> usize {
292
    let mut vec: Vec<_> = ranges.iter().map(|n| n.2 - n.1).collect();
293
    vec.sort();
294
    vec[vec.len() / 2]
295
}
296

            
297
pub fn print_ranges_to_csv(
298
    vcf: &PhasedMatrix,
299
    ranges: &[(usize, usize, usize)],
300
    decoy_samples: &Option<Vec<String>>,
301
    only_longest: &Option<Vec<usize>>,
302
    writer: &mut csv::Writer<Box<dyn std::io::Write>>,
303
) -> Result<()> {
304
30
    writer.write_record(vec![
305
30
        "id",
306
30
        "start",
307
30
        "stop",
308
30
        "length",
309
30
        "markers",
310
30
        "is_decoy",
311
30
        "is_longest",
312
30
    ])?;
313
618
    for (idx, start, stop) in ranges {
314
588
        let start_pos = vcf.get_pos(*start);
315
588
        let stop_pos = vcf.get_pos(*stop);
316

            
317
588
        let allele = if *idx >= vcf.nsamples() / 2 { 2 } else { 1 };
318

            
319
588
        let mut row = vec![
320
588
                format!("{}_{allele}",vcf.get_sample_name(*idx)),
321
588
                start_pos.to_string(),
322
588
                stop_pos.to_string(),
323
588
                (stop_pos - start_pos).to_string(),
324
588
                (stop - start).to_string(),
325
588
            ];
326
588

            
327
588
        match decoy_samples {
328
            Some(decoys) => match decoys.contains(&vcf.get_sample_name(*idx)) {
329
                true => row.push("true".to_string()),
330
                false => row.push("false".to_string()),
331
            },
332
588
            None => row.push("na".to_string()),
333
        }
334
588
        match only_longest {
335
84
            Some(longest) => match longest.contains(idx) {
336
                true => row.push("true".to_string()),
337
84
                false => row.push("false".to_string()),
338
            },
339
504
            None => row.push("na".to_string()),
340
        }
341
588
        writer.write_record(row)?;
342
    }
343
30
    Ok(())
344
30
}