use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use crate::agg;
use crate::errors;
use crate::io::fasta;
use crate::rmq;
use crate::taxon;
use crate::taxon::TaxonId;
use crate::tree;
#[derive(Debug, StructOpt)]
#[structopt(verbatim_doc_comment)]
pub struct TaxaToAgg {
#[structopt(short = "s", long = "scored")]
pub scored: bool,
#[structopt(short = "r", long = "ranked")]
pub ranked_only: bool,
#[structopt(
short = "m",
long = "method",
default_value = "tree",
possible_values = &Method::variants()
)]
pub method: Method,
#[structopt(
short = "a",
long = "aggregate",
default_value = "hybrid",
possible_values = &Strategy::variants()
)]
pub strategy: Strategy,
#[structopt(short = "f", long = "factor", default_value = "0.25")]
pub factor: f32,
#[structopt(short = "l", long = "lower-bound", default_value = "0")]
pub lower_bound: f32,
#[structopt(parse(from_os_str))]
pub taxon_file: PathBuf,
}
pub fn taxa2agg(args: TaxaToAgg) -> errors::Result<()> {
let taxons = taxon::read_taxa_file(args.taxon_file)?;
let tree = taxon::TaxonTree::new(&taxons);
let by_id = taxon::TaxonList::new(taxons);
let snapping = tree.snapping(&by_id, args.ranked_only);
let aggregator: errors::Result<Box<dyn agg::Aggregator>> = match (args.method, args.strategy) {
(Method::RangeMinimumQuery, Strategy::MaximumRootToLeafPath) => {
Ok(Box::new(rmq::rtl::RTLCalculator::new(tree.root, &by_id)))
}
(Method::RangeMinimumQuery, Strategy::LowestCommonAncestor) => {
Ok(Box::new(rmq::lca::LCACalculator::new(tree)))
}
(Method::RangeMinimumQuery, Strategy::Hybrid) => {
writeln!(
&mut io::stderr(),
"Warning: this is a hybrid between LCA/MRTL, not LCA*/MRTL"
)
.unwrap();
Ok(Box::new(rmq::mix::MixCalculator::new(tree, args.factor)))
}
(Method::Tree, Strategy::LowestCommonAncestor) => {
Ok(Box::new(tree::lca::LCACalculator::new(tree.root, &by_id)))
}
(Method::Tree, Strategy::Hybrid) => Ok(Box::new(tree::mix::MixCalculator::new(
tree.root,
&by_id,
args.factor,
))),
(m, s) => Err(errors::ErrorKind::InvalidInvocation(format!(
"{:?} and {:?} cannot be combined",
m, s
))
.into()),
};
let aggregator = aggregator?;
fn with_score(pair: &str) -> errors::Result<(TaxonId, f32)> {
let split = pair.split('=').collect::<Vec<_>>();
if split.len() != 2 {
return Err("Taxon without score".into());
}
Ok((split[0].parse::<TaxonId>()?, split[1].parse::<f32>()?))
}
fn not_scored(tid: &str) -> errors::Result<(TaxonId, f32)> {
Ok((tid.parse::<TaxonId>()?, 1.0))
}
let parser = if args.scored { with_score } else { not_scored };
let mut writer = fasta::Writer::new(io::stdout(), "\n", false);
for record in fasta::Reader::new(io::stdin(), false).records() {
let record = record?;
let taxons = record
.sequence
.iter()
.map(|s| parser(s))
.collect::<errors::Result<Vec<(TaxonId, f32)>>>()?;
let counts = agg::count(taxons.into_iter().filter(|&(tid, _)| tid != 0));
let counts = agg::filter(counts, args.lower_bound);
writer.write_record(fasta::Record {
header: record.header,
sequence: if counts.is_empty() {
vec!["1".into()]
} else {
let aggregate = aggregator.aggregate(&counts)?;
vec![snapping[aggregate].unwrap().to_string()]
},
})?;
}
Ok(())
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Method {
Tree,
RangeMinimumQuery,
}
static METHODS: &[&str] = &["tree", "rmq"];
impl Method {
fn variants() -> &'static [&'static str] {
METHODS
}
}
impl FromStr for Method {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"tree" => Ok(Method::Tree),
"rmq" => Ok(Method::RangeMinimumQuery),
_ => Err(ErrorKind::ParseMethodError(s.to_string()).into()),
}
}
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Strategy {
LowestCommonAncestor,
Hybrid,
MaximumRootToLeafPath,
}
static STRATEGIES: &[&str] = &["lca*", "hybrid", "mrtl"];
impl Strategy {
fn variants() -> &'static [&'static str] {
STRATEGIES
}
}
impl FromStr for Strategy {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"lca*" => Ok(Strategy::LowestCommonAncestor),
"hybrid" => Ok(Strategy::Hybrid),
"mrtl" => Ok(Strategy::MaximumRootToLeafPath),
_ => Err(ErrorKind::ParseStrategyError(s.to_string()).into()),
}
}
}
error_chain! {
errors {
ParseMethodError(method: String) {
description("Unparseable method")
display("Unparseable method: {}", method)
}
ParseStrategyError(strategy: String) {
description("Unparseable strategy")
display("Unparseable strategy: {}", strategy)
}
}
}