Command line parameter passing simplifications and make `-o` required.

Fri, 02 Dec 2022 21:20:04 +0200

author
Tuomo Valkonen <tuomov@iki.fi>
date
Fri, 02 Dec 2022 21:20:04 +0200
changeset 9
21b0e537ac0e
parent 8
ea3ca78873e8
child 10
b71edfd403aa

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(())
     }
 

mercurial