Lines
60.55 %
Functions
4.96 %
Branches
100 %
use std::path::PathBuf;
use color_eyre::{
eyre::{eyre, WrapErr},
Result,
};
use ndarray::parallel::prelude::*;
use ndarray::{s, Axis};
use crate::{args::Selection, graphs::matrix_graph::matrix_graph_png};
use crate::args::{GraphArgs, StandardArgs};
use crate::graphs::MatrixGraph;
use crate::io::{read_haplotype_file, read_sample_ids};
use crate::read_vcf::read_vcf_to_matrix;
use crate::structs::{HapVariant, PhasedMatrix};
use crate::utils::{
push_to_output, select_carrier_haplotypes, select_only_longest_alleles,
select_only_longest_haplotypes, shared_lengths_by_majority,
use crate::core::{open_csv_writer, parse_snp_coord};
#[doc(hidden)]
pub fn run(
args: StandardArgs,
haplotype_path: PathBuf,
decoy_samples: Option<PathBuf>,
mark_shorter_alleles: bool,
want_png: bool,
graph_args: GraphArgs,
) -> Result<()> {
if args.selection == Selection::Unphased {
return Err(eyre!("Running with unphased data is not supported."))
}
let decoy_samples = read_sample_ids(&decoy_samples)?;
let ht = read_haplotype_file(haplotype_path)?;
let start = ht.first().unwrap();
let end = ht.last().unwrap();
let (contig, variant_pos) = parse_snp_coord(&args.coords)?;
// Test svg writing before expensive computation
let img_type = match want_png {
true => "png",
false => "svg",
let mut img_output = args.output.clone();
let name = match mark_shorter_alleles {
true => "differences_shorter_alleles_marked",
false => "differences"
push_to_output(&args, &mut img_output, name, img_type);
svg::save(&img_output, &svg::Document::new())
.wrap_err(eyre!("Failed writing to {:?}", img_output))?;
// CSV output
let mut output = args.output.clone();
push_to_output(&args, &mut output, "differences", "csv");
let mut writer = open_csv_writer(output)?;
let mut only_longest: Option<Vec<usize>> = None;
tracing::debug!("Reading into a sample-variant matrix");
let vcf = match args.selection {
Selection::OnlyAlts | Selection::OnlyRefs => {
let vcf = read_vcf_to_matrix(
&args,
contig,
variant_pos,
Some((start.pos as u64, end.pos as u64)),
)?;
select_carrier_haplotypes(vcf, variant_pos, &args.coords, &args.selection)
Selection::OnlyLongest => {
let vcf = read_vcf_to_matrix(&args, contig, variant_pos, None)?;
let shared_lengths = shared_lengths_by_majority(&vcf, vcf.variant_idx());
let only_longest_lengths = select_only_longest_alleles(&shared_lengths);
only_longest = Some(only_longest_lengths.iter().map(|s| s.index).collect());
let mut vcf = select_only_longest_haplotypes(&shared_lengths, vcf);
// Use start and end from the haplotype to select columns from the matrix by range
vcf.select_columns_by_range(
vcf.idx_by_hapvariant(start).unwrap()..vcf.idx_by_hapvariant(end).unwrap() + 1,
);
vcf
Selection::All => read_vcf_to_matrix(
)?,
Selection::Unphased => panic!("impossible panic")
tracing::debug!("Finished reading the matrix");
let vcf = transform_gt_matrix_to_match_matrix(vcf, &ht);
tracing::debug!("Finished transforming to match matrix");
let mut npy_output = args.output.clone();
push_to_output(&args, &mut npy_output, "differences", "npy");
ndarray_npy::write_npy(npy_output, &vcf.matrix)?;
let index_order = sort_indexes_for_diff_graph(&vcf, &decoy_samples, mark_shorter_alleles);
tracing::debug!("Finished sorting");
let shared_ranges = find_shared_haplotype_ranges(&vcf);
print_ranges_to_csv(
&vcf,
&shared_ranges,
&decoy_samples,
&only_longest,
&mut writer,
tracing::debug!(
"Sample haplotypes: {}, average length: {}, median length: {}",
shared_ranges.len(),
range_length_avg(&shared_ranges),
range_length_median(&shared_ranges)
if want_png {
let imgbuf = matrix_graph_png(&vcf, graph_args, mark_shorter_alleles, decoy_samples, &index_order);
imgbuf.save(img_output.clone()).wrap_err(eyre!("failed writing to: {img_output:?}"))?;
} else {
let mut dg = MatrixGraph::new(&vcf, graph_args, decoy_samples, mark_shorter_alleles);
dg.draw_graph(&index_order);
svg::save(img_output, &dg.document)?;
tracing::debug!("Finished drawing graph");
Ok(())
fn sort_indexes_for_diff_graph(
vcf: &PhasedMatrix,
decoy_samples: &Option<Vec<String>>,
) -> Vec<usize> {
// Take all from start to variant index, reverse and calculate 1 count in parallel
// to get the amount of markers shared to the left
let mut values: Vec<(usize, i32)> = vcf
.matrix
.slice(s![.., 0..vcf.variant_idx()])
.axis_iter(Axis(0))
.into_par_iter()
.enumerate()
.map(|(y, row)| {
let mut count = 0;
for i in row.iter().rev() {
match i {
0 => break,
1 => count += 1,
_ => panic!(),
(y, count)
})
.collect();
// Sort shortest alleles to the top
if mark_shorter_alleles {
let shared_lengths = shared_lengths_by_majority(vcf, vcf.variant_idx());
let only_longest = select_only_longest_alleles(&shared_lengths);
let only_longest: Vec<usize> = only_longest.iter().map(|s| s.index).collect();
values.sort_by(|a, b| {
only_longest
.contains(&a.0)
.cmp(&only_longest.contains(&b.0))
});
// // Sort shortest alleles to the top
// if let Some(only_longest) = only_longest {
// values.sort_by(|a, b| only_longest.contains(&a.0).cmp(&only_longest.contains(&b.0)));
// }
//
// Sort decoy alleles to the top
if let Some(samples) = decoy_samples {
values.sort_by(|b, a| {
samples
.contains(&vcf.get_sample_name(a.0))
.cmp(&samples.contains(&vcf.get_sample_name(b.0)))
// Sort by left side length
values.sort_by(|a, b| a.1.cmp(&b.1));
values.iter().map(|v| v.0).collect::<Vec<usize>>()
pub fn transform_gt_matrix_to_match_matrix(
mut vcf: PhasedMatrix,
ht: &Vec<HapVariant>,
) -> PhasedMatrix {
// Info to users
match ht.len().cmp(&vcf.matrix().shape()[1]) {
std::cmp::Ordering::Greater => {
tracing::warn!(
"Haplotype has more variants than the given genotypes {} vs {}",
ht.len(),
vcf.matrix().shape()[1]
ht.iter()
.filter(|&ht| !vcf.coords().iter().any(|c| c == ht))
.for_each(|ht| tracing::warn!("Coord {ht} is not contained in the haplotype"));
std::cmp::Ordering::Less => {
"Haplotype has less variants than the given genotypes {} vs {}",
vcf.coords()
.iter()
.filter(|&c| !ht.iter().any(|ht| ht == c))
.for_each(|c| tracing::warn!("Coord {c} is not contained in the haplotype"));
std::cmp::Ordering::Equal => (),
// Swap coords to allow simultaneous iter_mut on the matrix
let coords = std::mem::take(vcf.coords_mut());
vcf.matrix
.axis_iter_mut(Axis(0))
.for_each_with(&coords, |coords, mut row| {
for (i, gt) in row.iter_mut().enumerate() {
let coord = coords.get(i).unwrap();
if let Some(variant_idx) = ht.iter().position(|c| c == coord) {
*gt = match &ht[variant_idx].gt == gt {
true => 1,
false => 0,
*gt = 1;
// Swap back
vcf.set_coords(coords);
pub fn find_shared_haplotype_ranges(vcf: &PhasedMatrix) -> Vec<(usize, usize, usize)> {
.map(|(idx, row)| {
let (mut last_start, mut last_end) = (0, 0);
for (i, gt) in row.iter().enumerate() {
match gt {
1 => last_end = i,
0 => {
if vcf.variant_idx() <= last_end && vcf.variant_idx() >= last_start {
return (idx, last_start, last_end);
last_start = i;
last_end = i;
(idx, 0, vcf.ncoords() - 1)
// panic!("No haplotye found in check-diff algo")
.collect()
pub fn range_length_avg(ranges: &[(usize, usize, usize)]) -> f32 {
let sum: usize = ranges.iter().map(|n| n.2 - n.1).sum();
sum as f32 / ranges.len() as f32
pub fn range_length_median(ranges: &[(usize, usize, usize)]) -> usize {
let mut vec: Vec<_> = ranges.iter().map(|n| n.2 - n.1).collect();
vec.sort();
vec[vec.len() / 2]
pub fn print_ranges_to_csv(
ranges: &[(usize, usize, usize)],
only_longest: &Option<Vec<usize>>,
writer: &mut csv::Writer<Box<dyn std::io::Write>>,
writer.write_record(vec![
"id",
"start",
"stop",
"length",
"markers",
"is_decoy",
"is_longest",
])?;
for (idx, start, stop) in ranges {
let start_pos = vcf.get_pos(*start);
let stop_pos = vcf.get_pos(*stop);
let allele = if *idx >= vcf.nsamples() / 2 { 2 } else { 1 };
let mut row = vec![
format!("{}_{allele}",vcf.get_sample_name(*idx)),
start_pos.to_string(),
stop_pos.to_string(),
(stop_pos - start_pos).to_string(),
(stop - start).to_string(),
];
match decoy_samples {
Some(decoys) => match decoys.contains(&vcf.get_sample_name(*idx)) {
true => row.push("true".to_string()),
false => row.push("false".to_string()),
},
None => row.push("na".to_string()),
match only_longest {
Some(longest) => match longest.contains(idx) {
writer.write_record(row)?;