From 26f01f1a39c675e9bb881fd60a01f0006771b407 Mon Sep 17 00:00:00 2001 From: "chinmay@wsl" <> Date: Tue, 20 Oct 2026 16:52:29 +0530 Subject: [PATCH] feat: add phylo member, added an iterator that emits phylo tree from multi tree newick files --- Cargo.toml | 2 +- microBioRust/cleaned_dna.fasta | 6 ++ microBioRust/new_output_embl.gbk | 104 +++++++++++++++++++++++++ phylo/Cargo.toml | 20 +++++ phylo/src/lib.rs | 11 +++ phylo/src/parser.rs | 126 +++++++++++++++++++++++++++++++ phylo/tests/data/basic_trees.nw | 8 ++ 7 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 microBioRust/cleaned_dna.fasta create mode 100644 microBioRust/new_output_embl.gbk create mode 100644 phylo/Cargo.toml create mode 100644 phylo/src/lib.rs create mode 100644 phylo/src/parser.rs create mode 100644 phylo/tests/data/basic_trees.nw diff --git a/Cargo.toml b/Cargo.toml index b53c35c..ceb1d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "microBioRust", "heatmap", - "seqmetrics"] + "seqmetrics", "phylo"] resolver = "2" [profile.dev] diff --git a/microBioRust/cleaned_dna.fasta b/microBioRust/cleaned_dna.fasta new file mode 100644 index 0000000..53dfabd --- /dev/null +++ b/microBioRust/cleaned_dna.fasta @@ -0,0 +1,6 @@ +>seq1 +ATGC-ATGCATGCATGC +>seq2 +ATGCAATGCTTGCATGC +>seq3 +TTGCAATCCATGCAAGC diff --git a/microBioRust/new_output_embl.gbk b/microBioRust/new_output_embl.gbk new file mode 100644 index 0000000..78a61bd --- /dev/null +++ b/microBioRust/new_output_embl.gbk @@ -0,0 +1,104 @@ +LOCUS source_1 928 bp DNA linear CON 20-OCT-2026 +DEFINITION Escherichia coli K-12 substr. MG1655. +ACCESSION source_1 +KEYWORDS . +SOURCE Escherichia coli K-12 substr. MG1655 + ORGANISM Escherichia coli K-12 substr. MG1655 +FEATURES Location/Qualifiers + source 1..910 + /organism="K-12 substr. MG1655" + /mol_type="DNA" + /strain="K-12 substr. MG1655" + /db_xref="PRJNA57779" + gene complement(1..354) + /locus_tag="b3304" + CDS complement(1..354) + /locus_tag="b3304" + /codon_start="1" + /gene="rplR" + /translation="MDKKSARIRRATRARRKLQELGATRLVVHRTPRHIYAQVIAPNGS + LVAASTVEKAIAEQLKYTGNKDAAAAVGKAVAERALEKGIKDVSFDRSGFQYHGRVQAL + DAAREAGLQ" + /product="50S ribosomal subunit protein L18" + gene complement(364..897) + /locus_tag="b3305" + CDS complement(364..897) + /locus_tag="b3305" + /codon_start="1" + /gene="rplF" + /translation="MSRVAKAPVVVPAGVDVKINGQVITIKGKNGELTRTLNDAVEVKH + NTLTFGPRDGYADGWAQAGTARALLNSMVIGVTEGFTKKLQLVGVGYRAAVKGNVINLS + GFSHPVDHQLPAGITAECPTQTEIVLKGADKQVIGQVAADLRAYRRPEPYKGKGVRYAD + VVRTKEAKK" + /product="50S ribosomal subunit protein L6" +ORIGIN + 1 acctctacct tagaactgaa ggccagcttc acgggcagca tctgccagtg cctggacacg + 61 accatgatat tggaacccgg aacggtcaaa ggatacatct ttgatgcctt tttccagagc + 121 gcgttcagcg acagctttac ccacagctgc agccgcgtct ttgttaccgg tgtacttcag + 181 ttgttcagcg atagcttttt ctacagtaga agcagctacc agaacttcag aaccgttcgg + 241 tgcaattacc tgtgcgtaaa tgtgacgcgg ggtacgatgt accaccaggc gagttgcgcc + 301 cagctcctgg agcttgcggc gtgcgcgggt cgcacgacgg atacgagcag atttcttatc + 361 catagtgtta ccttacttct tcttagcctc tttggtacgc acgacttcgt cggcgtaacg + 421 aacacccttg cctttataag gctcaggacg acggtaggcg cgcagatccg ctgcaacctg + 481 gccgatcacc tgcttatcag cgcctttcag cacgatttca gtctgagtcg gacattcagc + 541 agtgataccc gcaggcagct gatggtcaac aggatgagag aaacccagag acaggttaat + 601 cacattgcct ttaaccgctg cacggtaacc tacaccaacc agctgcagct tcttagtgaa + 661 gccttcggta acaccgataa ccattgagtt cagcagggca cgcgcggtac cagcctgtgc + 721 ccaaccgtct gcgtaaccat cacgcggacc gaaggtcagg gtattatctg catgtttaac + 781 ttcaacagca tcgttgagag tacgagtcag ctcgccgttt ttacctttga tcgtaataac + 841 ctgaccgttg atttttacgt caacgccggc aggaacaacg accggtgctt tagcaacacg + 901 agacattttt tcc + +// +LOCUS source_1 928 bp DNA linear CON 20-OCT-2026 +DEFINITION Escherichia coli K-12 substr. MG1655. +ACCESSION source_1 +KEYWORDS . +SOURCE Escherichia coli K-12 substr. MG1655 + ORGANISM Escherichia coli K-12 substr. MG1655 +FEATURES Location/Qualifiers + source 1..910 + /organism="K-12 substr. MG1655" + /mol_type="DNA" + /strain="K-12 substr. MG1655" + /db_xref="PRJNA57779" + gene complement(1..354) + /locus_tag="b3304" + CDS complement(1..354) + /locus_tag="b3304" + /codon_start="1" + /gene="rplR" + /translation="MDKKSARIRRATRARRKLQELGATRLVVHRTPRHIYAQVIAPNGS + LVAASTVEKAIAEQLKYTGNKDAAAAVGKAVAERALEKGIKDVSFDRSGFQYHGRVQAL + DAAREAGLQ" + /product="50S ribosomal subunit protein L18" + gene complement(364..897) + /locus_tag="b3305" + CDS complement(364..897) + /locus_tag="b3305" + /codon_start="1" + /gene="rplF" + /translation="MSRVAKAPVVVPAGVDVKINGQVITIKGKNGELTRTLNDAVEVKH + NTLTFGPRDGYADGWAQAGTARALLNSMVIGVTEGFTKKLQLVGVGYRAAVKGNVINLS + GFSHPVDHQLPAGITAECPTQTEIVLKGADKQVIGQVAADLRAYRRPEPYKGKGVRYAD + VVRTKEAKK" + /product="50S ribosomal subunit protein L6" +ORIGIN + 1 acctctacct tagaactgaa ggccagcttc acgggcagca tctgccagtg cctggacacg + 61 accatgatat tggaacccgg aacggtcaaa ggatacatct ttgatgcctt tttccagagc + 121 gcgttcagcg acagctttac ccacagctgc agccgcgtct ttgttaccgg tgtacttcag + 181 ttgttcagcg atagcttttt ctacagtaga agcagctacc agaacttcag aaccgttcgg + 241 tgcaattacc tgtgcgtaaa tgtgacgcgg ggtacgatgt accaccaggc gagttgcgcc + 301 cagctcctgg agcttgcggc gtgcgcgggt cgcacgacgg atacgagcag atttcttatc + 361 catagtgtta ccttacttct tcttagcctc tttggtacgc acgacttcgt cggcgtaacg + 421 aacacccttg cctttataag gctcaggacg acggtaggcg cgcagatccg ctgcaacctg + 481 gccgatcacc tgcttatcag cgcctttcag cacgatttca gtctgagtcg gacattcagc + 541 agtgataccc gcaggcagct gatggtcaac aggatgagag aaacccagag acaggttaat + 601 cacattgcct ttaaccgctg cacggtaacc tacaccaacc agctgcagct tcttagtgaa + 661 gccttcggta acaccgataa ccattgagtt cagcagggca cgcgcggtac cagcctgtgc + 721 ccaaccgtct gcgtaaccat cacgcggacc gaaggtcagg gtattatctg catgtttaac + 781 ttcaacagca tcgttgagag tacgagtcag ctcgccgttt ttacctttga tcgtaataac + 841 ctgaccgttg atttttacgt caacgccggc aggaacaacg accggtgctt tagcaacacg + 901 agacattttt tcc + +// diff --git a/phylo/Cargo.toml b/phylo/Cargo.toml new file mode 100644 index 0000000..cd3de36 --- /dev/null +++ b/phylo/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "microBioRust-phylo" +license = "MIT" +keywords = ["bioinformatics","micro","bio","genomics","sequence-analysis"] +description = "Microbiology friendly bioinformatics Rust functions" +categories = ["science::bioinformatics::sequence-analysis", "science::bioinformatics::genomics", "science::bioinformatics","visualization","data-structures"] +readme = "README.md" +exclude = [".git",".gitignore"] +repository = "https://github.com/LCrossman/microBioRust" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.wasm-pack.profile.release] +opt-level = "z" + +[dependencies] +phylo = "6.0.0" diff --git a/phylo/src/lib.rs b/phylo/src/lib.rs new file mode 100644 index 0000000..fd98ab7 --- /dev/null +++ b/phylo/src/lib.rs @@ -0,0 +1,11 @@ +/*! + The aim of this crate is to provide Microbiology friendly Rust functions for bioinformatics. + + With phylo module you should be able to + + 1. Parse newick trees into Rust equivalent data structure + 2. Conversion of newick tree format to other file formats like phyloXML + 3. Neighbor joining, bootstrapping from many files, visualizing, rooting etc. +*/ +#![allow(non_snake_case)] +pub mod parser; diff --git a/phylo/src/parser.rs b/phylo/src/parser.rs new file mode 100644 index 0000000..9aee9cf --- /dev/null +++ b/phylo/src/parser.rs @@ -0,0 +1,126 @@ +use std::io::{BufRead, Error, ErrorKind}; + +use phylo::prelude::*; + +struct Scanner { + col: usize, + line: usize, + reader: R, + scratch: Vec, +} + +impl Scanner { + fn new(reader: R) -> Self { + Self { + reader, + line: 1, + col: 0, + scratch: vec![], + } + } +} + +impl Iterator for Scanner { + type Item = std::io::Result>; + + fn next(&mut self) -> Option { + loop { + let buffer = self.reader.fill_buf().ok()?; + let mut consumed_amt = 0; + let mut flush = false; + let buffer_len = buffer.len(); + + if buffer.is_empty() { + if self.scratch.is_empty() { + return None; // reached EOF without any issues + } else { + return Some(Err(Error::new( + ErrorKind::InvalidData, + format!( + "Incomplete newick tree found at line: {}, col:{}", + self.line, self.col + ), + ))); + } + } + + while consumed_amt < buffer_len { + let b = buffer[consumed_amt]; + consumed_amt += 1; + + if b == b'\n' { + self.line += 1; + self.col = 0; + } else { + self.col += 1; + } + + if b.is_ascii_whitespace() { + continue; + } + self.scratch.push(b); + if b == b';' { + flush = true; + break; + } + } + self.reader.consume(consumed_amt); + if flush { + return Some(Ok(std::mem::take(&mut self.scratch))); + } + } + } +} + +struct PhyloParser { + scanner: Scanner, +} + +impl PhyloParser { + fn new(scanner: Scanner) -> Self { + Self { scanner } + } +} + +impl Iterator for PhyloParser { + type Item = std::io::Result; + + fn next(&mut self) -> Option { + let bytes = match self.scanner.next()? { + Ok(b) => b, + Err(e) => return Some(Err(e)), + }; + Some( + PhyloTree::from_newick(&bytes) + .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string())), + ) + } +} + +#[cfg(test)] +mod tests { + use std::{fs::File, io::BufReader}; + + use super::*; + + fn get_scanner(file_name: &str) -> Scanner> { + let path = format!("{}/tests/data/{}", env!("CARGO_MANIFEST_DIR"), file_name); + let file_handle = File::open(path).unwrap(); + Scanner::new(BufReader::new(file_handle)) + } + + #[test] + fn test_scanner_newick_multitree_file() { + let mut scanner = get_scanner("basic_trees.nw"); + let first_tree = scanner.next().unwrap().unwrap(); + assert_eq!(String::from_utf8(first_tree).unwrap(), "(,,(,));") + } + + #[test] + fn test_basic_parsing() { + let scanner = get_scanner("basic_trees.nw"); + let mut parser = PhyloParser::new(scanner); + let first_tree = parser.next().unwrap().unwrap(); + assert_eq!(first_tree.to_newick().to_string(), "(,,(,));"); + } +} diff --git a/phylo/tests/data/basic_trees.nw b/phylo/tests/data/basic_trees.nw new file mode 100644 index 0000000..7263852 --- /dev/null +++ b/phylo/tests/data/basic_trees.nw @@ -0,0 +1,8 @@ +(,,(,)); +(A,B,(C,D)); +(A,B,(C,D)E)F; +(:0.1,:0.2,(:0.3,:0.4):0.5); +(:0.1,:0.2,(:0.3,:0.4):0.5):0.0; +(A:0.1,B:0.2,(C:0.3,D:0.4):0.5); +(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5)F; +((B:0.2,(C:0.3,D:0.4)E:0.5)F:0.1)A;