extern crate speedytree;
use clap::Parser;
use speedytree::DistanceMatrix;
use speedytree::{Canonical, Hybrid, NeighborJoiningSolver, RapidBtrees};
use std::{
error,
io::{self, Write},
process,
};
type ResultBox<T> = std::result::Result<T, Box<dyn error::Error>>;
#[derive(Debug)]
pub struct Config {
pub(crate) algo: Algorithm,
pub(crate) threads: usize,
pub(crate) chunk_size: usize,
pub(crate) naive_percentage: usize,
}
impl Config {
pub fn build(args: Args) -> ResultBox<Config> {
let algo = if args.naive {
Algorithm::Naive
} else if args.rapidnj {
Algorithm::RapidNJ
} else {
Algorithm::Hybrid
};
let cores = args.cores;
let chunk_size = args.chunk_size;
if chunk_size == 0 {
return Err("Chunk size cannot be 0".into());
}
let naive_percentage = args.naive_percentage;
if naive_percentage == 0 {
return Err("Naive percentage cannot be 0".into());
}
if naive_percentage == 100 {
return Err("Naive percentage cannot be 100".into());
}
Ok(Config {
algo,
threads: cores,
chunk_size,
naive_percentage,
})
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(long, conflicts_with = "hybrid", conflicts_with = "naive")]
rapidnj: bool,
#[arg(long, conflicts_with = "hybrid", conflicts_with = "rapidnj")]
naive: bool,
#[arg(long, conflicts_with = "rapidnj", conflicts_with = "naive")]
hybrid: bool,
#[arg(short, long, default_value = "1")]
cores: usize,
#[arg(long, default_value = "30", conflicts_with = "naive")]
chunk_size: usize,
#[arg(
long,
default_value = "90",
conflicts_with = "naive",
conflicts_with = "rapidnj"
)]
naive_percentage: usize,
}
#[derive(Debug, Clone)]
pub enum Algorithm {
Naive,
RapidNJ,
Hybrid,
}
pub fn run(config: Config) {
rayon::ThreadPoolBuilder::new()
.num_threads(config.threads)
.build_global()
.unwrap();
let reader = io::stdin().lock();
let d = DistanceMatrix::read_from_phylip(reader).unwrap_or_else(|err| {
eprintln!("{err}");
process::exit(1);
});
let d = match config.algo {
Algorithm::Naive => NeighborJoiningSolver::<Canonical>::default(d).solve(),
Algorithm::RapidNJ => {
NeighborJoiningSolver::<RapidBtrees>::build(d, config.chunk_size).solve()
}
Algorithm::Hybrid => {
let naive_steps = d.size() * config.naive_percentage / 100;
NeighborJoiningSolver::<Hybrid>::build(d, config.chunk_size, naive_steps).solve()
}
};
let graph = d.unwrap_or_else(|err| {
eprintln!("{err}");
process::exit(1);
});
io::stdout()
.write_all(speedytree::to_newick(&graph).as_bytes())
.unwrap_or_else(|err| {
eprintln!("{err}");
process::exit(1);
});
}
fn main() {
let args = Args::parse();
let config = Config::build(args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
run(config);
}