From 405ea9ad6da4089068c733dc4c9f793c4dbb0bbb Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 13 May 2024 14:09:08 -0400 Subject: [PATCH 01/47] trying to move noodles but the api is entirely different and some functions seem to be lost --- Cargo.lock | 33 +++++++++++++++++++++++---------- Cargo.toml | 2 +- experiments/bedtest.sh | 7 ++++--- src/kplib/annotator.rs | 2 +- src/kplib/vcf_traits.rs | 16 +++++++++------- src/kplib/vcfreader.rs | 4 ++-- src/kplib/vcfwriter.rs | 4 ++-- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e7e3f6..50d187c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -540,9 +550,9 @@ dependencies = [ [[package]] name = "noodles-bgzf" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8970db2e84adb1007377dd3988258d7a64e3fc4c05602ebf94e1f8cba207c030" +checksum = "7dba1c82e9f92c00b23538359e5d191dff7ccb300cf659ee3a835af65c3cd143" dependencies = [ "byteorder", "bytes", @@ -552,15 +562,18 @@ dependencies = [ [[package]] name = "noodles-core" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7336c3be652de4e05444c9b12a32331beb5ba3316e8872d92bfdd8ef3b06c282" +checksum = "c5a8c6b020d1205abef2b0fab4463a6c5ecc3c8f4d561ca8b0d1a42323376200" +dependencies = [ + "bstr", +] [[package]] name = "noodles-csi" -version = "0.30.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60dfe0919f7ecbd081a82eb1d32e8f89f9041932d035fe8309073c8c01277bf" +checksum = "ad09737d94ec2674361219fb3d46a81561a15773585805de807cab323a15648a" dependencies = [ "bit-vec", "byteorder", @@ -571,9 +584,9 @@ dependencies = [ [[package]] name = "noodles-tabix" -version = "0.36.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1ab29335a68d0c2bdf41460a67714ca69e23a1cbeb950ac5c38a9afa446a62" +checksum = "6f8da8182c56b64d28d0330ce209857ba2a5a4981c6925838e4d4ffeea82db09" dependencies = [ "bit-vec", "byteorder", @@ -585,9 +598,9 @@ dependencies = [ [[package]] name = "noodles-vcf" -version = "0.49.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1f2fa749afaccadc596ec55ccb7bdcd8101fa79f8382384223c0dbae3e245b" +checksum = "2576e5b1e12d729c93d30ba25676b4a3efb19134f14f30fa30ae70a064d12eb2" dependencies = [ "indexmap", "memchr", diff --git a/Cargo.toml b/Cargo.toml index b9e61a0..3e3d023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ indicatif = "0.17.8" itertools = { version = "0.12.1" } lazy_static = "1.4.0" log = { version = "0.4", features = ["std", "serde"] } -noodles-vcf = { version = "0.49.0" } +noodles-vcf = { version = "0.56.0" } ordered-float = { version = "4.0", default-features = false } page_size = "0.6.0" petgraph = { version = "0.6.2" } diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 1d12d63..0ffd9ee 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -3,14 +3,14 @@ bed=test_rs/test.chr20.bed create() { #../target/release/kanpig \ - time cargo run --release -- \ + time cargo run -- \ --input test_rs/test2.vcf.gz \ --bam /Users/english/code/kanpig/experiments/test_rs/NA24385.chr20.bam \ --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 4 \ --maxpaths 20000 --mapq 5 --hapsim 0.98 \ - --chunksize 100 --maxhom 5 \ + --chunksize 100 --maxhom 5 --prune --try-exact \ -o test_rs/hc.vcf --bed $bed # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ # --bam /Users/english/code/kanpig/experiments/test_rs/GIABHG002.bam \ @@ -29,6 +29,7 @@ bench_medium() { truvari bench --includebed $bed \ -b test_rs/GRCh38_HG002-T2TQ100-V1.0_stvar.vcf.gz \ -c test_rs/hc.vcf.gz --no-ref a -o test_rs/hcbench_noref/ \ + -s 5 \ --pctsize 0.90 --pctseq 0.90 } @@ -42,4 +43,4 @@ bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf tabix test_rs/hc.vcf.gz #bench_lite bench_medium -#bench_full +bench_full diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 9693b0c..1f85b65 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -3,7 +3,7 @@ use bitflags::bitflags; use petgraph::graph::NodeIndex; -use noodles_vcf::{self as vcf, record::genotypes::sample::Value}; +use noodles_vcf::{self as vcf, record::sample::Value}; bitflags! { pub struct FiltFlags: u32 { diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index dbe2116..2793e2d 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -1,6 +1,8 @@ use crate::kplib::seq_to_kmer; use noodles_vcf::{ - self as vcf, record::alternate_bases::allele, record::info::field, record::Filters, + self as vcf, + variant::record::info::field::value::Value, + variant::record::info::field::key::Key, }; use std::cmp::Ordering; use std::str::FromStr; @@ -74,11 +76,11 @@ impl KdpVcf for vcf::Record { fn size(&self) -> u64 { let svlen = self .info() - .get(&field::Key::from_str("SVLEN").unwrap_or_else(|_| panic!("No SVLEN INFO"))); + .get(&Key::from_str("SVLEN").unwrap_or_else(|_| panic!("No SVLEN INFO"))); - if let Some(Some(field::Value::Integer(svlen))) = svlen { + if let Some(Some(Value::Integer(svlen))) = svlen { return svlen.unsigned_abs() as u64; - } else if let Some(Some(field::Value::Array(field::value::Array::Integer(svlen)))) = svlen { + } else if let Some(Some(Value::Array(field::value::Array::Integer(svlen)))) = svlen { return svlen .first() .unwrap_or_else(|| panic!("Bad SVLEN")) @@ -119,11 +121,11 @@ impl KdpVcf for vcf::Record { fn variant_type(&self) -> Svtype { match self .info() - .get(&field::Key::from_str("SVTYPE").expect("Unable to make key")) + .get(&Key::from_str("SVTYPE").expect("Unable to make key")) { // INFO/SVTYPE - Some(Some(field::Value::String(svtype))) => svtype.parse().expect("Bad SVTYPE"), - Some(Some(field::Value::Array(field::value::Array::String(svtype)))) => svtype + Some(Some(Value::String(svtype))) => svtype.parse().expect("Bad SVTYPE"), + Some(Some(Value::Array(field::value::Array::String(svtype)))) => svtype .first() .cloned() .unwrap_or_else(|| panic!("Bad SVTYPE")) diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index 9849580..37c9346 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -8,7 +8,7 @@ use crate::kplib::{KDParams, KdpVcf, Regions, VcfWriter}; /// Takes a vcf and filtering parameters to create in iterable which will /// return chunks of variants in the same neighborhood pub struct VcfChunker<'a, R: BufRead> { - pub m_vcf: vcf::reader::Reader, + pub m_vcf: vcf::io::Reader, pub m_header: vcf::Header, regions: Regions, params: KDParams, @@ -27,7 +27,7 @@ pub struct VcfChunker<'a, R: BufRead> { impl<'a, R: BufRead> VcfChunker<'a, R> { pub fn new( - m_vcf: vcf::reader::Reader, + m_vcf: vcf::io::Reader, m_header: vcf::Header, regions: Regions, params: KDParams, diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 8b858a3..33302a7 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -13,7 +13,7 @@ use noodles_vcf::{ }; pub struct VcfWriter { - writer: vcf::Writer>, + writer: vcf::io::Writer>, header: vcf::Header, keys: Keys, pub gtcounts: HashMap, @@ -129,7 +129,7 @@ impl VcfWriter { page_size::get() * 1000, File::create(out_path).expect("Error Creating Output File"), ); - let mut writer = vcf::Writer::new(out_buf); + let mut writer = vcf::io::Writer::new(out_buf); let _ = writer.write_header(&header); Self { From 667dee68308902e09edc7422b958a7da2540a704 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 13 May 2024 15:45:52 -0400 Subject: [PATCH 02/47] progress a little bit --- src/kplib/vcf_traits.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index 2793e2d..2d2d728 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -39,6 +39,8 @@ pub trait KdpVcf { fn size(&self) -> u64; fn is_filtered(&self) -> bool; fn variant_type(&self) -> Svtype; + fn is_symbolic(&self) -> bool; + fn is_bnd(&self) -> bool; } impl KdpVcf for vcf::Record { @@ -89,13 +91,14 @@ impl KdpVcf for vcf::Record { } let r_len: u64 = self.reference_bases().len() as u64; - let a_len: u64 = match self.alternate_bases().first() { - Some(allele::Allele::Bases(alt)) => alt.len() as u64, - Some(allele::Allele::Symbol(_alt)) => { + let a_len: u64 = if self.is_symbolic() { let (start, end) = self.boundaries(); start.abs_diff(end) + 1 - } - _ => 0, + } else { + match self.alternate_bases().first() { + Some(alt) => alt.len(), + None => 0 + } }; if r_len == a_len { @@ -148,4 +151,20 @@ impl KdpVcf for vcf::Record { }, } } + + /// Checks if its a symbolic allele e.g. + /// Returns false if its a monozygotic reference + fn is_symbolic(&self) -> bool { + match self.alternate_bases().first() { + Some(alt) => alt.contains('<'), + None => false + } + } + + fn is_bnd(&self) -> bool { + match self.alternate_bases().first() { + Some(alt) => (alt.contains('[') || alt.contains(']')) && alt.contains(':'), + None => false + } + } } From d3cbb4ceac799577dbd2417d784b0c226c431ad5 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 21 May 2024 17:33:18 -0400 Subject: [PATCH 03/47] move changelog to wiki --- changelog.md | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 changelog.md diff --git a/changelog.md b/changelog.md deleted file mode 100644 index d70df77..0000000 --- a/changelog.md +++ /dev/null @@ -1,31 +0,0 @@ -v0.2.0 -*in progress* - -* Up to 40% reduction in runtime -* Hemizygous and sex chromosome aware genotyping with new `--ploidy-bed` -* Variants with alternate alleles of stars, monozygotic reference, and BNDs are filtered out - -v0.1.2 -*May 5, 2024* - -* New optional hompolymer filter doesn't kmerize long homopolymers -* Improved logging info -* Correcting GQ field -* Correcting kmer counting -* Small speed/memory/io improvements - * Off-loaded annotation work from the single writer thread to the worker threads and using a large - multiple of page size for the BufWriter capacity - * Fewer bam file opens - -v0.1.1 -*Apr 11, 2024* - -* The `--no-prune` flag has been changed to `--prune` since not pruning is a better default. -* Partial haplotypes now only allow up to 3 false negatives for regions with fewer than 500 pileups. More than 500 do - not attempt partials. -* Partial haplotypes now respect the `--kmer` option. - -v0.1.0 -*Apr 9, 2024* - -Initial version. Works well enough to freeze. From 9120a8b020f09583508032543b411303a127abd3 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 02:11:44 -0400 Subject: [PATCH 04/47] Update README.md doc cleaning --- README.md | 80 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 0979b32..bace2c7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A fast tool for genotyping structural variants with long-reads. *Kanpig is currently under active research and development. We make no guarantees about its accuracy or the stability of features before version 1.0.* -# Install +# 📥 Install ``` git clone https://github.com/ACEnglish/kanpig cd kanpig @@ -15,20 +15,20 @@ cargo build --release ``` Alternatively, binaries are available in [releases](https://github.com/ACEnglish/kanpig/releases). -# Quick Start +# 🚀 Quick Start ``` kanpig --input variant.vcf.gz --bam alignments.bam --reference ref.fa --out output.vcf ``` See `kanpig -h` for all available parameters, most of which are detailed below. -# Current Limitations +# ⚠️ Current Limitations * Kanpig expects sequence resolved SVs. Variants with symbolic alts (e.g. ``) and BNDs are not parsed. * Kanpig only looks at read pileups and does not consider split or soft-clipped alignment information. This means variants above ~10kbp should be skipped with the `--sizemax` parameter. -* Please do not publish manuscripts with kanpig results until we've completed our manuscript. We're aiming to have a preprint +* Please do not publish manuscripts benchmarking kanpig results until we've completed our manuscript. We're aiming to have a preprint available early Q3 2024. -# Core Parameter Details +# 🔧 Core Parameter Details The default parameters are tuned to work generally well for genotyping a single sample's VCF, meaning the variants are all expected to be present in the sample. For a multi-sample VCF (a.k.a. a project-level VCF), the optimal parameters @@ -41,32 +41,28 @@ Sorted bed file restricts kanpig to only analyzing variants with starts and ends ### `--ploidy-bed` This bed file informs kanpig of special regions within chromosomes that should have non-diploid genotypes. For example, a female human sample shouldn't have any genotypes on chrY. A male human sample should have hemizygous genotypes on chrY and the -non-pseudoautosomal regions of chrX. The `ploidy_beds/` directory has example bed files for GRCh38. All regions not -within the `ploidy-bed` (or if no bed is provided) are assumed to be diploid. - -### `--hapsim` -After performing kmeans clustering on reads to determine the two haplotypes, if the two haplotypes have a size similarity above `hapsim`, they -are consolidated into a homozygous allele. +non-pseudoautosomal regions of chrX. The [ploidy_beds/](https://github.com/ACEnglish/kanpig/tree/develop/ploidy_beds) directory +has example bed files for GRCh38. All regions not within the `--ploidy-bed` (or if no bed is provided) are assumed to be diploid. ### `--chunksize` -Kanpig will build local variant graphs from windows of the genome. These windows are determined by `chunksize` where -the maximum end position of an upstream window's variants is at least `chunksize` base-pairs away from the next window's +Kanpig will build local variant graphs from windows of the genome. These windows are determined by `--chunksize` where +the maximum end position of an upstream window's variants is at least `--chunksize` base-pairs away from the next window's variants' minimum start position. This chunksize also determins the region over which read pileups are generated. Only reads which pass the `--mapq` and `--mapflag` filter are considered. Also, reads must fully span the minimum variant start and maximum variant end. -This is an important parameter because too small of a `chunksize` may not recruit read pileups which support variants +This is an important parameter because too small of a `--chunksize` may not recruit read pileups which support variants but are far away. Similarly, too large of a value may create windows with many SVs which are also too large for reads to have a fully-spanning alignment. ### `--sizemin` and `--sizemax` Variant sizes are determined by `INFO/SVLEN`. If `INFO/SVLEN` tag is not in the VCF entry, the variant's size is set as -`abs(length(ALT) - length(REF))`. +`abs(length(ALT) - length(REF))`. Genotypes of variants not within the size boundaries are set to missing (`./.`). ### `--sizesim` and `--seqsim` When applying a haplotype to a variant graph, only paths above these two thresholds are allowed. If there are multiple -paths above the threshold, the one with the higher `(sizesim + seqsim) / 2` is kept. Generally, `0.90` is well balanced +paths above the threshold, the one with the highest `(sizesim + seqsim) / 2` is kept. Generally, `0.90` is well balanced whereas lower thresholds will boost recall at the cost of precision and vice versa for higher thresholds. ### `--maxpaths` @@ -74,39 +70,47 @@ When performing path-finding, this threshold limits the number of paths which ar speed up runtime but may come at a cost of recall. A higher `--maxpaths` is slower and may come at a cost to specificity. +### `--hapsim` +After performing kmeans clustering on reads to determine the two haplotypes, if the two haplotypes have a size similarity +above `--hapsim`, they are consolidated into a homozygous allele. + ### `--threads` Number of analysis threads to use. Note that in addition to the analysis threads, kanpig keeps one dedicated IO thread for VCF reading and writing. -# Annotations +# 📝 Annotations The `SAMPLE` column fields populated by kanpig are: -* FT - Bit flag for properties of the variant's genotyping. Flags == 0 are considered PASS. The bits definitions are: - * 0x1 - The genotype observed from variants matching paths is not equal to the genotype observed from measuring the - proportions of reads supporting the two alleles. - * 0x2 - The genotype quality is less than 5 - * 0x4 - The depth (DP) is less than 5 - * 0x8 - The sample quality (SQ) is less than 5 (only present on non-ref variants) - * 0x16 - The number of reads supporting the alternate allele less than 5 (only present on non-ref variants) - * 0x32 - The best scoring path through the variant graph only used part of the haplotype. This may be indicative of a - false-negative in the variant graph. -* SQ - Phred scaled likelihood variant alternate is present in the sample -* GQ - Phred scale difference between most and second-most likely genotypes -* PG - Each chunk of variants is assigned a phase group -* DP - Read coverage over the region -* AD - Read coverage supporting the reference and alternate alleles. -* SZ - Size similarity of the two haplotypes to this variant -* SS - Sequence similarity of the two haplotypes to this variant - -# Experimental Parameter Details +| Field | Description | +|---------|-------------| +| **FT** | Bit flag for properties of the variant's genotyping. Flags == 0 are considered PASS. | +| **SQ** | Phred scaled likelihood variant alternate is present in the sample | +| **GQ** | Phred scale difference between most and second-most likely genotypes | +| **PG** | Each chunk of variants is assigned a phase group | +| **DP** | Read coverage over the region | +| **AD** | Read coverage supporting the reference and alternate alleles. | +| **SZ** | Size similarity of the two haplotypes to this variant | +| **SS** | Sequence similarity of the two haplotypes to this variant | + +Details of `FT` +| Flag | Description | +|--------|-------------| +| 0x1 | The genotype observed from variants matching paths is not equal to the genotype observed from measuring the proportions of reads supporting the two alleles. | +| 0x2 | The genotype quality is less than 5 | +| 0x4 | The depth (DP) is less than 5 | +| 0x8 | The sample quality (SQ) is less than 5 (only present on non-ref variants) | +| 0x16 | The number of reads supporting the alternate allele less than 5 (only present on non-ref variants) | +| 0x32 | The best scoring path through the variant graph only used part of the haplotype. This may be indicative of a false-negative in the variant graph. | + +# 🔬 Experimental Parameter Details These parameters have a varying effect on the results and are not guaranteed to be stable across releases. ### `--try-exact` -Before performing the path-finding algorithm that applies haplotypes to the variant graph, perform a 1-to-1 comparison -of the haplotypes to each node in the variant graph. If a single node matches above `--sizesim` and `--seqsim`, the -path-finding is skipped and haplotype applied to support the node. +Before performing the path-finding algorithm that applies a haplotype to the variant graph, perform a 1-to-1 comparison +of the haplotype to each node in the variant graph. If a single node matches above `--sizesim` and `--seqsim`, the +path-finding is skipped and haplotype applied to the node. This parameter will boost the specificity and speed of kanpig at the cost of recall. From cada4bc2d32dde5a85d8bf71da7b116d340c777b Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 02:21:07 -0400 Subject: [PATCH 05/47] Update README.md --- README.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index bace2c7..b63554d 100644 --- a/README.md +++ b/README.md @@ -45,16 +45,14 @@ non-pseudoautosomal regions of chrX. The [ploidy_beds/](https://github.com/ACEng has example bed files for GRCh38. All regions not within the `--ploidy-bed` (or if no bed is provided) are assumed to be diploid. ### `--chunksize` -Kanpig will build local variant graphs from windows of the genome. These windows are determined by `--chunksize` where -the maximum end position of an upstream window's variants is at least `--chunksize` base-pairs away from the next window's -variants' minimum start position. +Kanpig will build local variant graphs from windows of the genome. These windows are determined by making the maximum end position +of an upstream window's variants at least `chunksize` base-pairs away from the next window's variants' minimum start position. -This chunksize also determins the region over which read pileups are generated. Only reads which pass the `--mapq` and -`--mapflag` filter are considered. Also, reads must fully span the minimum variant start and maximum variant end. +This chunksize also determines the region over which read pileups are generated. Only reads with at least `mapq` mapping quality, +passing the `mapflag` filter, and which fully span the minimum variant start and maximum variant end are considered. -This is an important parameter because too small of a `--chunksize` may not recruit read pileups which support variants -but are far away. Similarly, too large of a value may create windows with many SVs which are also too large for reads to have a -fully-spanning alignment. +This is an important parameter because too small of a `chunksize` may not recruit read pileups that support variants but are +further away. Similarly, too large of a value may create windows with many SVs which are also too large for reads to fully-span. ### `--sizemin` and `--sizemax` Variant sizes are determined by `INFO/SVLEN`. If `INFO/SVLEN` tag is not in the VCF entry, the variant's size is set as @@ -66,13 +64,13 @@ paths above the threshold, the one with the highest `(sizesim + seqsim) / 2` is whereas lower thresholds will boost recall at the cost of precision and vice versa for higher thresholds. ### `--maxpaths` -When performing path-finding, this threshold limits the number of paths which are checked. A lower `--maxpaths` will -speed up runtime but may come at a cost of recall. A higher `--maxpaths` is slower and may come at a cost to +When performing path-finding, this threshold limits the number of paths which are checked. A lower `maxpaths` will +speed up runtime but may come at a cost of recall. A higher `maxpaths` is slower and may come at a cost to specificity. ### `--hapsim` After performing kmeans clustering on reads to determine the two haplotypes, if the two haplotypes have a size similarity -above `--hapsim`, they are consolidated into a homozygous allele. +above `hapsim`, they are consolidated into a homozygous allele. ### `--threads` Number of analysis threads to use. Note that in addition to the analysis threads, kanpig keeps one dedicated IO thread @@ -109,21 +107,21 @@ These parameters have a varying effect on the results and are not guaranteed to ### `--try-exact` Before performing the path-finding algorithm that applies a haplotype to the variant graph, perform a 1-to-1 comparison -of the haplotype to each node in the variant graph. If a single node matches above `--sizesim` and `--seqsim`, the +of the haplotype to each node in the variant graph. If a single node matches above `sizesim` and `seqsim`, the path-finding is skipped and haplotype applied to the node. This parameter will boost the specificity and speed of kanpig at the cost of recall. ### `--prune` -Similar to `--try-exact`, a 1-to-1 comparison is performed before path-finding. If any matches are found, all paths +Similar to `try-exact`, a 1-to-1 comparison is performed before path-finding. If any matches are found, all paths which do not traverse the matching nodes are pruned from the variant graph. This parameter will boost the specificity and speed of kanpig at the cost of recall. ### `--maxhom` -When performing kmer-featurization of sequences (from reads or variants), homopolymer runs above `--maxhom` are trimmed -to `--maxhom`. For example, `--maxhom 5` will only count two four-mers in all homopolymer runs above 5bp. +When performing kmer-featurization of sequences (from reads or variants), homopolymer runs above `maxhom` are trimmed +to `maxhom`. For example, `--maxhom 5` will only count two four-mers in homopolymer runs above 5bp. ### `--spanoff` From 337917f420743a82ccaf72992bd2e9c2c42041f0 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 12:01:41 -0400 Subject: [PATCH 06/47] New IO Thread pattern The writer has its own dedicated thread which can immediately start pumping out results. This should prevent the initial spike in memory usage while input is being read. --- Cargo.lock | 2 +- README.md | 8 +- experiments/bedtest.sh | 2 +- src/kplib/bedparser.rs | 2 +- src/kplib/vcfreader.rs | 33 ++++--- src/kplib/vcfwriter.rs | 7 +- src/main.rs | 193 +++++++++++++++++++++++++---------------- 7 files changed, 146 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cae7be..ccb314c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,7 @@ dependencies = [ [[package]] name = "kanpig" -version = "0.2.0-dev" +version = "0.2.0" dependencies = [ "bitflags", "clap", diff --git a/README.md b/README.md index 0979b32..b0f7bb8 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,6 @@ human sample shouldn't have any genotypes on chrY. A male human sample should ha non-pseudoautosomal regions of chrX. The `ploidy_beds/` directory has example bed files for GRCh38. All regions not within the `ploidy-bed` (or if no bed is provided) are assumed to be diploid. -### `--hapsim` -After performing kmeans clustering on reads to determine the two haplotypes, if the two haplotypes have a size similarity above `hapsim`, they -are consolidated into a homozygous allele. - ### `--chunksize` Kanpig will build local variant graphs from windows of the genome. These windows are determined by `chunksize` where the maximum end position of an upstream window's variants is at least `chunksize` base-pairs away from the next window's @@ -74,6 +70,10 @@ When performing path-finding, this threshold limits the number of paths which ar speed up runtime but may come at a cost of recall. A higher `--maxpaths` is slower and may come at a cost to specificity. +### `--hapsim` +After performing kmeans clustering on reads to determine the two haplotypes, if the two haplotypes have a size similarity above +`hapsim`, they are consolidated into a homozygous allele. + ### `--threads` Number of analysis threads to use. Note that in addition to the analysis threads, kanpig keeps one dedicated IO thread for VCF reading and writing. diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index d44e325..7a0c87d 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -11,7 +11,7 @@ create() { --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 4 \ --maxpaths 20000 --mapq 5 --hapsim 0.98 \ - --chunksize 100 --maxhom 5 --try-exact --prune \ + --chunksize 100 --maxhom 5 \ --sample doesthiswork \ -o test_rs/hc.vcf --bed $bed # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ diff --git a/src/kplib/bedparser.rs b/src/kplib/bedparser.rs index 5e8eb22..562c9f8 100644 --- a/src/kplib/bedparser.rs +++ b/src/kplib/bedparser.rs @@ -58,7 +58,7 @@ impl BedParser { }; if chrom != self.prev_chrom { - self.prev_chrom = chrom.clone(); + self.prev_chrom.clone_from(&chrom); self.prev_start = 0; } diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index 2862c52..c387874 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -1,13 +1,13 @@ +use crate::kplib::{GenotypeAnno, KDParams, KdpVcf, Ploidy, Regions}; +use crossbeam_channel::Sender; +use noodles_vcf::{self as vcf}; +use petgraph::graph::NodeIndex; use std::collections::VecDeque; use std::io::BufRead; -use noodles_vcf::{self as vcf}; - -use crate::kplib::{KDParams, KdpVcf, Regions, VcfWriter}; - /// Takes a vcf and filtering parameters to create in iterable which will /// return chunks of variants in the same neighborhood -pub struct VcfChunker<'a, R: BufRead> { +pub struct VcfChunker { pub m_vcf: vcf::reader::Reader, pub m_header: vcf::Header, regions: Regions, @@ -20,18 +20,18 @@ pub struct VcfChunker<'a, R: BufRead> { // next chunk hold_entry: Option, chunk_count: u64, - call_count: u64, - skip_count: u64, - writer: &'a mut VcfWriter, + pub call_count: u64, + pub skip_count: u64, + result_sender: Sender>>, } -impl<'a, R: BufRead> VcfChunker<'a, R> { +impl VcfChunker { pub fn new( m_vcf: vcf::reader::Reader, m_header: vcf::Header, regions: Regions, params: KDParams, - writer: &'a mut VcfWriter, + result_sender: Sender>>, ) -> Self { Self { m_vcf, @@ -44,7 +44,7 @@ impl<'a, R: BufRead> VcfChunker<'a, R> { chunk_count: 0, call_count: 0, skip_count: 0, - writer, + result_sender, } } @@ -99,7 +99,6 @@ impl<'a, R: BufRead> VcfChunker<'a, R> { /// Return the next vcf entry which passes parameter conditions fn get_next_entry(&mut self) -> Option { - //let mut entry = vcf::Record::default(); let mut entry = vcf::Record::default(); loop { @@ -114,7 +113,13 @@ impl<'a, R: BufRead> VcfChunker<'a, R> { return Some(entry); } else { self.skip_count += 1; - self.writer.write_entry(entry.clone()); + let _ = self.result_sender.send(Some(vec![GenotypeAnno::new( + entry.clone(), + &NodeIndex::new(0), + &[], + 0, + &Ploidy::Zero, + )])); } } } @@ -144,7 +149,7 @@ impl<'a, R: BufRead> VcfChunker<'a, R> { } } -impl<'a, R: BufRead> Iterator for VcfChunker<'a, R> { +impl Iterator for VcfChunker { type Item = Vec; fn next(&mut self) -> Option { diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 7a9adab..c58b93c 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -1,10 +1,9 @@ +use crate::kplib::{metrics::GTstate, GenotypeAnno}; use std::collections::HashMap; use std::fs::File; use std::io::BufWriter; use std::path::PathBuf; -use crate::kplib::{metrics::GTstate, GenotypeAnno}; - use noodles_vcf::{ self as vcf, header::record::value::map::format, @@ -36,7 +35,7 @@ impl VcfWriter { } }; - if header.sample_names().len() >= 1 { + if !header.sample_names().is_empty() { warn!( "clearing {} sample columns in output", header.sample_names().len() @@ -148,7 +147,7 @@ impl VcfWriter { let _result = self.writer.write_record(&self.header, &annot.entry); } - pub fn write_entry(&mut self, mut entry: vcf::Record) { + pub fn __write_entry(&mut self, mut entry: vcf::Record) { *entry.genotypes_mut() = Genotypes::new( "GT".parse().unwrap(), vec![vec![ diff --git a/src/main.rs b/src/main.rs index 9978699..75290ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,12 @@ extern crate pretty_env_logger; extern crate log; use clap::Parser; -use crossbeam_channel::{select, unbounded, Receiver, Sender}; -use indicatif::{ProgressBar, ProgressStyle}; +use crossbeam_channel::{unbounded, Receiver, Sender}; +//use indicatif::{ProgressBar, ProgressStyle}; use noodles_vcf::{self as vcf}; +use std::sync::{Arc, Mutex}; use std::thread; +use std::thread::JoinHandle; mod kplib; use kplib::{ @@ -46,112 +48,151 @@ fn main() { let ploidy = PloidyRegions::new(&args.io.ploidy_bed); - let mut writer = VcfWriter::new(&args.io.out, input_header.clone(), &args.io.sample); + let (result_sender, result_receiver): ( + Sender>, + Receiver>, + ) = unbounded(); + /* + * Each Thread is given a result_sender + * They don't need to do anything different except + * But spawning them I do need to collect JoinHandlers + * The VcfWriter needs to start first + * Needs to be joinable + * Then VcfChunker is run, sending things to the Threads + * We then join the threads + * We then send a None to VcfWriter so it knows when to stop + * We then join the VcfWriter + * We are then finished + */ + // This needs a channel for results + // These results may be genotype annos or blanks. + // But I have to make them a single type, so I'll send filtered as genotype anno from + // take_annotated(&[], 0, Ploidy::Zero) + let writer = Arc::new(Mutex::new(VcfWriter::new( + &args.io.out, + input_header.clone(), + &args.io.sample, + ))); + // This blanks work will need to be done by the chunker. So the chunker doesn't get + // the writer but the channel and it won't write_entry it will write_anno // We send the writer to the reader so that we can pipe filtered variants forward let mut m_input = VcfChunker::new( input_vcf, input_header.clone(), tree, args.kd.clone(), - &mut writer, + result_sender.clone(), ); // Create channels for communication between threads - let (sender, receiver): (Sender>, Receiver>) = unbounded(); - let (result_sender, result_receiver): (Sender, Receiver) = unbounded(); + let (task_sender, task_receiver): (Sender>, Receiver>) = + unbounded(); info!("spawning {} threads", args.io.threads); - for _ in 0..args.io.threads { - let m_args = args.clone(); - let receiver = receiver.clone(); - let result_sender = result_sender.clone(); - let m_ploidy = ploidy.clone(); - thread::spawn(move || { - let mut m_bam = BamParser::new(m_args.io.bam, m_args.io.reference, m_args.kd.clone()); - for chunk in receiver.into_iter().flatten() { - let mut m_graph = Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom); - - let ploidy = m_ploidy.get_ploidy(&m_graph.chrom, m_graph.start); - // For zero, we don't have to waste time going into the bam - if ploidy == Ploidy::Zero { - result_sender - .send(m_graph.take_annotated(&[], 0, &ploidy)) - .unwrap(); - continue; + let task_handles: Vec> = (0..args.io.threads) + .map(|_| { + let m_args = args.clone(); + let m_receiver = task_receiver.clone(); + let result_sender = result_sender.clone(); + let m_ploidy = ploidy.clone(); + thread::spawn(move || { + let mut m_bam = + BamParser::new(m_args.io.bam, m_args.io.reference, m_args.kd.clone()); + loop { + match m_receiver.recv() { + Ok(None) | Err(_) => break, + Ok(Some(chunk)) => { + let mut m_graph = + Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom); + + let ploidy = m_ploidy.get_ploidy(&m_graph.chrom, m_graph.start); + // For zero, we don't have to waste time going into the bam + if ploidy == Ploidy::Zero { + result_sender + .send(Some(m_graph.take_annotated(&[], 0, &ploidy))) + .unwrap(); + continue; + } + + let (haps, coverage) = + m_bam.find_haps(&m_graph.chrom, m_graph.start, m_graph.end); + + let haps = match ploidy { + Ploidy::Haploid => haploid_haplotypes(haps, coverage, &m_args.kd), + _ => diploid_haplotypes(haps, coverage, &m_args.kd), + // and then eventually this could allow a --ploidy flag to branch to + // polyploid_haplotypes + }; + + let paths: Vec = haps + .iter() + .map(|h| m_graph.apply_coverage(h, &m_args.kd)) + .collect(); + + result_sender + .send(Some(m_graph.take_annotated(&paths, coverage, &ploidy))) + .unwrap(); + } + } + } + info!("I finished"); + // This should give a result + }) + }) + .collect(); + + //Before we start the workers, we'll start the writer + let cloned_writer = writer.clone(); + let write_handler = std::thread::spawn(move || { + // When this goes out of scope (the thread finishes) + // So the Mutex might be my write_handler + let mut phase_group: i32 = 0; + loop { + match result_receiver.recv() { + Ok(None) | Err(_) => break, + Ok(Some(result)) => { + let mut m_writer = cloned_writer.lock().unwrap(); + for entry in result { + m_writer.anno_write(entry, phase_group); + } + phase_group += 1; } - - let (haps, coverage) = m_bam.find_haps(&m_graph.chrom, m_graph.start, m_graph.end); - - let haps = match ploidy { - Ploidy::Haploid => haploid_haplotypes(haps, coverage, &m_args.kd), - _ => diploid_haplotypes(haps, coverage, &m_args.kd), - // and then eventually this could allow a --ploidy flag to branch to - // polyploid_haplotypes - }; - - let paths: Vec = haps - .iter() - .map(|h| m_graph.apply_coverage(h, &m_args.kd)) - .collect(); - - result_sender - .send(m_graph.take_annotated(&paths, coverage, &ploidy)) - .unwrap(); } - }); - } + } + }); // Send items to worker threads let mut num_chunks: u64 = 0; info!("parsing input"); for i in &mut m_input { - sender.send(Some(i)).unwrap(); + task_sender.send(Some(i)).unwrap(); num_chunks += 1; } if num_chunks == 0 { error!("No variants to be analyzed"); + // This might need to still join. std::process::exit(1); } // Signal worker threads to exit for _ in 0..args.io.threads { - sender.send(None).unwrap(); + task_sender.send(None).unwrap(); } - info!("collecting output"); - let sty = - ProgressStyle::with_template(" [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed") - .unwrap() - .progress_chars("##-"); - let pbar = ProgressBar::new(num_chunks); - pbar.set_style(sty.clone()); - - let mut phase_group: i32 = 0; - loop { - select! { - recv(result_receiver) -> result => { - match result { - Ok(annotated_entries) => { - for entry in annotated_entries { - writer.anno_write(entry, phase_group); - } - phase_group += 1; - pbar.inc(1); - if phase_group as u64 == num_chunks { - break; - } - }, - Err(e) => { - debug!("Problem {:?}", e); - break; - } - } - }, - } + info!("running"); + for handle in task_handles { + handle.join().unwrap(); } - pbar.finish(); + + // There will be no more results to be made + result_sender.send(None).unwrap(); + + // Join on the tasks + info!("collecting output"); + write_handler.join().unwrap(); + let writer = writer.lock().unwrap(); info!("genotype counts: {:#?}", writer.gtcounts); info!("finished"); } From f248ba33dea8a9eb02562466984bcb4a793b3c2a Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 12:18:09 -0500 Subject: [PATCH 07/47] dev version bump and documenting memory usage Now have a better guide for how much memory it takes --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 10 ++++++++++ src/kplib/vcfwriter.rs | 2 +- src/main.rs | 4 ++-- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccb314c..c7aac0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,7 @@ dependencies = [ [[package]] name = "kanpig" -version = "0.2.0" +version = "0.2.1-dev" dependencies = [ "bitflags", "clap", diff --git a/Cargo.toml b/Cargo.toml index 0d64edb..932fe88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kanpig" -version = "0.2.0" +version = "0.2.1-dev" edition = "2021" [dependencies] diff --git a/README.md b/README.md index b63554d..688b9dc 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,16 @@ Details of `FT` | 0x16 | The number of reads supporting the alternate allele less than 5 (only present on non-ref variants) | | 0x32 | The best scoring path through the variant graph only used part of the haplotype. This may be indicative of a false-negative in the variant graph. | +# 🔌 Compute Resources + +Kanpig is highly parallelized and will fully utilize all threads it is given. However, hyperthreading doesn't seem to +help and therefore the number of threads should probably be limited to the number of physical processors available. + +For memory, a general rule is kanpig will need about 20x the size of the compressed `.vcf.gz`. The minimum required +memory is also dependent on the number of threads running as each will need space for its processing. For example, +a 1.6Gb vcf (~5 million SVs) using 16 cores needs at least 32Gb of RAM. That same vcf with 8 or 4 cores needs at least + 24Gb and 20Gb of RAM, respectively. + # 🔬 Experimental Parameter Details These parameters have a varying effect on the results and are not guaranteed to be stable across releases. diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index c58b93c..3d0edb1 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -125,7 +125,7 @@ impl VcfWriter { // Ready to make files let out_buf = BufWriter::with_capacity( - page_size::get() * 1000, + page_size::get() * 500, File::create(out_path).expect("Error Creating Output File"), ); let mut writer = vcf::Writer::new(out_buf); diff --git a/src/main.rs b/src/main.rs index 75290ff..44ba388 100644 --- a/src/main.rs +++ b/src/main.rs @@ -164,7 +164,7 @@ fn main() { // Send items to worker threads let mut num_chunks: u64 = 0; - info!("parsing input"); + info!("building variant graphs"); for i in &mut m_input { task_sender.send(Some(i)).unwrap(); num_chunks += 1; @@ -181,7 +181,7 @@ fn main() { task_sender.send(None).unwrap(); } - info!("running"); + info!("genotyping"); for handle in task_handles { handle.join().unwrap(); } From 60f6864f29348a35814dc376cb729941e153e073 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 14:41:42 -0400 Subject: [PATCH 08/47] reimplementing progressbars lost it during the dedicated IO thread refactor. Also, it now counts variants written instead of number of chunks of variants. --- src/kplib/regions.rs | 2 +- src/kplib/vcfreader.rs | 4 ++-- src/main.rs | 44 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/kplib/regions.rs b/src/kplib/regions.rs index 84f771e..13bedd7 100644 --- a/src/kplib/regions.rs +++ b/src/kplib/regions.rs @@ -42,7 +42,7 @@ pub fn build_region_tree( .filter(|entry| m_contigs.contains_key(&entry.chrom)) { if entry.chrom != prev_chrom { - prev_chrom = entry.chrom.clone(); + prev_chrom.clone_from(&entry.chrom); prev_start = 0; } diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index c387874..e236b0b 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -171,8 +171,8 @@ impl Iterator for VcfChunker { Some(ret) } else { info!( - "{} chunks of {} variants", - self.chunk_count, self.call_count + "{} variants in {} chunks", + self.call_count, self.chunk_count ); info!("{} variants skipped", self.skip_count); None diff --git a/src/main.rs b/src/main.rs index 44ba388..8efbc1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ extern crate log; use clap::Parser; use crossbeam_channel::{unbounded, Receiver, Sender}; -//use indicatif::{ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressStyle}; use noodles_vcf::{self as vcf}; use std::sync::{Arc, Mutex}; use std::thread; @@ -136,29 +136,56 @@ fn main() { } } } - info!("I finished"); // This should give a result }) }) .collect(); + let num_variants = Arc::new(Mutex::new(0)); //Before we start the workers, we'll start the writer let cloned_writer = writer.clone(); + let cloned_num_variants = num_variants.clone(); let write_handler = std::thread::spawn(move || { - // When this goes out of scope (the thread finishes) - // So the Mutex might be my write_handler + let sty = ProgressStyle::with_template( + " [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed", + ) + .unwrap() + .progress_chars("##-"); + let mut pbar: Option = None; let mut phase_group: i32 = 0; + let mut completed_variants: u64 = 0; loop { match result_receiver.recv() { - Ok(None) | Err(_) => break, + Ok(None) | Err(_) => { + pbar.expect("I actually shouldn't be expecting the bar") + .finish(); + break; + } Ok(Some(result)) => { + let mut rsize: u64 = 0; let mut m_writer = cloned_writer.lock().unwrap(); for entry in result { m_writer.anno_write(entry, phase_group); + rsize += 1; + } + if let Some(ref mut bar) = pbar { + bar.inc(rsize); + } else { + completed_variants += rsize; } phase_group += 1; } } + // check if the reader is finished so we can setup the pbar + { + let mut value_guard = cloned_num_variants.lock().unwrap(); + if *value_guard != 0 { + let t_bar = ProgressBar::new(*value_guard).with_style(sty.clone()); + t_bar.inc(completed_variants); + pbar = Some(t_bar); + *value_guard = 0; + } + } } }); @@ -182,6 +209,12 @@ fn main() { } info!("genotyping"); + + // We now know how many variants will be parsed and can turn on the bar + { + let mut value_guard = num_variants.lock().unwrap(); + *value_guard = m_input.call_count + m_input.skip_count; + } for handle in task_handles { handle.join().unwrap(); } @@ -190,7 +223,6 @@ fn main() { result_sender.send(None).unwrap(); // Join on the tasks - info!("collecting output"); write_handler.join().unwrap(); let writer = writer.lock().unwrap(); info!("genotype counts: {:#?}", writer.gtcounts); From b18d3a46f60188748650d538836e253045d2cb67 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 15:00:14 -0400 Subject: [PATCH 09/47] reducing mutex checks After the bar is setup, we no longer need to worry with it --- experiments/bedtest.sh | 6 +++--- src/main.rs | 17 +++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 7a0c87d..b51862d 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -40,8 +40,8 @@ bench_full() { } create -bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf -tabix test_rs/hc.vcf.gz +#bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf +#tabix test_rs/hc.vcf.gz #bench_lite -bench_medium +#bench_medium #bench_full diff --git a/src/main.rs b/src/main.rs index 8efbc1f..d064a20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -172,20 +172,17 @@ fn main() { bar.inc(rsize); } else { completed_variants += rsize; + // check if the reader is finished so we can setup the pbar + let value_guard = cloned_num_variants.lock().unwrap(); + if *value_guard != 0 { + let t_bar = ProgressBar::new(*value_guard).with_style(sty.clone()); + t_bar.inc(completed_variants); + pbar = Some(t_bar); + } } phase_group += 1; } } - // check if the reader is finished so we can setup the pbar - { - let mut value_guard = cloned_num_variants.lock().unwrap(); - if *value_guard != 0 { - let t_bar = ProgressBar::new(*value_guard).with_style(sty.clone()); - t_bar.inc(completed_variants); - pbar = Some(t_bar); - *value_guard = 0; - } - } } }); From 53b810cdebf93dc3737761efdc8c1dac99bee357 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 15:38:47 -0400 Subject: [PATCH 10/47] minor clean --- src/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index d064a20..70ee2a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,6 +154,7 @@ fn main() { let mut pbar: Option = None; let mut phase_group: i32 = 0; let mut completed_variants: u64 = 0; + let mut m_writer = cloned_writer.lock().unwrap(); loop { match result_receiver.recv() { Ok(None) | Err(_) => { @@ -163,7 +164,6 @@ fn main() { } Ok(Some(result)) => { let mut rsize: u64 = 0; - let mut m_writer = cloned_writer.lock().unwrap(); for entry in result { m_writer.anno_write(entry, phase_group); rsize += 1; @@ -196,7 +196,6 @@ fn main() { if num_chunks == 0 { error!("No variants to be analyzed"); - // This might need to still join. std::process::exit(1); } @@ -206,7 +205,6 @@ fn main() { } info!("genotyping"); - // We now know how many variants will be parsed and can turn on the bar { let mut value_guard = num_variants.lock().unwrap(); @@ -216,7 +214,7 @@ fn main() { handle.join().unwrap(); } - // There will be no more results to be made + // There will be no more results made result_sender.send(None).unwrap(); // Join on the tasks From 8ebe920160e3253148d317d7ddc3aa28092fe3e4 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 15:56:04 -0400 Subject: [PATCH 11/47] code clean --- src/main.rs | 70 ++++++++++++++++++++--------------------------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/src/main.rs b/src/main.rs index 70ee2a3..63a6991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,8 +17,8 @@ use kplib::{ PathScore, Ploidy, PloidyRegions, Variants, VcfChunker, VcfWriter, }; -type InputType = Vec; -type OutputType = Vec; +type InputType = Option>; +type OutputType = Option>; fn main() { let args = ArgParser::parse(); @@ -48,47 +48,15 @@ fn main() { let ploidy = PloidyRegions::new(&args.io.ploidy_bed); - let (result_sender, result_receiver): ( - Sender>, - Receiver>, - ) = unbounded(); - /* - * Each Thread is given a result_sender - * They don't need to do anything different except - * But spawning them I do need to collect JoinHandlers - * The VcfWriter needs to start first - * Needs to be joinable - * Then VcfChunker is run, sending things to the Threads - * We then join the threads - * We then send a None to VcfWriter so it knows when to stop - * We then join the VcfWriter - * We are then finished - */ - // This needs a channel for results - // These results may be genotype annos or blanks. - // But I have to make them a single type, so I'll send filtered as genotype anno from - // take_annotated(&[], 0, Ploidy::Zero) - let writer = Arc::new(Mutex::new(VcfWriter::new( - &args.io.out, - input_header.clone(), - &args.io.sample, - ))); - - // This blanks work will need to be done by the chunker. So the chunker doesn't get - // the writer but the channel and it won't write_entry it will write_anno - // We send the writer to the reader so that we can pipe filtered variants forward - let mut m_input = VcfChunker::new( - input_vcf, - input_header.clone(), - tree, - args.kd.clone(), - result_sender.clone(), - ); - // Create channels for communication between threads - let (task_sender, task_receiver): (Sender>, Receiver>) = + let (task_sender, task_receiver): (Sender, Receiver) = unbounded(); + let (result_sender, result_receiver): ( + Sender, + Receiver, + ) = unbounded(); + info!("spawning {} threads", args.io.threads); let task_handles: Vec> = (0..args.io.threads) .map(|_| { @@ -141,9 +109,14 @@ fn main() { }) .collect(); - let num_variants = Arc::new(Mutex::new(0)); //Before we start the workers, we'll start the writer + let writer = Arc::new(Mutex::new(VcfWriter::new( + &args.io.out, + input_header.clone(), + &args.io.sample, + ))); let cloned_writer = writer.clone(); + let num_variants = Arc::new(Mutex::new(0)); let cloned_num_variants = num_variants.clone(); let write_handler = std::thread::spawn(move || { let sty = ProgressStyle::with_template( @@ -186,9 +159,17 @@ fn main() { } }); + info!("building variant graphs"); + let mut m_input = VcfChunker::new( + input_vcf, + input_header.clone(), + tree, + args.kd.clone(), + result_sender.clone(), + ); + // Send items to worker threads let mut num_chunks: u64 = 0; - info!("building variant graphs"); for i in &mut m_input { task_sender.send(Some(i)).unwrap(); num_chunks += 1; @@ -204,12 +185,13 @@ fn main() { task_sender.send(None).unwrap(); } - info!("genotyping"); // We now know how many variants will be parsed and can turn on the bar { let mut value_guard = num_variants.lock().unwrap(); *value_guard = m_input.call_count + m_input.skip_count; } + + info!("genotyping"); for handle in task_handles { handle.join().unwrap(); } @@ -217,7 +199,7 @@ fn main() { // There will be no more results made result_sender.send(None).unwrap(); - // Join on the tasks + // Wait on the writer write_handler.join().unwrap(); let writer = writer.lock().unwrap(); info!("genotype counts: {:#?}", writer.gtcounts); From 67fcb86016434eed88043844079db9c7f0769314 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 19:06:57 -0400 Subject: [PATCH 12/47] code clean --- src/main.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 63a6991..138fffa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,14 +56,14 @@ fn main() { Receiver, ) = unbounded(); - info!("spawning {} threads", args.io.threads); let task_handles: Vec> = (0..args.io.threads) .map(|_| { let m_args = args.clone(); let m_receiver = task_receiver.clone(); - let result_sender = result_sender.clone(); + let m_result_sender = result_sender.clone(); let m_ploidy = ploidy.clone(); + thread::spawn(move || { let mut m_bam = BamParser::new(m_args.io.bam, m_args.io.reference, m_args.kd.clone()); @@ -77,7 +77,7 @@ fn main() { let ploidy = m_ploidy.get_ploidy(&m_graph.chrom, m_graph.start); // For zero, we don't have to waste time going into the bam if ploidy == Ploidy::Zero { - result_sender + m_result_sender .send(Some(m_graph.take_annotated(&[], 0, &ploidy))) .unwrap(); continue; @@ -98,7 +98,7 @@ fn main() { .map(|h| m_graph.apply_coverage(h, &m_args.kd)) .collect(); - result_sender + m_result_sender .send(Some(m_graph.take_annotated(&paths, coverage, &ploidy))) .unwrap(); } @@ -115,9 +115,11 @@ fn main() { input_header.clone(), &args.io.sample, ))); + let cloned_writer = writer.clone(); let num_variants = Arc::new(Mutex::new(0)); let cloned_num_variants = num_variants.clone(); + let write_handler = std::thread::spawn(move || { let sty = ProgressStyle::with_template( " [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed", @@ -128,6 +130,7 @@ fn main() { let mut phase_group: i32 = 0; let mut completed_variants: u64 = 0; let mut m_writer = cloned_writer.lock().unwrap(); + loop { match result_receiver.recv() { Ok(None) | Err(_) => { @@ -141,6 +144,7 @@ fn main() { m_writer.anno_write(entry, phase_group); rsize += 1; } + if let Some(ref mut bar) = pbar { bar.inc(rsize); } else { From 3409abbdbef3c167aee85693c2a5c965f2a13eb6 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 20:33:43 -0400 Subject: [PATCH 13/47] new --mem Attempts to throttle vcf reading to limit maximum memory usage --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + experiments/bedtest.sh | 2 +- src/kplib/cli.rs | 11 +++++++++++ src/kplib/vcfreader.rs | 2 +- src/main.rs | 42 ++++++++++++++++++++++++++++++++---------- 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7aac0b..99449cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,6 +458,7 @@ dependencies = [ "noodles-vcf", "ordered-float", "page_size", + "peak_alloc", "petgraph", "pretty_env_logger", "rust-htslib", @@ -655,6 +656,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "peak_alloc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c4e8e2dd832fd76346468f822e4e600d30ba4e5aa545a128abf12cfae7ea3e" + [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 932fe88..88b3069 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ log = { version = "0.4", features = ["std", "serde"] } noodles-vcf = { version = "0.49.0" } ordered-float = { version = "4.0", default-features = false } page_size = "0.6.0" +peak_alloc = "0.2.1" petgraph = { version = "0.6.2" } pretty_env_logger = { version = "0.4.0" } rust-htslib = { version = "0.46.0" } diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index b51862d..b0d9c73 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -12,7 +12,7 @@ create() { --sizesim 0.95 --seqsim 0.90 --threads 4 \ --maxpaths 20000 --mapq 5 --hapsim 0.98 \ --chunksize 100 --maxhom 5 \ - --sample doesthiswork \ + --sample doesthiswork --mem 1 \ -o test_rs/hc.vcf --bed $bed # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ # --bam /Users/english/code/kanpig/experiments/test_rs/GIABHG002.bam \ diff --git a/src/kplib/cli.rs b/src/kplib/cli.rs index 7b3a40c..51d4311 100644 --- a/src/kplib/cli.rs +++ b/src/kplib/cli.rs @@ -46,6 +46,10 @@ pub struct IOParams { #[arg(long, default_value_t = 1)] pub threads: usize, + /// Maximum memory (in GB) - should be at least `2 * threads` + #[arg(long, default_value_t = 16.0)] + pub mem: f32, + /// Verbose logging #[arg(long, default_value_t = false)] pub debug: bool, @@ -165,6 +169,13 @@ impl ArgParser { warn!("--kmer above 8 becomes memory intensive"); } + if self.io.mem < (self.io.threads as f32 * 2.0) { + warn!( + "{} GB of memory may be too little for {} threads", + self.io.mem, self.io.threads + ); + } + is_ok } } diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index e236b0b..d485ce7 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -19,7 +19,7 @@ pub struct VcfChunker { // fits in the current chunk. We need to hold on to it for the // next chunk hold_entry: Option, - chunk_count: u64, + pub chunk_count: u64, pub call_count: u64, pub skip_count: u64, result_sender: Sender>>, diff --git a/src/main.rs b/src/main.rs index 138fffa..79b8768 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use clap::Parser; use crossbeam_channel::{unbounded, Receiver, Sender}; use indicatif::{ProgressBar, ProgressStyle}; use noodles_vcf::{self as vcf}; +use peak_alloc::PeakAlloc; use std::sync::{Arc, Mutex}; use std::thread; use std::thread::JoinHandle; + mod kplib; use kplib::{ @@ -20,6 +22,9 @@ use kplib::{ type InputType = Option>; type OutputType = Option>; +#[global_allocator] +static PEAK_ALLOC: PeakAlloc = PeakAlloc; + fn main() { let args = ArgParser::parse(); let level = if args.io.debug { @@ -32,11 +37,11 @@ fn main() { .init(); info!("starting"); + info!("params: {:#?}", args); if !args.validate() { error!("please fix arguments"); std::process::exit(1); } - info!("params: {:#?}", args); let mut input_vcf = vcf::reader::Builder::default() .build_from_path(args.io.input.clone()) @@ -49,12 +54,8 @@ fn main() { let ploidy = PloidyRegions::new(&args.io.ploidy_bed); // Create channels for communication between threads - let (task_sender, task_receiver): (Sender, Receiver) = - unbounded(); - let (result_sender, result_receiver): ( - Sender, - Receiver, - ) = unbounded(); + let (task_sender, task_receiver): (Sender, Receiver) = unbounded(); + let (result_sender, result_receiver): (Sender, Receiver) = unbounded(); info!("spawning {} threads", args.io.threads); let task_handles: Vec> = (0..args.io.threads) @@ -173,13 +174,34 @@ fn main() { ); // Send items to worker threads - let mut num_chunks: u64 = 0; + let mut hang_tracker: i64 = 0; for i in &mut m_input { task_sender.send(Some(i)).unwrap(); - num_chunks += 1; + + // The reader can get way ahead of the tasks, so we monitor memory usage + // and let threads catch up + let mut num_waits = 0; + while PEAK_ALLOC.current_usage_as_gb() >= args.io.mem && num_waits < 10 { + std::thread::sleep(std::time::Duration::from_millis(100)); + warn!( + "throttling vcf reading with memory @ {}", + PEAK_ALLOC.current_usage_as_gb() + ); + num_waits += 1; + } + if num_waits != 0 { + hang_tracker += 1; + } else { + hang_tracker -= 1; + } + if hang_tracker >= 10 { + warn!("memory seems pretty full, consider setting a higher --mem"); + hang_tracker = 0; + } + hang_tracker = hang_tracker.max(-10); } - if num_chunks == 0 { + if m_input.chunk_count == 0 { error!("No variants to be analyzed"); std::process::exit(1); } From 0635d722e8ddcdb48ce914c25c481fe4c3d8c4a6 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 20:25:58 -0500 Subject: [PATCH 14/47] tweaking memory limiting but it doesn't seem to be working, so I'm putting this in a feature and will revisit later --- src/main.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 79b8768..51308eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -175,18 +175,23 @@ fn main() { // Send items to worker threads let mut hang_tracker: i64 = 0; + // PEAK_ALLOC seems to be off by about 2x on memory usage + let alcor = 2.2; for i in &mut m_input { task_sender.send(Some(i)).unwrap(); // The reader can get way ahead of the tasks, so we monitor memory usage // and let threads catch up let mut num_waits = 0; - while PEAK_ALLOC.current_usage_as_gb() >= args.io.mem && num_waits < 10 { - std::thread::sleep(std::time::Duration::from_millis(100)); - warn!( - "throttling vcf reading with memory @ {}", - PEAK_ALLOC.current_usage_as_gb() - ); + while (PEAK_ALLOC.current_usage_as_gb() * alcor) >= args.io.mem && num_waits < 10 { + let sleep_time = 100i64.max(hang_tracker * 100); + std::thread::sleep(std::time::Duration::from_millis(sleep_time as u64)); + if num_waits >= 5 { + warn!( + "throttling vcf reading with memory @ {}", + PEAK_ALLOC.current_usage_as_gb() * alcor + ); + } num_waits += 1; } if num_waits != 0 { From 701f91eccf97de03f7f86b94630b4cdd34ae35db Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 22 May 2024 21:27:55 -0400 Subject: [PATCH 15/47] moving mem to feature measuring memory usage cross platform is difficult --- Cargo.toml | 1 - src/kplib/cli.rs | 11 ----------- src/main.rs | 32 -------------------------------- 3 files changed, 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88b3069..932fe88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ log = { version = "0.4", features = ["std", "serde"] } noodles-vcf = { version = "0.49.0" } ordered-float = { version = "4.0", default-features = false } page_size = "0.6.0" -peak_alloc = "0.2.1" petgraph = { version = "0.6.2" } pretty_env_logger = { version = "0.4.0" } rust-htslib = { version = "0.46.0" } diff --git a/src/kplib/cli.rs b/src/kplib/cli.rs index 51d4311..7b3a40c 100644 --- a/src/kplib/cli.rs +++ b/src/kplib/cli.rs @@ -46,10 +46,6 @@ pub struct IOParams { #[arg(long, default_value_t = 1)] pub threads: usize, - /// Maximum memory (in GB) - should be at least `2 * threads` - #[arg(long, default_value_t = 16.0)] - pub mem: f32, - /// Verbose logging #[arg(long, default_value_t = false)] pub debug: bool, @@ -169,13 +165,6 @@ impl ArgParser { warn!("--kmer above 8 becomes memory intensive"); } - if self.io.mem < (self.io.threads as f32 * 2.0) { - warn!( - "{} GB of memory may be too little for {} threads", - self.io.mem, self.io.threads - ); - } - is_ok } } diff --git a/src/main.rs b/src/main.rs index 51308eb..acbc734 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ use clap::Parser; use crossbeam_channel::{unbounded, Receiver, Sender}; use indicatif::{ProgressBar, ProgressStyle}; use noodles_vcf::{self as vcf}; -use peak_alloc::PeakAlloc; use std::sync::{Arc, Mutex}; use std::thread; use std::thread::JoinHandle; @@ -22,9 +21,6 @@ use kplib::{ type InputType = Option>; type OutputType = Option>; -#[global_allocator] -static PEAK_ALLOC: PeakAlloc = PeakAlloc; - fn main() { let args = ArgParser::parse(); let level = if args.io.debug { @@ -174,36 +170,8 @@ fn main() { ); // Send items to worker threads - let mut hang_tracker: i64 = 0; - // PEAK_ALLOC seems to be off by about 2x on memory usage - let alcor = 2.2; for i in &mut m_input { task_sender.send(Some(i)).unwrap(); - - // The reader can get way ahead of the tasks, so we monitor memory usage - // and let threads catch up - let mut num_waits = 0; - while (PEAK_ALLOC.current_usage_as_gb() * alcor) >= args.io.mem && num_waits < 10 { - let sleep_time = 100i64.max(hang_tracker * 100); - std::thread::sleep(std::time::Duration::from_millis(sleep_time as u64)); - if num_waits >= 5 { - warn!( - "throttling vcf reading with memory @ {}", - PEAK_ALLOC.current_usage_as_gb() * alcor - ); - } - num_waits += 1; - } - if num_waits != 0 { - hang_tracker += 1; - } else { - hang_tracker -= 1; - } - if hang_tracker >= 10 { - warn!("memory seems pretty full, consider setting a higher --mem"); - hang_tracker = 0; - } - hang_tracker = hang_tracker.max(-10); } if m_input.chunk_count == 0 { From 377e98e45ce57f701bed900f02b5502987a64598 Mon Sep 17 00:00:00 2001 From: Adam English Date: Thu, 23 May 2024 09:40:49 -0400 Subject: [PATCH 16/47] cleaning removing /reducing jupyter notebooks --- experiments/Analysis.ipynb | 301 ------- experiments/Development.ipynb | 1584 --------------------------------- 2 files changed, 1885 deletions(-) delete mode 100644 experiments/Analysis.ipynb diff --git a/experiments/Analysis.ipynb b/experiments/Analysis.ipynb deleted file mode 100644 index bf6b9d5..0000000 --- a/experiments/Analysis.ipynb +++ /dev/null @@ -1,301 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 154, - "id": "7db0c486-584d-4193-81ca-ff96212f8b8b", - "metadata": {}, - "outputs": [], - "source": [ - "import networkx as nx\n", - "\n", - "G = nx.DiGraph()\n", - "G.add_nodes_from([\n", - " ('src', {'size':0}),\n", - " ('A', {'size': 5}),\n", - " ('B', {'size': 10}),\n", - " ('C', {'size': 7}),\n", - " ('D', {'size': 8}),\n", - " ('E', {'size': 3}),\n", - " ('F', {'size': 20}),\n", - " ('G', {'size': -4}),\n", - " ('snk', {'size': 0}),\n", - "])\n", - "\n", - "for i in \"ABCDEFG\":\n", - " G.add_edge('src', i)\n", - " G.add_edge(i, 'snk')\n", - "# Add directed edges\n", - "G.add_edges_from([\n", - " ('A', 'B'),\n", - " ('A', 'C'),\n", - " ('A', 'D'),\n", - " ('A', 'E'),\n", - " ('A', 'F'),\n", - " ('A', 'G'),\n", - " ('B', 'C'),\n", - " ('B', 'D'),\n", - " ('B', 'E'),\n", - " ('B', 'F'),\n", - " ('B', 'G'),\n", - " ('C', 'D'),\n", - " ('C', 'E'),\n", - " ('C', 'F'),\n", - " ('C', 'G'),\n", - " ('D', 'E'),\n", - " ('D', 'F'),\n", - " ('D', 'G'),\n", - " ('E', 'F'),\n", - " ('E', 'G'),\n", - " ('F', 'G'),\n", - "])" - ] - }, - { - "cell_type": "code", - "execution_count": 163, - "id": "fa3986aa-8553-4fe2-9e28-559d41713c7e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[1, 2, 4, 5] [2, 3, 5]\n", - "[1, 3, 4, 5] [2, 3, 5]\n", - "Closest path: [0, 2, 3, 4]\n", - "Path sizes: [1, 3, 4, 5]\n" - ] - } - ], - "source": [ - "\n", - "\n", - "def min_diff(a, b):\n", - " dp = [0] * (len(b) + 1)\n", - " for i in range(1, len(b) + 1):\n", - " dp[i] = float('inf')\n", - " for j in range(i):\n", - " dp[i] = min(dp[i], dp[j] + abs(a[j] - b[i - 1]))\n", - " print(a, b)\n", - " return dp[-1]\n", - "\n", - "def path_sum(graph, path):\n", - " return sum(graph.nodes[node]['size'] for node in path)\n", - "\n", - "def closest_path(graph, weights, start, end):\n", - " closest_path = None\n", - " min_diff_val = float('inf')\n", - "\n", - " for path in nx.all_simple_paths(graph, source=start, target=end):\n", - " path_weights = [graph.nodes[node]['size'] for node in path]\n", - " diff = min_diff(path_weights, weights)\n", - " if diff < min_diff_val:\n", - " min_diff_val = diff\n", - " closest_path = path\n", - "\n", - " return closest_path\n", - "\n", - "if __name__ == \"__main__\":\n", - " graph = nx.DiGraph()\n", - " graph.add_nodes_from([(0, {'size': 1}), (1, {'size': 2}), (2, {'size': 3}),\n", - " (3, {'size': 4}), (4, {'size': 5})])\n", - " graph.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)])\n", - "\n", - " weights = [2, 3, 5]\n", - " start = 0\n", - " end = 4\n", - "\n", - " path = closest_path(graph, weights, start, end)\n", - " if path:\n", - " path_weights = [graph.nodes[node]['size'] for node in path]\n", - " print(\"Closest path:\", path)\n", - " print(\"Path sizes:\", path_weights)\n", - " else:\n", - " print(\"No path found\")" - ] - }, - { - "cell_type": "code", - "execution_count": 164, - "id": "ea8e6771-96db-4d84-b759-b97287d189b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0, 5, 0] [15, 5]\n", - "[0, 5, 10, 0] [15, 5]\n", - "[0, 5, 10, 7, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 3, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 3, 20, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 3, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 20, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 8, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 3, 0] [15, 5]\n", - "[0, 5, 10, 7, 3, 20, 0] [15, 5]\n", - "[0, 5, 10, 7, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 3, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, 20, 0] [15, 5]\n", - "[0, 5, 10, 7, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 7, -4, 0] [15, 5]\n", - "[0, 5, 10, 8, 0] [15, 5]\n", - "[0, 5, 10, 8, 3, 0] [15, 5]\n", - "[0, 5, 10, 8, 3, 20, 0] [15, 5]\n", - "[0, 5, 10, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 8, 3, -4, 0] [15, 5]\n", - "[0, 5, 10, 8, 20, 0] [15, 5]\n", - "[0, 5, 10, 8, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 8, -4, 0] [15, 5]\n", - "[0, 5, 10, 3, 0] [15, 5]\n", - "[0, 5, 10, 3, 20, 0] [15, 5]\n", - "[0, 5, 10, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, 3, -4, 0] [15, 5]\n", - "[0, 5, 10, 20, 0] [15, 5]\n", - "[0, 5, 10, 20, -4, 0] [15, 5]\n", - "[0, 5, 10, -4, 0] [15, 5]\n", - "[0, 5, 7, 0] [15, 5]\n", - "[0, 5, 7, 8, 0] [15, 5]\n", - "[0, 5, 7, 8, 3, 0] [15, 5]\n", - "[0, 5, 7, 8, 3, 20, 0] [15, 5]\n", - "[0, 5, 7, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 7, 8, 3, -4, 0] [15, 5]\n", - "[0, 5, 7, 8, 20, 0] [15, 5]\n", - "[0, 5, 7, 8, 20, -4, 0] [15, 5]\n", - "[0, 5, 7, 8, -4, 0] [15, 5]\n", - "[0, 5, 7, 3, 0] [15, 5]\n", - "[0, 5, 7, 3, 20, 0] [15, 5]\n", - "[0, 5, 7, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 7, 3, -4, 0] [15, 5]\n", - "[0, 5, 7, 20, 0] [15, 5]\n", - "[0, 5, 7, 20, -4, 0] [15, 5]\n", - "[0, 5, 7, -4, 0] [15, 5]\n", - "[0, 5, 8, 0] [15, 5]\n", - "[0, 5, 8, 3, 0] [15, 5]\n", - "[0, 5, 8, 3, 20, 0] [15, 5]\n", - "[0, 5, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 8, 3, -4, 0] [15, 5]\n", - "[0, 5, 8, 20, 0] [15, 5]\n", - "[0, 5, 8, 20, -4, 0] [15, 5]\n", - "[0, 5, 8, -4, 0] [15, 5]\n", - "[0, 5, 3, 0] [15, 5]\n", - "[0, 5, 3, 20, 0] [15, 5]\n", - "[0, 5, 3, 20, -4, 0] [15, 5]\n", - "[0, 5, 3, -4, 0] [15, 5]\n", - "[0, 5, 20, 0] [15, 5]\n", - "[0, 5, 20, -4, 0] [15, 5]\n", - "[0, 5, -4, 0] [15, 5]\n", - "[0, 10, 0] [15, 5]\n", - "[0, 10, 7, 0] [15, 5]\n", - "[0, 10, 7, 8, 0] [15, 5]\n", - "[0, 10, 7, 8, 3, 0] [15, 5]\n", - "[0, 10, 7, 8, 3, 20, 0] [15, 5]\n", - "[0, 10, 7, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 10, 7, 8, 3, -4, 0] [15, 5]\n", - "[0, 10, 7, 8, 20, 0] [15, 5]\n", - "[0, 10, 7, 8, 20, -4, 0] [15, 5]\n", - "[0, 10, 7, 8, -4, 0] [15, 5]\n", - "[0, 10, 7, 3, 0] [15, 5]\n", - "[0, 10, 7, 3, 20, 0] [15, 5]\n", - "[0, 10, 7, 3, 20, -4, 0] [15, 5]\n", - "[0, 10, 7, 3, -4, 0] [15, 5]\n", - "[0, 10, 7, 20, 0] [15, 5]\n", - "[0, 10, 7, 20, -4, 0] [15, 5]\n", - "[0, 10, 7, -4, 0] [15, 5]\n", - "[0, 10, 8, 0] [15, 5]\n", - "[0, 10, 8, 3, 0] [15, 5]\n", - "[0, 10, 8, 3, 20, 0] [15, 5]\n", - "[0, 10, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 10, 8, 3, -4, 0] [15, 5]\n", - "[0, 10, 8, 20, 0] [15, 5]\n", - "[0, 10, 8, 20, -4, 0] [15, 5]\n", - "[0, 10, 8, -4, 0] [15, 5]\n", - "[0, 10, 3, 0] [15, 5]\n", - "[0, 10, 3, 20, 0] [15, 5]\n", - "[0, 10, 3, 20, -4, 0] [15, 5]\n", - "[0, 10, 3, -4, 0] [15, 5]\n", - "[0, 10, 20, 0] [15, 5]\n", - "[0, 10, 20, -4, 0] [15, 5]\n", - "[0, 10, -4, 0] [15, 5]\n", - "[0, 7, 0] [15, 5]\n", - "[0, 7, 8, 0] [15, 5]\n", - "[0, 7, 8, 3, 0] [15, 5]\n", - "[0, 7, 8, 3, 20, 0] [15, 5]\n", - "[0, 7, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 7, 8, 3, -4, 0] [15, 5]\n", - "[0, 7, 8, 20, 0] [15, 5]\n", - "[0, 7, 8, 20, -4, 0] [15, 5]\n", - "[0, 7, 8, -4, 0] [15, 5]\n", - "[0, 7, 3, 0] [15, 5]\n", - "[0, 7, 3, 20, 0] [15, 5]\n", - "[0, 7, 3, 20, -4, 0] [15, 5]\n", - "[0, 7, 3, -4, 0] [15, 5]\n", - "[0, 7, 20, 0] [15, 5]\n", - "[0, 7, 20, -4, 0] [15, 5]\n", - "[0, 7, -4, 0] [15, 5]\n", - "[0, 8, 0] [15, 5]\n", - "[0, 8, 3, 0] [15, 5]\n", - "[0, 8, 3, 20, 0] [15, 5]\n", - "[0, 8, 3, 20, -4, 0] [15, 5]\n", - "[0, 8, 3, -4, 0] [15, 5]\n", - "[0, 8, 20, 0] [15, 5]\n", - "[0, 8, 20, -4, 0] [15, 5]\n", - "[0, 8, -4, 0] [15, 5]\n", - "[0, 3, 0] [15, 5]\n", - "[0, 3, 20, 0] [15, 5]\n", - "[0, 3, 20, -4, 0] [15, 5]\n", - "[0, 3, -4, 0] [15, 5]\n", - "[0, 20, 0] [15, 5]\n", - "[0, 20, -4, 0] [15, 5]\n", - "[0, -4, 0] [15, 5]\n", - "Closest path: ['src', 'A', 'snk']\n", - "Path weights: [0, 5, 0]\n" - ] - } - ], - "source": [ - "weights = [15,5]\n", - "path = closest_path(G, weights, 'src', 'snk')\n", - "if path:\n", - " path_weights = [G.nodes[node]['size'] for node in path]\n", - " print(\"Closest path:\", path)\n", - " print(\"Path weights:\", path_weights)\n", - "else:\n", - " print(\"No path found\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab2cb934-57f9-42a4-bcd5-2c47f124c31c", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/experiments/Development.ipynb b/experiments/Development.ipynb index ad3e139..d3c4e11 100644 --- a/experiments/Development.ipynb +++ b/experiments/Development.ipynb @@ -159,1590 +159,6 @@ "p = sb.scatterplot(data=data, x='unroll', y='pj', hue='bin_len')\n", "p.set(title=\"Kmer estimate of unroll sequence similarity\", xlabel=\"Unroll Sequence Similarity\", ylabel=\"Kmer Count Similarity\")" ] - }, - { - "cell_type": "code", - "execution_count": 134, - "id": "4e584018-0fe7-495f-8a1d-83cb1d1a52e3", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/english/py/lib/python3.9/site-packages/seaborn/categorical.py:641: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.\n", - " grouped_vals = vals.groupby(grouper)\n" - ] - }, - { - "data": { - "text/plain": [ - "[Text(0.5, 1.0, 'Cosine Sim by Sequence Similarity'), Text(0.5, 0, 'pj')]" - ] - }, - "execution_count": 134, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABIu0lEQVR4nO3deXgUVd728bvTId1hSVACCYRI2BTZlTWiI2qcjKMIOigimxFwfEReIPg44kJQRnEc2YZNEXAjuCAuqEh0EMYRIzuKuLKDmrAJYUuA9Hn/4EmbJh3WTqq7+vu5rlwXXV1V/auuDrn71KlzHMYYIwAAAJuIsLoAAACAQCLcAAAAWyHcAAAAWyHcAAAAWyHcAAAAWyHcAAAAWyHcAAAAWyHcAAAAWyHcAAAAWyHcACdxOBwaNWqU1WWcUnJysu66664KeZ2bbrqp3F8H1hs1apQcDkdA93nXXXcpOTnZZ1l5/H699NJLcjgc2rJlS0D3i9BFuEFQ27hxo/7617+qQYMGcrvdiomJUadOnTRx4kQdOXLE6vICbt26derevbvq1asnt9utxMREXX/99Zo0aZLVpZWrXbt2aciQIWrSpImio6NVq1YttW/fXn/729908OBBq8sLaQcPHlRmZqaaN2+uKlWqqEaNGmrdurWGDBmiX375xeryys3UqVP10ksvWV0GLOJgbikEqw8//FC33XabXC6X+vbtq+bNm+vo0aP6/PPPNW/ePN11112aPn16wF+3oKBAkZGRioyMDPi+T+WLL77QNddco4suukj9+vVTQkKCtm/fri+//FIbN27Uhg0bvOsWFhYqIiJClSpVKteakpOT1bx5c33wwQfl9hp79+7VZZddpvz8fN19991q0qSJ9uzZo6+//loffPCBvv7661Lf/nFmjh07pg4dOuj7779Xv3791Lp1ax08eFDr16/X+++/r7lz56pz586SpOPHj+v48eNyu90BfX2PxyOXy+Vd5nA4lJmZGdDWm6KiIh07dkwul8vb+tS8eXPFxcVpyZIlAXsdhI6K/d8bOEObN2/WHXfcoXr16unTTz9V7dq1vc8NGjRIGzZs0Icfflgurx3I/9zPxpNPPqnY2FitWLFC1atX93lu586dPo9L/rEIdTNnztS2bdu0dOlSXXHFFT7P5efnKyoqyqLKQt+7776rNWvWKCsrS3feeafPcwUFBTp69Kj3cXkE+vIO34cOHVKVKlXkdDrldDrL9bUQWrgshaD0zDPP6ODBg5o5c6ZPsCnWqFEjDRkyxPv4+PHjGj16tBo2bCiXy6Xk5GQ9/PDDKiws9Nlu5cqVSktLU1xcnKKjo1W/fn3dfffdPuuc3CeguC/Chg0bdNddd6l69eqKjY1Venq6Dh8+XKq22bNnq02bNoqOjtaFF16oO+64Q9u3bz/tMW/cuFHNmjUrFWwkqVatWj6PT+5zU9zn4PPPP9f/+3//TzVr1lT16tX117/+VUePHtW+ffvUt29fXXDBBbrgggv04IMP6mwabT/++GO1bt1abrdbTZs21dtvv+19btOmTXI4HBo/fnyp7b744gs5HA699tprpzxup9Opjh07lnouJiamVNhctmyZ/vSnPyk2NlaVK1fW1VdfraVLl5ba9vPPP1e7du3kdrvVsGFDPf/886X6lWzZskUOh8Pv5Qt/fUN+/vln3X333YqPj5fL5VKzZs00a9Ysn3WWLFkih8OhN998U08++aTq1q0rt9ut6667zqf1reTx/PnPf9YFF1ygKlWqqGXLlpo4caLPOt9//726d++uCy+8UG63W23bttX8+fNL7etkGzdulCR16tSp1HPFl3mL+etz43A4dP/992vu3Llq2rSpoqOjlZKSonXr1kmSnn/+eTVq1Ehut1udO3cu1efFX5+bk23dulX33XefLrnkEkVHR6tGjRq67bbbSu2r+DP+n//8R/fdd59q1aqlunXr+jxXvE1ycrLWr1+v//znP3I4HHI4HOrcufN5f1YROmi5QVB6//331aBBg1Lf5MsyYMAAvfzyy+revbuGDx+uZcuWacyYMfruu+/0zjvvSDrR+vHHP/5RNWvW1EMPPaTq1atry5YtPn+oT+X2229X/fr1NWbMGK1evVozZsxQrVq19I9//MO7zpNPPqnHHntMt99+uwYMGKBdu3Zp0qRJ+sMf/qA1a9b4DS7F6tWrp5ycHH3zzTdq3rz5GdV0ssGDByshIUGPP/64vvzyS02fPl3Vq1fXF198oYsuukhPPfWUFixYoH/+859q3ry5+vbte9p9/vTTT+rRo4fuvfde9evXTy+++KJuu+02LVy4UNdff70aNGigTp06KSsrS8OGDfPZNisrS9WqVVPXrl1PedxFRUV69dVX1a9fv1PW8umnn+qGG25QmzZtlJmZqYiICL344ou69tpr9d///lft27eXdKLvUvG5HjVqlI4fP67MzEzFx8efwbvoX15enjp27Oj9g1+zZk199NFH6t+/v/Lz8zV06FCf9Z9++mlFRETogQce0P79+/XMM8+oV69eWrZsmXedTz75RDfddJNq166tIUOGKCEhQd99950++OADb3hfv369OnXqpMTERD300EOqUqWK3nzzTXXr1k3z5s3TLbfcUmbN9erVkyS98sorevTRR8+pw/B///tfzZ8/X4MGDZIkjRkzRjfddJMefPBBTZ06Vffdd59+++03PfPMM7r77rv16aefntX+V6xYoS+++EJ33HGH6tatqy1btmjatGnq3Lmzvv32W1WuXNln/fvuu081a9bUyJEjdejQIb/7nDBhggYPHqyqVavqkUcekSTFx8ef92cVIcQAQWb//v1GkunatesZrb927VojyQwYMMBn+QMPPGAkmU8//dQYY8w777xjJJkVK1accn+STGZmpvdxZmamkWTuvvtun/VuueUWU6NGDe/jLVu2GKfTaZ588kmf9datW2ciIyNLLT/Zxx9/bJxOp3E6nSYlJcU8+OCDJjs72xw9erTUuvXq1TP9+vXzPn7xxReNJJOWlmY8Ho93eUpKinE4HObee+/1Ljt+/LipW7euufrqq09ZT/HrSDLz5s3zLtu/f7+pXbu2ueyyy7zLnn/+eSPJfPfdd95lR48eNXFxcT51+pObm2tq1qxpJJkmTZqYe++918yZM8fs27fPZz2Px2MaN25c6hgPHz5s6tevb66//nrvsm7duhm32222bt3qXfbtt98ap9NpSv63t3nzZiPJvPjii6XqOvlz0L9/f1O7dm2ze/dun/XuuOMOExsbaw4fPmyMMWbx4sVGkrn00ktNYWGhd72JEycaSWbdunXGmBPnoX79+qZevXrmt99+K3Wsxa677jrTokULU1BQ4PP8FVdcYRo3blyq7pIOHz5sLrnkEiPJ1KtXz9x1111m5syZJi8vr9S6xZ/zk98Dl8tlNm/e7F1WfK4TEhJMfn6+d/mIESOMJJ91+/XrZ+rVq1dqnyXf1+L3raScnBwjybzyyiveZcWf8SuvvNIcP37cZ/3i50q+drNmzfx+xs/ns4rQwWUpBJ38/HxJUrVq1c5o/QULFkiSMjIyfJYPHz5ckrx9c4pbTT744AMdO3bsrOu69957fR5fddVV2rNnj7fet99+Wx6PR7fffrt2797t/UlISFDjxo21ePHiU+7/+uuvV05Ojm6++WZ99dVXeuaZZ5SWlqbExMQzugQhSf379/f5dt6hQwcZY9S/f3/vMqfTqbZt22rTpk1ntM86der4tA7ExMSob9++WrNmjXJzcyWdaNVyu93Kysryrpedna3du3erd+/ep9x/fHy8vvrqK91777367bff9Nxzz+nOO+9UrVq1NHr0aO/ls7Vr1+qnn37SnXfeqT179njf30OHDum6667TZ599Jo/Ho6KiImVnZ6tbt2666KKLvK9z6aWXKi0t7YyO+WTGGM2bN09dunSRMcbn/KalpWn//v1avXq1zzbp6ek+/YWuuuoqSfK+72vWrNHmzZs1dOjQUi16xedw7969+vTTT3X77bfrwIED3tfcs2eP0tLS9NNPP+nnn38us+7o6GgtW7ZM//u//yvpxOWb/v37q3bt2ho8eHCpy7b+XHfddT6Xljp06CBJ+stf/uLzO1q8/Ew/VyVrLHbs2DHt2bNHjRo1UvXq1Uu9p5I0cODA8+pfcz6fVYQOwg2CTnE/gAMHDpzR+lu3blVERIQaNWrkszwhIUHVq1fX1q1bJUlXX321/vKXv+jxxx9XXFycunbtqhdffPGM/oOX5POHUpIuuOACSdJvv/0m6cTlG2OMGjdurJo1a/r8fPfdd6U6BfvTrl07vf322/rtt9+0fPlyjRgxQgcOHFD37t317bffnnWNsbGxkqSkpKRSy4vrPp1GjRqVupxx8cUXS5K3j0P16tXVpUsXzZkzx7tOVlaWEhMTde211572NWrXrq1p06bp119/1Q8//KB//etf3ksPM2fOlHTi/ZWkfv36lXp/Z8yYocLCQu3fv1+7du3SkSNH1Lhx41Kvc8kll5zRMZ9s165d2rdvn6ZPn17qtdPT0yWV7vR9us9LcX+YU12C3LBhg4wxeuyxx0q9bmZmpt/XPVlsbKyeeeYZbdmyRVu2bNHMmTN1ySWXaPLkyRo9evRpj/1sPlMlj+9MHTlyRCNHjlRSUpJcLpfi4uJUs2ZN7du3T/v37y+1fv369c9q/yc7388qQgN9bhB0YmJiVKdOHX3zzTdntd3p+hM4HA699dZb+vLLL/X+++8rOztbd999t8aOHasvv/xSVatWPeX2ZX1bLG5Z8Hg8cjgc+uijj/yue7r9lxQVFaV27dqpXbt2uvjii5Wenq65c+d6/6CdbY3+lpsAjwLRt29fzZ07V1988YVatGih+fPn67777lNExJl/h3I4HLr44ot18cUX68Ybb1Tjxo2VlZWlAQMGyOPxSJL++c9/qnXr1n63r1q16hmH1eLX86eoqMjncfFr9+7du8x+QS1btvR5fLrPy5koft0HHnigzFank0P9qdSrV0933323brnlFjVo0EBZWVn6+9//fsptzuYzJZ3952rw4MF68cUXNXToUKWkpCg2NlYOh0N33HGH9/hLKtnSc64C8VlFcCPcICjddNNNmj59unJycpSSknLKdevVqyePx6OffvpJl156qXd5Xl6e9u3b5+1UWaxjx47q2LGjnnzySc2ZM0e9evXS66+/rgEDBpxXzQ0bNpQxRvXr1/e2bARC27ZtJUm//vprwPZ5NopbD0oGgR9//FGSfC5X/OlPf1LNmjWVlZWlDh066PDhw+rTp885v26DBg10wQUXeI+7YcOGkk6E39TU1DK3q1mzpqKjo70tPSX98MMPPo+LW1P27dvns7y4ta/kPqtVq6aioqJTvvbZKD6eb775psx9NmjQQNKJW6oD9brSieNu2LDhWX+BKA9vvfWW+vXrp7Fjx3qXFRQUlDonZ+tUX3YC/VlF8CGmIig9+OCDqlKligYMGKC8vLxSz2/cuNF7u+yf//xnSSfukChp3LhxkqQbb7xR0onm8pO/VRa3AJzNt/2y3HrrrXI6nXr88cdLvY4xRnv27Dnl9osXL/b7rbe4T9G5XlI5X7/88ov3jjPpRJ+oV155Ra1bt1ZCQoJ3eWRkpHr27Kk333xTL730klq0aFGqNcOfZcuW+b3rZfny5dqzZ4/3uNu0aaOGDRvq2Wef9Ttq8a5duySdaFFIS0vTu+++q23btnmf/+6775Sdne2zTUxMjOLi4vTZZ5/5LJ86darPY6fTqb/85S+aN2+e30BQ/Npn4/LLL1f9+vU1YcKEUn/Iiz8HtWrVUufOnfX888/7Dbene92vvvpKu3fvLrV869at+vbbby37TJXkdDpLfe4nTZpUqvXsbFWpUqXMgHSun1WEDlpuEJQaNmyoOXPmqEePHrr00kt9Rij+4osvNHfuXO84L61atVK/fv00ffp07du3T1dffbWWL1+ul19+Wd26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fgBKsrTlRpIyMjLUr18/tW3bVu3bt9eECRN06NAhpaenS5L69u2rxMREjRkzRm63W82bN/fZvnr16pJUajmA8NK7d28tWLBAu3fvVlxcHIP4AWHM8nDTo0cP7dq1SyNHjlRubq5at26thQsXejsZb9u2TRERIdU1CIAF3G63hg8frgkTJmjo0KFyu91WlwTAIg4TZvdJ5ufnKzY2Vvv371dMTIzfdY4cOaK0tDRJ0qG2/SRnpYosMfQVHVOVlS9LkrKzsxUdHW1xQQCAUHcmf7+L0SQCAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABshXADAABsJdLqAoKe57jVFZRmzO91RURKDoe19ZwsGN8zAEDYINycRpXVWVaXAAAAzgKXpQAAgK3QcuOH2+1Wdna21WWUqaCgQF27dpUkvffee3K73RZXVLZgrg0AYE+EGz8cDoeio6OtLuOMuN3ukKkVAICKwGUpAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK4QbAABgK0ERbqZMmaLk5GS53W516NBBy5cvL3PdF154QVdddZUuuOACXXDBBUpNTT3l+gAAILxYHm7eeOMNZWRkKDMzU6tXr1arVq2UlpamnTt3+l1/yZIl6tmzpxYvXqycnBwlJSXpj3/8o37++ecKrhwAAAQjhzHGWFlAhw4d1K5dO02ePFmS5PF4lJSUpMGDB+uhhx467fZFRUW64IILNHnyZPXt2/e06+fn5ys2Nlb79+9XTEzMeddvhSNHjigtLU2SlJ2drejoaIsrAgCgfJ3N329LW26OHj2qVatWKTU11bssIiJCqampysnJOaN9HD58WMeOHdOFF15YXmUCAIAQEmnli+/evVtFRUWKj4/3WR4fH6/vv//+jPbxt7/9TXXq1PEJ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1181depUpaSkSDrRd6ZWrVo+20ZGRurCCy9Ubm5umft/8803dezYMdWoUUMul0t//etf9c4776hRo0blelyhavfu3SoqKlJ8fLzP8vj4+DLf5zvvvFNPPPGErrzySlWqVEkNGzZU586d9fDDD3vX6dChg1566SUtXLhQ06ZN0+bNm3XVVVfpwIED5Xo8oexczkVaWprGjRunn376SR6PR5988onefvtt/frrr+e8T5TPuZD4vUD5IdxYbNKkSRo6dKiqVq2qDRs26J133tGtt96qqKiogOz/scce0759+/Tvf/9bK1euVEZGhm6//XatW7cuIPuHtGTJEj311FOaOnWqVq9erbffflsffvihRo8e7V3nhhtu0G233aaWLVsqLS1NCxYs0L59+/Tmm29aWLn9TJw4UY0bN1aTJk0UFRWl+++/X+np6YqI4L+6inYm54LfC5QXfuMtds8992j06NHKzc1Vs2bNlJ6erk8//VQej8dnvYSEBO3cudNn2fHjx7V3714lJCT43ffGjRs1efJkzZo1S9ddd51atWqlzMxMtW3bVlOmTCm3YwplcXFxcjqdysvL81mel5dX5vv82GOPqU+fPhowYIBatGihW265RU899ZTGjBlT6jwWq169ui6++GJt2LAh4MdgF+dyLmrWrKl3331Xhw4d0tatW/X999+ratWqatCgwTnvE+VzLvzh9wKBQrixWJ06dfToo4/qxx9/1MKFCxUVFaVbb71V9erV00MPPaT169dLklJSUrRv3z6tWrXKu21xCOrQoYPffR8+fFiSSn1rdTqdZf7RDXdRUVFq06aNFi1a5F3m8Xi0aNEi7yXCkx0+fNjveyxJpoyp2w4ePKiNGzeqdu3aAarcfs7lXBRzu91KTEzU8ePHNW/ePHXt2vW89xnOyuNc+MPvBQLG6h7NKO3IkSPmtddeM2lpacbpdJqvv/7aGHPiVvDLLrvMLFu2zHz++eemcePGPreC79ixw1xyySVm2bJlxhhjjh49aho1amSuuuoqs2zZMrNhwwbz7LPPGofDYT788ENLji0UvP7668blcpmXXnrJfPvtt+aee+4x1atXN7m5ucYYY/r06WMeeugh7/qZmZmmWrVq5rXXXjObNm0yH3/8sWnYsKG5/fbbvesMHz7cLFmyxGzevNksXbrUpKammri4OLNz584KP75Qcrbn4ssvvzTz5s0zGzduNJ999pm59tprTf369c1vv/12xvuEf+VxLvi9KH8HDhwwa9asMWvWrDGSzLhx48yaNWvM1q1brS6tXBFugtzPP/9s9u/fb4wxZs+ePaZnz56matWqJiYmxqSnp3vHwDHGmM2bNxtJZvHixd5lP/74o7n11ltNrVq1TOXKlU3Lli1L3RqO0iZNmmQuuugiExUVZdq3b2++/PJL73NXX3216devn/fxsWPHzKhRo0zDhg2N2+02SUlJ5r777vP5T7xHjx6mdu3aJioqyiQmJpoePXqYDRs2VOARha6zORdLliwxl156qXG5XKZGjRqmT58+fodKONU+UbZAnwt+L8rf4sWLjaRSPyXPlR05jCmj3RwAACAE0ecGAADYCuEGAADYCuEGAADYCuEGAADYCuEGAADYCuEGAADYCuEGAADYCuEGgG117txZQ4cOtboMABWMQfwA2NbevXtVqVIlVatWzepSAFQgwg0AALAVLksBCFmdO3fW/fffr/vvv1+xsbGKi4vTY4895p2NnctSQHgi3AAIaS+//LIiIyO1fPlyTZw4UePGjdOMGTOsLguAhSKtLgAAzkdSUpLGjx8vh8OhSy65ROvWrdP48eM1cOBAq0sDYBFabgCEtI4dO8rhcHgfp6Sk6KefflJRUZGFVQGwEuEGAADYCuEGQEhbtmyZz+Mvv/xSjRs3ltPptKgiAFYj3AAIadu2bVNGRoZ++OEHvfbaa5o0aZKGDBlidVkALESHYgAhrW/fvjpy5Ijat28vp9OpIUOG6J577rG6LAAWItwACGmVKlXShAkTNG3atFLPFRYWqmrVqhZUBcBKXJYCYDuFhYVauXKl1q9fr2bNmlldDoAKRrgBYDsfffSRrr32Wt18883q3r271eUAqGDMLQUAAGyFlhsAAGArhBsAAGArhBsAAGArhBsAAGArhBsAAGArhBsAAGArhBsAAGArhBsAAGArhBsAAGAr/x/4NqTx09VfMwAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "p = sb.boxplot(data=data, x=\"bin_sim\", y=\"pj\") \n", - "p.set(title=\"Cosine Sim by Sequence Similarity\", xlabel=\"pj\")" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "id": "9a67123a-d155-4ce9-9e40-4e1fe0879a30", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/english/py/lib/python3.9/site-packages/seaborn/categorical.py:641: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.\n", - " grouped_vals = vals.groupby(grouper)\n" - ] - }, - { - "data": { - "text/plain": [ - "[Text(0.5, 1.0, 'Cosine Sim by Sequence Similarity (≥90% size sim)'),\n", - " Text(0.5, 0, 'seqsim')]" - ] - }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABVJElEQVR4nO3deVhUZf8G8HsYZAZFQGVfBNx3UFAkMk0xUjOX3BCVEC1zSUHTyAV93cok7ee+pFZi7ra5kJlWIgqKlqalouKSIOKCC4syz+8PX+ZlBBRhmDMc7s91zaXzzJlnvmfOmeGec55zjkIIIUBEREQkEyZSF0BERESkTww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDeVlEKhwPTp06Uu45nc3d3x9ttvG+R13njjjXJ/HZLe9OnToVAo9Nrn22+/DXd3d5228vh8rVu3DgqFApcuXdJrv8VJSEiAmZkZUlJSDPJ6FcGBAwegUChw4MABqUvRUdQ6WFZt27bFxIkT9dqnITHcGIHk5GS8++67qFOnDtRqNSwtLeHv74/PP/8cWVlZUpendydPnkSfPn3g5uYGtVoNZ2dndO7cGYsWLZK6tHKVnp6OsWPHolGjRjA3N4ednR3atGmDSZMm4f79+1KXV6Hdv38fUVFRaNasGapVq4ZatWrBy8sLY8eOxb///it1eeVm6dKlWLduXbn0PXnyZAQFBcHNza3MfT169AgzZsxAnTp1oFKpUKdOHcyaNQuPHz8uNG1OTg4mTZoEJycnmJubw9fXF3v37i003YoVK+Dh4YGaNWti8ODByMzM1Hlco9GgZcuWmDNnTpnrr4wmTZqEJUuWIDU1VepSSkeQpH788Udhbm4urK2txfvvvy9WrlwpFi9eLAYMGCCqVKkihg8fXi6vm5WVJR49elQufT9LXFycMDMzE/Xq1RMzZ84Uq1atEtOmTROvvfaaqFu3rs602dnZIjc3t9xrcnNzE926dSvX18jIyBC1a9cW1tbWIiIiQqxcuVLMnTtXBAUFierVq4uLFy+W6+vLWW5urmjZsqUwNzcXI0aMEMuXLxfz588XoaGhwsbGRuzfv1877aNHj0RWVpbeXz87O1unDYCIiorS6+s8fvxYZGVlCY1Go21r2rSpaN++vV5fRwghjh8/LgCIQ4cO6aW/fv36CYVCIcLCwsSyZctESEiIAFDk99uAAQOEqampmDBhglixYoXw8/MTpqam4vfff9dO8/vvvwuFQiHGjh0rPv/8c+Hg4CDeeecdnX6WL18uPDw8Ci2bssjLyxNZWVkiLy9Pb33qQ1HrYFnl5eUJBwcHMXXqVL32aygMNxK6cOGCsLCwEI0aNRL//vtvocfPnTsnFi5cKEFl5adr167C1tZW3L59u9BjaWlphi9IGCbczJs3TwAQcXFxhR67e/eu3v/gViabN28WAERMTEyhx7KyssTdu3cNXpM+w839+/eLfay8ws37778vateurROkSishIUEAKPRHcvz48UKhUIg//vhD23bkyBEBQHz66afatqysLFG3bl3h5+enbZs0aZJ49dVXtffXrl0rHBwctPdv374tbGxsxLZt28pcf2U2evRo4ebmppf1wNC4W0pC8+bNw/379/HFF1/A0dGx0OP16tXD2LFjtfcfP36MmTNnom7dulCpVHB3d8dHH32EnJwcnecdPXoUgYGBsLGxgbm5OTw8PDB06FCdaZ4eE5A/FuH8+fN4++23YW1tDSsrK4SGhuLhw4eFalu/fj28vb1hbm6OmjVrYsCAAbhy5cpz5zk5ORlNmzaFtbV1ocfs7Ox07j895iZ/zMHBgwfx/vvvw9bWFtbW1nj33XeRm5uLO3fuYMiQIahRowZq1KiBiRMnQrzARe9/+ukneHl5Qa1Wo0mTJti+fbv2sQsXLkChUGDBggWFnnfo0CEoFAp88803z5xvpVKJtm3bFnrM0tISarVap+3IkSN4/fXXYWVlhapVq6J9+/aIi4sr9NyDBw+idevWUKvVqFu3LlasWFFoXMmlS5egUCiK3H1R1NiQa9euYejQobC3t4dKpULTpk2xZs0anWnyxx5s3rwZs2fPhouLC9RqNTp16oTz588Xep0jR46ga9euqFGjBqpVq4YWLVrg888/15nm77//Rp8+fVCzZk2o1Wr4+Pjg+++/L9TX05KTkwEA/v7+hR7L382br6gxNwqFAqNHj8aWLVvQpEkTmJubw8/PDydPngTwZPdHvXr1oFar0aFDh0JjXkoy3iElJQUjR45Ew4YNYW5ujlq1aqFv376F+spfx3/99VeMHDkSdnZ2cHFx0Xks/znu7u7466+/8Ouvv0KhUEChUKBDhw5l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zvOtra0BoFA7EVU+/v7+vOwCEUkfbvr374/09HRMmzYNqamp8PLywp49e7SDjC9fvgwTkwo1NIiIiIgkpBCV7AxXmZmZsLKywt27dyvsLqqsrCwEBgYCAGJjY3ltKSIikr0X+fvNTSJEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkK6ZSF1AZCCGQnZ2tt/4K9qXPfgFArVZDoVDotU8iIiJDYrgxgOzsbAQGBpZL3z169NBrf7GxsTA3N9drn0RERIbE3VJEREQkK9xyYwBqtRqxsbF6608IgZycHACASqXS624ktVqtt76IiIikwHBjAAqFQu+7eqpWrarX/oiIiOSCu6WIiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWGG6IiIhIVhhuiIiISFYYboiIiEhWjCLcLFmyBO7u7lCr1fD19UVCQkKx065atQrt2rVDjRo1UKNGDQQEBDxzeiIiIqpcJA83mzZtQkREBKKiopCUlARPT08EBgbixo0bRU5/4MABBAUFYf/+/YiPj4erqytee+01XLt2zcCVExERkTFSCCGElAX4+vqidevWWLx4MQBAo9HA1dUVY8aMwYcffvjc5+fl5aFGjRpYvHgxhgwZ8tzpMzMzYWVlhbt378LS0rLM9RMREVH5e5G/35JuucnNzcWxY8cQEBCgbTMxMUFAQADi4+NL1MfDhw/x6NEj1KxZs8jHc3JykJmZqXMjIiIi+ZI03Ny8eRN5eXmwt7fXabe3t0dqamqJ+pg0aRKcnJx0AlJBc+fOhZWVlfbm6upa5rqJiIjIeEk+5qYsPv74Y2zc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wDmquF5kWRw4cEA0btxYqFQqUatWLTF48OAiT5XwrD6pePpeFvxclL/9+/cLAIVuBZeVHCmEKGa7OREREVEFxDE3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RERCQrDDdEREQkKww3REREJCsMN0RUaVy6dAkKhQInTpyQuhQiKkc8iR8RVRp5eXlIT0+HjY0NTE1NpS6HiMoJww0RERHJCndLEZHBbd26Fc2bN4e5uTlq1aqFgIAAPHjwAACwevVqNG7cGGq1Go0aNcLSpUt1npuQkICWLVtCrVbDx8cHO3bs0NnVdPv2bQQHB8PW1hbm5uaoX78+1q5dC6DwbqkDBw5AoVAgNjYWLVu2hLm5OTp27IgbN25g9+7daNy4MSwtLTFw4EA8fPjQYO8PEZUNt8sSkUFdv34dQUFBmDdvHnr16oV79+7h999/hxACMTExmDZtGhYvXoyWLVvi+PHjGD58OKpVq4aQkBDcv38fb7zxBjp37oz169fj4sWLGDt2rE7/U6dOxenTp7F7927Y2Njg/PnzyMrKemZN06dPx+LFi1G1alX069cP/fr1g0qlwoYNG3D//n306tULixYtwqRJk8rzrSEiPWG4ISKDun79Oh4/fozevXvDzc0NANC8eXMAQFRUFKKjo9G7d28AgIeHB06fPo0VK1YgJCQEGzZsgEajwRdffAG1Wo2mTZvi6tWreO+997T9X758GS1btoSPjw8AwN3d/bk1zZo1C/7+/gCAsLAwREZGIjk5GXXq1AEA9OnTB/v372e4IaoguFuKiAzK09MTnTp1QvPmzdG3b1+sWrUKt2/fxoMHD5CcnIywsDBYWFhob7NmzUJycjIA4MyZM2jRogXUarW2Pz8/P53+33vvPWzcuBFeXl6YOHEiDh069NyaWrRoof2/vb09qlatqg02+W03btwo66wTkYEw3BCRQSmVSuzduxe7d+9GkyZNsGjRIjRs2BCnTp0CAKxatQonTpzQ3k6dOoXDhw+XuP8uXbogJSUF4eHh+Pfff9GpUydMmDDhmc+pUqWK9v8KhULnfn6bRqN5gbkkIikx3BCRwSkUCvj7+2PGjBk4fvw4zMzMEBcXBycnJ1y4cAH16tXTuXl4eAAAGjdujD///BPZ2dnavooKPra2tggJCcH69euxcOFCrFy50mDzRkTS45gbIjKoI0eOYN++fXjttddgZ2eHI0eOID09HY0bN8aMGTPw/vvvw8rKCq+//jpycnJw9OhR3L59GxERERg4cCAmT56M4cOHIzIyEpcuXcL8+fN1+p82bRq8vb3RtGlT5OTk4Mcff0Tjxo0lmlsikgLDDREZlKWlJX777TcsXLgQmZmZcHNzQ3R0NLp06QIAqFq1Kj799FN88MEHqFatGpo3b45x48YBACwsLPDDDz9gxIgRaNmyJZo0aYJPPvkEb731lrZ/MzMzbfAxNzdHu3btsHHjRilmlYgkwpP4EVGFdunSJXh4eOD48ePw8vKSuhwiMgIcc0NERESywnBDREREssLdUkRERCQr3HJDREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESywnBDREREssJwQ0RERLLCcENERESy8v95fVv+tDN5oQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "p = sb.boxplot(data=data[data['szsim'] >= 0.90], x=\"bin_sim\", y=\"cossim\") \n", - "p.set(title=\"Cosine Sim by Sequence Similarity (≥90% size sim)\", xlabel=\"seqsim\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "5ebf2ba9-b56c-41d6-9779-41b435130a5d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "8" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(list(nx.all_simple_paths(G, 'src', 'D')))" - ] - }, - { - "cell_type": "code", - "execution_count": 114, - "id": "1efe432f-6ff2-4403-a42e-a73bb50ad936", - "metadata": {}, - "outputs": [], - "source": [ - "import networkx as nx\n", - "import heapq\n", - "import random\n", - "\n", - "# Create a directed graph with node sizes\n", - "G = nx.DiGraph()\n", - "G.add_nodes_from([\n", - " ('src', {'size':0}),\n", - " ('A', {'size': 5}),\n", - " ('B', {'size': 10}),\n", - " ('C', {'size': 7}),\n", - " ('D', {'size': 8}),\n", - " ('E', {'size': 3}),\n", - " ('F', {'size': 20}),\n", - " ('G', {'size': -4}),\n", - "])\n", - "\n", - "for i in \"ABCDEFG\":\n", - " G.add_edge('src', i)\n", - " G.add_edge(i, 'snk')\n", - "# Add directed edges\n", - "G.add_edges_from([\n", - " ('A', 'B'),\n", - " ('A', 'C'),\n", - " ('A', 'D'),\n", - " ('A', 'E'),\n", - " ('A', 'F'),\n", - " ('A', 'G'),\n", - " ('B', 'C'),\n", - " ('B', 'D'),\n", - " ('B', 'E'),\n", - " ('B', 'F'),\n", - " ('B', 'G'),\n", - " ('C', 'D'),\n", - " ('C', 'E'),\n", - " ('C', 'F'),\n", - " ('C', 'G'),\n", - " ('D', 'E'),\n", - " ('D', 'F'),\n", - " ('D', 'G'),\n", - " ('E', 'F'),\n", - " ('E', 'G'),\n", - " ('F', 'G'),\n", - "])" - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "id": "af556f07-f3df-4780-877d-0e3d4ce670fa", - "metadata": {}, - "outputs": [], - "source": [ - "def remove_edges_not_passing_through(graph, source, sink, kept_nodes):\n", - " visited = set()\n", - "\n", - " def dfs(node):\n", - " visited.add(node)\n", - " for predecessor in graph.predecessors(node):\n", - " if predecessor not in visited:\n", - " dfs(predecessor)\n", - "\n", - " dfs(sink)\n", - "\n", - " edges_to_remove = [(predecessor, node) for node in visited for predecessor in graph.predecessors(node) if node not in kept_nodes]\n", - " graph.remove_edges_from(edges_to_remove)" - ] - }, - { - "cell_type": "code", - "execution_count": 113, - "id": "d84299da-a980-4d1a-bd12-8d57bbdd4e96", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "nattempts 128\n", - "Path 1: ['src', 'A', 'C', 'D'], Total Size: 20, Size Difference from Target: 0\n", - "Path 2: ['src', 'B', 'C', 'E'], Total Size: 20, Size Difference from Target: 0\n", - "Path 3: ['src', 'F'], Total Size: 20, Size Difference from Target: 0\n" - ] - } - ], - "source": [ - "\n", - "def find_top_n_paths(graph, start_node, target_size, n):\n", - " top_paths = []\n", - " attempts = 0\n", - " threshold = float('inf')\n", - " def dfs(current_path, current_size):\n", - " nonlocal attempts, threshold\n", - " attempts += 1\n", - " sizediff = abs(current_size - target_size)\n", - "\n", - " if sizediff > threshold:\n", - " return\n", - " \n", - " heapq.heappush(top_paths, (sizediff, current_path))\n", - " \n", - " #threshold = top_paths[0][0] + 100000 \n", - " # Without: nattempts 128\n", - " # Path 1: ['src', 'A', 'C', 'D'], Total Size: 20, Size Difference from Target: 0\n", - " # Path 2: ['src', 'B', 'C', 'E'], Total Size: 20, Size Difference from Target: 0\n", - " # Path 3: ['src', 'F'], Total Size: 20, Size Difference from Target: 0\n", - " \n", - " #threshold = sorted(top_paths)[min(3, len(top_paths)-1)][0] + 9 # But 8 fails.\n", - " # With: nattempts 37\n", - " # Path 1: ['src', 'F'], Total Size: 20, Size Difference from Target: 0\n", - " # Path 2: ['src', 'A', 'B', 'C', 'E', 'G'], Total Size: 21, Size Difference from Target: 1\n", - " # Path 3: ['src', 'A', 'B', 'D', 'G'], Total Size: 19, Size Difference from Target: 1\n", - " for successor in graph.successors(current_path[-1]):\n", - " next_path = current_path + [successor]\n", - " next_size = current_size + graph.nodes[successor]['size']\n", - " dfs(next_path, next_size)\n", - "\n", - " dfs([start_node], graph.nodes[start_node]['size'])\n", - " print('nattempts', attempts)\n", - " return sorted(top_paths)[:n]\n", - "\n", - "# Example usage\n", - "start_node = 'src'\n", - "target_size = 20\n", - "n = 3\n", - "top_paths = find_top_n_paths(G, start_node, target_size, n)\n", - "\n", - "for i, (size_diff, path) in enumerate(top_paths, 1):\n", - " total_size = sum(G.nodes[node]['size'] for node in path)\n", - " print(f\"Path {i}: {path}, Total Size: {total_size}, Size Difference from Target: {size_diff}\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 115, - "id": "dd7614a5-6ac6-4722-a276-ec42cc018fec", - "metadata": {}, - "outputs": [], - "source": [ - "remove_edges_not_passing_through(G, 'src', 'snk', list(\"BEF\")) " - ] - }, - { - "cell_type": "code", - "execution_count": 116, - "id": "9c74b2a3-ac91-4e02-944f-ac2f93d83427", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "nattempts 8\n", - "Path 1: ['src', 'F'], Total Size: 20, Size Difference from Target: 0\n", - "Path 2: ['src', 'E', 'F'], Total Size: 23, Size Difference from Target: 3\n", - "Path 3: ['src', 'B', 'E'], Total Size: 13, Size Difference from Target: 7\n" - ] - } - ], - "source": [ - "top_paths = find_top_n_paths(G, start_node, target_size, n)\n", - "\n", - "for i, (size_diff, path) in enumerate(top_paths, 1):\n", - " total_size = sum(G.nodes[node]['size'] for node in path)\n", - " print(f\"Path {i}: {path}, Total Size: {total_size}, Size Difference from Target: {size_diff}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "68afbcec-a30e-42bb-a793-6a39549d20a0", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "a6c14d06-3fc5-4ce0-b632-79d3f8d7e59f", - "metadata": {}, - "outputs": [], - "source": [ - "from dataclasses import dataclass\n", - "\n", - "@dataclass\n", - "class Node():\n", - " value: int = 0\n", - " def __hash__(self):\n", - " return self.value" - ] - }, - { - "cell_type": "code", - "execution_count": 69, - "id": "2f203f27-ead5-47ad-a086-4d9ee774bb34", - "metadata": {}, - "outputs": [], - "source": [ - "aNodes = [Node(1), Node(10), Node(20)]\n", - "import networkx as nx\n", - "g = nx.DiGraph()\n", - "one = Node(1)\n", - "nine = Node(9)\n", - "four = Node(4)\n", - "twenty = Node(20)\n", - "for i in [one, nine, four, twenty]:\n", - " g.add_edge('src', i)\n", - " g.add_edge(i, 'snk')\n", - "g.add_edge(one, nine)\n", - "g.add_edge(one, four)\n", - "g.add_edge(one, twenty)\n", - "g.add_edge(nine, twenty) # can only use one of nine/twenty\n", - "g.add_edge(four, twenty)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "33d47972-0432-407a-b60d-78641f9ea3ba", - "metadata": {}, - "outputs": [], - "source": [ - "import kdp\n", - "import logging\n", - "import pysam\n", - "import truvari\n", - "import networkx as nx\n", - "def vcf_haps(variants, kmer=4):\n", - " \"\"\"\n", - " Parse a set of phased variants and return the two Haplotypes\n", - " \"\"\"\n", - " h1 = []\n", - " h2 = []\n", - " for entry in variants:\n", - " m_hap = kdp.Haplotype.from_vcf(entry, kmer)\n", - " if entry.samples[0]['GT'][0] == 1:\n", - " h1.append(m_hap)\n", - " if len(entry.samples[0]['GT']) > 1 and entry.samples[0]['GT'][1] == 1:\n", - " h2.append(m_hap)\n", - " return h1, h2\n", - "\n", - "def vars_to_graph(variants, kmer=4):\n", - " \"\"\"\n", - " For a sorted set of variants, make a graph\n", - " Returns the digraph (and variants not used?)\n", - " \"\"\"\n", - " keep_vars = []\n", - " unused_vars = []\n", - " for entry in variants:\n", - " hap = kdp.Haplotype.from_vcf(entry, kmer)\n", - " if hap.size != 0:\n", - " keep_vars.append((truvari.entry_to_hash(entry), hap, entry))\n", - " else:\n", - " unused_vars.append(entry)\n", - "\n", - " graph = nx.DiGraph()\n", - " graph.add_node('src', hap=kdp.Haplotype.new(kmer), entry=None)\n", - " graph.add_node('snk', hap=kdp.Haplotype.new(kmer), entry=None)\n", - " for key, hap, entry in keep_vars:\n", - " logging.debug(\"%s %s\", key, str(v))\n", - " graph.add_node(key, hap=hap, entry=entry)\n", - " graph.add_edge('src', key)\n", - " graph.add_edge(key, 'snk')\n", - "\n", - " # link the variants\n", - " for i in range(len(keep_vars) - 1):\n", - " up_key, _, up_var = keep_vars[i]\n", - " up = truvari.entry_boundaries(up_var)\n", - " for j in range(i + 1, len(keep_vars)):\n", - " dn_key, _, dn_var = keep_vars[j]\n", - " dn = truvari.entry_boundaries(dn_var)\n", - " if not truvari.overlaps(up[0], up[1], dn[0], dn[1]):\n", - " graph.add_edge(up_key, dn_key)\n", - " return graph, unused_vars" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "8a755f39-00d9-4d81-b5cb-5c00149cac6b", - "metadata": {}, - "outputs": [], - "source": [ - "chrom, start, end = \"chr20\", 8031130, 8032374\n", - "\n", - "v = pysam.VariantFile(\"test/GRCh38_HG002-T2TQ100-V1.0_stvar.vcf.gz\")\n", - "hap1, hap2 = vcf_haps(v.fetch(chrom, start, end))\n", - "\n", - "v = pysam.VariantFile(\"/Users/english/code/aou_sv_merge/intermerge/hgsvc2/truvari_collapsed.vcf.gz\")\n", - "graph = vars_to_graph(v.fetch(chrom, start, end), 4)[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "4e8243d6-2adc-4a02-8e27-049a84d2f253", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "THRESH = 0.90\n", - "def longest_common_subsequence(graph, path1, path2):\n", - " # Compute the length of the longest common subsequence between two paths\n", - " m, n = len(path1), len(path2)\n", - " dp = [[0] * (n + 1) for _ in range(m + 1)]\n", - "\n", - " for i in range(1, m + 1):\n", - " for j in range(1, n + 1):\n", - " ssim, _ = truvari.sizesim(graph.nodes[path1[i-1]]['hap'].size, path2[j - 1].size)\n", - " csim = kdp.weighted_cosinesim(graph.nodes[path1[i - 1]]['hap'].kfeat, path2[j - 1].kfeat)\n", - " if ssim >= THRESH and csim >= THRESH: # If they're over the similarity minimums\n", - " dp[i][j] = dp[i - 1][j - 1] + csim # Matched, add one to the previous match\n", - " else:\n", - " dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # \n", - "\n", - " ret = dp[m][n]\n", - " #print(ret, '\\t\\t', \"\".join(path1), \"<---\\t--->\", \"\".join(path2))\n", - " return ret, None\n", - "\n", - "def dfs(g, target, cur_node=None, cur_len=0, path=None): \n", - " if not cur_node:\n", - " cur_node = 'src'\n", - " else:\n", - " if path is None:\n", - " path = []\n", - " path.append(cur_node)\n", - " cur_len += g.nodes[cur_node]['hap'].size\n", - " diffs = sorted([(abs(target - (cur_len + g.nodes[n]['hap'].size)), n)\n", - " for _, n in g.out_edges(cur_node)])\n", - " for _, next_node in diffs:\n", - " if next_node == 'snk' and cur_node != 'src':\n", - " yield list(path)\n", - " else:\n", - " n_path = list(path)\n", - " for sub_path in dfs(g, target, next_node, cur_len, n_path):\n", - " yield sub_path" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "483b6c4e-0ecd-47cf-91ab-29530b7bf7bc", - "metadata": {}, - "outputs": [], - "source": [ - "def align_to_graph(graph, haplotype, start_node='src', mincos=0.90, minsize=0.90, wcoslen=2000):\n", - " \"\"\"\n", - " Align a haplotype to a graph\n", - " Return the graph path and haplotype path used.\n", - " \"\"\"\n", - " best_path_hap = []\n", - " best_score = 0\n", - " best_path_graph = []\n", - " n_attempts = 0\n", - " target_len = sum(_.size for _ in haplotype)\n", - " for n_path in dfs(graph, target_len, start_node):\n", - " print(\"trying\", n_path)\n", - " n_attempts += 1\n", - " n_score, best_path_graph = longest_common_subsequence(graph, n_path, haplotype)\n", - " print(n_score, best_path_graph)\n", - " if n_score > best_score:\n", - " best_score = n_score\n", - " best_path_hap = n_path\n", - " elif n_score == best_score and (len(n_path) < len(best_path_hap)): #prefer fewer changes\n", - " best_path_hap = n_path\n", - " if n_attempts >= 100:\n", - " break\n", - " return best_path_graph, best_path_hap" - ] - }, - { - "cell_type": "code", - "execution_count": 122, - "id": "59f22d96-8583-4131-bb6f-2baeba0fce14", - "metadata": {}, - "outputs": [], - "source": [ - "import math\n", - "def genotyper(totCov, altCov, priors=None):\n", - " if totCov == 0:\n", - " return None\n", - "\n", - " # previously had avgCov\n", - " if priors is None:\n", - " priors = [0.05, 0.5, 0.95]\n", - "\n", - " # if len(priors) != 3: # raise exception?\n", - "\n", - " def log_choose(n, k):\n", - " \"\"\" swap for efficiency if k is more than half of n \"\"\"\n", - " r = 0.0\n", - " if k * 2 > n:\n", - " k = n - k\n", - "\n", - " for d in range(1, k + 1):\n", - " r += math.log(n, 10)\n", - " r -= math.log(d, 10)\n", - " n -= 1\n", - "\n", - " return r\n", - "\n", - " total = totCov # refCoverage + altCoverage if avgCov is None else avgCov\n", - " alt = altCov # int(spot.tags[\"szCount\"])\n", - " non_alt = total - alt\n", - "\n", - " gtList = []\n", - "\n", - " comb = log_choose(total, alt)\n", - " for p_alt in priors:\n", - " gtList.append(comb + alt * math.log(p_alt, 10) + non_alt * math.log(1 - p_alt, 10))\n", - "\n", - " return gtList" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfb4fea5-8287-404d-a6d9-111b45b968b3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 194, - "id": "df09471c-b2b5-4cd3-9846-b9521628f7ea", - "metadata": {}, - "outputs": [], - "source": [ - "def log_choose(n, k):\n", - " r = 0.0\n", - " # swap for efficiency if k is more than half of n\n", - " if k * 2 > n:\n", - " k = n - k\n", - "\n", - " for d in range(1,k+1):\n", - " r += math.log(n, 10)\n", - " r -= math.log(d, 10)\n", - " n -= 1\n", - "\n", - " return r\n", - "\n", - "def bayes_gt(ref, alt, is_dup):\n", - " # probability of seeing an alt read with true genotype of of hom_ref, het, hom_alt respectively\n", - " if is_dup: # specialized logic to handle non-destructive events such as duplications\n", - " p_alt = [1e-2, 0.2, 1/3.0]\n", - " else:\n", - " #p_alt = [1e-3, 0.5, 0.9]\n", - " p_alt = [0.05, 0.5, 0.95]\n", - "\n", - " total = ref + alt\n", - " log_combo = log_choose(total, alt)\n", - "\n", - " lp_homref = log_combo + alt * math.log(p_alt[0], 10) + ref * math.log(1 - p_alt[0], 10)\n", - " lp_het = log_combo + alt * math.log(p_alt[1], 10) + ref * math.log(1 - p_alt[1], 10)\n", - " lp_homalt = log_combo + alt * math.log(p_alt[2], 10) + ref * math.log(1 - p_alt[2], 10)\n", - "\n", - " return (lp_homref, lp_het, lp_homalt)\n", - "\n", - "def do_math(gt_lplist):\n", - " print(gt_lplist)\n", - " best, second_best = sorted([ (i, e) for i, e in enumerate(gt_lplist) ], key=(lambda x: x[1]), reverse=True)[0:2]\n", - "\n", - " gt_sum = 0\n", - " for gt in gt_lplist:\n", - " try:\n", - " gt_sum += 10**gt\n", - " except OverflowError:\n", - " print('no?')\n", - " gt_sum += 0\n", - "\n", - " gt_sum_log = math.log(gt_sum, 10)\n", - " # SQ - higher better?\n", - " sample_qual = min(abs(-10 * (gt_lplist[0] - gt_sum_log)),100) # phred-scaled probability site is non-reference in this sample\n", - " # GQ - higher better?\n", - " phred_gq = min(-10 * (second_best[1] - best[1]), 100)\n", - " return sample_qual, phred_gq" - ] - }, - { - "cell_type": "code", - "execution_count": 195, - "id": "7b44b09d-5fa4-4242-86e0-d92468bfbc51", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(-11.235308810520248, -1.7928160124259056, -1.0052800028976154)\n" - ] - }, - { - "data": { - "text/plain": [ - "(100, 7.875360095282902)" - ] - }, - "execution_count": 195, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "do_math(bayes_gt(2, 10, False))" - ] - }, - { - "cell_type": "code", - "execution_count": 138, - "id": "d9a2f480-0e53-4880-a8f7-c20f388e5515", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[-4.527426373031058, -0.806179973983887, -0.6911655701725709]\n" - ] - }, - { - "data": { - "text/plain": [ - "[3.2370816095851325, 0.06089080720808001, 0.042719299791641144]" - ] - }, - "execution_count": 138, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x = genotyper(5, 4)\n", - "print(x)\n", - "[10 ** (math.log(abs(i)))/10 for i in x]" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "316cae30-dd3e-48b1-a734-a9e0bffd7e46", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "src None\n", - "snk None\n", - "1ab39dff06fc5af6366551bc222faf4d1142fb99 chr20\t8031512\tpbsv.INS.183\tT\tTATGTATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATG\t3\tPASS\tSVTYPE=INS;SVLEN=192;SVANN=TANDEM;AC=1\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:6,3:9:3,3,2,1:1\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "9a4086d2c45a87751cebde7061f0c9705d6d8425 chr20\t8031517\tSniffles2.INS.50S13\tA\tATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATG\t2\tPASS\tPRECISE;SVTYPE=INS;SVLEN=95;SUPPORT=3;COVERAGE=8,9,9,9,9;STRAND=+-;AF=0.333;STDEV_LEN=0;STDEV_POS=0;SUPPORT_LONG=0;AC=1\tGT:GQ:DR:DV:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:13:6:3:2\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "30dbc4c3ecb41a50196a8dd592a77dbf6bef1aeb chr20\t8031531\tchr20-8031532-INS-124\tG\tGATGTATGTATGATATGTATGTATGTATGATGTATGTATTATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGATGT\t4\t.\tID=chr20-8031532-INS-124;SVTYPE=INS;SVLEN=124;TIG_REGION=h2tg006170l:2843-2966;QUERY_STRAND=-;HOM_REF=0,11;HOM_TIG=0,11;AC=1\tGT:SUPP\t./.:.\t./.:.\t0/1:4\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\n", - "\n", - "0cacd6858efe76ccf815b689378558bc98226ca6 chr20\t8031561\tpbsv.INS.170;chr20-8031562-INS-53\tG\tGATGTATGTATGATGTATGTATGTATGATGTATGTATGTATGTATGTATGATGT\t4\tPASS\tSVTYPE=INS;SVLEN=53;SVANN=TANDEM;ID=chr20-8031562-INS-53;TIG_REGION=h1tg004137l:95190-95242;QUERY_STRAND=-;HOM_REF=0,22;HOM_TIG=0,22;AC=1\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t0/1:4,6:10:1,3,4,2:5\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "aef483aba32454fdae99e87dc6b14cc375ddb362 chr20\t8031562\tSniffles2.INS.31S13\tA\tTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGATGTATGTATGTTGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGT\t2\tPASS\tIMPRECISE;SVTYPE=INS;SVLEN=95;SUPPORT=9;COVERAGE=10,10,10,10,11;STRAND=+-;AF=0.9;STDEV_LEN=30.162;STDEV_POS=43.894;SUPPORT_LONG=0;AC=2\tGT:GQ:DR:DV:SUPP\t./.:.:.:.:.\t1/1:15:1:9:2\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "4be2ce31efa8a1c8cf23c4e5e0b1d2aecb7c5510 chr20\t8031594\tpbsv.INS.238\tG\tGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGT\t3\tPASS\tSVTYPE=INS;SVLEN=142;SVANN=TANDEM;AC=1\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:4,4:8:2,2,2,2:1\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "3003b4b88dfa7ff1b8605d4d5439f6fe40092534 chr20\t8031594\tchr20-8031595-INS-61\tG\tGTATGTATGTATGATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGC\t4\t.\tID=chr20-8031595-INS-61;SVTYPE=INS;SVLEN=61;TIG_REGION=h1tg009636l:15255-15315,h2tg003757l:470716-470776;QUERY_STRAND=+,+;HOM_REF=0,0;HOM_TIG=0,0;NumCollapsed=2;NumConsolidated=2;CollapseId=27074.0;AC=1\tGT:AD:DP:DR:DV:GQ:SAC:SUPP\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t0/1:2,5:7:2:5:6:1,1,1,4:7\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\n", - "\n", - "9b630a836027cac054e863df7c97f0f9fea19f9e chr20\t8031631\tchr20-8031632-INS-86\tG\tGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGT\t4\t.\tID=chr20-8031632-INS-86;SVTYPE=INS;SVLEN=86;TIG_REGION=h2tg021944l:13667-13752;QUERY_STRAND=+;HOM_REF=0,0;HOM_TIG=0,0;NumCollapsed=1;NumConsolidated=1;CollapseId=28855.0;AC=2\tGT:SUPP:DR:DV:GQ\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:4:.:.:.\t./.:.:.:.:.\t0/1:6:8:9:54\t./.:.:.:.:.\n", - "\n", - "a6cae606c444f276efe66c46b4757d633a0b23db chr20\t8031631\tchr20-8031632-INS-132\tG\tGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGTATGT\t4\t.\tID=chr20-8031632-INS-132;SVTYPE=INS;SVLEN=132;TIG_REGION=h2tg006654l:15345-15476;QUERY_STRAND=-;HOM_REF=0,0;HOM_TIG=0,0;AC=1\tGT:SUPP\t0/1:4\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\t./.:.\n", - "\n", - "f2a285fa1cfc415fdaffbda752a0d1d90fb762c7 chr20\t8031632\tSniffles2.INS.55S13\tA\tTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGT\t2\tPASS\tPRECISE;SVTYPE=INS;SVLEN=104;SUPPORT=4;COVERAGE=8,8,8,8,8;STRAND=+-;AF=0.5;STDEV_LEN=0;STDEV_POS=0;SUPPORT_LONG=0;AC=1\tGT:GQ:DR:DV:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:28:4:4:2\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "eb5ba19503f7d198a58b21b9538778e25d88840c chr20\t8031646\tchr20-8031647-INS-143\tG\tGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGT\t4\t.\tID=chr20-8031647-INS-143;SVTYPE=INS;SVLEN=143;TIG_REGION=h2tg003866l:178061-178203;QUERY_STRAND=+;HOM_REF=0,0;HOM_TIG=0,0;NumCollapsed=1;NumConsolidated=1;CollapseId=83766.6;AC=1\tGT:DR:DV:GQ:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:4:8:17:2\t./.:.:.:.:.\t0/1:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "dcedc19956e539c5bbad3582ce6a1480c565cda2 chr20\t8031646\tchr20-8031647-INS-170\tG\tGTATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGT\t4\t.\tID=chr20-8031647-INS-170;SVTYPE=INS;SVLEN=170;TIG_REGION=h1tg002840l:315234-315403;QUERY_STRAND=+;HOM_REF=0,0;HOM_TIG=0,0;NumCollapsed=2;NumConsolidated=2;CollapseId=26784.1;AC=2\tGT:AD:DP:DR:DV:GQ:SAC:SUPP\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t1/1:0,3:3:1:2:4:0,0,0,3:7\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\t./.:.:.:.:.:.:.:.\n", - "\n", - "79667743877d64eb4ac1746e78ffe8deede061de chr20\t8031677\tSniffles2.INS.48S13\tA\tTATGTATGCGTGTATGACGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTGTGTATGCATGTATGATGT\t2\tPASS\tPRECISE;SVTYPE=INS;SVLEN=76;SUPPORT=5;COVERAGE=10,11,11,12,12;STRAND=+;AF=0.455;STDEV_LEN=2.309;STDEV_POS=0;SUPPORT_LONG=0;AC=1\tGT:GQ:DR:DV:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:33:6:5:2\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "efba80a423c9a50fb92f5f47f2ca6d8a7ce8b2dd chr20\t8031689\tpbsv.INS.207\tA\tATGTATGATGTGTGTATGCATGTATGATGTCTGTATGTATGTGTGTATGATGTATGTATGTATGTG\t3\tPASS\tSVTYPE=INS;SVLEN=65;SVANN=TANDEM;AC=2\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t1/1:0,3:3:0,0,0,3:1\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "94aeca41ecea467f214c43fe57a50425c6e85a62 chr20\t8031712\tchr20-8031713-INS-61\tA\tATGATGTGTGTATGCATGTATGATGTCTGTATGTATGTGTGTATGATGTATGTATGTATGTG\t4\t.\tID=chr20-8031713-INS-61;SVTYPE=INS;SVLEN=61;TIG_REGION=h1tg009304l:96829-96889;QUERY_STRAND=-;HOM_REF=0,2;HOM_TIG=0,2;NumCollapsed=1;NumConsolidated=1;CollapseId=29397.0;AC=3\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t1/1:0,11:11:0,0,5,6:5\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:8,5:13:3,5,2,3:5\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "716c0317c70673f9f7edfadc04bded59891880dc chr20\t8031731\tpbsv.INS.174;pbsv.INS.182\tA\tATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTATGTATGTATGTATGATGTATGTATGTATGTATGATGTATGTGTGTATGTATGATGTATGTG\t3\tPASS\tSVTYPE=INS;SVLEN=182;SVANN=TANDEM;NumCollapsed=1;NumConsolidated=3;CollapseId=83766.11;AC=2\tGT:AD:DP:SAC:DR:DV:GQ:SUPP\t0/1:.,.:.:.:.:.:.:.\t0/1:6,4:10:4,2,1,3:.:.:.:1\t./.:.,.:.:.:.:.:.:.\t0/1:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t./.:.,.:.:.:.:.:.:.\t0/1:6,7:13:3,3,2,5:.:.:.:1\t./.:.,.:.:.:.:.:.:.\t1/1:0,17:17:0,0,9,8:.:.:.:1\t./.:.,.:.:.:.:.:.:.\n", - "\n" - ] - } - ], - "source": [ - "for k,n in graph.nodes.items():\n", - " print(k, n['entry'])" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "c9c18464-8fb5-4243-8d24-1fb480fe6d23", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Haplotype(kfeat=array([-1., 6., 0., 0., 11., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 10., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 5.,\n", - " 5., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32), size=37, n=1, coverage=1),\n", - " Haplotype(kfeat=array([-1., 20., 0., 0., 25., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 25., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 20.,\n", - " 5., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32), size=95, n=1, coverage=1),\n", - " Haplotype(kfeat=array([-1., 4., 0., 0., 8., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 7., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 4.,\n", - " 3., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32), size=26, n=1, coverage=1)]" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hap2" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "24bf0450-83f2-43d8-aa3f-7daa17ea4dd7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "None\n", - "chr20\t8031594\tpbsv.INS.238\tG\tGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGT\t3\tPASS\tSVTYPE=INS;SVLEN=142;SVANN=TANDEM;AC=1\tGT:AD:DP:SAC:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:4,4:8:2,2,2,2:1\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n", - "chr20\t8031631\tchr20-8031632-INS-86\tG\tGTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGT\t4\t.\tID=chr20-8031632-INS-86;SVTYPE=INS;SVLEN=86;TIG_REGION=h2tg021944l:13667-13752;QUERY_STRAND=+;HOM_REF=0,0;HOM_TIG=0,0;NumCollapsed=1;NumConsolidated=1;CollapseId=28855.0;AC=2\tGT:SUPP:DR:DV:GQ\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:4:.:.:.\t./.:.:.:.:.\t0/1:6:8:9:54\t./.:.:.:.:.\n", - "\n", - "chr20\t8031632\tSniffles2.INS.55S13\tA\tTATGATGTATGTATGATGTATGTATGATGTATGATGTATGTATGTATGTATGATGTATGTATGTATGATGTATGTGTGTATGATGTATGTATGTATGTATGATGT\t2\tPASS\tPRECISE;SVTYPE=INS;SVLEN=104;SUPPORT=4;COVERAGE=8,8,8,8,8;STRAND=+-;AF=0.5;STDEV_LEN=0;STDEV_POS=0;SUPPORT_LONG=0;AC=1\tGT:GQ:DR:DV:SUPP\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t0/1:28:4:4:2\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\t./.:.:.:.:.\n", - "\n" - ] - } - ], - "source": [ - "for i in result[1]:\n", - " print(graph.nodes[i]['entry'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9392b520-094f-4e54-afa9-822390546366", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "THRESH = 0.90\n", - "def longest_common_subsequence(path1, path2):\n", - " # Compute the length of the longest common subsequence between two paths\n", - " m, n = len(path1), len(path2)\n", - " dp = [[0] * (n + 1) for _ in range(m + 1)]\n", - "\n", - " for i in range(1, m + 1):\n", - " for j in range(1, n + 1):\n", - " if path1[i - 1] == path2[j - 1]: # If they're over the similarity minimums\n", - " dp[i][j] = dp[i - 1][j - 1] + 1 # Matched, add one to the previous match\n", - " else:\n", - " dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # \n", - "\n", - " ret = dp[m][n]\n", - " #print(ret, '\\t\\t', \"\".join(path1), \"<---\\t--->\", \"\".join(path2))\n", - " return ret\n", - "\n", - "def dfs(g, target, cur_len=0, cur_node=None, path=None): \n", - " if not cur_node:\n", - " cur_node = 'src'\n", - " else:\n", - " if path is None:\n", - " path = []\n", - " path.append(cur_node)\n", - " cur_len += g.nodes[cur_node]['hap'].size\n", - " diffs = sorted([(abs(target - (cur_len + g.nodes[n]['hap'].size)), n)\n", - " for _, n in g.out_edges(cur_node)])\n", - " for next_node in diffs:\n", - " if next_node == 'snk' and cur_node != 'src':\n", - " yield list(path)\n", - " else:\n", - " n_path = list(path)\n", - " for sub_path in dfs(g, next_node, n_path):\n", - " yield sub_path" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "240a6589-c0aa-48b7-8c0c-e587ef12c295", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trying ['B', 'D', 'E', 'F', 'G']\n", - "trying ['B', 'D', 'E', 'F']\n", - "trying ['B', 'D', 'E', 'G']\n", - "trying ['B', 'D', 'E']\n", - "trying ['B', 'D', 'F', 'G']\n", - "trying ['B', 'D', 'F']\n", - "trying ['B', 'D', 'G']\n", - "trying ['B', 'D']\n", - "trying ['B', 'E', 'F', 'G']\n", - "trying ['B', 'E', 'F']\n", - "trying ['B', 'E', 'G']\n", - "trying ['B', 'E']\n", - "trying ['B', 'F', 'G']\n", - "trying ['B', 'F']\n", - "trying ['B', 'G']\n", - "trying ['B']\n", - "['B', 'D', 'G'] 16\n" - ] - } - ], - "source": [ - "# Example usage:\n", - "import itertools\n", - "graph = {\n", - " 'src': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'snk'],\n", - " 'A': ['B', 'C', 'D', 'E', 'F', 'G', 'snk'],\n", - " # B/C overlapping\n", - " 'B': ['D', 'E', 'F', 'G', 'snk'],\n", - " 'C': ['D', 'E', 'F', 'G', 'snk'],\n", - " 'D': ['E', 'F', 'G', 'snk'],\n", - " 'E': ['F', 'G', 'snk'],\n", - " 'F': ['G', 'snk'],\n", - " 'G': ['snk'],\n", - " 'snk': [],\n", - "}\n", - "\n", - "start_node = 'src'\n", - "target_path = ['Do', 'lamma', 'B', 'R', 'D', 'X', 'Y', 'G'] # Example target path with extra nodes\n", - "\n", - "graph_nodes = list(graph.keys())\n", - "t_idx = 0\n", - "g_idx = 0\n", - "\n", - "# Find the first graph node that matches to the target path\n", - "# If no graph node matches t_idx[0], then increase t_idx\n", - "# Pick the starting point\n", - "for t_start, start_node in itertools.product(target_path, graph_nodes[1:-1]):\n", - " if t_start == start_node:\n", - " break\n", - "target_path = target_path[target_path.index(t_start):] \n", - "\n", - "# Iterate all the paths, trying to find the best.\n", - "best_path = []\n", - "best_score = 0\n", - "n_attempts = 0\n", - "for n_path in dfs(graph, start_node):\n", - " print(\"trying\", n_path)\n", - " n_attempts += 1\n", - " n_score = longest_common_subsequence(n_path, target_path)\n", - " if n_score > best_score:\n", - " best_score = n_score\n", - " best_path = n_path\n", - " elif n_score == best_score and (len(n_path) < len(best_path)): #prefer fewer changes\n", - " best_path = n_path\n", - " \n", - "print(best_path, n_attempts)\n", - "\n", - "# So now I need to figure out what happens when we don't have an anchor?\n", - "# Like, If \n" - ] - }, - { - "cell_type": "code", - "execution_count": 143, - "id": "7082da1b-4dea-4e42-a9fa-c62504abbfe1", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['D'] ['B', 'R', 'D', 'X', 'Y', 'G'] 1\n", - "['E'] ['B', 'R', 'D', 'X', 'Y', 'G'] 0\n", - "['F'] ['B', 'R', 'D', 'X', 'Y', 'G'] 0\n", - "['G'] ['B', 'R', 'D', 'X', 'Y', 'G'] 1\n", - "['E'] ['B', 'R', 'D', 'X', 'Y', 'G'] 0\n", - "['F'] ['B', 'R', 'D', 'X', 'Y', 'G'] 0\n", - "['G'] ['B', 'R', 'D', 'X', 'Y', 'G'] 1\n", - "Most similar path: []\n" - ] - } - ], - "source": [ - "# Example usage:\n", - "# Try again, but this time have sizes\n", - "# Also, move this to a DiGraph\n", - "import itertools\n", - "import networkx as nx\n", - "\n", - "g = nx.DiGraph()\n", - "g.add_edge('src', 'snk')\n", - "variants = ['A', 'B', 'C', 'D', 'E', 'F', 'G']\n", - "sizes = [40, 100, 125, \n", - "for i in \n", - "for i in range(len(variants) - 1):\n", - " for j in range(i + 1, len(variants)):\n", - " g.add_node\n", - "for i in \n", - "graph = {\n", - " 'src': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'snk'],\n", - " 'A': ['B', 'C', 'D', 'E', 'F', 'G', 'snk'],\n", - " # B/C overlapping\n", - " 'B': ['D', 'E', 'F', 'G', 'snk'],\n", - " 'C': ['D', 'E', 'F', 'G', 'snk'],\n", - " 'D': ['E', 'F', 'G', 'snk'],\n", - " 'E': ['F', 'G', 'snk'],\n", - " 'F': ['G', 'snk'],\n", - " 'G': ['snk'],\n", - " 'snk': [],\n", - "}\n", - "\n", - "start_node = 'src'\n", - "target_path = ['D', 'lamma', 'B', 'R', 'D', 'X', 'Y', 'G'] # Example target path with extra nodes\n", - "\n", - "graph_nodes = list(graph.keys())\n", - "t_idx = 0\n", - "g_idx = 0\n", - "\n", - "# Find the first graph node that matches to the target path\n", - "# If no graph node matches t_idx[0], then increase t_idx\n", - "# Pick the starting point\n", - "for t_start, start_node in itertools.product(target_path, graph_nodes[1:-1]):\n", - " if t_start == start_node:\n", - " break\n", - "target_path = target_path[target_path.index(t_start):] \n", - "\n", - "# Iterate all the paths, trying to find the best.\n", - "best_path = []\n", - "best_score = 0\n", - "n_attempts = 0\n", - "for n_path in dfs(graph, start_node):\n", - " print(\"trying\", n_path)\n", - " n_attempts += 1\n", - " n_score = longest_common_subsequence(n_path, target_path)\n", - " if n_score > best_score:\n", - " best_score = n_score\n", - " best_path = n_path\n", - " elif n_score == best_score and (len(n_path) < len(best_path)): #prefer fewer changes\n", - " best_path = n_path\n", - " \n", - "print(best_path, n_attempts)\n", - "\n", - "# So now I need to figure out what happens when we don't have an anchor?\n", - "# Like, If \n" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "id": "8876c26d-bd94-42ed-9088-aa2ffa2fffd2", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 118, - "id": "6acb6272-1eea-4ebb-b56d-a9ed43d62dd8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3.7390368499084894" - ] - }, - "execution_count": 118, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "10**((5.727597453449266)/10)" - ] - }, - { - "cell_type": "code", - "execution_count": 187, - "id": "2a4ed4eb-b9e3-4a92-85b5-3e2dc7f22d96", - "metadata": {}, - "outputs": [], - "source": [ - "import pysam\n", - "bam = pysam.AlignmentFile(\"/Users/english/code/kfdphase/kdprs/test/GIABHG002.bam\")\n", - "aln = next(bam)" - ] - }, - { - "cell_type": "code", - "execution_count": 197, - "id": "badf2789-718b-4076-b744-c43e610cb7cf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['__class__',\n", - " '__delattr__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__enter__',\n", - " '__eq__',\n", - " '__exit__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__iter__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__next__',\n", - " '__pyx_vtable__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__setstate__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " '_open',\n", - " 'add_hts_options',\n", - " 'category',\n", - " 'check_index',\n", - " 'check_truncation',\n", - " 'close',\n", - " 'closed',\n", - " 'compression',\n", - " 'count',\n", - " 'count_coverage',\n", - " 'description',\n", - " 'duplicate_filehandle',\n", - " 'fetch',\n", - " 'filename',\n", - " 'find_introns',\n", - " 'find_introns_slow',\n", - " 'format',\n", - " 'get_index_statistics',\n", - " 'get_reference_length',\n", - " 'get_reference_name',\n", - " 'get_tid',\n", - " 'getrname',\n", - " 'gettid',\n", - " 'has_index',\n", - " 'head',\n", - " 'header',\n", - " 'index_filename',\n", - " 'is_bam',\n", - " 'is_bcf',\n", - " 'is_closed',\n", - " 'is_cram',\n", - " 'is_open',\n", - " 'is_read',\n", - " 'is_remote',\n", - " 'is_sam',\n", - " 'is_stream',\n", - " 'is_valid_reference_name',\n", - " 'is_valid_tid',\n", - " 'is_vcf',\n", - " 'is_write',\n", - " 'lengths',\n", - " 'mapped',\n", - " 'mate',\n", - " 'mode',\n", - " 'nocoordinate',\n", - " 'nreferences',\n", - " 'parse_region',\n", - " 'pileup',\n", - " 'reference_filename',\n", - " 'references',\n", - " 'reset',\n", - " 'seek',\n", - " 'tell',\n", - " 'text',\n", - " 'threads',\n", - " 'unmapped',\n", - " 'version',\n", - " 'write']" - ] - }, - "execution_count": 197, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dir(bam)" - ] - }, - { - "cell_type": "code", - "execution_count": 227, - "id": "a842e7bf-c2db-4539-8a44-c20866d54034", - "metadata": {}, - "outputs": [], - "source": [ - "x = pysam.AlignedSegment(bam.header)" - ] - }, - { - "cell_type": "code", - "execution_count": 211, - "id": "1fd1dcee-9902-4232-832e-09d2551ac09c", - "metadata": {}, - "outputs": [], - "source": [ - "x.tid = 0" - ] - }, - { - "cell_type": "code", - "execution_count": 223, - "id": "a392f415-66bf-44c1-9b53-058dc8da3a51", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['__class__',\n", - " '__copy__',\n", - " '__deepcopy__',\n", - " '__delattr__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__eq__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__pyx_vtable__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__setstate__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " 'aend',\n", - " 'alen',\n", - " 'aligned_pairs',\n", - " 'bin',\n", - " 'blocks',\n", - " 'cigar',\n", - " 'cigarstring',\n", - " 'cigartuples',\n", - " 'compare',\n", - " 'flag',\n", - " 'from_dict',\n", - " 'fromstring',\n", - " 'get_aligned_pairs',\n", - " 'get_blocks',\n", - " 'get_cigar_stats',\n", - " 'get_forward_qualities',\n", - " 'get_forward_sequence',\n", - " 'get_overlap',\n", - " 'get_reference_positions',\n", - " 'get_reference_sequence',\n", - " 'get_tag',\n", - " 'get_tags',\n", - " 'has_tag',\n", - " 'header',\n", - " 'infer_query_length',\n", - " 'infer_read_length',\n", - " 'inferred_length',\n", - " 'is_duplicate',\n", - " 'is_forward',\n", - " 'is_mapped',\n", - " 'is_paired',\n", - " 'is_proper_pair',\n", - " 'is_qcfail',\n", - " 'is_read1',\n", - " 'is_read2',\n", - " 'is_reverse',\n", - " 'is_secondary',\n", - " 'is_supplementary',\n", - " 'is_unmapped',\n", - " 'isize',\n", - " 'mapping_quality',\n", - " 'mapq',\n", - " 'mate_is_forward',\n", - " 'mate_is_mapped',\n", - " 'mate_is_reverse',\n", - " 'mate_is_unmapped',\n", - " 'modified_bases',\n", - " 'modified_bases_forward',\n", - " 'mpos',\n", - " 'mrnm',\n", - " 'next_reference_id',\n", - " 'next_reference_name',\n", - " 'next_reference_start',\n", - " 'opt',\n", - " 'overlap',\n", - " 'pnext',\n", - " 'pos',\n", - " 'positions',\n", - " 'qend',\n", - " 'qlen',\n", - " 'qname',\n", - " 'qqual',\n", - " 'qstart',\n", - " 'qual',\n", - " 'query',\n", - " 'query_alignment_end',\n", - " 'query_alignment_length',\n", - " 'query_alignment_qualities',\n", - " 'query_alignment_sequence',\n", - " 'query_alignment_start',\n", - " 'query_length',\n", - " 'query_name',\n", - " 'query_qualities',\n", - " 'query_sequence',\n", - " 'reference_end',\n", - " 'reference_id',\n", - " 'reference_length',\n", - " 'reference_name',\n", - " 'reference_start',\n", - " 'rlen',\n", - " 'rname',\n", - " 'rnext',\n", - " 'seq',\n", - " 'setTag',\n", - " 'set_tag',\n", - " 'set_tags',\n", - " 'tags',\n", - " 'template_length',\n", - " 'tid',\n", - " 'tlen',\n", - " 'to_dict',\n", - " 'to_string',\n", - " 'tostring']" - ] - }, - "execution_count": 223, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pysam.Align" - ] - }, - { - "cell_type": "code", - "execution_count": 267, - "id": "14d55b1d-ac5b-47ac-b3d5-c1194303924a", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "sequence item 3: expected str instance, int found", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn [267], line 52\u001b[0m\n\u001b[1;32m 50\u001b[0m input_bam_file \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/Users/english/code/kfdphase/kdprs/test/GIABHG002.bam\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 51\u001b[0m output_bam_file \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124moutput.sam\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 52\u001b[0m \u001b[43msplit_alignments\u001b[49m\u001b[43m(\u001b[49m\u001b[43minput_bam_file\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moutput_bam_file\u001b[49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[0;32mIn [267], line 45\u001b[0m, in \u001b[0;36msplit_alignments\u001b[0;34m(input_bam_path, output_bam_path, max_length)\u001b[0m\n\u001b[1;32m 42\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m pysam\u001b[38;5;241m.\u001b[39mAlignmentFile(input_bam_path, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m input_bam, \\\n\u001b[1;32m 43\u001b[0m pysam\u001b[38;5;241m.\u001b[39mAlignmentFile(output_bam_path, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m\"\u001b[39m, header\u001b[38;5;241m=\u001b[39minput_bam\u001b[38;5;241m.\u001b[39mheader) \u001b[38;5;28;01mas\u001b[39;00m output_bam:\n\u001b[1;32m 44\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m alignment \u001b[38;5;129;01min\u001b[39;00m input_bam:\n\u001b[0;32m---> 45\u001b[0m sub_alignments \u001b[38;5;241m=\u001b[39m \u001b[43msplit_alignment\u001b[49m\u001b[43m(\u001b[49m\u001b[43malignment\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmax_length\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minput_bam\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheader\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 46\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m sub_align \u001b[38;5;129;01min\u001b[39;00m sub_alignments:\n\u001b[1;32m 47\u001b[0m output_bam\u001b[38;5;241m.\u001b[39mwrite(sub_align)\n", - "Cell \u001b[0;32mIn [267], line 26\u001b[0m, in \u001b[0;36msplit_alignment\u001b[0;34m(alignment, max_length, header)\u001b[0m\n\u001b[1;32m 23\u001b[0m cur_align[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mseq\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m curd[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mseq\u001b[39m\u001b[38;5;124m'\u001b[39m][start:start\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m50\u001b[39m]\n\u001b[1;32m 24\u001b[0m cur_align[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcurd[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m_\u001b[39m\u001b[38;5;132;01m{\u001b[39;00midx\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 26\u001b[0m sub_align \u001b[38;5;241m=\u001b[39m \u001b[43mpysam\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mAlignedSegment\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_dict\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcur_align\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mheader\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mheader\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 27\u001b[0m sub_align\u001b[38;5;241m.\u001b[39mfrom_dict(cur_align)\n\u001b[1;32m 29\u001b[0m \u001b[38;5;66;03m#sub_align = pysam.AlignedSegment(header=header, \u001b[39;00m\n\u001b[1;32m 30\u001b[0m \u001b[38;5;66;03m# reference_name=alignment.reference_name,\u001b[39;00m\n\u001b[1;32m 31\u001b[0m \u001b[38;5;66;03m# pos=start)\u001b[39;00m\n", - "File \u001b[0;32m~/py/lib/python3.9/site-packages/pysam/libcalignedsegment.pyx:1142\u001b[0m, in \u001b[0;36mpysam.libcalignedsegment.AlignedSegment.from_dict\u001b[0;34m()\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: sequence item 3: expected str instance, int found" - ] - } - ], - "source": [ - "import pysam\n", - "import copy\n", - "def split_alignment(alignment, max_length, header):\n", - " \"\"\"\n", - " Split a single alignment into multiple alignments with a maximum length.\n", - " \"\"\"\n", - " if alignment.query_alignment_length <= max_length:\n", - " return [alignment]\n", - " \n", - " curd = alignment.to_dict()\n", - " sub_alignments = []\n", - " num_splits = (alignment.query_alignment_length + max_length - 1) // max_length\n", - " for idx,i in enumerate(range(num_splits)):\n", - " start = i * max_length\n", - " end = min(start + max_length, alignment.query_alignment_length)\n", - "\n", - " # dict_keys(['name', 'flag', 'ref_name', 'ref_pos', 'map_quality', 'cigar', 'next_ref_name', \n", - " # 'next_ref_pos', 'length', 'seq', 'qual', 'tags'])\n", - "\n", - " cur_align = copy.copy(curd)\n", - " cur_align['ref_pos'] = start\n", - " cur_align['cigar'] = None\n", - " cur_align['seq'] = curd['seq'][start:start+50]\n", - " cur_align['name'] = f\"{curd['name']}_{idx}\"\n", - "\n", - " sub_align = pysam.AlignedSegment.from_dict(cur_align, header=header)\n", - " sub_align.from_dict(cur_align)\n", - "\n", - " #sub_align = pysam.AlignedSegment(header=header, \n", - " # reference_name=alignment.reference_name,\n", - " # pos=start)\n", - " if alignment.has_tag(\"MD\"):\n", - " sub_align.set_tag(\"MD\", alignment.get_tag(\"MD\")[start:end])\n", - " print(sub_align)\n", - " sub_alignments.append(sub_align)\n", - " return sub_alignments\n", - "\n", - "def split_alignments(input_bam_path, output_bam_path, max_length=50000):\n", - " \"\"\"\n", - " Split alignments longer than max_length into multiple alignments.\n", - " \"\"\"\n", - " with pysam.AlignmentFile(input_bam_path, \"rb\") as input_bam, \\\n", - " pysam.AlignmentFile(output_bam_path, \"w\", header=input_bam.header) as output_bam:\n", - " for alignment in input_bam:\n", - " sub_alignments = split_alignment(alignment, max_length, input_bam.header)\n", - " for sub_align in sub_alignments:\n", - " output_bam.write(sub_align)\n", - "\n", - "# Example usage\n", - "input_bam_file = \"/Users/english/code/kfdphase/kdprs/test/GIABHG002.bam\"\n", - "output_bam_file = \"output.sam\"\n", - "split_alignments(input_bam_file, output_bam_file)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 262, - "id": "183f7a1c-9a5c-4a28-9af6-7c1b60296cc3", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'sub_align' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn [262], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m help(\u001b[43msub_align\u001b[49m)\n", - "\u001b[0;31mNameError\u001b[0m: name 'sub_align' is not defined" - ] - } - ], - "source": [ - "help(sub_align)" - ] - }, - { - "cell_type": "code", - "execution_count": 289, - "id": "c39e5c24-cb31-4d69-8221-1fffd29febf7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['name', 'flag', 'ref_name', 'ref_pos', 'map_quality', 'cigar', 'next_ref_name', 'next_ref_pos', 'length', 'seq', 'qual', 'tags'])" - ] - }, - "execution_count": 289, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x.keys()" - ] - }, - { - "cell_type": "code", - "execution_count": 290, - "id": "e29d5248-5e28-4a36-943e-570aea03ecd7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "18165H610M3I284M236D917M2I333M3D15M1D87M1I1024M1I214\n", - "2048 *\n" - ] - } - ], - "source": [ - "x = aln.to_dict()\n", - "print(x['cigar'][:52])\n", - "print(x['flag'], x['qual'])\n" - ] - }, - { - "cell_type": "code", - "execution_count": 286, - "id": "3e21d70c-14c1-4ca4-9d80-7894aaa7aa1e", - "metadata": {}, - "outputs": [], - "source": [ - "x = aln.to_dict()\n", - "x['seq'] = x['seq'][:10]\n", - "x['cigar'] = \"10M\" # x['cigar'][:10]\n", - "x['tags'] = []" - ] - }, - { - "cell_type": "code", - "execution_count": 288, - "id": "f837cfe9-e2ee-4510-8fdc-edb2420b84be", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "chr20_PATERNAL\t2048\t#0\t89154\t59\t10M\t*\t0\t0\tACACTGGTTG\tNone\t[]\n" - ] - } - ], - "source": [ - "print(pysam.AlignedSegment.from_dict(x, s.header))" - ] - }, - { - "cell_type": "code", - "execution_count": 264, - "id": "475966aa-3a4a-43c7-b55f-769d8d191e66", - "metadata": {}, - "outputs": [], - "source": [ - "s = pysam.AlignmentFile(input_bam_file, \"rb\")" - ] - }, - { - "cell_type": "code", - "execution_count": 266, - "id": "41956276-0288-4a1f-81f8-fa07b9628329", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "from_dict() takes exactly 2 positional arguments (0 given)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn [266], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mpysam\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mAlignedSegment\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_dict\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/py/lib/python3.9/site-packages/pysam/libcalignedsegment.pyx:1128\u001b[0m, in \u001b[0;36mpysam.libcalignedsegment.AlignedSegment.from_dict\u001b[0;34m()\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: from_dict() takes exactly 2 positional arguments (0 given)" - ] - } - ], - "source": [ - "pysam.AlignedSegment.from_dict()" - ] - }, - { - "cell_type": "code", - "execution_count": 248, - "id": "a6c8310b-446a-4055-b846-96a576ca2ad8", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "from_dict() takes exactly 2 positional arguments (1 given)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn [248], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m x[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mseq\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43maln\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_dict\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/py/lib/python3.9/site-packages/pysam/libcalignedsegment.pyx:1128\u001b[0m, in \u001b[0;36mpysam.libcalignedsegment.AlignedSegment.from_dict\u001b[0;34m()\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: from_dict() takes exactly 2 positional arguments (1 given)" - ] - } - ], - "source": [ - "x['seq'] = None\n", - "aln.from_dict(x, input)" - ] - }, - { - "cell_type": "code", - "execution_count": 249, - "id": "4ffe19ee-0a10-4c5a-9ac9-d39f7b9768ef", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on method from_dict in module pysam.libcalignedsegment:\n", - "\n", - "from_dict(sam_dict, header) method of builtins.type instance\n", - " AlignedSegment.from_dict(cls, sam_dict, AlignmentHeader header)\n", - " parses a dictionary representation of the aligned segment.\n", - " \n", - " Parameters\n", - " ----------\n", - " sam_dict:\n", - " dictionary of alignment values, keys corresponding to output from\n", - " :meth:`todict()`.\n", - "\n" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c2408709-5bf1-4107-92a0-fd36d07320af", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From cd79a9b66b4aa08eca055142fa84e695c5f3bf07 Mon Sep 17 00:00:00 2001 From: Adam English Date: Fri, 24 May 2024 20:01:19 -0400 Subject: [PATCH 17/47] closer to moving over I just have to move to the new Samples or whatever instead of Genotypes --- Cargo.lock | 23 +++------ Cargo.toml | 2 +- src/kplib/annotator.rs | 23 +++++---- src/kplib/regions.rs | 4 +- src/kplib/vargraph.rs | 2 +- src/kplib/vcf_traits.rs | 111 ++++++++++------------------------------ src/kplib/vcfreader.rs | 14 ++--- src/kplib/vcfwriter.rs | 85 +++++++++++++++--------------- src/main.rs | 3 +- 9 files changed, 105 insertions(+), 162 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cf4171..392ae79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -468,7 +468,6 @@ dependencies = [ "noodles-vcf", "ordered-float", "page_size", - "peak_alloc", "petgraph", "pretty_env_logger", "rust-htslib", @@ -552,9 +551,9 @@ dependencies = [ [[package]] name = "noodles-bgzf" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dba1c82e9f92c00b23538359e5d191dff7ccb300cf659ee3a835af65c3cd143" +checksum = "13f54d4840fd26ed94103ded9524aa5fdd757255a556f24653d162c0a45c47e8" dependencies = [ "byteorder", "bytes", @@ -573,9 +572,9 @@ dependencies = [ [[package]] name = "noodles-csi" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad09737d94ec2674361219fb3d46a81561a15773585805de807cab323a15648a" +checksum = "beb1618ca2aa88662d387197a188686105d6b5e25f6959c52b766276cbfc4620" dependencies = [ "bit-vec", "byteorder", @@ -586,9 +585,9 @@ dependencies = [ [[package]] name = "noodles-tabix" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8da8182c56b64d28d0330ce209857ba2a5a4981c6925838e4d4ffeea82db09" +checksum = "0cc110f78cb406f69f42c482d1986526c590b7295f37f0e37f1fc380413400ef" dependencies = [ "bit-vec", "byteorder", @@ -600,9 +599,9 @@ dependencies = [ [[package]] name = "noodles-vcf" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2576e5b1e12d729c93d30ba25676b4a3efb19134f14f30fa30ae70a064d12eb2" +checksum = "4df6e162ec2e898581b5ccf5e8972e376a7c41807061b66152280dea2c53a989" dependencies = [ "indexmap", "memchr", @@ -669,12 +668,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "peak_alloc" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c4e8e2dd832fd76346468f822e4e600d30ba4e5aa545a128abf12cfae7ea3e" - [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index fc4b1d7..18742c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ indicatif = "0.17.8" itertools = { version = "0.12.1" } lazy_static = "1.4.0" log = { version = "0.4", features = ["std", "serde"] } -noodles-vcf = { version = "0.56.0" } +noodles-vcf = { version = "0.57.0" } ordered-float = { version = "4.0", default-features = false } page_size = "0.6.0" petgraph = { version = "0.6.2" } diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 57d3d47..2011ad3 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -3,7 +3,10 @@ use bitflags::bitflags; use petgraph::graph::NodeIndex; -use noodles_vcf::{self as vcf, record::sample::Value}; +use noodles_vcf::{ + self as vcf, + variant::record_buf::samples::sample::value::{Array, Value}, +}; bitflags! { pub struct FiltFlags: u32 { @@ -50,15 +53,15 @@ impl GenotypeAnno { // These are tied to VcfWriter.keys pub fn make_fields(&self, phase_group: i32) -> Vec> { vec![ - Some(Value::from(self.gt.clone())), - Some(Value::from(self.filt.bits() as i32)), - Some(Value::from(self.sq)), - Some(Value::from(self.gq)), - Some(Value::from(phase_group)), - Some(Value::from(self.dp)), - Some(Value::from(self.ad.clone())), - Some(Value::from(self.zs.clone())), - Some(Value::from(self.ss.clone())), + Some(Value::Genotype(self.gt.parse().expect("Should have made GT correctly"))), + Some(Value::Integer(self.filt.bits() as i32)), + Some(Value::Integer(self.sq)), + Some(Value::Integer(self.gq)), + Some(Value::Integer(phase_group)), + Some(Value::Integer(self.dp)), + Some(Value::Array(Array::Integer(self.ad.clone()))), + Some(Value::Array(Array::Integer(self.zs.clone()))), + Some(Value::Array(Array::Integer(self.ss.clone()))), ] } } diff --git a/src/kplib/regions.rs b/src/kplib/regions.rs index 13bedd7..f1e960f 100644 --- a/src/kplib/regions.rs +++ b/src/kplib/regions.rs @@ -1,9 +1,9 @@ use indexmap::IndexMap; -use noodles_vcf::header::record::value::{map::contig::Name, map::Contig, Map}; +use noodles_vcf::header::record::value::{map::contig::Contig, Map}; use std::collections::HashMap; use std::collections::VecDeque; -pub type ContigMap = IndexMap>; +pub type ContigMap = IndexMap>; pub type Regions = HashMap>; use crate::kplib::BedParser; diff --git a/src/kplib/vargraph.rs b/src/kplib/vargraph.rs index 7b748f2..076fe47 100644 --- a/src/kplib/vargraph.rs +++ b/src/kplib/vargraph.rs @@ -113,7 +113,7 @@ impl Variants { /// Again, TR aware, we need to set the bounds for doing the pileup /// to the TR boundaries. fn get_region(entries: &[vcf::Record]) -> (String, u64, u64) { - let chrom = entries[0].chromosome().to_string(); + let chrom = entries[0].reference_sequence_name().to_string(); let (min_start, max_end) = entries.iter().fold((u64::MAX, 0), |acc, e| { let (start, end) = e.boundaries(); diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index f49d9e5..4bc8c02 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -1,10 +1,5 @@ use crate::kplib::seq_to_kmer; -use noodles_vcf::{ - self as vcf, - variant::record::info::field::value::Value, - variant::record::info::field::key::Key, -}; -use std::cmp::Ordering; +use noodles_vcf::{self as vcf, variant::record::AlternateBases}; use std::str::FromStr; #[derive(Debug, Hash, PartialEq, Eq, Clone)] @@ -38,20 +33,16 @@ pub trait KdpVcf { fn boundaries(&self) -> (u64, u64); fn size(&self) -> u64; fn is_filtered(&self) -> bool; - fn variant_type(&self) -> Svtype; fn is_symbolic(&self) -> bool; fn valid_alt(&self) -> bool; + fn get_alt(&self) -> String; } impl KdpVcf for vcf::Record { /// Convert variant sequence to Kfeat fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64) { let ref_seq = self.reference_bases().to_string(); - let alt_seq = self - .alternate_bases() - .first() - .expect("Can only work on sequence resolved variants") - .to_string(); + let alt_seq = self.get_alt(); let size = alt_seq.len() as i64 - ref_seq.len() as i64; @@ -69,37 +60,18 @@ impl KdpVcf for vcf::Record { /// start and end positions of an entry fn boundaries(&self) -> (u64, u64) { - let start: u64 = u64::try_from(usize::from(self.position())).unwrap() - 1; - let end: u64 = u64::try_from(usize::from(self.end().expect("No Variant End"))).unwrap(); + let start = match self.variant_start().and_then(|res| res.ok()) { + Some(pos) => pos.get() as u64 - 1, + None => panic!("Variant doesn't have a start"), + }; + let end: u64 = start + self.reference_bases().len() as u64; (start, end) } /// grab entry's length from either SVLEN field or infer it from the REF ALT fields fn size(&self) -> u64 { - let svlen = self - .info() - .get(&Key::from_str("SVLEN").unwrap_or_else(|_| panic!("No SVLEN INFO"))); - - if let Some(Some(Value::Integer(svlen))) = svlen { - return svlen.unsigned_abs() as u64; - } else if let Some(Some(Value::Array(field::value::Array::Integer(svlen)))) = svlen { - return svlen - .first() - .unwrap_or_else(|| panic!("Bad SVLEN")) - .unwrap() - .unsigned_abs() as u64; - } - let r_len: u64 = self.reference_bases().len() as u64; - let a_len: u64 = if self.is_symbolic() { - let (start, end) = self.boundaries(); - start.abs_diff(end) + 1 - } else { - match self.alternate_bases().first() { - Some(alt) => alt.len(), - None => 0 - } - }; + let a_len: u64 = self.get_alt().len() as u64; if r_len == a_len { if r_len == 1 { @@ -114,61 +86,34 @@ impl KdpVcf for vcf::Record { /// checks if an entry's FILTER is '.' or PASS, true if it is filtered fn is_filtered(&self) -> bool { - match &self.filters() { - Some(map) => **map != Filters::Pass, - None => false, - } + let binding = self.filters(); + let mfilt = binding.as_ref(); + mfilt == "." || mfilt == "PASS" } /// Alternate sequence isn't '.' or '*' fn valid_alt(&self) -> bool { - match self.alternate_bases().first() { - Some(alt) => { - let alt = alt.to_string(); - alt != "." && alt != "*" && !alt.contains(':') - } - _ => false, - } - } - - /// return the Svtype of a vcf entry - fn variant_type(&self) -> Svtype { - match self - .info() - .get(&Key::from_str("SVTYPE").expect("Unable to make key")) - { - // INFO/SVTYPE - Some(Some(Value::String(svtype))) => svtype.parse().expect("Bad SVTYPE"), - Some(Some(Value::Array(field::value::Array::String(svtype)))) => svtype - .first() - .cloned() - .unwrap_or_else(|| panic!("Bad SVTYPE")) - .expect("parsed") - .parse() - .unwrap(), - // Direct from REF/ALT - _ => match self.alternate_bases().first() { - Some(allele::Allele::Bases(alt)) => { - match alt.len().cmp(&self.reference_bases().len()) { - Ordering::Greater => Svtype::Ins, - Ordering::Less => Svtype::Del, - Ordering::Equal if alt.len() == 1 => Svtype::Snp, - _ => Svtype::Unk, - } - } - Some(allele::Allele::Symbol(alt)) => Svtype::from_str(&alt.to_string()) - .unwrap_or_else(|_| panic!("Bad Symbolic Alt")), - _ => Svtype::Unk, - }, - } + let alt = self.get_alt(); + alt != "." && alt != "*" && !alt.contains(':') } /// Checks if its a symbolic allele e.g. /// Returns false if its a monozygotic reference fn is_symbolic(&self) -> bool { - match self.alternate_bases().first() { - Some(alt) => alt.contains('<'), - None => false + self.get_alt().contains('<') + } + + /// Returns the first alternate allele or a blank string with '.' if there isn't any + fn get_alt(&self) -> String { + let alts = self.alternate_bases(); + match alts.len() { + 0 => ".".to_string(), + _ => alts + .iter() + .next() + .expect("I just checked") + .unwrap() + .to_string(), // I don't like all this String when str should be simplier } } } diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index 8994e02..eda268f 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -8,7 +8,7 @@ use std::io::BufRead; /// Takes a vcf and filtering parameters to create in iterable which will /// return chunks of variants in the same neighborhood pub struct VcfChunker { - pub m_vcf: vcf::reader::Reader, + pub m_vcf: vcf::io::Reader, pub m_header: vcf::Header, regions: Regions, params: KDParams, @@ -55,12 +55,12 @@ impl VcfChunker { return false; } - let size = entry.size(); - if self.params.sizemin > size || self.params.sizemax < size { + if !entry.valid_alt() { return false; } - if !entry.valid_alt() { + let size = entry.size(); + if self.params.sizemin > size || self.params.sizemax < size { return false; } @@ -68,7 +68,7 @@ impl VcfChunker { let mut default = VecDeque::new(); let m_coords = self .regions - .get_mut(&entry.chromosome().to_string()) + .get_mut(&entry.reference_sequence_name().to_string()) .unwrap_or(&mut default); if m_coords.is_empty() { @@ -102,7 +102,7 @@ impl VcfChunker { let mut entry = vcf::Record::default(); loop { - match self.m_vcf.read_record(&self.m_header, &mut entry) { + match self.m_vcf.read_record(&mut entry) { Ok(0) => return None, Err(e) => { error!("skipping invalid VCF entry {:?}", e); @@ -132,7 +132,7 @@ impl VcfChunker { /// cur_end but also the TR catalog. We want to chunk all TR changes together /// regardless of their distance. fn entry_in_chunk(&mut self, entry: &vcf::Record) -> bool { - let check_chrom = entry.chromosome().to_string(); + let check_chrom = entry.reference_sequence_name().to_string(); let new_chrom = !self.cur_chrom.is_empty() && check_chrom != self.cur_chrom; let (start, end) = entry.boundaries(); diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 17589fd..27fbc90 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -5,10 +5,8 @@ use std::io::BufWriter; use std::path::PathBuf; use noodles_vcf::{ - self as vcf, - header::record::value::map::format, - header::record::value::Map, - record::genotypes::{keys::Keys, sample::Value, Genotypes}, + self as vcf, header::record::value::map::format, header::record::value::Map, + header::record::Value, variant::record_buf::samples::keys::Keys, }; pub struct VcfWriter { @@ -47,81 +45,84 @@ impl VcfWriter { // Setup FORMAT header definitions // Overwrites existing definitions let all_formats = header.formats_mut(); - - let keys: Keys = "GT:FT:SQ:GQ:PG:DP:AD:ZS:SS".parse().unwrap(); + let new_fmts: Vec = "GT:FT:SQ:GQ:PG:DP:AD:ZS:SS" + .split(':') + .map(String::from) + .collect(); + let keys: Keys = Keys::from_iter(new_fmts); // GT - let gtid = keys[0].clone(); - let mut gtfmt = Map::::from(>id); - *gtfmt.number_mut() = vcf::header::Number::Count(1); + let gtid = "GT"; + let mut gtfmt = Map::::from(gtid); + *gtfmt.number_mut() = format::Number::Count(1); *gtfmt.type_mut() = format::Type::String; *gtfmt.description_mut() = "Kanplug genotype".to_string(); - all_formats.insert(gtid, gtfmt); + all_formats.insert(gtid.to_string(), gtfmt); // FT - let ftid = keys[1].clone(); - let mut ftfmt = Map::::from(&ftid); - *ftfmt.number_mut() = vcf::header::Number::Count(1); + let ftid = "FT"; + let mut ftfmt = Map::::from(ftid); + *ftfmt.number_mut() = format::Number::Count(1); *ftfmt.type_mut() = format::Type::Integer; *ftfmt.description_mut() = "Kanpig filter".to_string(); - all_formats.insert(ftid, ftfmt); + all_formats.insert(ftid.to_string(), ftfmt); // SQ - let sqid = keys[2].clone(); - let mut sqfmt = Map::::from(&sqid); - *sqfmt.number_mut() = vcf::header::Number::Count(1); + let sqid = "SQ"; + let mut sqfmt = Map::::from(sqid); + *sqfmt.number_mut() = format::Number::Count(1); *sqfmt.type_mut() = format::Type::Integer; *sqfmt.description_mut() = "Phred scaled quality of sample being non-ref at this variant".to_string(); - all_formats.insert(sqid, sqfmt); + all_formats.insert(sqid.to_string(), sqfmt); // GQ - let gqid = keys[3].clone(); - let mut gqfmt = Map::::from(&gqid); - *gqfmt.number_mut() = vcf::header::Number::Count(1); + let gqid = "GQ"; + let mut gqfmt = Map::::from(gqid); + *gqfmt.number_mut() = format::Number::Count(1); *gqfmt.type_mut() = format::Type::Integer; *gqfmt.description_mut() = "Phred scaled quality of genotype".to_string(); - all_formats.insert(gqid, gqfmt); + all_formats.insert(gqid.to_string(), gqfmt); // PG - let pgid = keys[4].clone(); - let mut pgfmt = Map::::from(&pgid); - *pgfmt.number_mut() = vcf::header::Number::Count(1); + let pgid = "PG"; + let mut pgfmt = Map::::from(pgid); + *pgfmt.number_mut() = format::Number::Count(1); *pgfmt.type_mut() = format::Type::Integer; *pgfmt.description_mut() = "Local phase group of entries".to_string(); - all_formats.insert(pgid, pgfmt); + all_formats.insert(pgid.to_string(), pgfmt); // DP - let dpid = keys[5].clone(); - let mut dpfmt = Map::::from(&dpid); - *dpfmt.number_mut() = vcf::header::Number::Count(1); + let dpid = "DP"; + let mut dpfmt = Map::::from(dpid); + *dpfmt.number_mut() = format::Number::Count(1); *dpfmt.type_mut() = format::Type::Integer; *dpfmt.description_mut() = "Coverage over region".to_string(); - all_formats.insert(dpid, dpfmt); + all_formats.insert(dpid.to_string(), dpfmt); // AD - let adid = keys[6].clone(); - let mut adfmt = Map::::from(&adid); - *adfmt.number_mut() = vcf::header::Number::R; + let adid = "AD"; + let mut adfmt = Map::::from(adid); + *adfmt.number_mut() = format::Number::ReferenceAlternateBases; *adfmt.type_mut() = format::Type::Integer; *adfmt.description_mut() = "Coverage for reference and alternate alleles".to_string(); - all_formats.insert(adid, adfmt); + all_formats.insert(adid.to_string(), adfmt); // ZS - let zsid = keys[7].clone(); - let mut zsfmt = Map::::from(&zsid); - *zsfmt.number_mut() = vcf::header::Number::R; + let zsid = "ZS"; + let mut zsfmt = Map::::from(zsid); + *zsfmt.number_mut() = format::Number::ReferenceAlternateBases; *zsfmt.type_mut() = format::Type::Integer; *zsfmt.description_mut() = "Size similarity of path to entry".to_string(); - all_formats.insert(zsid, zsfmt); + all_formats.insert(zsid.to_string(), zsfmt); // SS - let ssid = keys[8].clone(); - let mut ssfmt = Map::::from(&ssid); - *ssfmt.number_mut() = vcf::header::Number::R; + let ssid = "SS"; + let mut ssfmt = Map::::from(ssid); + *ssfmt.number_mut() = format::Number::ReferenceAlternateBases; *ssfmt.type_mut() = format::Type::Integer; *ssfmt.description_mut() = "Sequence similarity of path to entry".to_string(); - all_formats.insert(ssid, ssfmt); + all_formats.insert(ssid.to_string(), ssfmt); // Ready to make files let out_buf = BufWriter::with_capacity( diff --git a/src/main.rs b/src/main.rs index acbc734..045cd3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,6 +60,7 @@ fn main() { let m_receiver = task_receiver.clone(); let m_result_sender = result_sender.clone(); let m_ploidy = ploidy.clone(); + let m_header = input_header.clone(); thread::spawn(move || { let mut m_bam = @@ -69,7 +70,7 @@ fn main() { Ok(None) | Err(_) => break, Ok(Some(chunk)) => { let mut m_graph = - Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom); + Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom, &m_header); let ploidy = m_ploidy.get_ploidy(&m_graph.chrom, m_graph.start); // For zero, we don't have to waste time going into the bam From 66e0f451cdacf2b92ae1fcfdd00ab27c6e0c4518 Mon Sep 17 00:00:00 2001 From: Adam English Date: Fri, 24 May 2024 21:39:43 -0400 Subject: [PATCH 18/47] this might not be possible there is no more genotypes_mut. genotypes is now samples, but no samples_mut. I'm going to have to move to rust-htslib --- src/kplib/vcfwriter.rs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 27fbc90..61fc5b6 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use noodles_vcf::{ self as vcf, header::record::value::map::format, header::record::value::Map, - header::record::Value, variant::record_buf::samples::keys::Keys, + variant::record_buf::samples::{Sample, keys::Keys}, }; pub struct VcfWriter { @@ -142,20 +142,9 @@ impl VcfWriter { pub fn anno_write(&mut self, mut annot: GenotypeAnno, phase_group: i32) { *self.gtcounts.entry(annot.gt_state).or_insert(0) += 1; - *annot.entry.genotypes_mut() = - Genotypes::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); + let sample = annot.entry.samples().get_index(0).unwrap(); + //Sample::new(&self.keys, annot.make_fields(phase_group).as_slice()); let _result = self.writer.write_record(&self.header, &annot.entry); } - - pub fn __write_entry(&mut self, mut entry: vcf::Record) { - *entry.genotypes_mut() = Genotypes::new( - "GT".parse().unwrap(), - vec![vec![ - Some(Value::from("./.")), // GT - ]], - ); - *self.gtcounts.entry(GTstate::Non).or_insert(0) += 1; - let _result = self.writer.write_record(&self.header, &entry); - } } From 76c08bbc071a193c28cd4709cfea89559518c315 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 00:16:56 -0400 Subject: [PATCH 19/47] updating noodles .......... --- experiments/bedtest.sh | 4 ++-- src/kplib/annotator.rs | 16 +++++++++------- src/kplib/vargraph.rs | 8 ++++---- src/kplib/vcf_traits.rs | 32 ++++++++++++-------------------- src/kplib/vcfreader.rs | 18 +++++++++--------- src/kplib/vcfwriter.rs | 13 ++++++++----- src/main.rs | 8 +++----- 7 files changed, 47 insertions(+), 52 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 0d644f1..44e5417 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -3,7 +3,7 @@ bed=test_rs/test.chr20.bed create() { #../target/release/kanpig \ - time cargo run -- \ + time cargo run --release -- \ --input test_rs/test2.vcf.gz \ --bam /Users/english/code/kanpig/experiments/test_rs/NA24385.chr20.bam \ --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ @@ -11,7 +11,7 @@ create() { --sizesim 0.95 --seqsim 0.90 --threads 4 \ --maxpaths 20000 --mapq 5 --hapsim 0.98 \ --chunksize 100 --maxhom 5 \ - --sample doesthiswork --mem 1 \ + --sample doesthiswork \ -o test_rs/hc.vcf --bed $bed # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ # --bam /Users/english/code/kanpig/experiments/test_rs/GIABHG002.bam \ diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 2011ad3..0a8b65a 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use petgraph::graph::NodeIndex; use noodles_vcf::{ - self as vcf, + self as vcf, variant::record_buf::samples::sample::value::{Array, Value}, }; @@ -23,7 +23,7 @@ bitflags! { //Format Integer Type Number = G type IntG = Vec>; pub struct GenotypeAnno { - pub entry: vcf::Record, + pub entry: vcf::variant::RecordBuf, pub gt: String, pub filt: FiltFlags, pub sq: i32, @@ -37,7 +37,7 @@ pub struct GenotypeAnno { impl GenotypeAnno { pub fn new( - entry: vcf::Record, + entry: vcf::variant::RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, @@ -53,7 +53,9 @@ impl GenotypeAnno { // These are tied to VcfWriter.keys pub fn make_fields(&self, phase_group: i32) -> Vec> { vec![ - Some(Value::Genotype(self.gt.parse().expect("Should have made GT correctly"))), + Some(Value::Genotype( + self.gt.parse().expect("Should have made GT correctly"), + )), Some(Value::Integer(self.filt.bits() as i32)), Some(Value::Integer(self.sq)), Some(Value::Integer(self.gq)), @@ -68,7 +70,7 @@ impl GenotypeAnno { /// For annotating a variant in diploid regions fn diploid( - entry: vcf::Record, + entry: vcf::variant::RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, @@ -146,7 +148,7 @@ fn diploid( } /// For annotating a variant in a zero ploidy region -fn zero(entry: vcf::Record, coverage: u64) -> GenotypeAnno { +fn zero(entry: vcf::variant::RecordBuf, coverage: u64) -> GenotypeAnno { GenotypeAnno { entry, gt: "./.".to_string(), @@ -163,7 +165,7 @@ fn zero(entry: vcf::Record, coverage: u64) -> GenotypeAnno { /// For annotating a variant in a one ploidy region fn haploid( - entry: vcf::Record, + entry: vcf::variant::RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, diff --git a/src/kplib/vargraph.rs b/src/kplib/vargraph.rs index 076fe47..7ff6b35 100644 --- a/src/kplib/vargraph.rs +++ b/src/kplib/vargraph.rs @@ -17,12 +17,12 @@ pub struct VarNode { pub coverage: (Option, Option), pub seqsim: (Option, Option), pub sizesim: (Option, Option), - pub entry: Option, + pub entry: Option, pub kfeat: Vec, } impl VarNode { - pub fn new(entry: vcf::Record, kmer: u8, maxhom: usize) -> Self { + pub fn new(entry: vcf::variant::RecordBuf, kmer: u8, maxhom: usize) -> Self { // Want to make a hash for these names for debugging, I think. let name = "".to_string(); let (start, end) = entry.boundaries(); @@ -71,7 +71,7 @@ pub struct Variants { /// The graph has an upstream 'src' node that point to every variant node /// The graph has a dnstream 'snk' node that is pointed to by every variant node and 'src' impl Variants { - pub fn new(mut variants: Vec, kmer: u8, maxhom: usize) -> Self { + pub fn new(mut variants: Vec, kmer: u8, maxhom: usize) -> Self { if variants.is_empty() { panic!("Cannot create a graph from no variants"); } @@ -112,7 +112,7 @@ impl Variants { /// Again, TR aware, we need to set the bounds for doing the pileup /// to the TR boundaries. - fn get_region(entries: &[vcf::Record]) -> (String, u64, u64) { + fn get_region(entries: &[vcf::variant::RecordBuf]) -> (String, u64, u64) { let chrom = entries[0].reference_sequence_name().to_string(); let (min_start, max_end) = entries.iter().fold((u64::MAX, 0), |acc, e| { diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index 4bc8c02..392b3d9 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -1,5 +1,5 @@ use crate::kplib::seq_to_kmer; -use noodles_vcf::{self as vcf, variant::record::AlternateBases}; +use noodles_vcf::{self as vcf, variant::record::AlternateBases, variant::record::Filters}; use std::str::FromStr; #[derive(Debug, Hash, PartialEq, Eq, Clone)] @@ -32,13 +32,12 @@ pub trait KdpVcf { fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64); fn boundaries(&self) -> (u64, u64); fn size(&self) -> u64; - fn is_filtered(&self) -> bool; - fn is_symbolic(&self) -> bool; + fn is_filtered(&self, header: &vcf::Header) -> bool; fn valid_alt(&self) -> bool; fn get_alt(&self) -> String; } -impl KdpVcf for vcf::Record { +impl KdpVcf for vcf::variant::RecordBuf { /// Convert variant sequence to Kfeat fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64) { let ref_seq = self.reference_bases().to_string(); @@ -60,10 +59,7 @@ impl KdpVcf for vcf::Record { /// start and end positions of an entry fn boundaries(&self) -> (u64, u64) { - let start = match self.variant_start().and_then(|res| res.ok()) { - Some(pos) => pos.get() as u64 - 1, - None => panic!("Variant doesn't have a start"), - }; + let start: u64 = u64::try_from(usize::from(self.variant_start().unwrap())).unwrap() - 1; let end: u64 = start + self.reference_bases().len() as u64; (start, end) } @@ -85,22 +81,18 @@ impl KdpVcf for vcf::Record { } /// checks if an entry's FILTER is '.' or PASS, true if it is filtered - fn is_filtered(&self) -> bool { - let binding = self.filters(); - let mfilt = binding.as_ref(); - mfilt == "." || mfilt == "PASS" + fn is_filtered(&self, header: &vcf::Header) -> bool { + !(self.filters().is_empty() + || self.filters().iter(header).any(|res| match res { + Ok(s) => s == "PASS", + Err(_) => false, + })) } - /// Alternate sequence isn't '.' or '*' + /// Alternate sequence isn't '.' or '*' or bnd or symbolic fn valid_alt(&self) -> bool { let alt = self.get_alt(); - alt != "." && alt != "*" && !alt.contains(':') - } - - /// Checks if its a symbolic allele e.g. - /// Returns false if its a monozygotic reference - fn is_symbolic(&self) -> bool { - self.get_alt().contains('<') + alt != "." && alt != "*" && !alt.contains(':') && !alt.contains('<') } /// Returns the first alternate allele or a blank string with '.' if there isn't any diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index eda268f..e94356e 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -18,7 +18,7 @@ pub struct VcfChunker { // When iterating, we will encounter a variant that no longer // fits in the current chunk. We need to hold on to it for the // next chunk - hold_entry: Option, + hold_entry: Option, pub chunk_count: u64, pub call_count: u64, pub skip_count: u64, @@ -50,8 +50,8 @@ impl VcfChunker { /// Checks if entry passes all parameter conditions including /// within --bed regions, passing, and within expected size - fn filter_entry(&mut self, entry: &vcf::Record) -> bool { - if self.params.passonly & entry.is_filtered() { + fn filter_entry(&mut self, entry: &vcf::variant::RecordBuf) -> bool { + if self.params.passonly & entry.is_filtered(&self.m_header) { return false; } @@ -68,7 +68,7 @@ impl VcfChunker { let mut default = VecDeque::new(); let m_coords = self .regions - .get_mut(&entry.reference_sequence_name().to_string()) + .get_mut(entry.reference_sequence_name()) .unwrap_or(&mut default); if m_coords.is_empty() { @@ -98,11 +98,11 @@ impl VcfChunker { } /// Return the next vcf entry which passes parameter conditions - fn get_next_entry(&mut self) -> Option { - let mut entry = vcf::Record::default(); + fn get_next_entry(&mut self) -> Option { + let mut entry = vcf::variant::RecordBuf::default(); loop { - match self.m_vcf.read_record(&mut entry) { + match self.m_vcf.read_record_buf(&self.m_header, &mut entry) { Ok(0) => return None, Err(e) => { error!("skipping invalid VCF entry {:?}", e); @@ -131,7 +131,7 @@ impl VcfChunker { /// If we wanted to be TR aware, when checking new_chunk, we don't just look at /// cur_end but also the TR catalog. We want to chunk all TR changes together /// regardless of their distance. - fn entry_in_chunk(&mut self, entry: &vcf::Record) -> bool { + fn entry_in_chunk(&mut self, entry: &vcf::variant::RecordBuf) -> bool { let check_chrom = entry.reference_sequence_name().to_string(); let new_chrom = !self.cur_chrom.is_empty() && check_chrom != self.cur_chrom; @@ -150,7 +150,7 @@ impl VcfChunker { } impl Iterator for VcfChunker { - type Item = Vec; + type Item = Vec; fn next(&mut self) -> Option { let mut ret = self.hold_entry.take().into_iter().collect::>(); diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 61fc5b6..89abd6f 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -5,8 +5,11 @@ use std::io::BufWriter; use std::path::PathBuf; use noodles_vcf::{ - self as vcf, header::record::value::map::format, header::record::value::Map, - variant::record_buf::samples::{Sample, keys::Keys}, + self as vcf, + header::record::value::map::format, + header::record::value::Map, + variant::io::Write, + variant::record_buf::samples::{keys::Keys, Samples}, }; pub struct VcfWriter { @@ -142,9 +145,9 @@ impl VcfWriter { pub fn anno_write(&mut self, mut annot: GenotypeAnno, phase_group: i32) { *self.gtcounts.entry(annot.gt_state).or_insert(0) += 1; - let sample = annot.entry.samples().get_index(0).unwrap(); - //Sample::new(&self.keys, annot.make_fields(phase_group).as_slice()); + *annot.entry.samples_mut() = + Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - let _result = self.writer.write_record(&self.header, &annot.entry); + let _result = self.writer.write_variant_record(&self.header, &annot.entry); } } diff --git a/src/main.rs b/src/main.rs index 045cd3d..7af7272 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use kplib::{ PathScore, Ploidy, PloidyRegions, Variants, VcfChunker, VcfWriter, }; -type InputType = Option>; +type InputType = Option>; type OutputType = Option>; fn main() { @@ -38,8 +38,7 @@ fn main() { error!("please fix arguments"); std::process::exit(1); } - - let mut input_vcf = vcf::reader::Builder::default() + let mut input_vcf = vcf::io::reader::Builder::default() .build_from_path(args.io.input.clone()) .expect("Unable to parse vcf"); let input_header = input_vcf.read_header().expect("Unable to parse vcf header"); @@ -60,7 +59,6 @@ fn main() { let m_receiver = task_receiver.clone(); let m_result_sender = result_sender.clone(); let m_ploidy = ploidy.clone(); - let m_header = input_header.clone(); thread::spawn(move || { let mut m_bam = @@ -70,7 +68,7 @@ fn main() { Ok(None) | Err(_) => break, Ok(Some(chunk)) => { let mut m_graph = - Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom, &m_header); + Variants::new(chunk, m_args.kd.kmer, m_args.kd.maxhom); let ploidy = m_ploidy.get_ploidy(&m_graph.chrom, m_graph.start); // For zero, we don't have to waste time going into the bam From 7c3c9b595a60d15ff1ca21550ed3b683db136fef Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 01:10:25 -0400 Subject: [PATCH 20/47] code clean --- Cargo.toml | 12 ++++++------ experiments/bedtest.sh | 2 +- src/kplib/annotator.rs | 12 ++++++------ src/kplib/vargraph.rs | 10 +++++----- src/kplib/vcf_traits.rs | 11 ++++++----- src/kplib/vcfreader.rs | 10 +++++----- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18742c8..a274f41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,21 +4,21 @@ version = "0.2.1-dev" edition = "2021" [dependencies] -bitflags = "2.5.0" +bitflags = { version = "2.5.0" } clap = { version = "4.0", features = ["derive"] } crossbeam-channel = { version = "0.5.12" } -indexmap = "2.2.3" -indicatif = "0.17.8" +indexmap = { version = "2.2.3" } +indicatif = { version = "0.17.8" } itertools = { version = "0.12.1" } -lazy_static = "1.4.0" +lazy_static = { version = "1.4.0" } log = { version = "0.4", features = ["std", "serde"] } noodles-vcf = { version = "0.57.0" } ordered-float = { version = "4.0", default-features = false } -page_size = "0.6.0" +page_size = { version = "0.6.0" } petgraph = { version = "0.6.2" } pretty_env_logger = { version = "0.4.0" } rust-htslib = { version = "0.46.0" } -rust-lapper = "1.1.0" +rust-lapper = { version = "1.1.0" } [profile.release] opt-level = 3 diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 44e5417..8028892 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -9,7 +9,7 @@ create() { --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 4 \ - --maxpaths 20000 --mapq 5 --hapsim 0.98 \ + --maxpaths 1000 --mapq 5 --hapsim 0.98 \ --chunksize 100 --maxhom 5 \ --sample doesthiswork \ -o test_rs/hc.vcf --bed $bed diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 0a8b65a..fa22e54 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -4,8 +4,8 @@ use bitflags::bitflags; use petgraph::graph::NodeIndex; use noodles_vcf::{ - self as vcf, variant::record_buf::samples::sample::value::{Array, Value}, + variant::RecordBuf, }; bitflags! { @@ -23,7 +23,7 @@ bitflags! { //Format Integer Type Number = G type IntG = Vec>; pub struct GenotypeAnno { - pub entry: vcf::variant::RecordBuf, + pub entry: RecordBuf, pub gt: String, pub filt: FiltFlags, pub sq: i32, @@ -37,7 +37,7 @@ pub struct GenotypeAnno { impl GenotypeAnno { pub fn new( - entry: vcf::variant::RecordBuf, + entry: RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, @@ -70,7 +70,7 @@ impl GenotypeAnno { /// For annotating a variant in diploid regions fn diploid( - entry: vcf::variant::RecordBuf, + entry: RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, @@ -148,7 +148,7 @@ fn diploid( } /// For annotating a variant in a zero ploidy region -fn zero(entry: vcf::variant::RecordBuf, coverage: u64) -> GenotypeAnno { +fn zero(entry: RecordBuf, coverage: u64) -> GenotypeAnno { GenotypeAnno { entry, gt: "./.".to_string(), @@ -165,7 +165,7 @@ fn zero(entry: vcf::variant::RecordBuf, coverage: u64) -> GenotypeAnno { /// For annotating a variant in a one ploidy region fn haploid( - entry: vcf::variant::RecordBuf, + entry: RecordBuf, var_idx: &NodeIndex, paths: &[PathScore], coverage: u64, diff --git a/src/kplib/vargraph.rs b/src/kplib/vargraph.rs index 7ff6b35..4d8d6fd 100644 --- a/src/kplib/vargraph.rs +++ b/src/kplib/vargraph.rs @@ -4,7 +4,7 @@ use crate::kplib::{ Ploidy, }; use itertools::Itertools; -use noodles_vcf::{self as vcf}; +use noodles_vcf::variant::RecordBuf; use petgraph::graph::{DiGraph, NodeIndex}; /// Every --input variant is placed inside a node is turned into a graph. @@ -17,12 +17,12 @@ pub struct VarNode { pub coverage: (Option, Option), pub seqsim: (Option, Option), pub sizesim: (Option, Option), - pub entry: Option, + pub entry: Option, pub kfeat: Vec, } impl VarNode { - pub fn new(entry: vcf::variant::RecordBuf, kmer: u8, maxhom: usize) -> Self { + pub fn new(entry: RecordBuf, kmer: u8, maxhom: usize) -> Self { // Want to make a hash for these names for debugging, I think. let name = "".to_string(); let (start, end) = entry.boundaries(); @@ -71,7 +71,7 @@ pub struct Variants { /// The graph has an upstream 'src' node that point to every variant node /// The graph has a dnstream 'snk' node that is pointed to by every variant node and 'src' impl Variants { - pub fn new(mut variants: Vec, kmer: u8, maxhom: usize) -> Self { + pub fn new(mut variants: Vec, kmer: u8, maxhom: usize) -> Self { if variants.is_empty() { panic!("Cannot create a graph from no variants"); } @@ -112,7 +112,7 @@ impl Variants { /// Again, TR aware, we need to set the bounds for doing the pileup /// to the TR boundaries. - fn get_region(entries: &[vcf::variant::RecordBuf]) -> (String, u64, u64) { + fn get_region(entries: &[RecordBuf]) -> (String, u64, u64) { let chrom = entries[0].reference_sequence_name().to_string(); let (min_start, max_end) = entries.iter().fold((u64::MAX, 0), |acc, e| { diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index 392b3d9..857b1b1 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -1,5 +1,7 @@ use crate::kplib::seq_to_kmer; -use noodles_vcf::{self as vcf, variant::record::AlternateBases, variant::record::Filters}; +use noodles_vcf::{ + variant::record::AlternateBases, variant::record::Filters, variant::RecordBuf, Header, +}; use std::str::FromStr; #[derive(Debug, Hash, PartialEq, Eq, Clone)] @@ -27,17 +29,16 @@ impl FromStr for Svtype { } } -/// Convert vcf::Record to kfeat pub trait KdpVcf { fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64); fn boundaries(&self) -> (u64, u64); fn size(&self) -> u64; - fn is_filtered(&self, header: &vcf::Header) -> bool; + fn is_filtered(&self, header: &Header) -> bool; fn valid_alt(&self) -> bool; fn get_alt(&self) -> String; } -impl KdpVcf for vcf::variant::RecordBuf { +impl KdpVcf for RecordBuf { /// Convert variant sequence to Kfeat fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64) { let ref_seq = self.reference_bases().to_string(); @@ -81,7 +82,7 @@ impl KdpVcf for vcf::variant::RecordBuf { } /// checks if an entry's FILTER is '.' or PASS, true if it is filtered - fn is_filtered(&self, header: &vcf::Header) -> bool { + fn is_filtered(&self, header: &Header) -> bool { !(self.filters().is_empty() || self.filters().iter(header).any(|res| match res { Ok(s) => s == "PASS", diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index e94356e..db0f473 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -1,6 +1,6 @@ use crate::kplib::{GenotypeAnno, KDParams, KdpVcf, Ploidy, Regions}; use crossbeam_channel::Sender; -use noodles_vcf::{self as vcf}; +use noodles_vcf::{self as vcf, variant::RecordBuf}; use petgraph::graph::NodeIndex; use std::collections::VecDeque; use std::io::BufRead; @@ -98,8 +98,8 @@ impl VcfChunker { } /// Return the next vcf entry which passes parameter conditions - fn get_next_entry(&mut self) -> Option { - let mut entry = vcf::variant::RecordBuf::default(); + fn get_next_entry(&mut self) -> Option { + let mut entry = RecordBuf::default(); loop { match self.m_vcf.read_record_buf(&self.m_header, &mut entry) { @@ -131,7 +131,7 @@ impl VcfChunker { /// If we wanted to be TR aware, when checking new_chunk, we don't just look at /// cur_end but also the TR catalog. We want to chunk all TR changes together /// regardless of their distance. - fn entry_in_chunk(&mut self, entry: &vcf::variant::RecordBuf) -> bool { + fn entry_in_chunk(&mut self, entry: &RecordBuf) -> bool { let check_chrom = entry.reference_sequence_name().to_string(); let new_chrom = !self.cur_chrom.is_empty() && check_chrom != self.cur_chrom; @@ -150,7 +150,7 @@ impl VcfChunker { } impl Iterator for VcfChunker { - type Item = Vec; + type Item = Vec; fn next(&mut self) -> Option { let mut ret = self.hold_entry.take().into_iter().collect::>(); From f0095bd1182e0a124adce12727d7d4157c3382b7 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 02:14:58 -0400 Subject: [PATCH 21/47] clean --- src/kplib/vcf_traits.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcf_traits.rs index 857b1b1..79cc9df 100644 --- a/src/kplib/vcf_traits.rs +++ b/src/kplib/vcf_traits.rs @@ -35,7 +35,7 @@ pub trait KdpVcf { fn size(&self) -> u64; fn is_filtered(&self, header: &Header) -> bool; fn valid_alt(&self) -> bool; - fn get_alt(&self) -> String; + fn get_alt(&self) -> &str; } impl KdpVcf for RecordBuf { @@ -97,16 +97,12 @@ impl KdpVcf for RecordBuf { } /// Returns the first alternate allele or a blank string with '.' if there isn't any - fn get_alt(&self) -> String { + fn get_alt(&self) -> &str { let alts = self.alternate_bases(); match alts.len() { - 0 => ".".to_string(), - _ => alts - .iter() - .next() - .expect("I just checked") - .unwrap() - .to_string(), // I don't like all this String when str should be simplier + 0 => ".", + _ => alts.iter().next().expect("I just checked").unwrap(), + //.to_string(), // I don't like all this String when str should be simplier } } } From 80ec08725cdf2b64e5eef08dc98c360337d7f2db Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 08:33:12 -0400 Subject: [PATCH 22/47] saftey around edge case shouldn't happen, but still --- src/kplib/cluster.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/kplib/cluster.rs b/src/kplib/cluster.rs index 5c0e898..85a9f2b 100644 --- a/src/kplib/cluster.rs +++ b/src/kplib/cluster.rs @@ -129,6 +129,9 @@ pub fn diploid_haplotypes( if hap1.n == 0 { // HOMALT vec![hap2.clone(), hap2] + } else if hap2.n == 0 { + // Doesn't happen? + vec![hap1.clone(), hap1] } else { // Compound Het vec![hap1, hap2] From 0a54d35509fb92554cd4cc7e4bbda56203a12617 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 09:18:57 -0400 Subject: [PATCH 23/47] small clean --- experiments/bedtest.sh | 4 ++-- src/kplib/kmer.rs | 9 ++++----- src/kplib/vcfreader.rs | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 8028892..88434b4 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -9,8 +9,8 @@ create() { --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 4 \ - --maxpaths 1000 --mapq 5 --hapsim 0.98 \ - --chunksize 100 --maxhom 5 \ + --maxpaths 20000 --mapq 5 --hapsim 0.98 \ + --chunksize 100 --maxhom 0 \ --sample doesthiswork \ -o test_rs/hc.vcf --bed $bed # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ diff --git a/src/kplib/kmer.rs b/src/kplib/kmer.rs index 005ddae..9bf92e9 100644 --- a/src/kplib/kmer.rs +++ b/src/kplib/kmer.rs @@ -11,11 +11,10 @@ fn encode_nuc(nuc: u8) -> u64 { /// Count kmers in a sequence pub fn seq_to_kmer(sequence: &[u8], kmer: u8, negative: bool, maxhom: usize) -> Vec { - let sequence = if maxhom != 0 { - compress_homopolymer(sequence, maxhom) - } else { - sequence.to_vec() - }; + if maxhom != 0 { + return seq_to_kmer(&compress_homopolymer(sequence, maxhom), kmer, negative, 0) + } + let ukmer = kmer as usize; let mut kcounts = vec![0f32; 1 << (2 * ukmer)]; let cnt = if negative { -1.0 } else { 1.0 }; diff --git a/src/kplib/vcfreader.rs b/src/kplib/vcfreader.rs index db0f473..2efa4be 100644 --- a/src/kplib/vcfreader.rs +++ b/src/kplib/vcfreader.rs @@ -18,7 +18,7 @@ pub struct VcfChunker { // When iterating, we will encounter a variant that no longer // fits in the current chunk. We need to hold on to it for the // next chunk - hold_entry: Option, + hold_entry: Option, pub chunk_count: u64, pub call_count: u64, pub skip_count: u64, @@ -50,7 +50,7 @@ impl VcfChunker { /// Checks if entry passes all parameter conditions including /// within --bed regions, passing, and within expected size - fn filter_entry(&mut self, entry: &vcf::variant::RecordBuf) -> bool { + fn filter_entry(&mut self, entry: &RecordBuf) -> bool { if self.params.passonly & entry.is_filtered(&self.m_header) { return false; } From ad21904eb9c03604788f1fe1c094f3cd7c69b8c1 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 11:20:26 -0400 Subject: [PATCH 24/47] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 688b9dc..e40852c 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,11 @@ of an upstream window's variants at least `chunksize` base-pairs away from the n This chunksize also determines the region over which read pileups are generated. Only reads with at least `mapq` mapping quality, passing the `mapflag` filter, and which fully span the minimum variant start and maximum variant end are considered. -This is an important parameter because too small of a `chunksize` may not recruit read pileups that support variants but are -further away. Similarly, too large of a value may create windows with many SVs which are also too large for reads to fully-span. +This is an important parameter because too small of a `chunksize` may not recruit distant read pileups which support variants. Similarly, +too large of a value may create windows with many SVs which are also too large for reads to fully-span. ### `--sizemin` and `--sizemax` -Variant sizes are determined by `INFO/SVLEN`. If `INFO/SVLEN` tag is not in the VCF entry, the variant's size is set as -`abs(length(ALT) - length(REF))`. Genotypes of variants not within the size boundaries are set to missing (`./.`). +Variant sizes are determined by `abs(length(ALT) - length(REF))`. Genotypes of variants not within the size boundaries are set to missing (`./.`). ### `--sizesim` and `--seqsim` When applying a haplotype to a variant graph, only paths above these two thresholds are allowed. If there are multiple @@ -85,7 +84,7 @@ The `SAMPLE` column fields populated by kanpig are: | **FT** | Bit flag for properties of the variant's genotyping. Flags == 0 are considered PASS. | | **SQ** | Phred scaled likelihood variant alternate is present in the sample | | **GQ** | Phred scale difference between most and second-most likely genotypes | -| **PG** | Each chunk of variants is assigned a phase group | +| **PS** | Each chunk of variants is assigned a phase set | | **DP** | Read coverage over the region | | **AD** | Read coverage supporting the reference and alternate alleles. | | **SZ** | Size similarity of the two haplotypes to this variant | From 62dc256e5394f1f6785d500d5e8e358fefb7c3c3 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 11:53:27 -0400 Subject: [PATCH 25/47] change PG to PS that's the vcf spec name --- src/kplib/kmer.rs | 3 +-- src/kplib/vcfwriter.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/kplib/kmer.rs b/src/kplib/kmer.rs index 9bf92e9..7d0722e 100644 --- a/src/kplib/kmer.rs +++ b/src/kplib/kmer.rs @@ -12,7 +12,7 @@ fn encode_nuc(nuc: u8) -> u64 { /// Count kmers in a sequence pub fn seq_to_kmer(sequence: &[u8], kmer: u8, negative: bool, maxhom: usize) -> Vec { if maxhom != 0 { - return seq_to_kmer(&compress_homopolymer(sequence, maxhom), kmer, negative, 0) + return seq_to_kmer(&compress_homopolymer(sequence, maxhom), kmer, negative, 0); } let ukmer = kmer as usize; @@ -39,7 +39,6 @@ pub fn seq_to_kmer(sequence: &[u8], kmer: u8, negative: bool, maxhom: usize) -> // rolling sum masks off first nuc and adds the next one let mask: u64 = (1 << (2 * (kmer - 1) as usize)) - 1; - //for i in sequence[1..(sequence.len() - ukmer + 1)].iter() { for i in sequence.iter().skip(ukmer) { let f_nuc = encode_nuc(*i); f_result = ((f_result & mask) << 2) + f_nuc; diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 89abd6f..4090823 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -48,7 +48,7 @@ impl VcfWriter { // Setup FORMAT header definitions // Overwrites existing definitions let all_formats = header.formats_mut(); - let new_fmts: Vec = "GT:FT:SQ:GQ:PG:DP:AD:ZS:SS" + let new_fmts: Vec = "GT:FT:SQ:GQ:PS:DP:AD:ZS:SS" .split(':') .map(String::from) .collect(); @@ -87,13 +87,13 @@ impl VcfWriter { *gqfmt.description_mut() = "Phred scaled quality of genotype".to_string(); all_formats.insert(gqid.to_string(), gqfmt); - // PG - let pgid = "PG"; - let mut pgfmt = Map::::from(pgid); - *pgfmt.number_mut() = format::Number::Count(1); - *pgfmt.type_mut() = format::Type::Integer; - *pgfmt.description_mut() = "Local phase group of entries".to_string(); - all_formats.insert(pgid.to_string(), pgfmt); + // PS + let psid = "PS"; + let mut psfmt = Map::::from(psid); + *psfmt.number_mut() = format::Number::Count(1); + *psfmt.type_mut() = format::Type::Integer; + *psfmt.description_mut() = "Local phase group of entries".to_string(); + all_formats.insert(psid.to_string(), psfmt); // DP let dpid = "DP"; From 976115c2a2f962531f071fcb8e25d219162e31c8 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 12:08:53 -0400 Subject: [PATCH 26/47] write refactor --- experiments/bedtest.sh | 2 +- src/main.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 88434b4..f848ec3 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -39,7 +39,7 @@ bench_full() { } create -#bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf +bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf #tabix test_rs/hc.vcf.gz #bench_lite #bench_medium diff --git a/src/main.rs b/src/main.rs index 7af7272..efb3226 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,16 +106,13 @@ fn main() { .collect(); //Before we start the workers, we'll start the writer - let writer = Arc::new(Mutex::new(VcfWriter::new( - &args.io.out, - input_header.clone(), - &args.io.sample, - ))); - - let cloned_writer = writer.clone(); + // This is the semaphore for the progress bar that communicates between main and writer let num_variants = Arc::new(Mutex::new(0)); - let cloned_num_variants = num_variants.clone(); + let wthread_num_variants = num_variants.clone(); + let wthread_io = args.io.clone(); + let wthread_header = input_header.clone(); + let write_handler = std::thread::spawn(move || { let sty = ProgressStyle::with_template( " [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed", @@ -125,7 +122,11 @@ fn main() { let mut pbar: Option = None; let mut phase_group: i32 = 0; let mut completed_variants: u64 = 0; - let mut m_writer = cloned_writer.lock().unwrap(); + let mut m_writer = VcfWriter::new( + &wthread_io.out, + wthread_header.clone(), + &wthread_io.sample, + ); loop { match result_receiver.recv() { @@ -146,7 +147,7 @@ fn main() { } else { completed_variants += rsize; // check if the reader is finished so we can setup the pbar - let value_guard = cloned_num_variants.lock().unwrap(); + let value_guard = wthread_num_variants.lock().unwrap(); if *value_guard != 0 { let t_bar = ProgressBar::new(*value_guard).with_style(sty.clone()); t_bar.inc(completed_variants); @@ -157,6 +158,7 @@ fn main() { } } } + info!("genotype counts: {:#?}", m_writer.gtcounts); }); info!("building variant graphs"); @@ -199,7 +201,5 @@ fn main() { // Wait on the writer write_handler.join().unwrap(); - let writer = writer.lock().unwrap(); - info!("genotype counts: {:#?}", writer.gtcounts); info!("finished"); } From 4d9d6b823167b86446f9e895f860e0506a9541b0 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 14:35:32 -0400 Subject: [PATCH 27/47] panic on write fail better than writing a corrupted vcf --- src/kplib/vcfwriter.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 4090823..fefd5bf 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -148,6 +148,9 @@ impl VcfWriter { *annot.entry.samples_mut() = Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - let _result = self.writer.write_variant_record(&self.header, &annot.entry); + match self.writer.write_variant_record(&self.header, &annot.entry) { + Ok(_) => {} + Err(error) => panic!("Couldn't write record {:?}", error), + } } } From d1f255a3c73fe935d54c1a8cfcbd972f9188cf4e Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 17:16:36 -0400 Subject: [PATCH 28/47] iupac translator --- src/kplib/vcfwriter.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index fefd5bf..29c34f0 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -147,10 +147,62 @@ impl VcfWriter { *self.gtcounts.entry(annot.gt_state).or_insert(0) += 1; *annot.entry.samples_mut() = Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - + //*annot.entry.reference_bases_mut() = replace_iupac(annot.entry.reference_bases()); + replace_iupac_inplace(annot.entry.reference_bases_mut()); match self.writer.write_variant_record(&self.header, &annot.entry) { Ok(_) => {} Err(error) => panic!("Couldn't write record {:?}", error), } } } + +lazy_static::lazy_static! { + static ref IUPAC: [u8; 128] = { + let mut arr = [0u8; 128]; + arr[b'R' as usize] = b'A'; // A or G + arr[b'Y' as usize] = b'C'; // C or T + arr[b'S' as usize] = b'C'; // C or G + arr[b'W' as usize] = b'A'; // A or T + arr[b'K' as usize] = b'G'; // G or T + arr[b'M' as usize] = b'A'; // A or C + arr[b'B' as usize] = b'C'; // C, G, or T + arr[b'D' as usize] = b'A'; // A, G, or T + arr[b'H' as usize] = b'A'; // A, C, or T + arr[b'V' as usize] = b'A'; // A, C, or G + arr[b'N' as usize] = b'A'; // Any base (A, C, G, or T) + arr[b'r' as usize] = b'a'; // a or g + arr[b'y' as usize] = b'c'; // c or t + arr[b's' as usize] = b'c'; // c or g + arr[b'w' as usize] = b'a'; // a or t + arr[b'k' as usize] = b'g'; // g or t + arr[b'm' as usize] = b'a'; // a or c + arr[b'b' as usize] = b'c'; // c, g, or t + arr[b'd' as usize] = b'a'; // a, g, or t + arr[b'h' as usize] = b'a'; // a, c, or t + arr[b'v' as usize] = b'a'; // a, c, or g + arr[b'n' as usize] = b'a'; // any base (a, c, g, or t) + arr + }; +} + +fn replace_iupac_inplace(sequence: &mut String) { + unsafe { + let bytes = sequence.as_bytes_mut(); + bytes.iter_mut().for_each(|b| { + let t = IUPAC[*b as usize]; + if t != 0u8 { + *b = t; + } + }); + } +} + +/*fn replace_iupac(sequence: &str) -> String { + let mut result = String::with_capacity(sequence.len()); + + for c in sequence.chars() { + result.push(REPLACEMENTS[c as usize]); + } + + result +}*/ From 80039c77a971ea39c8b92af701b875ca4592c2b8 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 17:35:16 -0400 Subject: [PATCH 29/47] default stdout allows pipining into e.g. bcftools sort --- experiments/bedtest.sh | 5 +++-- src/kplib/cli.rs | 4 ++-- src/kplib/vcfwriter.rs | 28 ++++++++++++++++++---------- src/main.rs | 9 +++------ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index f848ec3..862c79d 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -9,10 +9,11 @@ create() { --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 4 \ - --maxpaths 20000 --mapq 5 --hapsim 0.98 \ + --maxpaths 1000 --mapq 5 --hapsim 0.98 \ --chunksize 100 --maxhom 0 \ --sample doesthiswork \ - -o test_rs/hc.vcf --bed $bed + --bed $bed -o test_rs/hc.vcf.gz + #| bcftools sort -O z -o test_rs/hc.vcf.gz # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ # --bam /Users/english/code/kanpig/experiments/test_rs/GIABHG002.bam \ } diff --git a/src/kplib/cli.rs b/src/kplib/cli.rs index 7b3a40c..bf0daeb 100644 --- a/src/kplib/cli.rs +++ b/src/kplib/cli.rs @@ -26,9 +26,9 @@ pub struct IOParams { #[arg(short = 'f', long)] pub reference: std::path::PathBuf, - /// Output vcf (unsorted) + /// Output vcf (unsorted, uncompressed, default stdout) #[arg(short, long)] - pub out: std::path::PathBuf, + pub out: Option, /// Regions to analyze #[arg(long)] diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 29c34f0..cdc20e2 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -1,19 +1,19 @@ use crate::kplib::{metrics::GTstate, GenotypeAnno}; use std::collections::HashMap; use std::fs::File; -use std::io::BufWriter; +use std::io::{BufWriter, Write}; use std::path::PathBuf; use noodles_vcf::{ self as vcf, header::record::value::map::format, header::record::value::Map, - variant::io::Write, + variant::io::Write as vcfWrite, variant::record_buf::samples::{keys::Keys, Samples}, }; pub struct VcfWriter { - writer: vcf::io::Writer>, + writer: vcf::io::Writer>, header: vcf::Header, keys: Keys, pub gtcounts: HashMap, @@ -21,7 +21,11 @@ pub struct VcfWriter { impl VcfWriter { /// Given a path and a header, setup a new output vcf - pub fn new(out_path: &PathBuf, mut header: vcf::Header, sample: &Option) -> Self { + pub fn new( + out_path: &Option, + mut header: vcf::Header, + sample: &Option, + ) -> Self { // Ensure sample is correctly setup let sample_name = match sample { Some(name) => name.clone(), @@ -128,10 +132,14 @@ impl VcfWriter { all_formats.insert(ssid.to_string(), ssfmt); // Ready to make files - let out_buf = BufWriter::with_capacity( - page_size::get() * 500, - File::create(out_path).expect("Error Creating Output File"), - ); + let m_page = page_size::get() * 1000; + let out_buf: Box = match out_path { + Some(ref path) => { + let file = File::create(path).expect("Error Creating Output File"); + Box::new(BufWriter::with_capacity(m_page, file)) + } + None => Box::new(BufWriter::with_capacity(m_page, std::io::stdout())), + }; let mut writer = vcf::io::Writer::new(out_buf); let _ = writer.write_header(&header); @@ -185,11 +193,11 @@ lazy_static::lazy_static! { }; } -fn replace_iupac_inplace(sequence: &mut String) { +fn replace_iupac_inplace(sequence: &mut str) { unsafe { let bytes = sequence.as_bytes_mut(); bytes.iter_mut().for_each(|b| { - let t = IUPAC[*b as usize]; + let t = IUPAC[*b as usize]; if t != 0u8 { *b = t; } diff --git a/src/main.rs b/src/main.rs index efb3226..1c30b26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,7 +112,7 @@ fn main() { let wthread_num_variants = num_variants.clone(); let wthread_io = args.io.clone(); let wthread_header = input_header.clone(); - + let write_handler = std::thread::spawn(move || { let sty = ProgressStyle::with_template( " [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed", @@ -122,11 +122,8 @@ fn main() { let mut pbar: Option = None; let mut phase_group: i32 = 0; let mut completed_variants: u64 = 0; - let mut m_writer = VcfWriter::new( - &wthread_io.out, - wthread_header.clone(), - &wthread_io.sample, - ); + let mut m_writer = + VcfWriter::new(&wthread_io.out, wthread_header.clone(), &wthread_io.sample); loop { match result_receiver.recv() { From ded265729a926587e48d3fd9858d3d87b5f69532 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 18:08:19 -0400 Subject: [PATCH 30/47] cleaning --- src/kplib/vcfwriter.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index cdc20e2..adc7064 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -155,8 +155,9 @@ impl VcfWriter { *self.gtcounts.entry(annot.gt_state).or_insert(0) += 1; *annot.entry.samples_mut() = Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - //*annot.entry.reference_bases_mut() = replace_iupac(annot.entry.reference_bases()); + replace_iupac_inplace(annot.entry.reference_bases_mut()); + match self.writer.write_variant_record(&self.header, &annot.entry) { Ok(_) => {} Err(error) => panic!("Couldn't write record {:?}", error), @@ -204,13 +205,3 @@ fn replace_iupac_inplace(sequence: &mut str) { }); } } - -/*fn replace_iupac(sequence: &str) -> String { - let mut result = String::with_capacity(sequence.len()); - - for c in sequence.chars() { - result.push(REPLACEMENTS[c as usize]); - } - - result -}*/ From d2c5f362d371b93e4ffacb73695bcb0d53456061 Mon Sep 17 00:00:00 2001 From: Adam English Date: Sat, 25 May 2024 22:37:28 -0400 Subject: [PATCH 31/47] added iupac warning users will be informed once if there are any iupac bases changed in the output --- src/kplib/vcfwriter.rs | 13 +++++++++++-- src/main.rs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index adc7064..e613b98 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -17,6 +17,7 @@ pub struct VcfWriter { header: vcf::Header, keys: Keys, pub gtcounts: HashMap, + iupac_warned: bool, } impl VcfWriter { @@ -148,6 +149,7 @@ impl VcfWriter { header, keys, gtcounts: HashMap::new(), + iupac_warned: false, } } @@ -156,7 +158,11 @@ impl VcfWriter { *annot.entry.samples_mut() = Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - replace_iupac_inplace(annot.entry.reference_bases_mut()); + let changed = replace_iupac_inplace(annot.entry.reference_bases_mut()); + if changed && !self.iupac_warned { + warn!("Some IUPAC codes in REF sequences have been fixed"); + self.iupac_warned = true; + } match self.writer.write_variant_record(&self.header, &annot.entry) { Ok(_) => {} @@ -194,14 +200,17 @@ lazy_static::lazy_static! { }; } -fn replace_iupac_inplace(sequence: &mut str) { +fn replace_iupac_inplace(sequence: &mut str) -> bool { + let mut any_change = false; unsafe { let bytes = sequence.as_bytes_mut(); bytes.iter_mut().for_each(|b| { let t = IUPAC[*b as usize]; if t != 0u8 { + any_change = true; *b = t; } }); } + any_change } diff --git a/src/main.rs b/src/main.rs index 1c30b26..51e2f05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,9 +109,9 @@ fn main() { // This is the semaphore for the progress bar that communicates between main and writer let num_variants = Arc::new(Mutex::new(0)); - let wthread_num_variants = num_variants.clone(); let wthread_io = args.io.clone(); let wthread_header = input_header.clone(); + let wthread_num_variants = num_variants.clone(); let write_handler = std::thread::spawn(move || { let sty = ProgressStyle::with_template( From 5568247a9b791e049121217bdd257e4dcd3e117b Mon Sep 17 00:00:00 2001 From: Adam English Date: Sun, 26 May 2024 00:11:07 -0400 Subject: [PATCH 32/47] iupac fix Ns are allowed. warning printed at the end as to not interrupt progress bar --- src/kplib/vcfwriter.rs | 11 +++-------- src/main.rs | 3 +++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index e613b98..c54b91a 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -17,7 +17,7 @@ pub struct VcfWriter { header: vcf::Header, keys: Keys, pub gtcounts: HashMap, - iupac_warned: bool, + pub iupac_fixed: bool, } impl VcfWriter { @@ -149,7 +149,7 @@ impl VcfWriter { header, keys, gtcounts: HashMap::new(), - iupac_warned: false, + iupac_fixed: false, } } @@ -159,10 +159,7 @@ impl VcfWriter { Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); let changed = replace_iupac_inplace(annot.entry.reference_bases_mut()); - if changed && !self.iupac_warned { - warn!("Some IUPAC codes in REF sequences have been fixed"); - self.iupac_warned = true; - } + self.iupac_fixed = self.iupac_fixed | changed; match self.writer.write_variant_record(&self.header, &annot.entry) { Ok(_) => {} @@ -184,7 +181,6 @@ lazy_static::lazy_static! { arr[b'D' as usize] = b'A'; // A, G, or T arr[b'H' as usize] = b'A'; // A, C, or T arr[b'V' as usize] = b'A'; // A, C, or G - arr[b'N' as usize] = b'A'; // Any base (A, C, G, or T) arr[b'r' as usize] = b'a'; // a or g arr[b'y' as usize] = b'c'; // c or t arr[b's' as usize] = b'c'; // c or g @@ -195,7 +191,6 @@ lazy_static::lazy_static! { arr[b'd' as usize] = b'a'; // a, g, or t arr[b'h' as usize] = b'a'; // a, c, or t arr[b'v' as usize] = b'a'; // a, c, or g - arr[b'n' as usize] = b'a'; // any base (a, c, g, or t) arr }; } diff --git a/src/main.rs b/src/main.rs index 51e2f05..b72d2cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,6 +155,9 @@ fn main() { } } } + if m_writer.iupac_fixed { + warn!("Some IUPAC codes in REF sequences have been fixed in output"); + } info!("genotype counts: {:#?}", m_writer.gtcounts); }); From 8203bbfc56223fff5ef989c0fb756cfef553da1d Mon Sep 17 00:00:00 2001 From: Adam English Date: Sun, 26 May 2024 16:00:02 -0400 Subject: [PATCH 33/47] clean --- src/main.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index b72d2cc..043c102 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,22 +109,22 @@ fn main() { // This is the semaphore for the progress bar that communicates between main and writer let num_variants = Arc::new(Mutex::new(0)); - let wthread_io = args.io.clone(); - let wthread_header = input_header.clone(); - let wthread_num_variants = num_variants.clone(); + let wt_io = args.io.clone(); + let wt_header = input_header.clone(); + let wt_num_variants = num_variants.clone(); let write_handler = std::thread::spawn(move || { + let mut m_writer = VcfWriter::new(&wt_io.out, wt_header.clone(), &wt_io.sample); + + let mut pbar: Option = None; let sty = ProgressStyle::with_template( " [{elapsed_precise}] {bar:44.cyan/blue} > {pos} completed", ) .unwrap() .progress_chars("##-"); - let mut pbar: Option = None; + let mut phase_group: i32 = 0; let mut completed_variants: u64 = 0; - let mut m_writer = - VcfWriter::new(&wthread_io.out, wthread_header.clone(), &wthread_io.sample); - loop { match result_receiver.recv() { Ok(None) | Err(_) => { @@ -144,9 +144,9 @@ fn main() { } else { completed_variants += rsize; // check if the reader is finished so we can setup the pbar - let value_guard = wthread_num_variants.lock().unwrap(); - if *value_guard != 0 { - let t_bar = ProgressBar::new(*value_guard).with_style(sty.clone()); + let value = *wt_num_variants.lock().unwrap(); + if value != 0 { + let t_bar = ProgressBar::new(value).with_style(sty.clone()); t_bar.inc(completed_variants); pbar = Some(t_bar); } From b411f5ac1c370240d38fdf6ae7b86fc775db55eb Mon Sep 17 00:00:00 2001 From: Adam English Date: Sun, 26 May 2024 19:56:27 -0400 Subject: [PATCH 34/47] Fix #1 invalid reference base errors are caught and fixed --- experiments/bedtest.sh | 7 ++++--- src/kplib/vcfwriter.rs | 24 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 862c79d..4ef2cef 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -2,13 +2,14 @@ set -e bed=test_rs/test.chr20.bed create() { - #../target/release/kanpig \ + #time kanpig-v0.2.0-x86_64-apple-darwin/kanpig \ + #time ../target/release/kanpig \ time cargo run --release -- \ --input test_rs/test2.vcf.gz \ --bam /Users/english/code/kanpig/experiments/test_rs/NA24385.chr20.bam \ --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ - --sizesim 0.95 --seqsim 0.90 --threads 4 \ + --sizesim 0.95 --seqsim 0.90 --threads 5 \ --maxpaths 1000 --mapq 5 --hapsim 0.98 \ --chunksize 100 --maxhom 0 \ --sample doesthiswork \ @@ -40,7 +41,7 @@ bench_full() { } create -bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf +#bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf #tabix test_rs/hc.vcf.gz #bench_lite #bench_medium diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index c54b91a..99443b1 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -18,6 +18,7 @@ pub struct VcfWriter { keys: Keys, pub gtcounts: HashMap, pub iupac_fixed: bool, + buf: Vec, } impl VcfWriter { @@ -150,6 +151,7 @@ impl VcfWriter { keys, gtcounts: HashMap::new(), iupac_fixed: false, + buf: vec![], } } @@ -158,12 +160,22 @@ impl VcfWriter { *annot.entry.samples_mut() = Samples::new(self.keys.clone(), vec![annot.make_fields(phase_group)]); - let changed = replace_iupac_inplace(annot.entry.reference_bases_mut()); - self.iupac_fixed = self.iupac_fixed | changed; - - match self.writer.write_variant_record(&self.header, &annot.entry) { - Ok(_) => {} - Err(error) => panic!("Couldn't write record {:?}", error), + self.buf.clear(); + let mut tmp = vcf::io::Writer::new(&mut self.buf); + // Let noodles check it, first + match tmp.write_variant_record(&self.header, &annot.entry) { + Ok(_) => { + let _ = self.writer.get_mut().write_all(&self.buf); + } + Err(_) => { + let changed = replace_iupac_inplace(annot.entry.reference_bases_mut()); + self.iupac_fixed |= changed; + // Assuming it will work now + match self.writer.write_variant_record(&self.header, &annot.entry) { + Ok(_) => {} + Err(error) => panic!("Couldn't write record {:?}", error), + } + } } } } From 171d0b9c293c21f7b4a64431ca5c6231b0269c0c Mon Sep 17 00:00:00 2001 From: Adam English Date: Sun, 26 May 2024 21:59:57 -0400 Subject: [PATCH 35/47] documentation --- src/kplib/annotator.rs | 20 +++++- src/kplib/kmer.rs | 40 ++++++++++- src/kplib/metrics.rs | 82 ++++++++++++++++++++--- src/kplib/mod.rs | 4 +- src/kplib/pathscore.rs | 21 ------ src/kplib/regions.rs | 3 +- src/kplib/traverse.rs | 10 +-- src/kplib/{vcf_traits.rs => vcftraits.rs} | 0 8 files changed, 136 insertions(+), 44 deletions(-) rename src/kplib/{vcf_traits.rs => vcftraits.rs} (100%) diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index fa22e54..31132e0 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -36,6 +36,17 @@ pub struct GenotypeAnno { } impl GenotypeAnno { + /// Constructs a new `GenotypeAnno` instance based on the provided parameters. + /// + /// # Parameters + /// - `entry`: A `RecordBuf` representing the variant record. + /// - `var_idx`: A reference to the node index. + /// - `paths`: A slice of `PathScore` representing the paths. + /// - `coverage`: An unsigned 64-bit integer representing the coverage. + /// - `ploidy`: A reference to the `Ploidy` enum representing the ploidy level. + /// + /// # Returns + /// A `GenotypeAnno` instance initialized based on the provided parameters. pub fn new( entry: RecordBuf, var_idx: &NodeIndex, @@ -50,7 +61,14 @@ impl GenotypeAnno { } } - // These are tied to VcfWriter.keys + /// Generates fields for the `GenotypeAnno` instance. + /// These fields correspond to the keys defined in `VcfWriter`. + /// + /// # Parameters + /// - `phase_group`: An integer representing the phase group. + /// + /// # Returns + /// A vector containing optional `Value` instances representing the fields of the `GenotypeAnno`. pub fn make_fields(&self, phase_group: i32) -> Vec> { vec![ Some(Value::Genotype( diff --git a/src/kplib/kmer.rs b/src/kplib/kmer.rs index 7d0722e..140c10d 100644 --- a/src/kplib/kmer.rs +++ b/src/kplib/kmer.rs @@ -1,3 +1,11 @@ +/// Encodes a nucleotide character into its 2-bit representation. +/// +/// # Parameters +/// - `nuc`: A byte representing the nucleotide character to encode. +/// +/// # Returns +/// A 64-bit unsigned integer representing the binary encoding of the nucleotide. +/// ``` #[inline] fn encode_nuc(nuc: u8) -> u64 { match nuc.to_ascii_uppercase() { @@ -9,7 +17,28 @@ fn encode_nuc(nuc: u8) -> u64 { } } -/// Count kmers in a sequence +/// Converts a DNA sequence into k-mer counts. +/// Optionally compresses homopolymers before counting k-mers. +/// The k-mers are counted as either positive or negative counts based on the `negative` flag. +/// +/// # Parameters +/// - `sequence`: A slice of bytes representing the DNA sequence. +/// - `kmer`: The length of the k-mers to count. +/// - `negative`: If true, the k-mer counts are negative. +/// - `maxhom`: The maximum length of homopolymers; if non-zero, compresses homopolymers before counting k-mers. +/// +/// # Returns +/// A vector of k-mer counts represented as floats. +/// +/// # Example +/// ``` +/// let sequence = b"ACGTACGTAC"; +/// let kmer = 3; +/// let negative = false; +/// let maxhom = 2; +/// let kmer_counts = kanpig::seq_to_kmer(sequence, kmer, negative, maxhom); +/// assert_eq!(kmer_counts.len(), 64); // Example length for k=3 +/// ``` pub fn seq_to_kmer(sequence: &[u8], kmer: u8, negative: bool, maxhom: usize) -> Vec { if maxhom != 0 { return seq_to_kmer(&compress_homopolymer(sequence, maxhom), kmer, negative, 0); @@ -51,6 +80,15 @@ pub fn seq_to_kmer(sequence: &[u8], kmer: u8, negative: bool, maxhom: usize) -> kcounts } +/// Compresses sequences of repeated bytes (homopolymers) in the input vector. +/// Limits the length of any sequence of repeated bytes to `maxspan`. +/// +/// # Parameters +/// - `vector`: A slice of bytes to be compressed. +/// - `maxspan`: The maximum allowable length for sequences of repeated bytes. +/// +/// # Returns +/// A vector of bytes with homopolymer lengths limited to `maxspan`. pub fn compress_homopolymer(vector: &[u8], maxspan: usize) -> Vec { let mut result = Vec::new(); let mut count = 0; diff --git a/src/kplib/metrics.rs b/src/kplib/metrics.rs index 434b8bd..7614223 100644 --- a/src/kplib/metrics.rs +++ b/src/kplib/metrics.rs @@ -1,6 +1,17 @@ use ordered_float::OrderedFloat; -/// Canberra distance of featurized kmers +/// Computes the Canberra distance similarity between two featurized k-mer vectors. +/// The similarity is calculated as 1 minus the Canberra distance, providing a measure of similarity between 0 and 1. +/// +/// # Parameters +/// - `a`: A slice of floating-point numbers representing the first k-mer vector. +/// - `b`: A slice of floating-point numbers representing the second k-mer vector. +/// - `mink`: A floating-point threshold below which differences are ignored. +/// +/// # Returns +/// A floating-point value representing the similarity between the two vectors: +/// - 1.0 indicates identical vectors. +/// - 0.0 indicates no kmers or maximum dissimilarity. pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { let mut deno: f32 = 0.0; let mut neum: f32 = 0.0; @@ -14,12 +25,10 @@ pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { neum += (x - y).abs(); } - // no kmers if deno == 0.0 { return 0.0; } - // identical if neum == 0.0 { return 1.0; } @@ -27,8 +36,18 @@ pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { 1.0 - (neum / deno) } -/// size similarity of two variant sizes -/// sizes must be positive +/// Computes size similarity +/// The similarity is defined as the ratio of the smaller size to the larger size, +/// with special handling for cases where either size is zero. +/// +/// # Parameters +/// - `size_a`: The first size as a 64-bit unsigned integer. +/// - `size_b`: The second size as a 64-bit unsigned integer. +/// +/// # Returns +/// A floating-point value representing the similarity score between the two sizes. +/// - If both sizes are zero, the function returns 1.0. +/// - Otherwise, the similarity is calculated as the ratio of the smaller size to the larger size. pub fn sizesim(size_a: u64, size_b: u64) -> f32 { debug!("sz: {} <-> {}", size_a, size_b); if ((size_a == 0) || (size_b == 0)) && size_a == size_b { @@ -38,7 +57,18 @@ pub fn sizesim(size_a: u64, size_b: u64) -> f32 { / std::cmp::max(std::cmp::max(size_a, size_b), 1) as f32 } -/// do two intervals overlap +/// Determines if two intervals overlap. +/// Each interval is defined by a start and an end position. +/// The intervals overlap if the maximum of the start positions is less than the minimum of the end positions. +/// +/// # Parameters +/// - `s1`: The start position of the first interval as a 64-bit unsigned integer. +/// - `e1`: The end position of the first interval as a 64-bit unsigned integer. +/// - `s2`: The start position of the second interval as a 64-bit unsigned integer. +/// - `e2`: The end position of the second interval as a 64-bit unsigned integer. +/// +/// # Returns +/// A boolean value indicating whether the intervals overlap (`true`) or not (`false`). pub fn overlaps(s1: u64, e1: u64, s2: u64, e2: u64) -> bool { std::cmp::max(s1, s2) < std::cmp::min(e1, e2) } @@ -52,7 +82,19 @@ pub enum GTstate { //Hemi should be a thing } -/// Generate genotypes given observed allele coverages +/// Determines the genotype state based on coverage values for two alternate alleles. +/// The genotype state can be one of three: reference (Ref), heterozygous (Het), or homozygous (Hom). +/// If both coverage values are zero, the state is `Non`. +/// +/// # Parameters +/// - `alt1_cov`: The coverage value for the first alternate allele as a floating-point number. +/// - `alt2_cov`: The coverage value for the second alternate allele as a floating-point number. +/// +/// # Returns +/// A `GTstate` enum value representing the genotype state +/// +/// # Panics +/// This function will panic if an invalid state is encountered, which should be impossible under normal circumstances. pub fn genotyper(alt1_cov: f64, alt2_cov: f64) -> GTstate { if (alt1_cov + alt2_cov) == 0.0 { return GTstate::Non; @@ -72,7 +114,19 @@ pub fn genotyper(alt1_cov: f64, alt2_cov: f64) -> GTstate { ret } -/// Probabilities of each genotype given the allele coveages +/// Calculates genotype scores for three possible genotypes (reference, heterozygous, homozygous) +/// based on the coverage values for two alternate alleles. +/// The scores are adjusted based on the total coverage to account for lower coverage scenarios. +/// +/// # Parameters +/// - `alt1_cov`: The coverage value for the first alternate allele as a floating-point number. +/// - `alt2_cov`: The coverage value for the second alternate allele as a floating-point number. +/// +/// # Returns +/// An array of three floating-point values representing the log-probabilities for each genotype: +/// - The first value corresponds to the reference genotype. +/// - The second value corresponds to the heterozygous genotype. +/// - The third value corresponds to the homozygous genotype. fn genotype_scores(alt1_cov: f64, alt2_cov: f64) -> [f64; 3] { // Needs to be more pure for lower coverage let p_alt: &[f64] = if alt1_cov + alt2_cov < 10.0 { @@ -91,8 +145,16 @@ fn genotype_scores(alt1_cov: f64, alt2_cov: f64) -> [f64; 3] { ] } -/// Genotype quality: confidence in the assigned genotype -/// Sample quality: confidence that there is non-reference present +/// Calculates genotype quality (GQ) and sample quality (SQ) based on the coverage values for reference and alternate alleles. +/// +/// # Parameters +/// - `ref_cov`: The coverage value for the reference allele as a floating-point number. +/// - `alt_cov`: The coverage value for the alternate allele as a floating-point number. +/// +/// # Returns +/// A tuple containing two floating-point values: +/// - The first value is the genotype quality (GQ). +/// - The second value is the sample quality (SQ). pub fn genotype_quals(ref_cov: f64, alt_cov: f64) -> (f64, f64) { let mut gt_lplist = genotype_scores(ref_cov, alt_cov); gt_lplist.sort_by(|a, b| b.partial_cmp(a).unwrap()); diff --git a/src/kplib/mod.rs b/src/kplib/mod.rs index a9ecda4..3451793 100644 --- a/src/kplib/mod.rs +++ b/src/kplib/mod.rs @@ -42,8 +42,8 @@ pub use crate::kplib::traverse::brute_force_find_path; mod vargraph; pub use crate::kplib::vargraph::{VarNode, Variants}; -mod vcf_traits; -pub use crate::kplib::vcf_traits::{KdpVcf, Svtype}; +mod vcftraits; +pub use crate::kplib::vcftraits::{KdpVcf, Svtype}; mod vcfreader; pub use crate::kplib::vcfreader::VcfChunker; diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index 3cfeec1..1b259e6 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -37,27 +37,6 @@ impl Ord for PathScore { other_ordering => other_ordering, } } - // Sort by size then sequence - /* fn cmp(&self, other: &Self) -> Ordering { - match self - .align_pct - .partial_cmp(&other.align_pct) - .unwrap_or(Ordering::Equal) - { - Ordering::Equal => match self - .sizesim - .partial_cmp(&other.sizesim) - .unwrap_or(Ordering::Equal) - { - Ordering::Equal => self - .seqsim - .partial_cmp(&other.seqsim) - .unwrap_or(Ordering::Equal), - other_ordering => other_ordering, - }, - other_ordering => other_ordering, - } - }*/ } impl PartialOrd for PathScore { diff --git a/src/kplib/regions.rs b/src/kplib/regions.rs index f1e960f..26bf736 100644 --- a/src/kplib/regions.rs +++ b/src/kplib/regions.rs @@ -3,9 +3,10 @@ use noodles_vcf::header::record::value::{map::contig::Contig, Map}; use std::collections::HashMap; use std::collections::VecDeque; +use crate::kplib::BedParser; + pub type ContigMap = IndexMap>; pub type Regions = HashMap>; -use crate::kplib::BedParser; /// create a HashMap with keys of chromsome names and /// values a list of start, end positions with regions diff --git a/src/kplib/traverse.rs b/src/kplib/traverse.rs index 9df4820..fa183a6 100644 --- a/src/kplib/traverse.rs +++ b/src/kplib/traverse.rs @@ -52,10 +52,11 @@ pub fn brute_force_find_path( path: vec![], }; let mut stack: BinaryHeap = BinaryHeap::new(); - //let mut stack: Vec = Vec::new(); stack.push(start_path); + let mut best_path = PathScore::default(); let mut npaths = 0; + let partial_haps = target.partial_haplotypes(params.kmer); let snk_node = NodeIndex::new(graph.node_count() - 1); @@ -63,7 +64,6 @@ pub fn brute_force_find_path( // Throw all of cur_node's neighbors on the stack // Except snk_node, which is an indicator that the // current path has ended - //let mut any_push = false; for next_node in graph.edges(cur_path.node).filter_map(|edge| { if skip_edges.contains(&edge.id()) { None @@ -83,7 +83,6 @@ pub fn brute_force_find_path( npaths += 1; } else { let nsize = cur_path.size + graph.node_weight(next_node).unwrap().size; - //any_push = true; let mut npath = cur_path.path.clone(); npath.push(next_node); stack.push(PathNodeState { @@ -95,10 +94,6 @@ pub fn brute_force_find_path( } } - /*if any_push { - stack.sort_by_key(|node| std::cmp::Reverse(node.dist)); - }*/ - if npaths > params.maxpaths { break; } @@ -128,7 +123,6 @@ pub fn get_one_to_one( } else { None } - //(node.size >= size_range_lower) & (node.size <= size_range_upper) & ( }) .collect() } diff --git a/src/kplib/vcf_traits.rs b/src/kplib/vcftraits.rs similarity index 100% rename from src/kplib/vcf_traits.rs rename to src/kplib/vcftraits.rs From dae201b6874eb86513ad2ebea18b422d544c1825 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 27 May 2024 09:57:10 -0400 Subject: [PATCH 36/47] speed boost path-searching, only create path_kfeat if ever needed and reuse the PathNodeState size instead of recomputing. ~8% faster. --- experiments/bedtest.sh | 4 ++-- src/kplib/pathscore.rs | 39 ++++++++++++++++++++++----------------- src/kplib/traverse.rs | 20 ++++++++++---------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 4ef2cef..5b8abaa 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -3,8 +3,8 @@ bed=test_rs/test.chr20.bed create() { #time kanpig-v0.2.0-x86_64-apple-darwin/kanpig \ - #time ../target/release/kanpig \ - time cargo run --release -- \ + #time cargo run --release -- \ + time ../target/release/kanpig \ --input test_rs/test2.vcf.gz \ --bam /Users/english/code/kanpig/experiments/test_rs/NA24385.chr20.bam \ --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index 1b259e6..2fbebfb 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -61,26 +61,12 @@ impl PathScore { pub fn new( graph: &DiGraph, path: Vec, + path_size: i64, targets: &Vec, target_size: i64, params: &KDParams, ) -> Self { - let path_size: i64 = path - .iter() - .filter_map(|&node_index| graph.node_weight(node_index)) - .map(|x| x.size) - .sum(); - - let path_k: Vec = path - .iter() - .filter_map(|&node_index| graph.node_weight(node_index)) - .map(|x| x.kfeat.as_ref()) - .fold( - vec![0f32; 4_usize.pow(params.kmer.into())], - |acc: Vec, other: &Vec| { - acc.iter().zip(other).map(|(x, y)| x + y).collect() - }, - ); + let mut path_k: Option> = None; // Return the partials in order from all to least for hap_parts in targets { @@ -94,7 +80,26 @@ impl PathScore { continue; } - let seqsim = metrics::seqsim(&path_k, &hap_parts.kfeat, params.minkfreq as f32); + if !path_k.is_some() { + // only make if it is ever needed + path_k = Some( + path.iter() + .filter_map(|&node_index| graph.node_weight(node_index)) + .map(|x| x.kfeat.as_ref()) + .fold( + vec![0f32; 4_usize.pow(params.kmer.into())], + |acc: Vec, other: &Vec| { + acc.iter().zip(other).map(|(x, y)| x + y).collect() + }, + ), + ); + } + + let seqsim = metrics::seqsim( + path_k.as_ref().unwrap(), + &hap_parts.kfeat, + params.minkfreq as f32, + ); debug!("sqsim: {}", seqsim); if seqsim < params.seqsim { continue; diff --git a/src/kplib/traverse.rs b/src/kplib/traverse.rs index fa183a6..e8da502 100644 --- a/src/kplib/traverse.rs +++ b/src/kplib/traverse.rs @@ -45,20 +45,18 @@ pub fn brute_force_find_path( params: &KDParams, skip_edges: &[EdgeIndex], ) -> PathScore { - let start_path = PathNodeState { + let mut npaths = 0; + let mut best_path = PathScore::default(); + let snk_node = NodeIndex::new(graph.node_count() - 1); + let partial_haps = target.partial_haplotypes(params.kmer); + + let mut stack: BinaryHeap = BinaryHeap::new(); + stack.push(PathNodeState { dist: target.size.unsigned_abs(), // this is for sorting size: 0, node: NodeIndex::new(0), path: vec![], - }; - let mut stack: BinaryHeap = BinaryHeap::new(); - stack.push(start_path); - - let mut best_path = PathScore::default(); - let mut npaths = 0; - - let partial_haps = target.partial_haplotypes(params.kmer); - let snk_node = NodeIndex::new(graph.node_count() - 1); + }); while let Some(cur_path) = stack.pop() { // Throw all of cur_node's neighbors on the stack @@ -75,6 +73,7 @@ pub fn brute_force_find_path( best_path = best_path.max(PathScore::new( graph, cur_path.path.clone(), + cur_path.size, &partial_haps, target.size, params, @@ -114,6 +113,7 @@ pub fn get_one_to_one( let candidate = PathScore::new( graph, vec![target_node], + graph.node_weight(target_node).unwrap().size, vec![target.clone()].as_ref(), target.size, params, From 6b7b632918250de9885fa4036038aa9a35ba02a4 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 27 May 2024 10:22:24 -0400 Subject: [PATCH 37/47] slight optimization traverse is the performance critical section of the code, so any speed we can get from that helps --- src/kplib/metrics.rs | 20 ++++++++++---------- src/kplib/pathscore.rs | 4 ++-- src/kplib/traverse.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/kplib/metrics.rs b/src/kplib/metrics.rs index 7614223..d346d2e 100644 --- a/src/kplib/metrics.rs +++ b/src/kplib/metrics.rs @@ -15,14 +15,14 @@ use ordered_float::OrderedFloat; pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { let mut deno: f32 = 0.0; let mut neum: f32 = 0.0; + let mut total_d: f32; + for (&x, &y) in a.iter().zip(b.iter()) { - let d = x.abs() + y.abs(); - if d <= mink { - continue; + total_d = x.abs() + y.abs(); + if total_d > mink { + deno += total_d; + neum += (x - y).abs(); } - deno += d; - - neum += (x - y).abs(); } if deno == 0.0 { @@ -49,12 +49,12 @@ pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { /// - If both sizes are zero, the function returns 1.0. /// - Otherwise, the similarity is calculated as the ratio of the smaller size to the larger size. pub fn sizesim(size_a: u64, size_b: u64) -> f32 { - debug!("sz: {} <-> {}", size_a, size_b); - if ((size_a == 0) || (size_b == 0)) && size_a == size_b { + if size_a == 0 && size_b == 0 { return 1.0; } - std::cmp::max(std::cmp::min(size_a, size_b), 1) as f32 - / std::cmp::max(std::cmp::max(size_a, size_b), 1) as f32 + let min_size = size_a.min(size_b).max(1) as f32; + let max_size = size_a.max(size_b).max(1) as f32; + min_size / max_size } /// Determines if two intervals overlap. diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index 2fbebfb..98a4f79 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -75,7 +75,7 @@ impl PathScore { } let sizesim = metrics::sizesim(path_size.unsigned_abs(), hap_parts.size.unsigned_abs()); - debug!("szsim: {}", sizesim); + //debug!("szsim: {}", sizesim); if sizesim < params.sizesim { continue; } @@ -100,7 +100,7 @@ impl PathScore { &hap_parts.kfeat, params.minkfreq as f32, ); - debug!("sqsim: {}", seqsim); + //debug!("sqsim: {}", seqsim); if seqsim < params.seqsim { continue; } diff --git a/src/kplib/traverse.rs b/src/kplib/traverse.rs index e8da502..642f165 100644 --- a/src/kplib/traverse.rs +++ b/src/kplib/traverse.rs @@ -78,7 +78,6 @@ pub fn brute_force_find_path( target.size, params, )); - debug!("best path {:?}", best_path); npaths += 1; } else { let nsize = cur_path.size + graph.node_weight(next_node).unwrap().size; @@ -98,6 +97,7 @@ pub fn brute_force_find_path( } } + debug!("best path {:?}", best_path); best_path } From 3dce53cf232b0ebbea09b788cf17dd37ab038b79 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 27 May 2024 10:43:51 -0400 Subject: [PATCH 38/47] sizesim fix --- src/kplib/metrics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kplib/metrics.rs b/src/kplib/metrics.rs index d346d2e..8a2e505 100644 --- a/src/kplib/metrics.rs +++ b/src/kplib/metrics.rs @@ -49,7 +49,7 @@ pub fn seqsim(a: &[f32], b: &[f32], mink: f32) -> f32 { /// - If both sizes are zero, the function returns 1.0. /// - Otherwise, the similarity is calculated as the ratio of the smaller size to the larger size. pub fn sizesim(size_a: u64, size_b: u64) -> f32 { - if size_a == 0 && size_b == 0 { + if size_a == size_b { return 1.0; } let min_size = size_a.min(size_b).max(1) as f32; From 862f409cb85994e2e2a11b388578cac9e8168578 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 27 May 2024 12:46:39 -0400 Subject: [PATCH 39/47] default buffer on stdout --- src/kplib/vcfwriter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kplib/vcfwriter.rs b/src/kplib/vcfwriter.rs index 99443b1..b4ff4e6 100644 --- a/src/kplib/vcfwriter.rs +++ b/src/kplib/vcfwriter.rs @@ -134,13 +134,13 @@ impl VcfWriter { all_formats.insert(ssid.to_string(), ssfmt); // Ready to make files - let m_page = page_size::get() * 1000; let out_buf: Box = match out_path { Some(ref path) => { + let m_page = page_size::get() * 1000; let file = File::create(path).expect("Error Creating Output File"); Box::new(BufWriter::with_capacity(m_page, file)) } - None => Box::new(BufWriter::with_capacity(m_page, std::io::stdout())), + None => Box::new(BufWriter::new(std::io::stdout())), }; let mut writer = vcf::io::Writer::new(out_buf); let _ = writer.write_header(&header); From f4db6c69b7e3ba8d232ff36ce8c57b965bb577c2 Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 27 May 2024 14:19:32 -0400 Subject: [PATCH 40/47] clippy --- src/kplib/pathscore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index 98a4f79..cbdaf1d 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -80,7 +80,7 @@ impl PathScore { continue; } - if !path_k.is_some() { + if path_k.is_none() { // only make if it is ever needed path_k = Some( path.iter() From 7d7b23b12db9453321aed162f89890bb75b041e1 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 28 May 2024 00:13:43 -0400 Subject: [PATCH 41/47] argument validation, fewer happarts errors for invalid arguments. haplotype parts capped to 100 --- src/kplib/cli.rs | 30 ++++++++++++++++++++++++++++++ src/kplib/haplotype.rs | 2 +- src/kplib/vcftraits.rs | 6 +++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/kplib/cli.rs b/src/kplib/cli.rs index bf0daeb..d3c8256 100644 --- a/src/kplib/cli.rs +++ b/src/kplib/cli.rs @@ -165,6 +165,36 @@ impl ArgParser { warn!("--kmer above 8 becomes memory intensive"); } + if self.kd.kmer < 1 { + error!("--kmer must be at least 1"); + is_ok = false; + } + + if self.kd.sizesim < 0.0 || self.kd.sizesim > 1.0 { + error!("--sizesim must be between 0.0 and 1.0"); + is_ok = false; + } + + if self.kd.seqsim < 0.0 || self.kd.seqsim > 1.0 { + error!("--seqsim must be between 0.0 and 1.0"); + is_ok = false; + } + + if self.kd.hapsim < 0.0 || self.kd.hapsim >= 1.0 { + error!("--hapsim must be between 0.0 and 1.0"); + is_ok = false; + } + + if self.kd.maxpaths < 1 { + error!("--maxpaths must be at least 1"); + is_ok = false; + } + + if self.io.threads < 1 { + error!("--threads must be at least 1"); + is_ok = false; + } + is_ok } } diff --git a/src/kplib/haplotype.rs b/src/kplib/haplotype.rs index 116b7dd..982c78f 100644 --- a/src/kplib/haplotype.rs +++ b/src/kplib/haplotype.rs @@ -52,7 +52,7 @@ impl Haplotype { pub fn partial_haplotypes(&self, kmer: u8) -> Vec { // Consider putting these as parameters. But really we just need smarter clustering let max_fns = 3; // Most number of false-negatives we'll attempt to apply to the graph - let max_parts = 500; // Most number of pileups we'll even attempt to split + let max_parts = 100; // Most number of pileups we'll even attempt to split // If its more than this, we can't evaluate the region reasonably, // anyway. let mut ret = vec![]; diff --git a/src/kplib/vcftraits.rs b/src/kplib/vcftraits.rs index 79cc9df..2b1d23b 100644 --- a/src/kplib/vcftraits.rs +++ b/src/kplib/vcftraits.rs @@ -41,7 +41,7 @@ pub trait KdpVcf { impl KdpVcf for RecordBuf { /// Convert variant sequence to Kfeat fn to_kfeat(&self, kmer: u8, maxhom: usize) -> (Vec, i64) { - let ref_seq = self.reference_bases().to_string(); + let ref_seq = self.reference_bases(); let alt_seq = self.get_alt(); let size = alt_seq.len() as i64 - ref_seq.len() as i64; @@ -101,8 +101,8 @@ impl KdpVcf for RecordBuf { let alts = self.alternate_bases(); match alts.len() { 0 => ".", - _ => alts.iter().next().expect("I just checked").unwrap(), - //.to_string(), // I don't like all this String when str should be simplier + 1 => alts.iter().next().expect("I just checked").unwrap(), + _ => panic!("multi-allelic records not supported"), } } } From 48bd6beef3626a93dba34e58f89b0fb9a5840338 Mon Sep 17 00:00:00 2001 From: Adam English Date: Wed, 29 May 2024 02:14:45 -0400 Subject: [PATCH 42/47] compressing notebooks --- experiments/Development.ipynb | 99 +++++------------------------------ 1 file changed, 13 insertions(+), 86 deletions(-) diff --git a/experiments/Development.ipynb b/experiments/Development.ipynb index d3c4e11..b393e46 100644 --- a/experiments/Development.ipynb +++ b/experiments/Development.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 179, + "execution_count": null, "id": "e63d221c-abfa-4d5d-b5df-94d8b33398fb", "metadata": {}, "outputs": [], @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 180, + "execution_count": null, "id": "5a3008ef-fee9-45b7-8e86-aad8cd022e13", "metadata": {}, "outputs": [], @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": null, "id": "70f8aaef-09a9-4aa2-b6a8-3e2e0da5fa4d", "metadata": {}, "outputs": [], @@ -35,72 +35,30 @@ }, { "cell_type": "code", - "execution_count": 182, + "execution_count": null, "id": "46f87f7c-833e-484d-8815-811f50ccb94f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "904" - ] - }, - "execution_count": 182, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "len(data)" ] }, { "cell_type": "code", - "execution_count": 176, + "execution_count": null, "id": "dc315ca1-8e34-4754-aec2-21de1614cf31", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PearsonRResult(statistic=0.953444540233332, pvalue=0.0)\n" - ] - } - ], + "outputs": [], "source": [ "print(pearsonr(data['seqsim'], data['pj']))" ] }, { "cell_type": "code", - "execution_count": 183, + "execution_count": null, "id": "3a1650ae-4f28-4f0b-b433-8da399f35c2b", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Text(0.5, 1.0, 'Kmer estimate of sequence similarity'),\n", - " Text(0.5, 0, 'Sequence Similarity'),\n", - " Text(0, 0.5, 'Kmer Count Similarity')]" - ] - }, - "execution_count": 183, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import seaborn as sb\n", "p = sb.scatterplot(data=data, x='seqsim', y='pj', hue='bin_len')\n", @@ -109,51 +67,20 @@ }, { "cell_type": "code", - "execution_count": 166, + "execution_count": null, "id": "768670af-cc99-4264-b966-6874bee29473", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PearsonRResult(statistic=0.9361582055174525, pvalue=0.0)\n" - ] - } - ], + "outputs": [], "source": [ "print(pearsonr(data['unroll'], data['pj']))" ] }, { "cell_type": "code", - "execution_count": 178, + "execution_count": null, "id": "b0373a3f-55ce-47ec-9867-07bfff9c99e0", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Text(0.5, 1.0, 'Kmer estimate of unroll sequence similarity'),\n", - " Text(0.5, 0, 'Unroll Sequence Similarity'),\n", - " Text(0, 0.5, 'Kmer Count Similarity')]" - ] - }, - "execution_count": 178, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import seaborn as sb\n", "p = sb.scatterplot(data=data, x='unroll', y='pj', hue='bin_len')\n", From 79bacb677c731bc3adf6e04128af1f91802dbedb Mon Sep 17 00:00:00 2001 From: Adam English Date: Mon, 10 Jun 2024 14:13:12 -0400 Subject: [PATCH 43/47] hapsim correction 1.0 is allowed --- src/kplib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kplib/cli.rs b/src/kplib/cli.rs index d3c8256..4885467 100644 --- a/src/kplib/cli.rs +++ b/src/kplib/cli.rs @@ -180,7 +180,7 @@ impl ArgParser { is_ok = false; } - if self.kd.hapsim < 0.0 || self.kd.hapsim >= 1.0 { + if self.kd.hapsim < 0.0 || self.kd.hapsim > 1.0 { error!("--hapsim must be between 0.0 and 1.0"); is_ok = false; } From d8b53c39365c4ba818e1cbfe60c20af289de1664 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 11 Jun 2024 11:27:47 -0400 Subject: [PATCH 44/47] annotation fixing partial is now just a binary. FT flags are fixed. SQ is now informative --- experiments/bedtest.sh | 14 ++++++------- src/kplib/annotator.rs | 46 ++++++++++++++++++++++++++++-------------- src/kplib/metrics.rs | 10 ++------- src/kplib/pathscore.rs | 19 ++++++++--------- src/kplib/traverse.rs | 2 -- 5 files changed, 48 insertions(+), 43 deletions(-) diff --git a/experiments/bedtest.sh b/experiments/bedtest.sh index 5b8abaa..579293f 100644 --- a/experiments/bedtest.sh +++ b/experiments/bedtest.sh @@ -2,18 +2,18 @@ set -e bed=test_rs/test.chr20.bed create() { + #time ../target/release/kanpig \ #time kanpig-v0.2.0-x86_64-apple-darwin/kanpig \ - #time cargo run --release -- \ - time ../target/release/kanpig \ + time cargo run --release -- \ --input test_rs/test2.vcf.gz \ --bam /Users/english/code/kanpig/experiments/test_rs/NA24385.chr20.bam \ --reference /Users/english/code/references/grch38/GRCh38_1kg_mainchrs.fa \ --sizemin 50 \ --sizesim 0.95 --seqsim 0.90 --threads 5 \ - --maxpaths 1000 --mapq 5 --hapsim 0.98 \ + --maxpaths 1000 --mapq 20 --hapsim 0.98 \ --chunksize 100 --maxhom 0 \ --sample doesthiswork \ - --bed $bed -o test_rs/hc.vcf.gz + --bed $bed -o test_rs/hc.vcf #| bcftools sort -O z -o test_rs/hc.vcf.gz # --bed /Users/english/code/kanpig/test/GRCh38_HG002-T2TQ100-V1.0_stvar.benchmark.bed \ # --bam /Users/english/code/kanpig/experiments/test_rs/GIABHG002.bam \ @@ -41,8 +41,8 @@ bench_full() { } create -#bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf -#tabix test_rs/hc.vcf.gz +bcftools sort -O z -o test_rs/hc.vcf.gz test_rs/hc.vcf +tabix test_rs/hc.vcf.gz #bench_lite -#bench_medium +bench_medium #bench_full diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 31132e0..5a40528 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -10,13 +10,13 @@ use noodles_vcf::{ bitflags! { pub struct FiltFlags: u32 { - const PASS = 0x0; // passing - const GTMISMATCH = 0x1; // genotype from AD doesn't match path genotype - const LOWGQ = 0x2; // genotype quality below 5 - const LOWCOV = 0x4; // coverage below 5 - const LOWSQ = 0x8; // sample quality below below 5 (only on non-ref genotypes) - const LOWALT = 0x16; // alt coverage below 5 (only on non-ref genotypes) - const PARTIAL = 0x32; // best scoring path only used part of the haplotype + const PASS = 0b00000000; // passing + const GTMISMATCH = 0b00000001; // genotype from AD doesn't match path genotype + const LOWGQ = 0b00000010; // genotype quality below 5 + const LOWCOV = 0b00000100; // coverage below 5 + const LOWSQ = 0b00001000; // sample quality below below 5 (only on non-ref genotypes) + const LOWALT = 0b00010000; // alt coverage below 5 (only on non-ref genotypes) + const PARTIAL = 0b00100000; // best scoring path only used part of the haplotype } } @@ -96,19 +96,35 @@ fn diploid( let path1 = &paths[0]; let path2 = &paths[1]; - let (gt_str, gt_path, alt_cov) = + let (gt_str, gt_path, alt_cov, full_target) = match (path1.path.contains(var_idx), path2.path.contains(var_idx)) { (true, true) if path1 != path2 => ( "1|1", metrics::GTstate::Hom, (path1.coverage.unwrap() + path2.coverage.unwrap()) as f64, + path1.full_target && path2.full_target, ), // sometimes I used the same path twice - (true, true) => ("1|1", metrics::GTstate::Hom, path1.coverage.unwrap() as f64), - (true, false) => ("1|0", metrics::GTstate::Het, path1.coverage.unwrap() as f64), - (false, true) => ("0|1", metrics::GTstate::Het, path2.coverage.unwrap() as f64), - (false, false) if coverage != 0 => ("0|0", metrics::GTstate::Ref, 0.0), - (false, false) => ("./.", metrics::GTstate::Non, 0.0), + (true, true) => ( + "1|1", + metrics::GTstate::Hom, + path1.coverage.unwrap() as f64, + path1.full_target, + ), + (true, false) => ( + "1|0", + metrics::GTstate::Het, + path1.coverage.unwrap() as f64, + path1.full_target, + ), + (false, true) => ( + "0|1", + metrics::GTstate::Het, + path2.coverage.unwrap() as f64, + path2.full_target, + ), + (false, false) if coverage != 0 => ("0|0", metrics::GTstate::Ref, 0.0, true), + (false, false) => ("./.", metrics::GTstate::Non, 0.0, true), }; let ref_cov = (coverage as f64) - alt_cov; @@ -147,7 +163,7 @@ fn diploid( filt |= FiltFlags::LOWALT; } } - if (path1.align_pct != 1.0) || (path2.align_pct != 1.0) { + if !full_target { filt |= FiltFlags::PARTIAL; } @@ -221,7 +237,7 @@ fn haploid( filt |= FiltFlags::LOWALT; } } - if path1.align_pct != 1.0 { + if !path1.full_target { filt |= FiltFlags::PARTIAL; } diff --git a/src/kplib/metrics.rs b/src/kplib/metrics.rs index 8a2e505..21ec09f 100644 --- a/src/kplib/metrics.rs +++ b/src/kplib/metrics.rs @@ -157,19 +157,13 @@ fn genotype_scores(alt1_cov: f64, alt2_cov: f64) -> [f64; 3] { /// - The second value is the sample quality (SQ). pub fn genotype_quals(ref_cov: f64, alt_cov: f64) -> (f64, f64) { let mut gt_lplist = genotype_scores(ref_cov, alt_cov); - gt_lplist.sort_by(|a, b| b.partial_cmp(a).unwrap()); + let sq = f64::min((-10.0 * (gt_lplist[0] - (gt_lplist[1] + gt_lplist[2]))).abs(), 100.0); + gt_lplist.sort_by(|a, b| b.partial_cmp(a).unwrap()); let best = gt_lplist[0]; let second_best = gt_lplist[1]; let gq = f64::min(-10.0 * (second_best - best), 100.0); - let mut gt_sum = 0.0; - for gt in >_lplist { - gt_sum += 10.0_f64.powf(*gt); - } - - let gt_sum_log = gt_sum.log10(); - let sq = f64::min((-10.0 * (gt_lplist[0] - gt_sum_log)).abs(), 100.0); (gq, sq) } diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index cbdaf1d..d905e02 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -8,14 +8,14 @@ pub struct PathScore { pub seqsim: f32, pub coverage: Option, pub path: Vec, - pub align_pct: f32, // percent of the haplotype used + pub full_target: bool, // is this path against the full target } impl Eq for PathScore {} impl PartialEq for PathScore { fn eq(&self, other: &Self) -> bool { - self.align_pct == other.align_pct + self.full_target == other.full_target && self.sizesim == other.sizesim && self.seqsim == other.seqsim } @@ -25,8 +25,8 @@ impl Ord for PathScore { // Sort by mean of size and sequence fn cmp(&self, other: &Self) -> Ordering { match self - .align_pct - .partial_cmp(&other.align_pct) + .full_target + .partial_cmp(&other.full_target) .unwrap_or(Ordering::Equal) { Ordering::Equal => { @@ -52,7 +52,7 @@ impl Default for PathScore { sizesim: 0.0, seqsim: 0.0, coverage: None, - align_pct: 0.0, + full_target: false, } } } @@ -62,14 +62,13 @@ impl PathScore { graph: &DiGraph, path: Vec, path_size: i64, - targets: &Vec, - target_size: i64, + targets: &[Haplotype], params: &KDParams, ) -> Self { let mut path_k: Option> = None; // Return the partials in order from all to least - for hap_parts in targets { + for (i, hap_parts) in targets.iter().enumerate() { if path_size.signum() != hap_parts.size.signum() { continue; } @@ -111,9 +110,7 @@ impl PathScore { sizesim, seqsim, coverage: None, - align_pct: (hap_parts.size.unsigned_abs() as f32 - / target_size.unsigned_abs() as f32) - .abs(), + full_target: i == 0, }; } PathScore::default() diff --git a/src/kplib/traverse.rs b/src/kplib/traverse.rs index 642f165..9220408 100644 --- a/src/kplib/traverse.rs +++ b/src/kplib/traverse.rs @@ -75,7 +75,6 @@ pub fn brute_force_find_path( cur_path.path.clone(), cur_path.size, &partial_haps, - target.size, params, )); npaths += 1; @@ -115,7 +114,6 @@ pub fn get_one_to_one( vec![target_node], graph.node_weight(target_node).unwrap().size, vec![target.clone()].as_ref(), - target.size, params, ); if candidate.seqsim > 0.0 { From b389ab4669376cc9918376284ab258ce6dbcdac7 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 11 Jun 2024 12:02:50 -0400 Subject: [PATCH 45/47] SQ fix Better represents status of is an alt present --- src/kplib/metrics.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/kplib/metrics.rs b/src/kplib/metrics.rs index 21ec09f..9b69688 100644 --- a/src/kplib/metrics.rs +++ b/src/kplib/metrics.rs @@ -157,7 +157,13 @@ fn genotype_scores(alt1_cov: f64, alt2_cov: f64) -> [f64; 3] { /// - The second value is the sample quality (SQ). pub fn genotype_quals(ref_cov: f64, alt_cov: f64) -> (f64, f64) { let mut gt_lplist = genotype_scores(ref_cov, alt_cov); - let sq = f64::min((-10.0 * (gt_lplist[0] - (gt_lplist[1] + gt_lplist[2]))).abs(), 100.0); + + let mut gt_sum = 0.0; + for gt in >_lplist { + gt_sum += 10.0_f64.powf(*gt); + } + let gt_sum_log = gt_sum.log10(); + let sq = f64::min((-10.0 * (gt_lplist[0] - gt_sum_log)).abs(), 100.0); gt_lplist.sort_by(|a, b| b.partial_cmp(a).unwrap()); let best = gt_lplist[0]; From b4b572b11073bc4a676a8f86d445c0f47e0f1340 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 11 Jun 2024 14:14:27 -0500 Subject: [PATCH 46/47] experimental ignore errors When the hap1 is non-ref, has low coverage, and isn't applied to any paths, use the ref/alt coverage to genotype. --- src/kplib/annotator.rs | 16 ++++++++++++++-- src/kplib/pathscore.rs | 9 ++++++++- src/kplib/vargraph.rs | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/kplib/annotator.rs b/src/kplib/annotator.rs index 5a40528..cd9f6c5 100644 --- a/src/kplib/annotator.rs +++ b/src/kplib/annotator.rs @@ -96,7 +96,7 @@ fn diploid( let path1 = &paths[0]; let path2 = &paths[1]; - let (gt_str, gt_path, alt_cov, full_target) = + let (mut gt_str, gt_path, alt_cov, full_target) = match (path1.path.contains(var_idx), path2.path.contains(var_idx)) { (true, true) if path1 != path2 => ( "1|1", @@ -126,9 +126,21 @@ fn diploid( (false, false) if coverage != 0 => ("0|0", metrics::GTstate::Ref, 0.0, true), (false, false) => ("./.", metrics::GTstate::Non, 0.0, true), }; - let ref_cov = (coverage as f64) - alt_cov; let gt_obs = metrics::genotyper(ref_cov, alt_cov); + + // Alt haplotypes without a path can be filtered if we think it is + // more likely to be an error + let bcov = path1.coverage.unwrap() as f64; + if !path1.is_ref && *path1 == PathScore::default() && bcov < ref_cov && gt_path != gt_obs { + gt_str = match gt_obs { + metrics::GTstate::Ref => "0|0", + metrics::GTstate::Het => "0|1", + metrics::GTstate::Hom => "1|1", + _ => "./.", + }; + } + // we're now assuming that ref/alt are the coverages used for these genotypes. no bueno let (gq, sq) = metrics::genotype_quals(ref_cov, alt_cov); diff --git a/src/kplib/pathscore.rs b/src/kplib/pathscore.rs index d905e02..19be4ce 100644 --- a/src/kplib/pathscore.rs +++ b/src/kplib/pathscore.rs @@ -9,6 +9,7 @@ pub struct PathScore { pub coverage: Option, pub path: Vec, pub full_target: bool, // is this path against the full target + pub is_ref: bool, // This path tried to use a reference allele } impl Eq for PathScore {} @@ -53,6 +54,7 @@ impl Default for PathScore { seqsim: 0.0, coverage: None, full_target: false, + is_ref: true, } } } @@ -111,8 +113,13 @@ impl PathScore { seqsim, coverage: None, full_target: i == 0, + is_ref: false, }; } - PathScore::default() + PathScore { + is_ref: false, + ..Default::default() + } + //PathScore::default() } } diff --git a/src/kplib/vargraph.rs b/src/kplib/vargraph.rs index 4d8d6fd..bb07a86 100644 --- a/src/kplib/vargraph.rs +++ b/src/kplib/vargraph.rs @@ -131,6 +131,7 @@ impl Variants { if hap.n == 0 { return PathScore { coverage: Some(hap.coverage), + is_ref: true, ..Default::default() }; } From 3b10b52a82e96d3d769a5085686dd1b0c00d17c8 Mon Sep 17 00:00:00 2001 From: Adam English Date: Tue, 11 Jun 2024 15:55:40 -0400 Subject: [PATCH 47/47] version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a274f41..81759fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kanpig" -version = "0.2.1-dev" +version = "0.3.0" edition = "2021" [dependencies]