diff --git a/Cargo.toml b/Cargo.toml index 8ab386045e0f8e12380479442f2efdf72fcb87cb..c256cf0d2ab12a3f8ba40447dc76bfc08edbcc65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ edition = "2021" [dependencies] arrayvec = { version = "0.7.4", features = ["serde"] } clap = { version = "4.5.0", features = ["derive"] } -colored = "2.1.0" itertools = "0.12.1" lexical-sort = "0.3.1" num-traits = "0.2.18" @@ -28,6 +27,8 @@ unicode-width = "0.1.11" float_eq = { version = "1.0.1", features = ["derive"] } snafu = "0.8.2" serde_json = { version = "1.0.127", features = ["preserve_order"] } +color-print = { git = "https://gitlab.com/iago-lito/color-print", branch = "dev", version = "0.3.6" } +color-print-proc-macro = { git = "https://gitlab.com/iago-lito/color-print", branch = "dev", version = "0.3.6" } [dev-dependencies] rand = "0.8.5" diff --git a/src/bin/aphid/main.rs b/src/bin/aphid/main.rs index 415a42f1a9d4fe8ef4fa379d715b9fe47cc2b06c..bbeb0821e05049b81015f65a4ec4c7cb940c486f 100644 --- a/src/bin/aphid/main.rs +++ b/src/bin/aphid/main.rs @@ -15,7 +15,7 @@ use aphid::{ Config, GeneTree, GeneTriplet, GenesForest, LocalGeneTriplet, VERSION, }; use clap::Parser; -use colored::Colorize; +use color_print::{ceprintln, cformat, cprintln}; use float_eq::float_eq; use serde_json::json; use snafu::{ensure, Snafu}; @@ -33,10 +33,10 @@ fn main() { println!("Running Aphid v{VERSION}."); match run() { Ok(()) => { - println!("Success. {}", "✓".bold().green()); + cprintln!("Success. <bold,green>✓</>"); } Err(e) => { - eprintln!("{} {e}", "🗙 Error:".bold().red()); + ceprintln!("<bold,red>🗙 Error:</> {e}"); process::exit(1); } } @@ -100,13 +100,13 @@ fn run() -> Result<(), Error> { fn read_inputs(args: &args::Args, interner: &mut Interner) -> Result<(Config, GenesForest), Error> { let path = &args.config; - println!("Read config from {}.", format!("{path:?}").blue()); + cprintln!("Read config from <b>{}</>.", path.display()); let config = Config::from_file(path, interner)?; let path = &config.trees; - println!("Read gene trees from {}:", format!("{path:?}").blue()); + cprintln!("Read gene trees from <b>{}</>:", path.display()); let forest = GenesForest::from_file(path, interner)?; - println!(" Found {} gene trees.", forest.len()); + cprintln!(" Found <g>{}</> gene trees.", forest.len()); Ok((config, forest)) } @@ -151,19 +151,15 @@ fn prepare_output(args: &args::Args) -> Result<PathBuf, Error> { let output = path::absolute(&args.output)?; match (output.exists(), args.force) { (true, true) => { - println!( - " {} existing folder: {}.", - "Removing".yellow(), - format!("{}", output.display()).blue() + cprintln!( + " <y>Removing</> existing folder: <b>{}</>.", + output.display() ); fs::remove_dir_all(&output)?; } (true, false) => OutputExistsErr { path: &output }.fail()?, (false, _) => { - println!( - " Creating empty folder: {}.", - format!("{}", output.display()).blue() - ); + cprintln!(" Creating empty folder: <b>{}</>.", output.display()); } }; fs::create_dir_all(&output)?; @@ -173,10 +169,7 @@ fn prepare_output(args: &args::Args) -> Result<PathBuf, Error> { fn write_config(output: &Path, config: &Config, interner: &Interner) -> Result<(), Error> { let path = output.join(out::CONFIG); - println!( - " Write full configuration to {}.", - format!("{}", path.display()).blue() - ); + cprintln!(" Write full configuration to <b>{}</>.", path.display()); let mut file = File::create(path)?; writeln!(file, "{:#}", config.resolve(interner).json())?; Ok(()) @@ -272,13 +265,13 @@ fn topology_filter<'i>( } // Report. - println!( - " {n_excluded} tree{s_were} excluded from analysis based on their topology.", + cprintln!( + " <g>{n_excluded}</> tree{s_were} excluded from analysis based on their topology.", s_were = if n_excluded > 1 { "s were" } else { " was" }, ); if let Some(min) = unrl { - println!( - " {n_unresolved} included triplet{s_were} unresolved (internal length <= {min}).", + cprintln!( + " <g>{n_unresolved}</> included triplet{s_were} unresolved (internal length ⩽ {min}).", s_were = if n_unresolved > 1 { "s were" } else { " was" }, ); } @@ -307,12 +300,12 @@ fn geometry_filter( let (triplet_longer, imbalance) = imbalance(triplet_means_sum, rest_means_sum); let (t, r) = ("'triplet' section", "'outgroup' and 'other' section"); let (small, large) = if triplet_longer { (t, r) } else { (r, t) }; - println!( - " Branch lengths in {large} are on average {imbalance} times longer \ + cprintln!( + " Branch lengths in {large} are on average <g>{imbalance}</> times longer \ than in {small}." ); let global_shape = triplet_means_sum / rest_means_sum; - println!(" Mean tree shape: {global_shape}"); + cprintln!(" Mean tree shape: <g>{global_shape}</>"); for i in topology_included { let tree = &pruned[i]; @@ -344,8 +337,8 @@ fn geometry_filter( // Report. if n_excluded > 0 { - println!( - " --> {n_excluded} tree{s_were} excluded from analysis based on their geometry.", + cprintln!( + " --> <g>{n_excluded}</> tree{s_were} excluded from analysis based on their geometry.", s_were = if n_excluded > 1 { "s were" } else { " was" }, ); } else { @@ -366,7 +359,7 @@ fn final_tree_selection( .map(|&i| details[i].mean_lengths.total.unwrap()) .summed_mean::<usize>(); - println!(" mean branch length accross trees: l = {global_mean}"); + cprintln!(" mean branch length accross trees: l = <g>{global_mean}</>"); let mut triplets = Vec::new(); for i in included { let local = local_triplets[i].take().unwrap(); // Exists since included. @@ -382,9 +375,9 @@ fn final_tree_selection( fn write_detail(output: &Path, details: &[detail::Tree]) -> Result<(), Error> { let path = output.join(out::DETAIL); - println!( - "Write full trees analysis/filtering detail to {}.", - format!("{}", path.display()).blue() + cprintln!( + "Write full trees analysis/filtering detail to <b>{}</>.", + path.display() ); let mut file = File::create(path)?; writeln!(file, "{:#}", json!(details))?; @@ -396,8 +389,8 @@ fn display_summary(included: &[usize], details: &[detail::Tree], config: &Config let s = |n| if n > 1 { "s" } else { "" }; let n = details.iter().map(|d| u64::from(!d.triplet.included)).sum(); - println!( - " - {n} triplet{} rejected (incomplete or non-monophyletic).", + cprintln!( + " - <g>{n}</> triplet{} rejected (incomplete or non-monophyletic).", s(n), ); @@ -406,8 +399,8 @@ fn display_summary(included: &[usize], details: &[detail::Tree], config: &Config .iter() .filter_map(|d| d.triplet.analysis.as_ref().map(|a| u64::from(!a.resolved))) .sum(); - println!( - " - {n} triplet{} considered unresolved (internal branch length ⩽ {min}).", + cprintln!( + " - <g>{n}</> triplet{} considered unresolved (internal branch length ⩽ {min}).", s(n), ); } @@ -416,8 +409,8 @@ fn display_summary(included: &[usize], details: &[detail::Tree], config: &Config .iter() .map(|d| u64::from(!d.outgroup.included)) .sum(); - println!( - " - {n} outgroup{} rejected (empty or non-monophyletic).", + cprintln!( + " - <g>{n}</> outgroup{} rejected (empty or non-monophyletic).", s(n) ); @@ -425,8 +418,8 @@ fn display_summary(included: &[usize], details: &[detail::Tree], config: &Config .iter() .map(|d| d.top.as_ref().map_or(1, |top| (!top.included).into())) .sum::<u64>(); - println!( - " - {n} tree top{} rejected (non-root LCA(triplet, outgroup){}).", + cprintln!( + " - <g>{n}</> tree top{} rejected (non-root LCA(triplet, outgroup){}).", s(n), if config.filters.triplet_other_monophyly { " or non-monophyletic (triplet, other)" @@ -440,15 +433,15 @@ fn display_summary(included: &[usize], details: &[detail::Tree], config: &Config .iter() .map(|d| u64::from(!d.included_geometry)) .sum(); - println!( - " - {n} tree shape{} rejected \ + cprintln!( + " - <g>{n}</> tree shape{} rejected \ (triplet/outgroup imbalance larger than {max} times the average).", s(n) ); } let n = included.len() as u64; - println!(" => {n} tree{} kept for analysis.", s(n)); + cprintln!(" ==> <s,g>{n}</> tree{} kept for analysis.", s(n)); } fn learn(triplets: &[GeneTriplet], config: &Config) -> Result<(), Error> { @@ -456,8 +449,8 @@ fn learn(triplets: &[GeneTriplet], config: &Config) -> Result<(), Error> { let parms = &config.search.init_parms; let (opt, opt_lnl) = optimize_likelihood(triplets, parms, &config.search)?; - println!("Optimized ln-likelihood: {opt_lnl}"); - println!("Optimized parameters: {opt:#?}\n"); + cprintln!("Optimized ln-likelihood: <g>{opt_lnl}</>"); + println!("Optimized parameters: {}\n", opt.colored()); // Smoke test: // TODO: turn into actual testing by including 'official' example data. @@ -479,7 +472,8 @@ enum Error { Io { source: std::io::Error }, #[snafu(transparent)] Check { source: aphid::config::check::Error }, - #[snafu(display("Output folder already exists: {}.", format!("{}", path.display()).blue()))] + // TODO: have color_print feature this without the need to allocate an intermediate string? + #[snafu(display("Output folder already exists: {}.", cformat!("<b>{}</>", path.display())))] OutputExists { path: PathBuf }, #[snafu(transparent)] ForestParse { diff --git a/src/config/check.rs b/src/config/check.rs index 16bb02207142c12663d24c3a2cb6e522306ae931..0d855f6f97695975435d715fd50a5ecd2137e474 100644 --- a/src/config/check.rs +++ b/src/config/check.rs @@ -8,7 +8,6 @@ use std::{ }; use arrayvec::ArrayVec; -use colored::Colorize; use regex::Regex; use snafu::{ensure, Snafu}; @@ -212,14 +211,12 @@ impl BfgsConfig { let linsearch_trace_path = check_path(linsearch_trace)?; if let (None, Some(path)) = (&main_trace_path, &linsearch_trace) { return err!(( - "A path is specified with {linopt} \ + "A path is specified with <b>`search.bfgs.linsearch_trace`</> \ to log the detailed trace of BFGS linear search during each step, \ but none is specified to log the global trace of each step. \ - Consider setting {mainopt} option.\n\ - The path given was: {path}", - linopt = "`search.bfgs.linsearch_trace`".blue(), - mainopt = "`search.bfgs.main_trace`".blue(), - path = format!("{:?}", path.to_string_lossy()).black(), + Consider setting <b>`search.bfgs.main_trace`</> option.\n\ + The path given was: <k>{}</>", + path.display(), )) .fail(); } diff --git a/src/model/parameters.rs b/src/model/parameters.rs index bc59a80716189e603c0a68824a1eb564c98c03f3..b570a860c337e227886df3a96c6c65760e52212b 100644 --- a/src/model/parameters.rs +++ b/src/model/parameters.rs @@ -4,6 +4,7 @@ use std::fmt::{self, Display}; use arrayvec::ArrayVec; +use color_print::cwriteln; use serde::Serialize; use tch::Tensor; @@ -102,3 +103,41 @@ impl fmt::Debug for ValGrad { write!(f, "{self}") } } + +// Colored parameters for stdout. +pub struct Colored<'p, F: Num + Display + Sized>(&'p Parameters<F>); + +impl<F: Num + Display + Sized + fmt::Debug> Display for Colored<'_, F> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Parameters {{")?; + let Parameters { + theta, + tau_1, + tau_2, + p_ab, + p_ac, + p_bc, + p_ancient, + gf_times, + } = &self.0; + cwriteln!(f, " theta: <b>{theta:?}</>,")?; + cwriteln!(f, " tau_1: <b>{tau_1:?}</>,")?; + cwriteln!(f, " tau_2: <b>{tau_2:?}</>,")?; + cwriteln!(f, " p_ab: <b>{p_ab:?}</>,")?; + cwriteln!(f, " p_ac: <b>{p_ac:?}</>,")?; + cwriteln!(f, " p_bc: <b>{p_bc:?}</>,")?; + cwriteln!(f, " p_ancient: <b>{p_ancient:?}</>,")?; + writeln!(f, " gf_times: [")?; + for t in &gf_times.0 { + cwriteln!(f, " <b>{t}</>,")?; + } + writeln!(f, " ]")?; + writeln!(f, "}}") + } +} + +impl<F: Num + Display + Sized> Parameters<F> { + pub fn colored(&self) -> Colored<F> { + Colored(self) + } +}