Fri, 02 Dec 2022 21:20:04 +0200
Command line parameter passing simplifications and make `-o` required.
Remove separate Configuration, using CommandLineArgs directly.
README.md | file | annotate | diff | comparison | revisions | |
src/main.rs | file | annotate | diff | comparison | revisions | |
src/run.rs | file | annotate | diff | comparison | revisions |
--- a/README.md Fri Dec 02 18:14:03 2022 +0200 +++ b/README.md Fri Dec 02 21:20:04 2022 +0200 @@ -54,11 +54,14 @@ To compile the code and run the experiments in the manuscript, use ```console -cargo run --release +cargo run --release -- -o results ``` When doing this for the first time, several dependencies will be downloaded. -The `--release` flag is required to build optimised high performance code. -Without that flag the performance will be significantly worse. +The double-dash (`--`) separates the arguments of Cargo and this software, +`pointsource_algs`. The `--release` option to Cargo is required for `rustc` to +build optimised high performance code. Without that flag the performance will +be significantly worse. The `-o results` option tells `pointsource_algs` to +write results in the `results` directory. The option is required. Alternatively, you may build the executable with ```console @@ -66,14 +69,15 @@ ``` and then run it with ``` -target/release/pointsource_algs +target/release/pointsource_algs -o results ``` ### Documentation -Use the `--help` option to get an extensive listing of command line options. -If using `cargo` to run the executable, you have to pass any arguments to this -program after a double-dash: +Use the `--help` option to get an extensive listing of command line options to +customise algorithm parameters and the experiments performed. As above with +`-o`, if using `cargo` to run the executable, you have to pass any arguments +to `pointsource_algs` after a double-dash: ```console cargo run --release -- --help ```
--- a/src/main.rs Fri Dec 02 18:14:03 2022 +0200 +++ b/src/main.rs Fri Dec 02 21:20:04 2022 +0200 @@ -14,11 +14,11 @@ #![feature(drain_filter)] use clap::Parser; +use serde::{Serialize, Deserialize}; +use serde_json; use itertools::Itertools; -use serde_json; use std::num::NonZeroUsize; -use alg_tools::iterate::Verbose; use alg_tools::parallelism::{ set_num_threads, set_max_threads, @@ -43,7 +43,6 @@ use types::{float, ClapFloat}; use run::{ DefaultAlgorithm, - Configuration, PlotLevel, Named, AlgorithmConfig, @@ -54,7 +53,7 @@ use DefaultAlgorithm::*; /// Command line parameters -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Serialize)] #[clap( about = env!("CARGO_PKG_DESCRIPTION"), author = env!("CARGO_PKG_AUTHORS"), @@ -63,12 +62,14 @@ after_long_help = "", )] pub struct CommandLineArgs { - #[arg(long, short = 'm', value_name = "M")] + #[arg(long, short = 'm', value_name = "M", default_value_t = 2000)] /// Maximum iteration count - max_iter : Option<usize>, + max_iter : usize, #[arg(long, short = 'n', value_name = "N")] /// Output status every N iterations. Set to 0 to disable. + /// + /// The default is to output status based on logarithmic increments. verbose_iter : Option<usize>, #[arg(long, short = 'q')] @@ -94,12 +95,12 @@ #[arg(value_name = "JSON_FILE", long)] saved_algorithm : Vec<String>, - /// Write plots for every verbose iteration + /// Plot saving scheme #[arg(value_enum, long, short = 'p', default_value_t = PlotLevel::Data)] plot : PlotLevel, /// Directory for saving results - #[arg(long, short = 'o', default_value = "out")] + #[arg(long, short = 'o', required = true, default_value = "out")] outdir : String, #[arg(long, help_heading = "Multi-threading", default_value = "4")] @@ -120,7 +121,7 @@ } /// Command line experiment setup overrides -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Serialize, Deserialize)] pub struct ExperimentOverrides<F : ClapFloat> { #[arg(long)] /// Regularisation parameter override. @@ -143,7 +144,7 @@ } /// Command line algorithm parametrisation overrides -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Serialize, Deserialize)] pub struct AlgorithmOverrides<F : ClapFloat> { #[arg(long, value_names = &["COUNT", "EACH"])] /// Override bootstrap insertion iterations for --algorithm. @@ -222,7 +223,6 @@ for experiment_shorthand in cli.experiments.iter().unique() { let experiment = experiment_shorthand.get_experiment(&cli.experiment_overrides).unwrap(); - let mut config : Configuration<float> = experiment.default_config(); let mut algs : Vec<Named<AlgorithmConfig<float>>> = cli.algorithm.iter() .map(|alg| experiment.algorithm_defaults(*alg, &cli.algoritm_overrides)) @@ -232,16 +232,7 @@ let alg = serde_json::from_reader(f).unwrap(); algs.push(alg); } - cli.max_iter.map(|m| config.iterator_options.max_iter = m); - cli.verbose_iter.map(|n| config.iterator_options.verbose_iter = Verbose::Every(n)); - config.plot = cli.plot; - config.iterator_options.quiet = cli.quiet; - config.outdir = cli.outdir.clone(); - if !algs.is_empty() { - config.algorithms = algs.clone(); - } - - experiment.runall(config) + experiment.runall(&cli, (!algs.is_empty()).then_some(algs)) .unwrap() } }
--- a/src/run.rs Fri Dec 02 18:14:03 2022 +0200 +++ b/src/run.rs Fri Dec 02 21:20:04 2022 +0200 @@ -63,7 +63,7 @@ use crate::subproblem::InnerSettings; use crate::seminorms::*; use crate::plot::*; -use crate::AlgorithmOverrides; +use crate::{AlgorithmOverrides, CommandLineArgs}; /// Available algorithms and their configurations #[derive(Copy, Clone, Debug, Serialize, Deserialize)] @@ -120,7 +120,7 @@ } /// Shorthand algorithm configurations, to be used with the command line parser -#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum DefaultAlgorithm { /// The μFB forward-backward method #[clap(name = "fb")] @@ -192,23 +192,6 @@ Iter, } -/// Algorithm and iterator config for the experiments - -#[derive(Clone, Debug, Serialize)] -#[serde(default)] -pub struct Configuration<F : Float> { - /// Algorithms to run - pub algorithms : Vec<Named<AlgorithmConfig<F>>>, - /// Options for algorithm step iteration (verbosity, etc.) - pub iterator_options : AlgIteratorOptions, - /// Plotting level - pub plot : PlotLevel, - /// Directory where to save results - pub outdir : String, - /// Bisection tree depth - pub bt_depth : DynamicDepth, -} - type DefaultBT<F, const N : usize> = BT< DynamicDepth, F, @@ -322,11 +305,9 @@ /// Trait for runnable experiments pub trait RunnableExperiment<F : ClapFloat> { - /// Run all algorithms of the [`Configuration`] `config` on the experiment. - fn runall(&self, config : Configuration<F>) -> DynError; - - /// Returns the default configuration - fn default_config(&self) -> Configuration<F>; + /// Run all algorithms provided, or default algorithms if none provided, on the experiment. + fn runall(&self, cli : &CommandLineArgs, + algs : Option<Vec<Named<AlgorithmConfig<F>>>>) -> DynError; /// Return algorithm default config fn algorithm_defaults(&self, alg : DefaultAlgorithm, cli : &AlgorithmOverrides<F>) @@ -361,26 +342,9 @@ ) } - fn default_config(&self) -> Configuration<F> { - let default_alg = match self.data.dataterm { - DataTerm::L2Squared => DefaultAlgorithm::FB.get_named(), - DataTerm::L1 => DefaultAlgorithm::PDPS.get_named(), - }; - - Configuration{ - algorithms : vec![default_alg], - iterator_options : AlgIteratorOptions{ - max_iter : 2000, - verbose_iter : Verbose::Logarithmic(10), - quiet : false, - }, - plot : PlotLevel::Data, - outdir : "out".to_string(), - bt_depth : DynamicDepth(8), - } - } - - fn runall(&self, config : Configuration<F>) -> DynError { + fn runall(&self, cli : &CommandLineArgs, + algs : Option<Vec<Named<AlgorithmConfig<F>>>>) -> DynError { + // Get experiment configuration let &Named { name : ref experiment_name, data : Experiment { @@ -390,11 +354,25 @@ } } = self; - // Set path - let prefix = format!("{}/{}/", config.outdir, experiment_name); + // Set up output directory + let prefix = format!("{}/{}/", cli.outdir, self.name); + + // Set up algorithms + let iterator_options = AlgIteratorOptions{ + max_iter : cli.max_iter, + verbose_iter : cli.verbose_iter + .map_or(Verbose::Logarithmic(10), + |n| Verbose::Every(n)), + quiet : cli.quiet, + }; + let algorithms = match (algs, self.data.dataterm) { + (Some(algs), _) => algs, + (None, DataTerm::L2Squared) => vec![DefaultAlgorithm::FB.get_named()], + (None, DataTerm::L1) => vec![DefaultAlgorithm::PDPS.get_named()], + }; // Set up operators - let depth = config.bt_depth; + let depth = DynamicDepth(8); let opA = DefaultSG::new(domain, sensor_count, sensor, spread, depth); let op𝒟 = DefaultSeminormOp::new(depth, domain, kernel); @@ -413,20 +391,20 @@ let mkname_e = |t| format!("{prefix}{t}.json", prefix = prefix, t = t); std::fs::create_dir_all(&prefix)?; write_json(mkname_e("experiment"), self)?; - write_json(mkname_e("config"), &config)?; + write_json(mkname_e("config"), cli)?; write_json(mkname_e("stats"), &stats)?; - plotall(&config, &prefix, &domain, &sensor, &kernel, &spread, + plotall(cli, &prefix, &domain, &sensor, &kernel, &spread, &μ_hat, &op𝒟, &opA, &b_hat, &b, kernel_plot_width)?; // Run the algorithm(s) - for named @ Named { name : alg_name, data : alg } in config.algorithms.iter() { + for named @ Named { name : alg_name, data : alg } in algorithms.iter() { let this_prefix = format!("{}{}/", prefix, alg_name); - let running = || { + let running = || if !cli.quiet { println!("{}\n{}\n{}", format!("Running {} on experiment {}…", alg_name, experiment_name).cyan(), - format!("{:?}", config.iterator_options).bright_black(), + format!("{:?}", iterator_options).bright_black(), format!("{:?}", alg).bright_black()); }; @@ -473,15 +451,14 @@ this_iters } }; - let iterator = config.iterator_options - .instantiate() - .timed() - .mapped(logmap) - .into_log(&mut logger); + let iterator = iterator_options.instantiate() + .timed() + .mapped(logmap) + .into_log(&mut logger); let plotgrid = lingrid(&domain, &[if N==1 { 1000 } else { 100 }; N]); // Create plotter and directory if needed. - let plot_count = if config.plot >= PlotLevel::Iter { 2000 } else { 0 }; + let plot_count = if cli.plot >= PlotLevel::Iter { 2000 } else { 0 }; let plotter = SeqPlotter::new(this_prefix, plot_count, plotgrid); // Run the algorithm @@ -505,8 +482,8 @@ pointsource_pdps(&opA, &b, α, &op𝒟, &algconfig, iterator, plotter, L1) }, _ => { - let msg = format!("Algorithm “{}” not implemented for dataterm {:?}. Skipping.", - alg_name, dataterm).red(); + let msg = format!("Algorithm “{alg_name}” not implemented for \ + dataterm {dataterm:?}. Skipping.").red(); eprintln!("{}", msg); continue } @@ -519,8 +496,7 @@ // Save results println!("{}", "Saving results…".green()); - let mkname = | - t| format!("{p}{n}_{t}", p = prefix, n = alg_name, t = t); + let mkname = |t| format!("{prefix}{alg_name}_{t}"); write_json(mkname("config.json"), &named)?; write_json(mkname("stats.json"), &AlgorithmStats { cpu_time, elapsed })?; @@ -535,7 +511,7 @@ /// Plot experiment setup #[replace_float_literals(F::cast_from(literal))] fn plotall<F, Sensor, Kernel, Spread, 𝒟, A, const N : usize>( - config : &Configuration<F>, + cli : &CommandLineArgs, prefix : &String, domain : &Cube<F, N>, sensor : &Sensor, @@ -560,7 +536,7 @@ PlotLookup : Plotting<N>, Cube<F, N> : SetOrd { - if config.plot < PlotLevel::Data { + if cli.plot < PlotLevel::Data { return Ok(()) }